|
@@ -3,6 +3,7 @@ using System.Globalization;
|
|
|
using System.Text.Json;
|
|
using System.Text.Json;
|
|
|
using Admin.NET.Plugin.AiDOP.Entity.S8;
|
|
using Admin.NET.Plugin.AiDOP.Entity.S8;
|
|
|
using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
|
|
using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
|
|
|
|
|
+using Microsoft.Extensions.Logging;
|
|
|
using SqlSugar;
|
|
using SqlSugar;
|
|
|
|
|
|
|
|
namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules;
|
|
namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules;
|
|
@@ -22,10 +23,14 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
private const string SqlDataSourceType = "SQL";
|
|
private const string SqlDataSourceType = "SQL";
|
|
|
|
|
|
|
|
private readonly SqlSugarRepository<AdoS8DataSource> _dataSourceRep;
|
|
private readonly SqlSugarRepository<AdoS8DataSource> _dataSourceRep;
|
|
|
|
|
+ private readonly ILogger<S8TimeoutRuleEvaluator> _logger;
|
|
|
|
|
|
|
|
- public S8TimeoutRuleEvaluator(SqlSugarRepository<AdoS8DataSource> dataSourceRep)
|
|
|
|
|
|
|
+ public S8TimeoutRuleEvaluator(
|
|
|
|
|
+ SqlSugarRepository<AdoS8DataSource> dataSourceRep,
|
|
|
|
|
+ ILogger<S8TimeoutRuleEvaluator> logger)
|
|
|
{
|
|
{
|
|
|
_dataSourceRep = dataSourceRep;
|
|
_dataSourceRep = dataSourceRep;
|
|
|
|
|
+ _logger = logger;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public async Task<List<S8RuleHit>> EvaluateAsync(
|
|
public async Task<List<S8RuleHit>> EvaluateAsync(
|
|
@@ -62,10 +67,14 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
|| !string.Equals(dataSource.Type?.Trim(), SqlDataSourceType, StringComparison.OrdinalIgnoreCase))
|
|
|| !string.Equals(dataSource.Type?.Trim(), SqlDataSourceType, StringComparison.OrdinalIgnoreCase))
|
|
|
throw new S8RuleEvaluatorException("data_source_unavailable", $"TIMEOUT 规则 {rule.RuleCode} 数据源不可用(id={rule.DataSourceId})");
|
|
throw new S8RuleEvaluatorException("data_source_unavailable", $"TIMEOUT 规则 {rule.RuleCode} 数据源不可用(id={rule.DataSourceId})");
|
|
|
|
|
|
|
|
|
|
+ // S8-SQL-EVALUATOR-GUARD-P2-1:每次评估解析 timeout / maxRows(env 优先,回退代码默认)。
|
|
|
|
|
+ var timeoutSeconds = S8EvaluatorGuard.ResolveCommandTimeoutSeconds(_logger);
|
|
|
|
|
+ var maxRows = S8EvaluatorGuard.ResolveMaxRows(_logger);
|
|
|
|
|
+
|
|
|
DataTable table;
|
|
DataTable table;
|
|
|
try
|
|
try
|
|
|
{
|
|
{
|
|
|
- using var db = CreateSqlScope(dataSource.Endpoint!);
|
|
|
|
|
|
|
+ using var db = CreateSqlScope(dataSource.Endpoint!, timeoutSeconds);
|
|
|
table = await db.Ado.GetDataTableAsync(rule.Expression!);
|
|
table = await db.Ado.GetDataTableAsync(rule.Expression!);
|
|
|
}
|
|
}
|
|
|
catch (Exception ex)
|
|
catch (Exception ex)
|
|
@@ -73,6 +82,10 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
throw new S8RuleEvaluatorException("query_failed", $"TIMEOUT 规则 {rule.RuleCode} SQL 执行失败:{ex.Message}", ex);
|
|
throw new S8RuleEvaluatorException("query_failed", $"TIMEOUT 规则 {rule.RuleCode} SQL 执行失败:{ex.Message}", ex);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 超过安全上限 → result_too_many_rows(由既有 EVALUATE_FAILED 路径承接)。
|
|
|
|
|
+ // 必须置于 try-catch 之外,避免被 query_failed 误捕获再包装。
|
|
|
|
|
+ S8EvaluatorGuard.EnsureRowCountWithinLimit(table.Rows.Count, maxRows, RuleTypeCode, rule.RuleCode);
|
|
|
|
|
+
|
|
|
var detectedAt = DateTime.Now;
|
|
var detectedAt = DateTime.Now;
|
|
|
var threshold = detectedAt.AddMinutes(-parameters.GraceMinutes);
|
|
var threshold = detectedAt.AddMinutes(-parameters.GraceMinutes);
|
|
|
var sourceObjectType = string.IsNullOrWhiteSpace(rule.SourceObjectType)
|
|
var sourceObjectType = string.IsNullOrWhiteSpace(rule.SourceObjectType)
|
|
@@ -123,10 +136,10 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
return hits;
|
|
return hits;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private SqlSugarScope CreateSqlScope(string connectionString)
|
|
|
|
|
|
|
+ private SqlSugarScope CreateSqlScope(string connectionString, int commandTimeoutSeconds)
|
|
|
{
|
|
{
|
|
|
var dbType = _dataSourceRep.Context.CurrentConnectionConfig.DbType;
|
|
var dbType = _dataSourceRep.Context.CurrentConnectionConfig.DbType;
|
|
|
- return new SqlSugarScope(new ConnectionConfig
|
|
|
|
|
|
|
+ var scope = new SqlSugarScope(new ConnectionConfig
|
|
|
{
|
|
{
|
|
|
ConfigId = $"s8-timeout-eval-{Guid.NewGuid():N}",
|
|
ConfigId = $"s8-timeout-eval-{Guid.NewGuid():N}",
|
|
|
DbType = dbType,
|
|
DbType = dbType,
|
|
@@ -134,6 +147,9 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
InitKeyType = InitKeyType.Attribute,
|
|
InitKeyType = InitKeyType.Attribute,
|
|
|
IsAutoCloseConnection = true
|
|
IsAutoCloseConnection = true
|
|
|
});
|
|
});
|
|
|
|
|
+ // 该独立 scope 不继承 SqlSugarSetup.SetDbAop 的全局 30s,必须显式设置。
|
|
|
|
|
+ S8EvaluatorGuard.ApplyCommandTimeout(scope, commandTimeoutSeconds);
|
|
|
|
|
+ return scope;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>构造 R2 dedup_key 稳定字符串:T{tenant}:F{factory}:R{ruleCode}:{sourceObjectType}:{sourceObjectId}。internal 暴露供测试。</summary>
|
|
/// <summary>构造 R2 dedup_key 稳定字符串:T{tenant}:F{factory}:R{ruleCode}:{sourceObjectType}:{sourceObjectId}。internal 暴露供测试。</summary>
|