S8EvaluatorGuard.cs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. using System.Globalization;
  2. using System.Threading;
  3. using Microsoft.Extensions.Logging;
  4. using SqlSugar;
  5. namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules;
  6. /// <summary>
  7. /// S8-SQL-EVALUATOR-GUARD-P2-1:S8 三类 SQL evaluator 公共防护工具。
  8. /// 承载:
  9. /// 1) AIDOP_S8_EVALUATOR_COMMAND_TIMEOUT_SECONDS / AIDOP_S8_EVALUATOR_MAX_ROWS env 解析(含默认值与下限保护,解析失败单次 warn);
  10. /// 2) ApplyCommandTimeout:在 evaluator 独立 SqlSugarScope 上显式设置 Ado.CommandTimeOut;
  11. /// 3) EnsureRowCountWithinLimit:检查返回行数,超限抛 S8RuleEvaluatorException(result_too_many_rows)。
  12. /// 不承载业务规则判断;不访问数据库;不修改 rule;不写业务数据明细;不读取 rule.Expression / dataSource.Endpoint / rule.ParamsJson。
  13. /// </summary>
  14. internal static class S8EvaluatorGuard
  15. {
  16. public const int DefaultCommandTimeoutSeconds = 60;
  17. public const int MinCommandTimeoutSeconds = 5;
  18. public const int DefaultMaxRows = 1000;
  19. public const int MinMaxRows = 1;
  20. public const string EnvCommandTimeoutSeconds = "AIDOP_S8_EVALUATOR_COMMAND_TIMEOUT_SECONDS";
  21. public const string EnvMaxRows = "AIDOP_S8_EVALUATOR_MAX_ROWS";
  22. // 单进程内 AIDOP_S8_EVALUATOR_* 解析失败 warn 仅输出一次,避免日志刷屏。
  23. // 与 S8WatchSchedulerJob._firstParseFailLogged 同形(Interlocked.Exchange 单次锚点)。
  24. private static int _firstParseFailLogged;
  25. public static int ResolveCommandTimeoutSeconds(ILogger? logger = null) =>
  26. ResolveIntFromEnv(EnvCommandTimeoutSeconds, DefaultCommandTimeoutSeconds, MinCommandTimeoutSeconds, logger);
  27. public static int ResolveMaxRows(ILogger? logger = null) =>
  28. ResolveIntFromEnv(EnvMaxRows, DefaultMaxRows, MinMaxRows, logger);
  29. public static void ApplyCommandTimeout(SqlSugarScope scope, int seconds)
  30. {
  31. scope.Ado.CommandTimeOut = seconds;
  32. }
  33. /// <summary>
  34. /// 检查 evaluator SQL 返回行数;超限抛 S8RuleEvaluatorException(result_too_many_rows)。
  35. /// 异常 message 仅含 ruleType / ruleCode / 实际行数 / 上限值,禁止携带 SQL 全文、连接串、ParamsJson、返回行内容。
  36. /// </summary>
  37. public static void EnsureRowCountWithinLimit(int actualRows, int maxRows, string ruleType, string ruleCode)
  38. {
  39. if (actualRows > maxRows)
  40. {
  41. throw new S8RuleEvaluatorException(
  42. "result_too_many_rows",
  43. $"{ruleType} 规则 {ruleCode} 返回 {actualRows} 行,超过安全上限 {maxRows},请缩小规则条件或拆分规则");
  44. }
  45. }
  46. private static int ResolveIntFromEnv(string envName, int defaultValue, int minValue, ILogger? logger)
  47. {
  48. var raw = Environment.GetEnvironmentVariable(envName);
  49. if (string.IsNullOrWhiteSpace(raw)) return defaultValue;
  50. if (int.TryParse(raw.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var v) && v >= minValue)
  51. return v;
  52. if (Interlocked.Exchange(ref _firstParseFailLogged, 1) == 0 && logger != null)
  53. {
  54. logger.LogWarning(
  55. "s8_evaluator_guard_env_invalid env={Env} fallback={Fallback}",
  56. envName, defaultValue);
  57. }
  58. return defaultValue;
  59. }
  60. }