Просмотр исходного кода

fix(s8): reduce detection log write volume

YY968XX 1 день назад
Родитель
Сommit
08bd61f62b

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.135</AssemblyVersion>
-    <FileVersion>1.0.135</FileVersion>
-    <Version>1.0.135</Version>
+    <AssemblyVersion>1.0.136</AssemblyVersion>
+    <FileVersion>1.0.136</FileVersion>
+    <Version>1.0.136</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 52 - 14
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8WatchSchedulerService.cs

@@ -39,6 +39,11 @@ public class S8WatchSchedulerService : ITransient
     private const string DetectResultNoHit = "NO_HIT";
     private const string DetectResultEvaluateFailed = "EVALUATE_FAILED";
 
+    // S8-DETECTION-LOG-WRITE-REDUCE-P1-1:REFRESHED 明细写入限频窗口。
+    // 同 (tenant, factory, rule_code, dedup_key) 在 10 分钟内最多写一条 REFRESHED detection_log;
+    // 业务侧 RefreshDetectionAsync / BackfillLegacyExceptionAsync 不受限频影响。
+    private const int RefreshedRateLimitMinutes = 10;
+
     private const string DefaultTriggerType = "VALUE_DEVIATION";
     private const string SqlDataSourceType = "SQL";
 
@@ -849,6 +854,41 @@ public class S8WatchSchedulerService : ITransient
         }
     }
 
+    /// <summary>
+    /// S8-DETECTION-LOG-WRITE-REDUCE-P1-1:REFRESHED 明细限频判定。
+    /// 查询 cutoff = detectedAt - RefreshedRateLimitMinutes 之后,
+    /// 是否已有同 (tenant_id, factory_id, rule_code, dedup_key, DetectResult=REFRESHED) 的 detection_log;
+    /// 命中既有索引 idx_s8_detection_log_dedup_time。查询失败仅 LogWarning 后返回 false,
+    /// 宁可多写一条 REFRESHED 明细,也不阻断调度主链路;ruleCode / dedupKey 空白时同样返回 false(保留写入)。
+    /// 业务侧 RefreshDetectionAsync / BackfillLegacyExceptionAsync 不在限频范围。
+    /// </summary>
+    private async Task<bool> HasRecentRefreshedDetectionLogAsync(
+        long tenantId, long factoryId, string ruleCode, string dedupKey, DateTime detectedAt)
+    {
+        if (string.IsNullOrWhiteSpace(ruleCode) || string.IsNullOrWhiteSpace(dedupKey))
+            return false;
+
+        var cutoff = detectedAt.AddMinutes(-RefreshedRateLimitMinutes);
+        try
+        {
+            return await _detectionLogRep.AsQueryable()
+                .Where(x => x.TenantId == tenantId
+                            && x.FactoryId == factoryId
+                            && x.DedupKey == dedupKey
+                            && x.RuleCode == ruleCode
+                            && x.DetectResult == DetectResultRefreshed
+                            && x.DetectedAt >= cutoff)
+                .AnyAsync();
+        }
+        catch (Exception ex)
+        {
+            _logger.LogWarning(ex,
+                "detection_log_refreshed_ratelimit_check_failed ruleCode={RuleCode} dedupKey={DedupKey}",
+                ruleCode, dedupKey);
+            return false;
+        }
+    }
+
     private static string Truncate(string? s, int max) =>
         string.IsNullOrEmpty(s) ? string.Empty : (s.Length <= max ? s : s.Substring(0, max));
 
@@ -1287,18 +1327,10 @@ public class S8WatchSchedulerService : ITransient
             recoveredIds = new();
         }
 
-        if (hits.Count == 0 && recoveredIds.Count == 0)
-        {
-            await WriteDetectionLogAsync(new AdoS8DetectionLog
-            {
-                TenantId = tenantId, FactoryId = factoryId,
-                RuleId = rule.Id, RuleCode = rule.RuleCode, RuleType = ruleType, SceneCode = rule.SceneCode,
-                SourceObjectType = rule.SourceObjectType,
-                DetectResult = DetectResultNoHit,
-                DetectedAt = DateTime.Now,
-                RunId = runId, TriggerSource = DetectionTriggerSource
-            });
-        }
+        // S8-DETECTION-LOG-WRITE-REDUCE-P1-1:NO_HIT 不再写 detection_log 明细。
+        // 规则级"被定期评估"信号由 ado_s8_watch_rule.LastRunAt / LastStatus / LastRunId / LastDurationMs
+        // 在 OnRuleCompletedAsync / ApplyRunOnceCompletionAsync 中承接,无需 detection_log 冗余记录。
+        // DetectResultNoHit 常量保留(历史数据反查 / 未来如需恢复明细写入)。
 
         foreach (var hit in hits)
         {
@@ -1324,7 +1356,10 @@ public class S8WatchSchedulerService : ITransient
                 try
                 {
                     await RefreshDetectionAsync(matchedId, hit);
-                    await WriteDetectionLogAsync(BuildHitLog(tenantId, factoryId, rule, ruleType, hit, DetectResultRefreshed, matchedId, runId));
+                    if (!await HasRecentRefreshedDetectionLogAsync(tenantId, factoryId, rule.RuleCode, hit.DedupKey, hit.DetectedAt))
+                    {
+                        await WriteDetectionLogAsync(BuildHitLog(tenantId, factoryId, rule, ruleType, hit, DetectResultRefreshed, matchedId, runId));
+                    }
                     results.Add(BuildSkippedDuplicate(rule, hit, matchedId));
                 }
                 catch (Exception ex)
@@ -1351,7 +1386,10 @@ public class S8WatchSchedulerService : ITransient
                     try
                     {
                         await BackfillLegacyExceptionAsync(compatId, hit);
-                        await WriteDetectionLogAsync(BuildHitLog(tenantId, factoryId, rule, ruleType, hit, DetectResultRefreshed, compatId, runId));
+                        if (!await HasRecentRefreshedDetectionLogAsync(tenantId, factoryId, rule.RuleCode, hit.DedupKey, hit.DetectedAt))
+                        {
+                            await WriteDetectionLogAsync(BuildHitLog(tenantId, factoryId, rule, ruleType, hit, DetectResultRefreshed, compatId, runId));
+                        }
                         results.Add(BuildSkippedDuplicate(rule, hit, compatId));
                     }
                     catch (Exception ex)