RuleConfigResolver.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. using System.Reflection;
  2. using System.Text.Json;
  3. namespace Admin.NET.Plugin.AiDOP.Rule;
  4. /// <summary>
  5. /// 规则配置纯逻辑解析器。只负责层级合并和类型转换,不访问数据库、不执行业务动作。
  6. /// </summary>
  7. public static class RuleConfigResolver
  8. {
  9. public static RuleConfigResolveResult<TOptions> Resolve<TOptions>(TOptions defaults, IEnumerable<RuleConfigLayer> layers)
  10. where TOptions : class, new()
  11. {
  12. var result = new RuleConfigResolveResult<TOptions> { Options = CopyOptions(defaults) };
  13. var properties = typeof(TOptions)
  14. .GetProperties(BindingFlags.Public | BindingFlags.Instance)
  15. .Where(x => x.CanRead && x.CanWrite)
  16. .ToDictionary(x => Normalize(x.Name), x => x);
  17. var orderedLayers = layers.OrderBy(x => x.Priority).ToList();
  18. foreach (var priorityGroup in orderedLayers.GroupBy(x => x.Priority))
  19. {
  20. var duplicateRules = priorityGroup
  21. .SelectMany(x => x.Items)
  22. .GroupBy(x => Normalize(x.RuleCode))
  23. .Where(x => x.Count() > 1)
  24. .Select(x => x.Key)
  25. .ToList();
  26. foreach (var duplicate in duplicateRules)
  27. {
  28. result.Errors.Add($"同一优先级存在重复规则:{duplicate}");
  29. }
  30. if (duplicateRules.Count > 0) continue;
  31. foreach (var layer in priorityGroup)
  32. {
  33. foreach (var item in layer.Items.Where(x => x.IsEnabled))
  34. {
  35. if (!properties.TryGetValue(Normalize(item.RuleCode), out var property))
  36. {
  37. result.Warnings.Add($"未找到规则对应属性:{item.RuleCode}");
  38. continue;
  39. }
  40. if (TryConvert(item, property.PropertyType, out var value, out var error))
  41. {
  42. property.SetValue(result.Options, value);
  43. result.AppliedItems.Add(new RuleConfigAppliedItem(layer.Scope, item.RuleCode, item.RuleValue));
  44. continue;
  45. }
  46. if (item.Required)
  47. {
  48. result.Errors.Add(error);
  49. continue;
  50. }
  51. result.Warnings.Add(error);
  52. if (item.IsHighRiskSwitch && property.PropertyType == typeof(bool))
  53. {
  54. property.SetValue(result.Options, false);
  55. result.Warnings.Add($"高风险开关配置非法,已按保守值 false 处理:{item.RuleCode}");
  56. }
  57. }
  58. }
  59. }
  60. return result;
  61. }
  62. private static TOptions CopyOptions<TOptions>(TOptions source)
  63. where TOptions : class, new()
  64. {
  65. var target = new TOptions();
  66. foreach (var property in typeof(TOptions).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanRead && x.CanWrite))
  67. {
  68. property.SetValue(target, property.GetValue(source));
  69. }
  70. return target;
  71. }
  72. private static bool TryConvert(RuleConfigItem item, Type targetType, out object? value, out string error)
  73. {
  74. var raw = item.RuleValue ?? string.Empty;
  75. var nullableType = Nullable.GetUnderlyingType(targetType);
  76. targetType = nullableType ?? targetType;
  77. try
  78. {
  79. if (targetType == typeof(string))
  80. {
  81. value = raw;
  82. error = string.Empty;
  83. return true;
  84. }
  85. if (targetType == typeof(bool))
  86. {
  87. if (bool.TryParse(raw, out var boolValue))
  88. {
  89. value = boolValue;
  90. error = string.Empty;
  91. return true;
  92. }
  93. }
  94. else if (targetType == typeof(int))
  95. {
  96. if (int.TryParse(raw, out var intValue))
  97. {
  98. value = intValue;
  99. error = string.Empty;
  100. return true;
  101. }
  102. }
  103. else if (targetType == typeof(long))
  104. {
  105. if (long.TryParse(raw, out var longValue))
  106. {
  107. value = longValue;
  108. error = string.Empty;
  109. return true;
  110. }
  111. }
  112. else if (targetType == typeof(decimal))
  113. {
  114. if (decimal.TryParse(raw, out var decimalValue))
  115. {
  116. value = decimalValue;
  117. error = string.Empty;
  118. return true;
  119. }
  120. }
  121. else if (targetType == typeof(List<string>))
  122. {
  123. value = JsonSerializer.Deserialize<List<string>>(raw) ?? new List<string>();
  124. error = string.Empty;
  125. return true;
  126. }
  127. else if (targetType == typeof(List<int>))
  128. {
  129. value = JsonSerializer.Deserialize<List<int>>(raw) ?? new List<int>();
  130. error = string.Empty;
  131. return true;
  132. }
  133. else if (targetType == typeof(List<decimal>))
  134. {
  135. value = JsonSerializer.Deserialize<List<decimal>>(raw) ?? new List<decimal>();
  136. error = string.Empty;
  137. return true;
  138. }
  139. else
  140. {
  141. value = JsonSerializer.Deserialize(raw, targetType);
  142. error = string.Empty;
  143. return true;
  144. }
  145. }
  146. catch (Exception ex)
  147. {
  148. value = null;
  149. error = $"规则 {item.RuleCode} 类型转换失败:{ex.Message}";
  150. return false;
  151. }
  152. value = null;
  153. error = $"规则 {item.RuleCode} 类型转换失败:值 {raw} 无法转换为 {targetType.Name}";
  154. return false;
  155. }
  156. private static string Normalize(string value)
  157. {
  158. return value.Replace("_", string.Empty).ToLowerInvariant();
  159. }
  160. }
  161. public sealed class RuleConfigLayer
  162. {
  163. public string Scope { get; set; } = string.Empty;
  164. public int Priority { get; set; }
  165. public List<RuleConfigItem> Items { get; set; } = new();
  166. }
  167. public sealed class RuleConfigItem
  168. {
  169. public string RuleCode { get; set; } = string.Empty;
  170. public string ValueType { get; set; } = string.Empty;
  171. public string? RuleValue { get; set; }
  172. public bool Required { get; set; }
  173. public bool IsEnabled { get; set; } = true;
  174. public bool IsHighRiskSwitch { get; set; }
  175. }
  176. public sealed class RuleConfigResolveResult<TOptions>
  177. {
  178. public TOptions Options { get; set; } = default!;
  179. public List<string> Warnings { get; set; } = new();
  180. public List<string> Errors { get; set; } = new();
  181. public List<RuleConfigAppliedItem> AppliedItems { get; set; } = new();
  182. public bool Success => Errors.Count == 0;
  183. }
  184. public sealed record RuleConfigAppliedItem(string Scope, string RuleCode, string? RuleValue);