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;
}
}
}