using System.Text.RegularExpressions; using Admin.NET.Core; using Microsoft.Extensions.Logging; namespace Admin.NET.Plugin.AiDOP.DataPlatform; public interface IMdpSourcePasswordResolver { /// 解析数据源口令。返回值不得写入日志、实体或数据库。 string Resolve(string sourceCode, string? storedSecret); } public sealed class MdpSourceSecretException : Exception { public string Code { get; } public MdpSourceSecretException(string code, string message) : base(message) { Code = code; } } /// /// 口令只在建连时解析:环境变量优先,其次系统密文,最后才是迁移期明文。 /// public sealed class MdpSourcePasswordResolver : IMdpSourcePasswordResolver, ITransient { private readonly ILogger? _logger; public MdpSourcePasswordResolver(ILogger? logger = null) { _logger = logger; } public static string EnvironmentVariableName(string sourceCode) { if (string.IsNullOrWhiteSpace(sourceCode)) throw new ArgumentException("sourceCode 不能为空", nameof(sourceCode)); var token = Regex.Replace(sourceCode.Trim().ToUpperInvariant(), "[^A-Z0-9]", "_"); return "AIDOP_MDP_SOURCE_" + token + "_PASSWORD"; } public string Resolve(string sourceCode, string? storedSecret) => ResolveCore(sourceCode, storedSecret, DecryptStored, _logger); internal static string ResolveCore( string sourceCode, string? storedSecret, Func decrypt, ILogger? logger) { var envName = EnvironmentVariableName(sourceCode); var fromEnv = Environment.GetEnvironmentVariable(envName); if (!string.IsNullOrEmpty(fromEnv)) return fromEnv; if (!string.IsNullOrEmpty(storedSecret)) { if (TryDecrypt(storedSecret, decrypt, out var plain)) return plain; logger?.LogWarning( "mdp source {SourceCode} still uses a legacy plaintext secret. Set {EnvName} and clear the stored value.", sourceCode, envName); return storedSecret; } throw new MdpSourceSecretException( "SECRET_MISSING", $"源 {sourceCode} 未配置口令。请设置环境变量 {envName}。"); } private static string? DecryptStored(string stored) { var decrypted = CryptogramUtil.Decrypt(stored); return decrypted; } private static bool TryDecrypt(string stored, Func decrypt, out string plain) { plain = ""; try { var decrypted = decrypt(stored); // 原样返回入参不是解密成功,按迁移期明文处理。 if (string.IsNullOrEmpty(decrypted) || string.Equals(decrypted, stored, StringComparison.Ordinal)) return false; plain = decrypted; return true; } catch { return false; } } }