RuleConfigRepository.cs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. namespace Admin.NET.Plugin.AiDOP.Rule;
  2. /// <summary>
  3. /// 规则配置只读数据源边界。后续数据库实现只需替换该接口实现。
  4. /// </summary>
  5. public interface IRuleConfigRepository
  6. {
  7. Task<IReadOnlyList<RuleConfigLayer>> GetEffectiveLayersAsync(
  8. string moduleCode,
  9. string scenarioCode,
  10. long tenantId,
  11. long? factoryId,
  12. CancellationToken cancellationToken = default);
  13. }
  14. /// <summary>
  15. /// 默认空数据源,主要用于单元测试或手动回退。
  16. /// </summary>
  17. public sealed class EmptyRuleConfigRepository : IRuleConfigRepository
  18. {
  19. public Task<IReadOnlyList<RuleConfigLayer>> GetEffectiveLayersAsync(
  20. string moduleCode,
  21. string scenarioCode,
  22. long tenantId,
  23. long? factoryId,
  24. CancellationToken cancellationToken = default)
  25. {
  26. cancellationToken.ThrowIfCancellationRequested();
  27. _ = moduleCode;
  28. _ = scenarioCode;
  29. _ = tenantId;
  30. _ = factoryId;
  31. return Task.FromResult<IReadOnlyList<RuleConfigLayer>>(Array.Empty<RuleConfigLayer>());
  32. }
  33. }
  34. /// <summary>
  35. /// `ado_rule_*` 数据库只读仓储。研发阶段默认启用,用于验证规则配置链路。
  36. /// </summary>
  37. public sealed class DbRuleConfigRepository : IRuleConfigRepository, ITransient
  38. {
  39. private readonly ISqlSugarClient _db;
  40. public DbRuleConfigRepository(ISqlSugarClient db)
  41. {
  42. _db = db;
  43. }
  44. public async Task<IReadOnlyList<RuleConfigLayer>> GetEffectiveLayersAsync(
  45. string moduleCode,
  46. string scenarioCode,
  47. long tenantId,
  48. long? factoryId,
  49. CancellationToken cancellationToken = default)
  50. {
  51. cancellationToken.ThrowIfCancellationRequested();
  52. var now = DateTime.Now;
  53. var normalizedFactoryId = factoryId.GetValueOrDefault();
  54. var rows = await _db.Ado.SqlQueryAsync<RuleConfigDbRow>(
  55. """
  56. SELECT
  57. p.id AS ProfileId,
  58. p.tenant_id AS TenantId,
  59. p.factory_id AS FactoryId,
  60. p.profile_code AS ProfileCode,
  61. i.rule_code AS RuleCode,
  62. i.value_type AS ValueType,
  63. i.rule_value AS RuleValue,
  64. i.is_enabled AS ItemEnabled,
  65. i.sort_no AS SortNo,
  66. s.required AS Required,
  67. s.ui_control AS UiControl
  68. FROM ado_rule_profile p
  69. INNER JOIN ado_rule_item i ON i.profile_id = p.id
  70. LEFT JOIN ado_rule_schema s
  71. ON s.module_code = p.module_code
  72. AND s.scenario_code = p.scenario_code
  73. AND s.rule_code = i.rule_code
  74. AND s.is_enabled = 1
  75. WHERE p.module_code = @ModuleCode
  76. AND p.scenario_code = @ScenarioCode
  77. AND p.is_enabled = 1
  78. AND i.is_enabled = 1
  79. AND (p.effective_from IS NULL OR p.effective_from <= @Now)
  80. AND (p.effective_to IS NULL OR p.effective_to >= @Now)
  81. AND (
  82. (p.tenant_id = 0 AND p.factory_id = 0)
  83. OR (p.tenant_id = @TenantId AND p.factory_id = 0)
  84. OR (p.tenant_id = @TenantId AND p.factory_id = @FactoryId)
  85. )
  86. ORDER BY p.tenant_id, p.factory_id, p.version, i.sort_no, i.id
  87. """,
  88. new SugarParameter("@ModuleCode", moduleCode),
  89. new SugarParameter("@ScenarioCode", scenarioCode),
  90. new SugarParameter("@TenantId", tenantId),
  91. new SugarParameter("@FactoryId", normalizedFactoryId),
  92. new SugarParameter("@Now", now));
  93. return rows
  94. .GroupBy(x => new { x.ProfileId, x.TenantId, x.FactoryId, x.ProfileCode })
  95. .Select(x => new RuleConfigLayer
  96. {
  97. Scope = BuildScope(x.Key.ProfileCode, x.Key.TenantId, x.Key.FactoryId),
  98. Priority = BuildPriority(x.Key.TenantId, x.Key.FactoryId),
  99. Items = x
  100. .OrderBy(item => item.SortNo)
  101. .Select(item => new RuleConfigItem
  102. {
  103. RuleCode = item.RuleCode,
  104. ValueType = item.ValueType,
  105. RuleValue = item.RuleValue,
  106. Required = item.Required == 1,
  107. IsEnabled = item.ItemEnabled == 1,
  108. IsHighRiskSwitch = false
  109. })
  110. .ToList()
  111. })
  112. .OrderBy(x => x.Priority)
  113. .ToList();
  114. }
  115. private static int BuildPriority(long tenantId, long factoryId)
  116. {
  117. if (tenantId > 0 && factoryId > 0) return 300;
  118. if (tenantId > 0) return 200;
  119. return 100;
  120. }
  121. private static string BuildScope(string profileCode, long tenantId, long factoryId)
  122. {
  123. if (tenantId > 0 && factoryId > 0) return $"factory:{tenantId}:{factoryId}:{profileCode}";
  124. if (tenantId > 0) return $"tenant:{tenantId}:{profileCode}";
  125. return $"global:{profileCode}";
  126. }
  127. private sealed class RuleConfigDbRow
  128. {
  129. public long ProfileId { get; set; }
  130. public long TenantId { get; set; }
  131. public long FactoryId { get; set; }
  132. public string ProfileCode { get; set; } = string.Empty;
  133. public string RuleCode { get; set; } = string.Empty;
  134. public string ValueType { get; set; } = string.Empty;
  135. public string? RuleValue { get; set; }
  136. public int ItemEnabled { get; set; }
  137. public int SortNo { get; set; }
  138. public int? Required { get; set; }
  139. public string? UiControl { get; set; }
  140. }
  141. }