using System.Globalization; using System.Threading; using Microsoft.Extensions.Logging; using SqlSugar; namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules; /// /// S8-SQL-EVALUATOR-GUARD-P2-1:S8 三类 SQL evaluator 公共防护工具。 /// 承载: /// 1) AIDOP_S8_EVALUATOR_COMMAND_TIMEOUT_SECONDS / AIDOP_S8_EVALUATOR_MAX_ROWS env 解析(含默认值与下限保护,解析失败单次 warn); /// 2) ApplyCommandTimeout:在 evaluator 独立 SqlSugarScope 上显式设置 Ado.CommandTimeOut; /// 3) EnsureRowCountWithinLimit:检查返回行数,超限抛 S8RuleEvaluatorException(result_too_many_rows)。 /// 不承载业务规则判断;不访问数据库;不修改 rule;不写业务数据明细;不读取 rule.Expression / dataSource.Endpoint / rule.ParamsJson。 /// internal static class S8EvaluatorGuard { public const int DefaultCommandTimeoutSeconds = 60; public const int MinCommandTimeoutSeconds = 5; public const int DefaultMaxRows = 1000; public const int MinMaxRows = 1; public const string EnvCommandTimeoutSeconds = "AIDOP_S8_EVALUATOR_COMMAND_TIMEOUT_SECONDS"; public const string EnvMaxRows = "AIDOP_S8_EVALUATOR_MAX_ROWS"; // 单进程内 AIDOP_S8_EVALUATOR_* 解析失败 warn 仅输出一次,避免日志刷屏。 // 与 S8WatchSchedulerJob._firstParseFailLogged 同形(Interlocked.Exchange 单次锚点)。 private static int _firstParseFailLogged; public static int ResolveCommandTimeoutSeconds(ILogger? logger = null) => ResolveIntFromEnv(EnvCommandTimeoutSeconds, DefaultCommandTimeoutSeconds, MinCommandTimeoutSeconds, logger); public static int ResolveMaxRows(ILogger? logger = null) => ResolveIntFromEnv(EnvMaxRows, DefaultMaxRows, MinMaxRows, logger); public static void ApplyCommandTimeout(SqlSugarScope scope, int seconds) { scope.Ado.CommandTimeOut = seconds; } /// /// 检查 evaluator SQL 返回行数;超限抛 S8RuleEvaluatorException(result_too_many_rows)。 /// 异常 message 仅含 ruleType / ruleCode / 实际行数 / 上限值,禁止携带 SQL 全文、连接串、ParamsJson、返回行内容。 /// public static void EnsureRowCountWithinLimit(int actualRows, int maxRows, string ruleType, string ruleCode) { if (actualRows > maxRows) { throw new S8RuleEvaluatorException( "result_too_many_rows", $"{ruleType} 规则 {ruleCode} 返回 {actualRows} 行,超过安全上限 {maxRows},请缩小规则条件或拆分规则"); } } private static int ResolveIntFromEnv(string envName, int defaultValue, int minValue, ILogger? logger) { var raw = Environment.GetEnvironmentVariable(envName); if (string.IsNullOrWhiteSpace(raw)) return defaultValue; if (int.TryParse(raw.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var v) && v >= minValue) return v; if (Interlocked.Exchange(ref _firstParseFailLogged, 1) == 0 && logger != null) { logger.LogWarning( "s8_evaluator_guard_env_invalid env={Env} fallback={Fallback}", envName, defaultValue); } return defaultValue; } }