S8DataSourceEndpointNormalizer.cs 3.9 KB

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