| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- using Admin.NET.Plugin.AiDOP.Entity.DataPlatform;
- using Microsoft.Extensions.Logging;
- namespace Admin.NET.Plugin.AiDOP.DataPlatform;
- /// <summary>数据源配置 API(供配置中心 sources 页读取与保存)。</summary>
- [ApiDescriptionSettings(Order = 324, Description = "数据中台数据源")]
- [Route("api/DataPlatform")]
- [NonUnify]
- public class MdpSourceConfigService : IDynamicApiController, ITransient
- {
- private readonly ISqlSugarClient _db;
- private readonly UserManager _user;
- private readonly MdpSourceScopeFactory _scopes;
- private readonly MdpSourcePasswordResolver _passwords;
- private readonly ILogger<MdpSourceConfigService> _logger;
- public MdpSourceConfigService(
- ISqlSugarClient db,
- UserManager user,
- MdpSourceScopeFactory scopes,
- MdpSourcePasswordResolver passwords,
- ILogger<MdpSourceConfigService> logger)
- {
- _db = db;
- _user = user;
- _scopes = scopes;
- _passwords = passwords;
- _logger = logger;
- }
- [DisplayName("数据源列表")]
- [HttpGet("sources")]
- public async Task<object> GetList([FromQuery] string? keyword, [FromQuery] string? type)
- {
- var q = _db.Queryable<MdpSource>();
- if (!_user.SuperAdmin && !_user.SysAdmin)
- q = q.Where(x => x.TenantId == _user.TenantId);
- if (!string.IsNullOrWhiteSpace(keyword))
- {
- var kw = keyword.Trim();
- q = q.Where(x => x.SourceCode.Contains(kw) || x.SourceName.Contains(kw));
- }
- if (!string.IsNullOrWhiteSpace(type))
- q = q.Where(x => x.SourceType == type.Trim());
- var list = await q.OrderBy(x => x.SourceCode).ToListAsync();
- return new
- {
- list = list.Select(x => new
- {
- id = x.Id,
- sourceCode = x.SourceCode,
- systemCode = string.IsNullOrWhiteSpace(x.SystemCode) ? x.SourceCode : x.SystemCode,
- connMode = string.IsNullOrWhiteSpace(x.ConnMode) ? "EXTERNAL" : x.ConnMode,
- sourceName = x.SourceName,
- sourceType = x.SourceType,
- status = x.Status,
- direction = string.Equals(x.SourceType, "API", StringComparison.OrdinalIgnoreCase) ? "拉取"
- : string.Equals(x.SourceType, "FILE_EXCEL", StringComparison.OrdinalIgnoreCase) ? "文件入站" : "入站",
- dbType = x.DbType ?? "-",
- dbHost = x.DbHost,
- dbPort = x.DbPort,
- dbName = x.DbName,
- dbUser = x.DbUser,
- apiBaseUrl = x.ApiBaseUrl,
- host = string.Equals(x.SourceType, "API", StringComparison.OrdinalIgnoreCase)
- ? (x.ApiBaseUrl ?? "-")
- : string.Equals(x.SourceType, "FILE_EXCEL", StringComparison.OrdinalIgnoreCase)
- ? "-"
- : $"{x.DbHost}/{x.DbName}",
- health = string.Equals(x.SourceType, "FILE_EXCEL", StringComparison.OrdinalIgnoreCase)
- ? (string.IsNullOrWhiteSpace(x.SourceCode) ? "配置异常" : "可用")
- : x.HealthStatus switch
- {
- 1 => "正常",
- 0 => "异常",
- _ => "未知"
- },
- healthType = x.HealthStatus switch
- {
- 1 => "success",
- 0 => "danger",
- _ => "info"
- },
- lastCheck = x.LastHealthCheck?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-",
- healthMsg = x.HealthMsg,
- dbExtraParams = x.DbExtraParams,
- remark = x.Remark,
- secretSource = string.Equals(x.SourceType, "DB", StringComparison.OrdinalIgnoreCase)
- ? _passwords.Classify(x.SourceCode, x.DbPasswordEnc)
- : null
- })
- };
- }
- [DisplayName("新建或更新数据源")]
- [HttpPost("sources")]
- public async Task<object> Save([FromBody] MdpSourceSaveInput input)
- {
- if (string.IsNullOrWhiteSpace(input.SourceCode) || string.IsNullOrWhiteSpace(input.SourceName) || string.IsNullOrWhiteSpace(input.SourceType))
- throw Oops.Oh("sourceCode、sourceName、sourceType 必填");
- var code = input.SourceCode.Trim();
- var tenantId = _user.TenantId;
- var mode = string.IsNullOrWhiteSpace(input.ConnMode) ? "EXTERNAL" : input.ConnMode.Trim().ToUpperInvariant();
- if (mode is not ("SELF" or "EXTERNAL"))
- throw Oops.Oh("connMode 只能是 SELF 或 EXTERNAL");
- if (tenantId == 0)
- throw Oops.Oh("系统种子来源只能由迁移脚本维护");
- var sourceType = input.SourceType.Trim();
- ValidateDbSource(input, sourceType, mode);
- var existing = await _db.Queryable<MdpSource>()
- .Where(x => x.TenantId == tenantId && x.SourceCode == code)
- .FirstAsync();
- if (existing == null)
- {
- await _db.Insertable(new MdpSource
- {
- TenantId = tenantId,
- SourceCode = code,
- SourceName = input.SourceName.Trim(),
- SourceType = sourceType,
- SystemCode = input.SystemCode?.Trim(),
- ConnMode = mode,
- Status = 1,
- DbType = NormalizeDbType(input.DbType),
- DbHost = mode == "SELF" ? null : input.DbHost?.Trim(),
- DbPort = mode == "SELF" ? null : input.DbPort,
- DbName = mode == "SELF" ? null : input.DbName?.Trim(),
- DbUser = mode == "SELF" ? null : input.DbUser?.Trim(),
- DbPasswordEnc = mode == "SELF" ? null : EncryptPassword(input.DbPassword),
- DbExtraParams = mode == "SELF" ? null : MdpSourceScopeFactory.SanitizeStoredExtra(input.DbType, input.DbExtraParams),
- ApiBaseUrl = input.ApiBaseUrl?.Trim(),
- Remark = input.Remark?.Trim(),
- CreateTime = DateTime.Now,
- UpdateTime = DateTime.Now
- }).ExecuteCommandAsync();
- }
- else
- {
- if (existing.TenantId == 0)
- throw Oops.Oh("系统种子来源只能由迁移脚本维护");
- existing.SourceName = input.SourceName.Trim();
- existing.SourceType = sourceType;
- existing.SystemCode = input.SystemCode?.Trim();
- existing.ConnMode = mode;
- existing.DbType = NormalizeDbType(input.DbType);
- existing.DbHost = mode == "SELF" ? null : input.DbHost?.Trim();
- existing.DbPort = mode == "SELF" ? null : input.DbPort;
- existing.DbName = mode == "SELF" ? null : input.DbName?.Trim();
- existing.DbUser = mode == "SELF" ? null : input.DbUser?.Trim();
- if (mode == "SELF")
- existing.DbPasswordEnc = null;
- else if (!string.IsNullOrEmpty(input.DbPassword))
- existing.DbPasswordEnc = EncryptPassword(input.DbPassword);
- existing.DbExtraParams = mode == "SELF" ? null : MdpSourceScopeFactory.SanitizeStoredExtra(input.DbType, input.DbExtraParams);
- existing.ApiBaseUrl = input.ApiBaseUrl?.Trim();
- existing.Remark = input.Remark?.Trim();
- existing.UpdateTime = DateTime.Now;
- await _db.Updateable(existing).ExecuteCommandAsync();
- }
- return new { sourceCode = code };
- }
- private static void ValidateDbSource(MdpSourceSaveInput input, string sourceType, string mode)
- {
- if (!string.Equals(sourceType, "DB", StringComparison.OrdinalIgnoreCase))
- return;
- var dbType = NormalizeDbType(input.DbType);
- if (!MdpDbSyncRules.IsSupportedDbType(dbType))
- throw Oops.Oh("数据库类型只支持 SQLSERVER 或 MYSQL");
- if (mode == "EXTERNAL" && (string.IsNullOrWhiteSpace(input.DbHost) || input.DbPort is null or <= 0
- || string.IsNullOrWhiteSpace(input.DbName) || string.IsNullOrWhiteSpace(input.DbUser)))
- throw Oops.Oh("外部数据库必须填写主机、端口、库名和账号");
- }
- private static string? NormalizeDbType(string? dbType) =>
- string.IsNullOrWhiteSpace(dbType) ? null : dbType.Trim().ToUpperInvariant();
- private static string? EncryptPassword(string? password) =>
- string.IsNullOrEmpty(password) ? null : CryptogramUtil.Encrypt(password);
- [DisplayName("测试数据源连接")]
- [HttpPost("sources/{sourceCode}/test")]
- public async Task<object> Test([FromRoute] string sourceCode)
- {
- var source = await FindOwnSourceAsync(sourceCode);
- if (string.Equals(source.SourceType, "DB", StringComparison.OrdinalIgnoreCase))
- {
- try
- {
- var started = DateTime.UtcNow;
- var scope = await _scopes.GetScopeAsync(source);
- await scope.Ado.GetScalarAsync("SELECT 1");
- var elapsed = (int)(DateTime.UtcNow - started).TotalMilliseconds;
- return new { ok = true, message = "数据库连接成功", dbType = source.DbType, elapsedMs = elapsed };
- }
- catch (Exception ex)
- {
- _logger.LogWarning(ex, "数据源 {SourceCode} 测试连接失败,tenant={TenantId}", source.SourceCode, source.TenantId);
- var error = MdpDbConnectionError.Describe(ex);
- return new { ok = false, code = error.Code, message = error.Message };
- }
- }
- if (string.Equals(source.SourceType, "API", StringComparison.OrdinalIgnoreCase))
- {
- if (string.IsNullOrWhiteSpace(source.ApiBaseUrl))
- return new { ok = false, message = "未填写接口地址" };
- try
- {
- using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
- using var resp = await http.GetAsync(source.ApiBaseUrl);
- return new { ok = resp.IsSuccessStatusCode, message = $"HTTP {(int)resp.StatusCode}" };
- }
- catch (Exception)
- {
- return new { ok = false, message = "接口探测失败" };
- }
- }
- return new { ok = true, message = "对方推送不需要探测对端连接" };
- }
- [DisplayName("停用数据源前的登记检查")]
- [HttpGet("sources/{sourceCode}/bindings")]
- public async Task<object> Bindings([FromRoute] string sourceCode)
- {
- var source = await FindOwnSourceAsync(sourceCode);
- var system = string.IsNullOrWhiteSpace(source.SystemCode) ? source.SourceCode : source.SystemCode;
- var rows = await _db.Ado.SqlQueryAsync<dynamic>(
- """
- SELECT tenant_id AS tenantId, std_object AS stdObject
- FROM mdp_tenant_std_source
- WHERE source_system=@sys
- ORDER BY tenant_id, std_object
- """,
- new { sys = system });
- return new { systemCode = system, bindings = rows };
- }
- [DisplayName("停用数据源")]
- [HttpPost("sources/{sourceCode}/disable")]
- public async Task<object> Disable([FromRoute] string sourceCode, [FromBody] MdpSourceDisableInput input)
- {
- var source = await FindOwnSourceAsync(sourceCode);
- var system = string.IsNullOrWhiteSpace(source.SystemCode) ? source.SourceCode : source.SystemCode;
- var rows = await _db.Ado.SqlQueryAsync<dynamic>(
- "SELECT std_object AS stdObject FROM mdp_tenant_std_source WHERE source_system=@sys",
- new { sys = system });
- if (rows.Count > 0 && input.Confirm != true)
- return new { disabled = false, needConfirm = true, bindings = rows };
- source.Status = 0;
- source.UpdateTime = DateTime.Now;
- await _db.Updateable(source).UpdateColumns(x => new { x.Status, x.UpdateTime }).ExecuteCommandAsync();
- return new { disabled = true, sourceCode = source.SourceCode };
- }
- private async Task<MdpSource> FindOwnSourceAsync(string sourceCode)
- {
- var code = sourceCode.Trim();
- var source = await _db.Queryable<MdpSource>().Where(x => x.SourceCode == code).FirstAsync()
- ?? throw Oops.Oh($"未找到来源 {code}");
- if (!_user.SuperAdmin && !_user.SysAdmin && source.TenantId != _user.TenantId)
- throw Oops.Oh("不能操作其他租户的数据源");
- return source;
- }
- }
- public sealed class MdpSourceSaveInput
- {
- public string SourceCode { get; set; } = "";
- public string SourceName { get; set; } = "";
- public string SourceType { get; set; } = "DB";
- public string? SystemCode { get; set; }
- public string? ConnMode { get; set; }
- public string? DbType { get; set; }
- public string? DbHost { get; set; }
- public int? DbPort { get; set; }
- public string? DbName { get; set; }
- public string? DbUser { get; set; }
- public string? DbPassword { get; set; }
- public string? DbExtraParams { get; set; }
- public string? ApiBaseUrl { get; set; }
- public string? Remark { get; set; }
- }
- public sealed class MdpSourceDisableInput
- {
- public bool? Confirm { get; set; }
- }
|