MdpDbSourceMetadataService.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. using Admin.NET.Plugin.AiDOP.Entity.DataPlatform;
  2. using Microsoft.Extensions.Logging;
  3. namespace Admin.NET.Plugin.AiDOP.DataPlatform;
  4. [ApiDescriptionSettings(Order = 323, Description = "数据中台数据库元数据")]
  5. [Route("api/DataPlatform")]
  6. [NonUnify]
  7. public class MdpDbSourceMetadataService : IDynamicApiController, ITransient
  8. {
  9. private readonly ISqlSugarClient _db;
  10. private readonly UserManager _user;
  11. private readonly MdpSourceScopeFactory _scopes;
  12. private readonly ILogger<MdpDbSourceMetadataService> _logger;
  13. public MdpDbSourceMetadataService(
  14. ISqlSugarClient db,
  15. UserManager user,
  16. MdpSourceScopeFactory scopes,
  17. ILogger<MdpDbSourceMetadataService> logger)
  18. {
  19. _db = db;
  20. _user = user;
  21. _scopes = scopes;
  22. _logger = logger;
  23. }
  24. [DisplayName("读取数据库表和视图")]
  25. [HttpGet("sources/{sourceCode}/db/tables")]
  26. public async Task<object> Tables(string sourceCode)
  27. {
  28. await EnsureMenuAsync(MdpDbSyncRules.SourceMenuId);
  29. var source = await FindDbSourceAsync(sourceCode);
  30. try
  31. {
  32. var scope = await OpenAsync(source);
  33. var isSqlServer = IsSqlServer(source);
  34. var sql = isSqlServer
  35. ? """
  36. SELECT TABLE_SCHEMA AS schemaName, TABLE_NAME AS name, TABLE_TYPE AS type
  37. FROM INFORMATION_SCHEMA.TABLES
  38. WHERE TABLE_TYPE IN ('BASE TABLE', 'VIEW')
  39. ORDER BY TABLE_SCHEMA, TABLE_NAME
  40. """
  41. : """
  42. SELECT TABLE_SCHEMA AS databaseName, TABLE_NAME AS name, TABLE_TYPE AS type
  43. FROM information_schema.TABLES
  44. WHERE TABLE_SCHEMA = DATABASE()
  45. AND TABLE_TYPE IN ('BASE TABLE', 'VIEW')
  46. ORDER BY TABLE_NAME
  47. """;
  48. var rows = await scope.Ado.SqlQueryAsync<dynamic>(sql);
  49. return new { list = rows };
  50. }
  51. catch (Exception ex)
  52. {
  53. throw SafeMetadataError(source, ex);
  54. }
  55. }
  56. [DisplayName("读取数据库字段")]
  57. [HttpGet("sources/{sourceCode}/db/columns")]
  58. public async Task<object> Columns(string sourceCode, [FromQuery] string table)
  59. {
  60. await EnsureMenuAsync(MdpDbSyncRules.SourceMenuId);
  61. var source = await FindDbSourceAsync(sourceCode);
  62. var safeTable = MdpDbSyncRules.RequireSourceTable(table);
  63. try
  64. {
  65. var scope = await OpenAsync(source);
  66. var isSqlServer = IsSqlServer(source);
  67. var simple = SimpleName(safeTable);
  68. MdpDbSyncRules.RequireIdentifier(simple, "table");
  69. var sql = isSqlServer
  70. ? """
  71. SELECT c.name AS name, ty.name AS dataType,
  72. CASE WHEN c.is_nullable = 1 THEN 1 ELSE 0 END AS nullable,
  73. CASE WHEN pk.column_id IS NULL THEN 0 ELSE 1 END AS primaryKey
  74. FROM sys.columns c
  75. INNER JOIN sys.types ty ON c.user_type_id = ty.user_type_id
  76. LEFT JOIN (
  77. SELECT ic.object_id, ic.column_id
  78. FROM sys.indexes i
  79. INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
  80. WHERE i.is_primary_key = 1
  81. ) pk ON pk.object_id = c.object_id AND pk.column_id = c.column_id
  82. WHERE c.object_id = OBJECT_ID(@table)
  83. ORDER BY c.column_id
  84. """
  85. : """
  86. SELECT COLUMN_NAME AS name, DATA_TYPE AS dataType,
  87. CASE WHEN IS_NULLABLE = 'YES' THEN 1 ELSE 0 END AS nullable,
  88. CASE WHEN COLUMN_KEY = 'PRI' THEN 1 ELSE 0 END AS primaryKey
  89. FROM information_schema.COLUMNS
  90. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = @table
  91. ORDER BY ORDINAL_POSITION
  92. """;
  93. var rows = await scope.Ado.SqlQueryAsync<dynamic>(sql, new SugarParameter("@table", simple));
  94. return new { table = safeTable, list = rows };
  95. }
  96. catch (Exception ex)
  97. {
  98. throw SafeMetadataError(source, ex);
  99. }
  100. }
  101. private async Task<MdpSource> FindDbSourceAsync(string sourceCode)
  102. {
  103. var code = (sourceCode ?? "").Trim();
  104. var source = await _db.Queryable<MdpSource>()
  105. .Where(x => x.SourceCode == code && x.TenantId == _user.TenantId && x.Status == 1)
  106. .FirstAsync()
  107. ?? throw Oops.Oh("数据源不存在或未启用");
  108. if (!string.Equals(source.SourceType, "DB", StringComparison.OrdinalIgnoreCase))
  109. throw Oops.Oh("只有数据库来源可以读取表结构");
  110. if (!MdpDbSyncRules.IsSupportedDbType(source.DbType))
  111. throw Oops.Oh("数据库类型只支持 SQLSERVER 或 MYSQL");
  112. return source;
  113. }
  114. private async Task<ISqlSugarClient> OpenAsync(MdpSource source)
  115. {
  116. var scope = await _scopes.GetScopeAsync(source);
  117. scope.Ado.CommandTimeOut = 15;
  118. return scope;
  119. }
  120. private Exception SafeMetadataError(MdpSource source, Exception exception)
  121. {
  122. _logger.LogWarning(
  123. exception,
  124. "读取数据源 {SourceCode} 结构失败,tenant={TenantId}",
  125. source.SourceCode,
  126. source.TenantId);
  127. var error = MdpDbConnectionError.Describe(exception);
  128. return Oops.Oh(error.Message);
  129. }
  130. private async Task EnsureMenuAsync(long menuId)
  131. {
  132. if (_user.SuperAdmin || _user.SysAdmin)
  133. return;
  134. var roleIds = await _db.Queryable<SysUserRole>()
  135. .Where(x => x.UserId == _user.UserId)
  136. .Select(x => x.RoleId)
  137. .ToListAsync();
  138. var allowed = roleIds.Count > 0 && await _db.Queryable<SysRoleMenu>()
  139. .Where(x => roleIds.Contains(x.RoleId) && x.MenuId == menuId)
  140. .AnyAsync();
  141. if (!allowed)
  142. throw Oops.Oh("没有数据源管理权限");
  143. }
  144. private static bool IsSqlServer(MdpSource source) =>
  145. string.Equals(source.DbType, "SQLSERVER", StringComparison.OrdinalIgnoreCase)
  146. || string.Equals(source.DbType, "MSSQL", StringComparison.OrdinalIgnoreCase);
  147. private static string SimpleName(string table)
  148. {
  149. var text = table.Trim().Trim('[', ']');
  150. var parts = text.Split('.', StringSplitOptions.RemoveEmptyEntries);
  151. return parts[^1].Trim('[', ']');
  152. }
  153. }