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;
}
}