|
|
@@ -0,0 +1,80 @@
|
|
|
+using System.Data.Common;
|
|
|
+using System.Security.Cryptography;
|
|
|
+using System.Text;
|
|
|
+
|
|
|
+namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules;
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// S8-DYNAMIC-SQLSUGAR-SCOPE-FACTORY-1:S8 data_source endpoint 规范化与 hash 派生。
|
|
|
+/// 纯字符串变换:
|
|
|
+/// 1) 用 DbConnectionStringBuilder 解析 raw endpoint 的 key/value(大小写无关);
|
|
|
+/// 2) 按 OrdinalIgnoreCase 全部 key lower-case 化,避免不同输入 case 导致 hash 漂移;
|
|
|
+/// 3) 补齐缺失参数(已有 key 不覆盖),与主库 Database.json 池层参数对齐:
|
|
|
+/// AllowPublicKeyRetrieval / Pooling / Minimum Pool Size / Maximum Pool Size /
|
|
|
+/// Connection Timeout / Connection Idle Timeout / Connection LifeTime;
|
|
|
+/// 4) 按 key 字母序输出 normalized canonical connection string;
|
|
|
+/// 5) 对 normalized 做 SHA256 → 64 字符小写 hex EndpointHash。
|
|
|
+/// 不访问数据库、不读写文件、不读 env、不输出连接串原文、不输出密码。
|
|
|
+/// 输入为空或空白时抛 ArgumentException,message 不携带 endpoint 原文。
|
|
|
+/// </summary>
|
|
|
+internal static class S8DataSourceEndpointNormalizer
|
|
|
+{
|
|
|
+ public const string KeyAllowPublicKeyRetrieval = "allowpublickeyretrieval";
|
|
|
+ public const string KeyPooling = "pooling";
|
|
|
+ public const string KeyMinimumPoolSize = "minimum pool size";
|
|
|
+ public const string KeyMaximumPoolSize = "maximum pool size";
|
|
|
+ public const string KeyConnectionTimeout = "connection timeout";
|
|
|
+ public const string KeyConnectionIdleTimeout = "connection idle timeout";
|
|
|
+ public const string KeyConnectionLifeTime = "connection lifetime";
|
|
|
+
|
|
|
+ private static readonly (string Key, string Value)[] _normalizeDefaults =
|
|
|
+ {
|
|
|
+ (KeyAllowPublicKeyRetrieval, "True"),
|
|
|
+ (KeyPooling, "True"),
|
|
|
+ (KeyMinimumPoolSize, "0"),
|
|
|
+ (KeyMaximumPoolSize, "20"),
|
|
|
+ (KeyConnectionTimeout, "10"),
|
|
|
+ (KeyConnectionIdleTimeout, "180"),
|
|
|
+ (KeyConnectionLifeTime, "300"),
|
|
|
+ };
|
|
|
+
|
|
|
+ public static S8DataSourceEndpointNormalizeResult Normalize(string rawEndpoint)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrWhiteSpace(rawEndpoint))
|
|
|
+ throw new ArgumentException("S8 data_source endpoint 不能为空或空白", nameof(rawEndpoint));
|
|
|
+
|
|
|
+ var parsed = new DbConnectionStringBuilder { ConnectionString = rawEndpoint };
|
|
|
+
|
|
|
+ // SortedDictionary(Ordinal) + 全部 key 小写化 → 相同语义 endpoint 落到相同 canonical 顺序,hash 稳定。
|
|
|
+ var canonical = new SortedDictionary<string, string>(StringComparer.Ordinal);
|
|
|
+ foreach (string key in parsed.Keys)
|
|
|
+ {
|
|
|
+ var canonicalKey = key.ToLowerInvariant();
|
|
|
+ canonical[canonicalKey] = parsed[key]?.ToString() ?? string.Empty;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 补齐缺失默认值;已有 key(如自定义 Maximum Pool Size)保留原值,不覆盖。
|
|
|
+ foreach (var (defaultKey, defaultValue) in _normalizeDefaults)
|
|
|
+ {
|
|
|
+ if (!canonical.ContainsKey(defaultKey))
|
|
|
+ canonical[defaultKey] = defaultValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 用 DbConnectionStringBuilder 按 sorted key 顺序重组;其 ToString 自动处理 value quoting。
|
|
|
+ var normalizedBuilder = new DbConnectionStringBuilder();
|
|
|
+ foreach (var kv in canonical)
|
|
|
+ normalizedBuilder[kv.Key] = kv.Value;
|
|
|
+ var normalized = normalizedBuilder.ConnectionString;
|
|
|
+
|
|
|
+ var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(normalized));
|
|
|
+ var hashHex = Convert.ToHexString(bytes).ToLowerInvariant();
|
|
|
+
|
|
|
+ return new S8DataSourceEndpointNormalizeResult(normalized, hashHex);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// S8 data_source endpoint 规范化结果。NormalizedConnectionString 用于建 SqlSugarScope;
|
|
|
+/// EndpointHash 是 SHA256(normalized) 的 64 字符小写 hex,调用方可取前 N 位派生 ConfigId。
|
|
|
+/// </summary>
|
|
|
+public readonly record struct S8DataSourceEndpointNormalizeResult(string NormalizedConnectionString, string EndpointHash);
|