|
|
@@ -10,7 +10,7 @@ namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules;
|
|
|
/// R2 TIMEOUT 类规则 evaluator MVP。
|
|
|
/// params_json 约定(首版最小集合):
|
|
|
/// { dueAtField, statusField, completedStates[], objectCodeField, objectIdField, graceMinutes, exceptionTypeCode }
|
|
|
-/// 判定:dueAt <= now - graceMinutes && status NOT IN completedStates → HIT。
|
|
|
+/// 判定:dueAt <= now - graceMinutes 且 status 不在 completedStates 内 → HIT。
|
|
|
/// 不做严重度阶梯、不做 SLA 升级、不做事件触发。
|
|
|
/// </summary>
|
|
|
public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
@@ -39,8 +39,8 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
if (string.IsNullOrWhiteSpace(rule.Expression) || string.IsNullOrWhiteSpace(rule.ParamsJson))
|
|
|
return hits;
|
|
|
|
|
|
- TimeoutParams parameters;
|
|
|
- try { parameters = TimeoutParams.Parse(rule.ParamsJson!); }
|
|
|
+ S8TimeoutParams parameters;
|
|
|
+ try { parameters = S8TimeoutParams.Parse(rule.ParamsJson!); }
|
|
|
catch { return hits; }
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(parameters.DueAtField)
|
|
|
@@ -108,7 +108,7 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
SceneCode = rule.SceneCode,
|
|
|
Severity = string.IsNullOrWhiteSpace(rule.Severity) ? "MEDIUM" : rule.Severity,
|
|
|
DedupKey = dedupKey,
|
|
|
- SourcePayload = BuildPayload(row, due.Value, status, parameters),
|
|
|
+ SourcePayload = BuildPayload(row, sourceObjectType, sourceObjectId, due.Value, status, parameters),
|
|
|
DetectedAt = detectedAt,
|
|
|
Title = $"[超时] {sourceObjectType} {sourceObjectId} 已超期至 {due.Value:yyyy-MM-dd HH:mm:ss}(状态 {status})",
|
|
|
DataSourceId = dataSource.Id,
|
|
|
@@ -133,10 +133,11 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- private static string BuildDedupKey(long tenantId, long factoryId, string ruleCode, string sourceObjectType, string sourceObjectId) =>
|
|
|
+ /// <summary>构造 R2 dedup_key 稳定字符串:T{tenant}:F{factory}:R{ruleCode}:{sourceObjectType}:{sourceObjectId}。internal 暴露供测试。</summary>
|
|
|
+ internal static string BuildDedupKey(long tenantId, long factoryId, string ruleCode, string sourceObjectType, string sourceObjectId) =>
|
|
|
$"T{tenantId}:F{factoryId}:R{ruleCode}:{sourceObjectType}:{sourceObjectId}";
|
|
|
|
|
|
- private static string BuildPayload(DataRow row, DateTime dueAt, string status, TimeoutParams parameters)
|
|
|
+ private static string BuildPayload(DataRow row, string sourceObjectType, string sourceObjectId, DateTime dueAt, string status, S8TimeoutParams parameters)
|
|
|
{
|
|
|
var payload = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
|
|
|
foreach (DataColumn column in row.Table.Columns)
|
|
|
@@ -145,10 +146,12 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
payload[column.ColumnName] = value == DBNull.Value ? null : value;
|
|
|
}
|
|
|
payload["__ruleType"] = RuleTypeCode;
|
|
|
+ payload["__sourceObjectType"] = sourceObjectType;
|
|
|
+ payload["__sourceObjectId"] = sourceObjectId;
|
|
|
payload["__dueAt"] = dueAt;
|
|
|
payload["__status"] = status;
|
|
|
payload["__graceMinutes"] = parameters.GraceMinutes;
|
|
|
- payload["__sourceObjectType"] = parameters.ObjectIdField;
|
|
|
+ payload["__exceptionTypeCode"] = parameters.ExceptionTypeCode;
|
|
|
return JsonSerializer.Serialize(payload);
|
|
|
}
|
|
|
|
|
|
@@ -177,36 +180,4 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
|
|
|
return long.TryParse(Convert.ToString(v, CultureInfo.InvariantCulture), out var r) ? r : null;
|
|
|
}
|
|
|
|
|
|
- private sealed class TimeoutParams
|
|
|
- {
|
|
|
- public string? DueAtField { get; set; }
|
|
|
- public string? StatusField { get; set; }
|
|
|
- public List<string> CompletedStates { get; set; } = new();
|
|
|
- public string? ObjectCodeField { get; set; }
|
|
|
- public string? ObjectIdField { get; set; }
|
|
|
- public int GraceMinutes { get; set; }
|
|
|
- public string? ExceptionTypeCode { get; set; }
|
|
|
-
|
|
|
- public static TimeoutParams Parse(string json)
|
|
|
- {
|
|
|
- using var doc = JsonDocument.Parse(json);
|
|
|
- var root = doc.RootElement;
|
|
|
- var p = new TimeoutParams();
|
|
|
- if (root.TryGetProperty("dueAtField", out var v1)) p.DueAtField = v1.GetString();
|
|
|
- if (root.TryGetProperty("statusField", out var v2)) p.StatusField = v2.GetString();
|
|
|
- if (root.TryGetProperty("objectCodeField", out var v3)) p.ObjectCodeField = v3.GetString();
|
|
|
- if (root.TryGetProperty("objectIdField", out var v4)) p.ObjectIdField = v4.GetString();
|
|
|
- if (root.TryGetProperty("graceMinutes", out var v5) && v5.ValueKind == JsonValueKind.Number) p.GraceMinutes = v5.GetInt32();
|
|
|
- if (root.TryGetProperty("exceptionTypeCode", out var v6)) p.ExceptionTypeCode = v6.GetString();
|
|
|
- if (root.TryGetProperty("completedStates", out var v7) && v7.ValueKind == JsonValueKind.Array)
|
|
|
- {
|
|
|
- foreach (var s in v7.EnumerateArray())
|
|
|
- {
|
|
|
- var str = s.GetString();
|
|
|
- if (!string.IsNullOrWhiteSpace(str)) p.CompletedStates.Add(str!);
|
|
|
- }
|
|
|
- }
|
|
|
- return p;
|
|
|
- }
|
|
|
- }
|
|
|
}
|