S8DatasetEnableGate.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. using Admin.NET.Plugin.AiDOP.Entity.S8;
  2. namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess;
  3. /// <summary>Enable Gate 的检查结果。失败时带明确 reason code,便于前端与运维定位。</summary>
  4. public sealed class S8DatasetEnableCheck
  5. {
  6. private S8DatasetEnableCheck(bool ok, string? reasonCode, string? message)
  7. {
  8. Ok = ok;
  9. ReasonCode = reasonCode;
  10. Message = message;
  11. }
  12. public bool Ok { get; }
  13. public string? ReasonCode { get; }
  14. public string? Message { get; }
  15. public static S8DatasetEnableCheck Pass() => new(true, null, null);
  16. public static S8DatasetEnableCheck Fail(string reasonCode, string message) => new(false, reasonCode, message);
  17. }
  18. /// <summary>
  19. /// STANDARD_DATASET 规则的统一启用前校验。
  20. ///
  21. /// 校验顺序:<b>先查目录层可判定的问题,再查实现层问题</b>。
  22. /// 理由——"这个数据集做不了 TIMEOUT"是定义问题,等 Provider 上线也不会变好;
  23. /// 若先报 provider_not_registered 会误导配置方以为等实现即可。
  24. ///
  25. /// 1. dataset_code 是否填写 → dataset_code_missing
  26. /// 2. Catalog 中是否定义 → dataset_not_defined
  27. /// 3. 定义是否停用 → dataset_not_ready
  28. /// 4. 规则类型是否已知 → dataset_unknown_rule_type
  29. /// 5. 目录机制能力是否支持 → dataset_capability_mismatch
  30. /// 6. 目录 canonical 字段是否齐备 → dataset_missing_required_field
  31. /// 7. Provider 是否注册 → dataset_provider_not_registered
  32. /// 8. Provider 编码是否一致 → dataset_provider_code_mismatch
  33. /// 9. Provider 能力是否同样满足 → dataset_capability_mismatch / dataset_missing_required_field
  34. /// 10. 租户作用域 → invalid_tenant_scope
  35. ///
  36. /// S8-TENANT-ONLY-BATCH5:第 ⑩ 步的工厂检查已删除。它此前会因 factoryId &lt;= 0 拒绝启用,
  37. /// 而 Batch 2 供给出来的运行策略行 factory_id 恒为 0 —— 那些规则本来永远启用不了。
  38. ///
  39. /// S8-STANDARD-DATASET-HARD-CUTOVER-1:取数路径唯一,Gate 不再有"先判模式"这一步。
  40. /// </summary>
  41. public class S8DatasetEnableGate : ITransient
  42. {
  43. private readonly IS8DatasetCatalog _catalog;
  44. private readonly S8MonitoringDataProviderRegistry _registry;
  45. public S8DatasetEnableGate(IS8DatasetCatalog catalog, S8MonitoringDataProviderRegistry registry)
  46. {
  47. _catalog = catalog;
  48. _registry = registry;
  49. }
  50. /// <summary>
  51. /// 按数据库行检查。<b>S8-RULE-GOVERNANCE-BATCH1 起,正式启用路径不再走这个重载</b>——
  52. /// 它读的是 <c>rule.DatasetCode</c> / <c>rule.RuleType</c>,而这两列现在只是
  53. /// provisioning 维护的投影,可能与代码定义不一致。保留是为了既有校验路径与测试不受影响。
  54. /// </summary>
  55. public S8DatasetEnableCheck Check(AdoS8WatchRule rule, long tenantId) =>
  56. Check(rule.DatasetCode, rule.RuleType, rule.RuleCode, tenantId);
  57. /// <summary>
  58. /// 按**显式的数据集编码与规则类型**检查。启用路径应使用本重载并传入代码定义的值,
  59. /// 这样「能不能启用」的判定就不受任何人手工改 DB 投影列的影响。
  60. /// </summary>
  61. public S8DatasetEnableCheck Check(string? datasetCode, string? ruleType, string? ruleCode, long tenantId)
  62. {
  63. // ① dataset_code
  64. if (string.IsNullOrWhiteSpace(datasetCode))
  65. return S8DatasetEnableCheck.Fail(
  66. S8DatasetReasonCode.CodeMissing,
  67. "STANDARD_DATASET 模式的规则必须提供 dataset_code");
  68. // ② Catalog 定义
  69. var definition = _catalog.Find(datasetCode);
  70. if (definition == null)
  71. return S8DatasetEnableCheck.Fail(
  72. S8DatasetReasonCode.NotDefined,
  73. $"数据集 {datasetCode} 未在目录中定义");
  74. // ③ 定义是否停用
  75. if (!definition.IsActive)
  76. return S8DatasetEnableCheck.Fail(
  77. S8DatasetReasonCode.NotReady,
  78. $"数据集 {definition.DatasetCode} 已停用,不能用于规则执行");
  79. // ④ 规则类型已知
  80. if (!S8RuleTypeFieldRequirements.IsKnown(ruleType))
  81. return S8DatasetEnableCheck.Fail(
  82. S8DatasetReasonCode.UnknownRuleType,
  83. $"规则类型 {ruleType} 不在 canonical 词表内,无法校验数据集能力");
  84. // ⑤ 目录机制能力(定义层问题,先于 Provider 报出)
  85. if (!definition.Capabilities.Supports(ruleType))
  86. return S8DatasetEnableCheck.Fail(
  87. S8DatasetReasonCode.CapabilityMismatch,
  88. $"数据集 {definition.DatasetCode} 不支持 {ruleType} 类规则");
  89. // ⑥ 目录 canonical 字段齐备(防"声明支持某机制但拿不出必需字段"的自相矛盾配置)
  90. var missing = definition.Capabilities.MissingFieldsFor(ruleType);
  91. if (missing is { Count: > 0 })
  92. return S8DatasetEnableCheck.Fail(
  93. S8DatasetReasonCode.MissingRequiredField,
  94. $"数据集 {definition.DatasetCode} 缺少 {ruleType} 必需字段:{string.Join(", ", missing)}");
  95. // ⑦ Provider 注册
  96. if (!_registry.IsRegistered(definition.DatasetCode))
  97. return S8DatasetEnableCheck.Fail(
  98. S8DatasetReasonCode.ProviderNotRegistered,
  99. $"数据集 {definition.DatasetCode} 已定义但尚无 Provider 实现,可保存配置但不能启用");
  100. // ⑧ Provider 自报编码与目录一致
  101. var provider = _registry.Resolve(definition.DatasetCode);
  102. if (!string.Equals(provider.DatasetCode?.Trim(), definition.DatasetCode, StringComparison.OrdinalIgnoreCase))
  103. return S8DatasetEnableCheck.Fail(
  104. S8DatasetReasonCode.ProviderCodeMismatch,
  105. $"Provider 自报数据集编码 {provider.DatasetCode} 与目录定义 {definition.DatasetCode} 不一致");
  106. // ⑨ Provider 实际能力必须与目录声明同样满足(不一致时按更严格的一方拒绝)
  107. if (!provider.Capabilities.Supports(ruleType))
  108. return S8DatasetEnableCheck.Fail(
  109. S8DatasetReasonCode.CapabilityMismatch,
  110. $"Provider 实现不支持 {ruleType} 类规则(数据集 {definition.DatasetCode})");
  111. var providerMissing = provider.Capabilities.MissingFieldsFor(ruleType);
  112. if (providerMissing is { Count: > 0 })
  113. return S8DatasetEnableCheck.Fail(
  114. S8DatasetReasonCode.MissingRequiredField,
  115. $"Provider 实现缺少 {ruleType} 必需字段:{string.Join(", ", providerMissing)}");
  116. // ⑩ 作用域。只认 Tenant —— Factory 不是 S8 的隔离边界。
  117. if (tenantId <= 0)
  118. return S8DatasetEnableCheck.Fail(S8DatasetReasonCode.InvalidTenantScope, "租户作用域非法");
  119. return S8DatasetEnableCheck.Pass();
  120. }
  121. /// <summary>检查并在失败时抛 <see cref="S8BizException"/>(配置类错误,API 层直接回给调用方)。</summary>
  122. public void EnsureCanEnable(AdoS8WatchRule rule, long tenantId) =>
  123. EnsureCanEnable(rule.DatasetCode, rule.RuleType, rule.RuleCode, tenantId);
  124. /// <summary>按显式数据集编码与规则类型检查并在失败时抛 <see cref="S8BizException"/>。启用路径用这一个。</summary>
  125. public void EnsureCanEnable(string? datasetCode, string? ruleType, string? ruleCode, long tenantId)
  126. {
  127. var result = Check(datasetCode, ruleType, ruleCode, tenantId);
  128. if (!result.Ok)
  129. throw new S8BizException($"[{result.ReasonCode}] {result.Message}");
  130. }
  131. }