using Admin.NET.Plugin.AiDOP.Entity.S8; using Admin.NET.Plugin.AiDOP.Infrastructure; using System.Text.RegularExpressions; namespace Admin.NET.Plugin.AiDOP.Service.S8; public class S8DataSourceService : ITransient { private readonly SqlSugarRepository _rep; public S8DataSourceService(SqlSugarRepository rep) => _rep = rep; public async Task> ListAsync(long tenantId, long factoryId) { var rows = await _rep.AsQueryable() .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId) .ToListAsync(); foreach (var r in rows) r.Endpoint = MaskSecret(r.Endpoint); return rows; } // S8-TENANT-FACTORY-P0-CLOSURE-1:归属一律由服务端可信作用域盖章,忽略 body.TenantId / body.FactoryId。 public async Task CreateAsync(AdoS8DataSource body, S8TrustedScope scope) { if (string.IsNullOrWhiteSpace(body.DataSourceCode) || string.IsNullOrWhiteSpace(body.Type)) throw new S8BizException("数据源编码和类型必填"); body.TenantId = scope.TenantId; body.FactoryId = scope.FactoryId; var exists = await _rep.AsQueryable() .AnyAsync(x => x.TenantId == body.TenantId && x.FactoryId == body.FactoryId && x.DataSourceCode == body.DataSourceCode); if (exists) throw new S8BizException("数据源编码已存在"); body.Id = 0; body.CreatedAt = DateTime.Now; await _rep.InsertAsync(body); body.Endpoint = MaskSecret(body.Endpoint); return body; } // S8-TENANT-FACTORY-P0-CLOSURE-1:按 Id + 可信作用域绑行;越权 Id 视为不存在,归属不可被 body 改写。 public async Task UpdateAsync(long id, AdoS8DataSource body, S8TrustedScope scope) { var e = await LoadScopedAsync(id, scope); if (string.IsNullOrWhiteSpace(body.DataSourceCode) || string.IsNullOrWhiteSpace(body.Type)) throw new S8BizException("数据源编码和类型必填"); var exists = await _rep.AsQueryable() .AnyAsync(x => x.Id != id && x.TenantId == e.TenantId && x.FactoryId == e.FactoryId && x.DataSourceCode == body.DataSourceCode); if (exists) throw new S8BizException("数据源编码已存在"); // 入参 endpoint 含掩码占位符(Pwd=****** / Password=******)时保留旧值的真实密码段,避免前端 // 回填脱敏值后误覆盖。Endpoint 全空时也不覆盖原密码。 body.Endpoint = MergeEndpointPreservingSecret(body.Endpoint, e.Endpoint); body.Id = id; body.TenantId = e.TenantId; body.FactoryId = e.FactoryId; body.CreatedAt = e.CreatedAt; body.UpdatedAt = DateTime.Now; await _rep.UpdateAsync(body); body.Endpoint = MaskSecret(body.Endpoint); return body; } // S8-TENANT-FACTORY-P0-CLOSURE-1:删除必须先按可信作用域绑行,禁止裸 DeleteByIdAsync(id)。 public async Task DeleteAsync(long id, S8TrustedScope scope) { var e = await LoadScopedAsync(id, scope); await _rep.DeleteByIdAsync(e.Id); } /// 按 Id + 可信作用域取行;不在作用域内一律按「不存在」处理,不泄露他租户资源是否存在。 private async Task LoadScopedAsync(long id, S8TrustedScope scope) => await _rep.AsQueryable() .Where(x => x.Id == id && x.TenantId == scope.TenantId && x.FactoryId == scope.FactoryId) .FirstAsync() ?? throw new S8NotFoundException(); public async Task TestAsync(long id, S8TrustedScope scope) { var entity = await LoadScopedAsync(id, scope); var success = !string.IsNullOrWhiteSpace(entity.Endpoint); entity.LastCheckAt = DateTime.Now; entity.LastCheckStatus = success ? "SUCCESS" : "FAILED: endpoint is empty"; entity.UpdatedAt = DateTime.Now; await _rep.UpdateAsync(entity); return new { id, success, message = success ? "连接信息校验通过" : "连接地址为空,未通过校验", entity.LastCheckAt, entity.LastCheckStatus }; } // BUG-13:endpoint 中的 Pwd=xxx / Password=xxx(大小写不敏感)替换为 ******,保留其它字段。 private static readonly Regex SecretPattern = new( @"(?i)(Pwd|Password)\s*=\s*([^;]*)", RegexOptions.Compiled); private static string? MaskSecret(string? endpoint) { if (string.IsNullOrWhiteSpace(endpoint)) return endpoint; return SecretPattern.Replace(endpoint, m => $"{m.Groups[1].Value}=******"); } private static string? MergeEndpointPreservingSecret(string? incoming, string? existing) { if (string.IsNullOrWhiteSpace(incoming)) return existing; if (string.IsNullOrWhiteSpace(existing)) return incoming; // 提取旧 endpoint 中的真实密码值(首个匹配为准) var oldMatch = SecretPattern.Match(existing); if (!oldMatch.Success) return incoming; var realSecret = oldMatch.Groups[2].Value; // 把入参里 Pwd=****** 之类的占位还原为真实密码 return SecretPattern.Replace(incoming, m => { var v = m.Groups[2].Value; return IsMaskedPlaceholder(v) ? $"{m.Groups[1].Value}={realSecret}" : m.Value; }); } private static bool IsMaskedPlaceholder(string? v) => !string.IsNullOrEmpty(v) && v.All(c => c == '*'); }