|
|
@@ -90,17 +90,26 @@ public class S8WatchSchedulerService : ITransient
|
|
|
_ruleCatalog = ruleCatalog;
|
|
|
}
|
|
|
|
|
|
- public async Task<List<S8TenantFactoryScope>> ListEnabledScopesAsync()
|
|
|
+ /// <summary>
|
|
|
+ /// S8-TENANT-ONLY-BATCH5:调度遍历改为 <b>Tenant → Rule</b>。
|
|
|
+ ///
|
|
|
+ /// <para>原实现返回 DISTINCT (tenant_id, factory_id),于是同一租户在 N 个工厂下会被 tick N 次;
|
|
|
+ /// 而规则的运行策略按 Batch 2 的口径每租户只有一条,重复 tick 只会互相抢同一把租约。</para>
|
|
|
+ ///
|
|
|
+ /// <para>同时删掉 <c>AND r.factory_id > 0</c>:Batch 2 供给出来的运行策略行
|
|
|
+ /// <c>factory_id</c> 恒为 0,那条谓词会让它们**永远不被调度、且不报任何错**
|
|
|
+ /// —— 页面显示已启用,实际从不执行。这是本批必须与供给同时闭合的缺口。</para>
|
|
|
+ /// </summary>
|
|
|
+ public async Task<List<long>> ListEnabledTenantsAsync()
|
|
|
{
|
|
|
- return await _ruleRep.Context.Ado.SqlQueryAsync<S8TenantFactoryScope>(
|
|
|
+ return await _ruleRep.Context.Ado.SqlQueryAsync<long>(
|
|
|
"""
|
|
|
- SELECT DISTINCT r.tenant_id AS TenantId, r.factory_id AS FactoryId
|
|
|
+ SELECT DISTINCT r.tenant_id
|
|
|
FROM ado_s8_watch_rule r
|
|
|
INNER JOIN SysTenant t ON t.Id = r.tenant_id AND t.Status = 1
|
|
|
WHERE r.enabled = 1
|
|
|
AND r.tenant_id > 0
|
|
|
- AND r.factory_id > 0
|
|
|
- ORDER BY r.tenant_id, r.factory_id
|
|
|
+ ORDER BY r.tenant_id
|
|
|
""");
|
|
|
}
|
|
|
|
|
|
@@ -122,15 +131,15 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// 经证实其唯一入口是 404 门禁的 debug controller,且 LoadExecutionRulesAsync 的过滤条件
|
|
|
/// 在当前数据下恒返回空,已整体删除。本方法现在只保留三类正式 evaluator 路径。
|
|
|
/// </summary>
|
|
|
- public async Task<List<S8WatchCreationResult>> CreateExceptionsAsync(long tenantId, long factoryId)
|
|
|
+ public async Task<List<S8WatchCreationResult>> CreateExceptionsAsync(long tenantId)
|
|
|
{
|
|
|
var results = new List<S8WatchCreationResult>();
|
|
|
|
|
|
// R6 RunId:本次 CreateExceptionsAsync 调用对应的统一关联 id,落入 detection_log。
|
|
|
var runId = Guid.NewGuid().ToString("N").Substring(0, 16);
|
|
|
- results.AddRange(await ProcessRulesByTypeAsync(tenantId, factoryId, _timeoutEvaluator, S8TimeoutRuleEvaluator.RuleTypeCode, runId));
|
|
|
- results.AddRange(await ProcessRulesByTypeAsync(tenantId, factoryId, _shortageEvaluator, S8ShortageRuleEvaluator.RuleTypeCode, runId));
|
|
|
- results.AddRange(await ProcessRulesByTypeAsync(tenantId, factoryId, _outOfRangeEvaluator, S8OutOfRangeRuleEvaluator.RuleTypeCode, runId));
|
|
|
+ results.AddRange(await ProcessRulesByTypeAsync(tenantId, _timeoutEvaluator, S8TimeoutRuleEvaluator.RuleTypeCode, runId));
|
|
|
+ results.AddRange(await ProcessRulesByTypeAsync(tenantId, _shortageEvaluator, S8ShortageRuleEvaluator.RuleTypeCode, runId));
|
|
|
+ results.AddRange(await ProcessRulesByTypeAsync(tenantId, _outOfRangeEvaluator, S8OutOfRangeRuleEvaluator.RuleTypeCode, runId));
|
|
|
|
|
|
return results;
|
|
|
}
|
|
|
@@ -138,14 +147,14 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// <summary>
|
|
|
/// R2 TIMEOUT 类规则主链:薄包装,复用 <see cref="ProcessRulesByTypeAsync"/>。RunId 由内部生成。
|
|
|
/// </summary>
|
|
|
- public Task<List<S8WatchCreationResult>> ProcessTimeoutRulesAsync(long tenantId, long factoryId) =>
|
|
|
- ProcessRulesByTypeAsync(tenantId, factoryId, _timeoutEvaluator, S8TimeoutRuleEvaluator.RuleTypeCode, Guid.NewGuid().ToString("N").Substring(0, 16));
|
|
|
+ public Task<List<S8WatchCreationResult>> ProcessTimeoutRulesAsync(long tenantId) =>
|
|
|
+ ProcessRulesByTypeAsync(tenantId, _timeoutEvaluator, S8TimeoutRuleEvaluator.RuleTypeCode, Guid.NewGuid().ToString("N").Substring(0, 16));
|
|
|
|
|
|
/// <summary>
|
|
|
/// R3 SHORTAGE 类规则主链:薄包装,复用 <see cref="ProcessRulesByTypeAsync"/>。RunId 由内部生成。
|
|
|
/// </summary>
|
|
|
- public Task<List<S8WatchCreationResult>> ProcessShortageRulesAsync(long tenantId, long factoryId) =>
|
|
|
- ProcessRulesByTypeAsync(tenantId, factoryId, _shortageEvaluator, S8ShortageRuleEvaluator.RuleTypeCode, Guid.NewGuid().ToString("N").Substring(0, 16));
|
|
|
+ public Task<List<S8WatchCreationResult>> ProcessShortageRulesAsync(long tenantId) =>
|
|
|
+ ProcessRulesByTypeAsync(tenantId, _shortageEvaluator, S8ShortageRuleEvaluator.RuleTypeCode, Guid.NewGuid().ToString("N").Substring(0, 16));
|
|
|
|
|
|
/// <summary>
|
|
|
/// R3-OUT_OF_RANGE-REWRITE-1:OUT_OF_RANGE 类规则主链。
|
|
|
@@ -153,8 +162,8 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// (source_rule_id=rule.Id AND related_object_code=hit AND status!=CLOSED AND dedup_key IS NULL AND is_deleted=0)
|
|
|
/// 命中则 backfill 6 列,避免重复建单。RunId 由内部生成。
|
|
|
/// </summary>
|
|
|
- public Task<List<S8WatchCreationResult>> ProcessOutOfRangeRulesAsync(long tenantId, long factoryId) =>
|
|
|
- ProcessRulesByTypeAsync(tenantId, factoryId, _outOfRangeEvaluator, S8OutOfRangeRuleEvaluator.RuleTypeCode, Guid.NewGuid().ToString("N").Substring(0, 16));
|
|
|
+ public Task<List<S8WatchCreationResult>> ProcessOutOfRangeRulesAsync(long tenantId) =>
|
|
|
+ ProcessRulesByTypeAsync(tenantId, _outOfRangeEvaluator, S8OutOfRangeRuleEvaluator.RuleTypeCode, Guid.NewGuid().ToString("N").Substring(0, 16));
|
|
|
|
|
|
/// <summary>
|
|
|
/// R2/R3 通用规则主链。S8-SCHED-CLEANUP-LEGACY-PATH-1:本方法已收敛为 thin wrapper,
|
|
|
@@ -171,13 +180,12 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// 与正在持锁运行的 Job 撕扯。
|
|
|
/// </summary>
|
|
|
private async Task<List<S8WatchCreationResult>> ProcessRulesByTypeAsync(
|
|
|
- long tenantId, long factoryId, IS8RuleEvaluator evaluator, string ruleType, string runId)
|
|
|
+ long tenantId, IS8RuleEvaluator evaluator, string ruleType, string runId)
|
|
|
{
|
|
|
var aggregate = new List<S8WatchCreationResult>();
|
|
|
|
|
|
var rules = await _ruleRep.AsQueryable()
|
|
|
.Where(x => x.TenantId == tenantId
|
|
|
- && x.FactoryId == factoryId
|
|
|
&& x.Enabled
|
|
|
&& x.RuleType == ruleType)
|
|
|
.ToListAsync();
|
|
|
@@ -214,7 +222,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
S8RuleRunResult completion;
|
|
|
try
|
|
|
{
|
|
|
- var ruleResults = await ProcessSingleRuleAsync(tenantId, factoryId, rule, ruleType, evaluator, runId);
|
|
|
+ var ruleResults = await ProcessSingleRuleAsync(tenantId, rule, ruleType, evaluator, runId);
|
|
|
aggregate.AddRange(ruleResults);
|
|
|
completion = new S8RuleRunResult
|
|
|
{
|
|
|
@@ -316,12 +324,11 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// 注意:本函数不消费 trigger_count_required;上游决定是否进入 CreateFromHitAsync。
|
|
|
/// </summary>
|
|
|
private async Task<(AdoS8RuleDetectionState? state, int hitCount)> UpsertDetectionStateOnHitAsync(
|
|
|
- long tenantId, long factoryId, AdoS8WatchRule rule, S8RuleHit hit)
|
|
|
+ long tenantId, AdoS8WatchRule rule, S8RuleHit hit)
|
|
|
{
|
|
|
var now = DateTime.Now;
|
|
|
var existing = await _detectionStateRep.AsQueryable()
|
|
|
.Where(x => x.TenantId == tenantId
|
|
|
- && x.FactoryId == factoryId
|
|
|
&& x.RuleCode == rule.RuleCode
|
|
|
&& x.DedupKey == hit.DedupKey)
|
|
|
.FirstAsync();
|
|
|
@@ -331,7 +338,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
var fresh = new AdoS8RuleDetectionState
|
|
|
{
|
|
|
TenantId = tenantId,
|
|
|
- FactoryId = factoryId,
|
|
|
+ FactoryId = rule.FactoryId,
|
|
|
RuleCode = rule.RuleCode,
|
|
|
DedupKey = hit.DedupKey,
|
|
|
SourceObjectType = string.IsNullOrEmpty(hit.SourceObjectType) ? null : hit.SourceObjectType,
|
|
|
@@ -368,11 +375,10 @@ public class S8WatchSchedulerService : ITransient
|
|
|
return (existing, newHitCount);
|
|
|
}
|
|
|
|
|
|
- private async Task<long> FindOpenExceptionByDedupKeyAsync(long tenantId, long factoryId, string dedupKey)
|
|
|
+ private async Task<long> FindOpenExceptionByDedupKeyAsync(long tenantId, string dedupKey)
|
|
|
{
|
|
|
var ids = await _exceptionRep.AsQueryable()
|
|
|
.Where(x => x.TenantId == tenantId
|
|
|
- && x.FactoryId == factoryId
|
|
|
&& !x.IsDeleted
|
|
|
&& x.Status != "CLOSED"
|
|
|
&& x.DedupKey == dedupKey)
|
|
|
@@ -386,12 +392,11 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// R3 OUT_OF_RANGE compat fallback 查找:用 (source_rule_id, related_object_code, status!=CLOSED,
|
|
|
/// dedup_key IS NULL, is_deleted=0) 严格条件定位旧 AlertRule 主链留下的历史记录。
|
|
|
/// </summary>
|
|
|
- private async Task<long> FindLegacyOutOfRangeExceptionAsync(long tenantId, long factoryId, long sourceRuleId, string relatedObjectCode)
|
|
|
+ private async Task<long> FindLegacyOutOfRangeExceptionAsync(long tenantId, long sourceRuleId, string relatedObjectCode)
|
|
|
{
|
|
|
if (string.IsNullOrWhiteSpace(relatedObjectCode)) return 0L;
|
|
|
var ids = await _exceptionRep.AsQueryable()
|
|
|
.Where(x => x.TenantId == tenantId
|
|
|
- && x.FactoryId == factoryId
|
|
|
&& !x.IsDeleted
|
|
|
&& x.Status != "CLOSED"
|
|
|
&& x.DedupKey == null
|
|
|
@@ -432,7 +437,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// R6 返回 recoveredIds 供上游决定是否写 NO_HIT 日志,并对每个 recovered exception 写一条 RECOVERED 日志。
|
|
|
/// </summary>
|
|
|
private async Task<List<long>> ReconcileRecoveriesForRuleAsync(
|
|
|
- long tenantId, long factoryId, AdoS8WatchRule rule, string ruleType, List<S8RuleHit> hits, string runId)
|
|
|
+ long tenantId, AdoS8WatchRule rule, string ruleType, List<S8RuleHit> hits, string runId)
|
|
|
{
|
|
|
var hitDedupKeys = hits
|
|
|
.Where(h => !string.IsNullOrWhiteSpace(h.DedupKey))
|
|
|
@@ -441,7 +446,6 @@ public class S8WatchSchedulerService : ITransient
|
|
|
|
|
|
var candidates = await _exceptionRep.AsQueryable()
|
|
|
.Where(x => x.TenantId == tenantId
|
|
|
- && x.FactoryId == factoryId
|
|
|
&& !x.IsDeleted
|
|
|
&& x.Status != "CLOSED"
|
|
|
&& x.SourceRuleCode == rule.RuleCode
|
|
|
@@ -484,8 +488,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
UpdatedAt = now
|
|
|
})
|
|
|
.Where(x => x.TenantId == tenantId
|
|
|
- && x.FactoryId == factoryId
|
|
|
- && x.RuleCode == rule.RuleCode
|
|
|
+ && x.RuleCode == rule.RuleCode
|
|
|
&& x.DedupKey == c.DedupKey)
|
|
|
.ExecuteCommandAsync();
|
|
|
|
|
|
@@ -509,7 +512,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
|
|
|
await WriteDetectionLogAsync(new AdoS8DetectionLog
|
|
|
{
|
|
|
- TenantId = tenantId, FactoryId = factoryId,
|
|
|
+ TenantId = tenantId, FactoryId = rule.FactoryId,
|
|
|
RuleId = rule.Id, RuleCode = rule.RuleCode, RuleType = ruleType, SceneCode = rule.SceneCode,
|
|
|
SourceObjectType = c.SourceObjectType, SourceObjectId = c.SourceObjectId,
|
|
|
RelatedObjectCode = c.RelatedObjectCode, DedupKey = c.DedupKey,
|
|
|
@@ -537,10 +540,10 @@ public class S8WatchSchedulerService : ITransient
|
|
|
|
|
|
/// <summary>R6 通用 hit 日志构造(CREATED / REFRESHED 共用)。</summary>
|
|
|
private static AdoS8DetectionLog BuildHitLog(
|
|
|
- long tenantId, long factoryId, AdoS8WatchRule rule, string ruleType, S8RuleHit hit,
|
|
|
+ long tenantId, AdoS8WatchRule rule, string ruleType, S8RuleHit hit,
|
|
|
string detectResult, long exceptionId, string runId) => new()
|
|
|
{
|
|
|
- TenantId = tenantId, FactoryId = factoryId,
|
|
|
+ TenantId = tenantId, FactoryId = rule.FactoryId,
|
|
|
RuleId = rule.Id, RuleCode = rule.RuleCode, RuleType = ruleType, SceneCode = rule.SceneCode,
|
|
|
SourceObjectType = hit.SourceObjectType, SourceObjectId = hit.SourceObjectId,
|
|
|
RelatedObjectCode = hit.RelatedObjectCode, DedupKey = hit.DedupKey,
|
|
|
@@ -576,7 +579,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// 业务侧 RefreshDetectionAsync / BackfillLegacyExceptionAsync 不在限频范围。
|
|
|
/// </summary>
|
|
|
private async Task<bool> HasRecentRefreshedDetectionLogAsync(
|
|
|
- long tenantId, long factoryId, string ruleCode, string dedupKey, DateTime detectedAt)
|
|
|
+ long tenantId, string ruleCode, string dedupKey, DateTime detectedAt)
|
|
|
{
|
|
|
if (string.IsNullOrWhiteSpace(ruleCode) || string.IsNullOrWhiteSpace(dedupKey))
|
|
|
return false;
|
|
|
@@ -586,8 +589,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
{
|
|
|
return await _detectionLogRep.AsQueryable()
|
|
|
.Where(x => x.TenantId == tenantId
|
|
|
- && x.FactoryId == factoryId
|
|
|
- && x.DedupKey == dedupKey
|
|
|
+ && x.DedupKey == dedupKey
|
|
|
&& x.RuleCode == ruleCode
|
|
|
&& x.DetectResult == DetectResultRefreshed
|
|
|
&& x.DetectedAt >= cutoff)
|
|
|
@@ -721,7 +723,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// S8-SCHED-EXEC-1:释放过期 lease(lock_until < NOW),不修改 last_status / last_error,仅清空 lock 三件套 + running_started_at。
|
|
|
/// 返回释放的行数。
|
|
|
/// </summary>
|
|
|
- public async Task<int> ResetExpiredLeasesAsync(long tenantId, long factoryId)
|
|
|
+ public async Task<int> ResetExpiredLeasesAsync(long tenantId)
|
|
|
{
|
|
|
var now = DateTime.Now;
|
|
|
var affected = await _ruleRep.Context.Updateable<AdoS8WatchRule>()
|
|
|
@@ -734,15 +736,14 @@ public class S8WatchSchedulerService : ITransient
|
|
|
UpdatedAt = now
|
|
|
})
|
|
|
.Where(x => x.TenantId == tenantId
|
|
|
- && x.FactoryId == factoryId
|
|
|
&& x.LockUntil != null
|
|
|
&& x.LockUntil < now)
|
|
|
.ExecuteCommandAsync();
|
|
|
if (affected > 0)
|
|
|
{
|
|
|
_logger.LogWarning(
|
|
|
- "lease_reset tenantId={Tenant} factoryId={Factory} releasedCount={Count}",
|
|
|
- tenantId, factoryId, affected);
|
|
|
+ "lease_reset tenantId={Tenant} releasedCount={Count}",
|
|
|
+ tenantId, affected);
|
|
|
}
|
|
|
return affected;
|
|
|
}
|
|
|
@@ -755,14 +756,13 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// 抢锁回写:lock_token / locked_by / lock_until = NOW + 5min / running_started_at = NOW / last_run_id = runId。
|
|
|
/// affectedRows == 1 才算抢到;后续 OnRuleCompletedAsync 必须按 lockToken 回写,避免旧进程覆盖新 lease。
|
|
|
/// </summary>
|
|
|
- public async Task<List<S8RuleLease>> PickReadyRulesAsync(long tenantId, long factoryId, int batchSize, string lockedBy, string runId)
|
|
|
+ public async Task<List<S8RuleLease>> PickReadyRulesAsync(long tenantId, int batchSize, string lockedBy, string runId)
|
|
|
{
|
|
|
if (batchSize <= 0) batchSize = 16;
|
|
|
var now = DateTime.Now;
|
|
|
|
|
|
var candidates = await _ruleRep.AsQueryable()
|
|
|
.Where(x => x.TenantId == tenantId
|
|
|
- && x.FactoryId == factoryId
|
|
|
&& x.Enabled
|
|
|
// S8-STANDARD-DATASET-HARD-CUTOVER-1:取数唯一依赖 dataset_code。
|
|
|
// 保留这条谓词而非依赖 Enable Gate:Gate 只在"切换启用"那一刻生效,
|
|
|
@@ -830,24 +830,24 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// S8-SCHED-EXEC-1:执行单条已抢锁规则的 evaluator → 抗抖去重 → 建单/刷新 → 恢复 reconcile。
|
|
|
/// 不释放 lease(OnRuleCompletedAsync 负责);evaluator 抛异常时 Result.Success=false 并保留 ErrorMessage。
|
|
|
/// </summary>
|
|
|
- public async Task<S8RuleRunResult> RunSingleRuleAsync(long tenantId, long factoryId, S8RuleLease lease)
|
|
|
+ public async Task<S8RuleRunResult> RunSingleRuleAsync(long tenantId, S8RuleLease lease)
|
|
|
{
|
|
|
// S8-P0-1-SCHEDULER-TRUSTED-SCOPE-1:执行入口必须按本次 tick 的可信作用域绑行。
|
|
|
// 原实现只按 lease.RuleId 装载,不校验 rule 归属;一旦 lease 来源被改写 / 未来新增调用方,
|
|
|
// 就会在 A 租户的 scope 上下文里执行 B 租户的规则,而下游全部以传入的 tenantId/factoryId 落库。
|
|
|
// 这里同时做「谓词绑行」与「归属复核」两层,任一不符即 fail-fast,绝不静默降级。
|
|
|
var rule = await _ruleRep.AsQueryable()
|
|
|
- .Where(x => x.Id == lease.RuleId && x.TenantId == tenantId && x.FactoryId == factoryId)
|
|
|
+ .Where(x => x.Id == lease.RuleId && x.TenantId == tenantId)
|
|
|
.FirstAsync();
|
|
|
if (rule == null)
|
|
|
{
|
|
|
return new S8RuleRunResult { Success = false, ErrorMessage = "rule_not_found", Stats = new() };
|
|
|
}
|
|
|
- if (rule.TenantId != tenantId || rule.FactoryId != factoryId)
|
|
|
+ if (rule.TenantId != tenantId)
|
|
|
{
|
|
|
_logger.LogError(
|
|
|
- "rule_scope_mismatch ruleId={RuleId} ruleTenant={RuleTenant} ruleFactory={RuleFactory} scopeTenant={ScopeTenant} scopeFactory={ScopeFactory}",
|
|
|
- rule.Id, rule.TenantId, rule.FactoryId, tenantId, factoryId);
|
|
|
+ "rule_scope_mismatch ruleId={RuleId} ruleTenant={RuleTenant} scopeTenant={ScopeTenant}",
|
|
|
+ rule.Id, rule.TenantId, tenantId);
|
|
|
return new S8RuleRunResult { Success = false, ErrorMessage = "rule_scope_mismatch", Stats = new() };
|
|
|
}
|
|
|
|
|
|
@@ -880,7 +880,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- var results = await ProcessSingleRuleAsync(tenantId, factoryId, rule, ruleType, evaluator, lease.RunId);
|
|
|
+ var results = await ProcessSingleRuleAsync(tenantId, rule, ruleType, evaluator, lease.RunId);
|
|
|
var stats = new S8RuleRunStats
|
|
|
{
|
|
|
Hits = results.Count,
|
|
|
@@ -907,21 +907,21 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// 与 ProcessRulesByTypeAsync 内单规则循环体语义一致;此处抽出便于新调度路径直接调用单条 rule。
|
|
|
/// </summary>
|
|
|
private async Task<List<S8WatchCreationResult>> ProcessSingleRuleAsync(
|
|
|
- long tenantId, long factoryId, AdoS8WatchRule rule, string ruleType,
|
|
|
+ long tenantId, AdoS8WatchRule rule, string ruleType,
|
|
|
IS8RuleEvaluator evaluator, string runId)
|
|
|
{
|
|
|
var results = new List<S8WatchCreationResult>();
|
|
|
List<S8RuleHit> hits;
|
|
|
try
|
|
|
{
|
|
|
- hits = await evaluator.EvaluateAsync(tenantId, factoryId, rule);
|
|
|
+ hits = await evaluator.EvaluateAsync(tenantId, rule);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
var failureReason = ex is S8RuleEvaluatorException sre ? sre.Reason : ex.GetType().Name;
|
|
|
await WriteDetectionLogAsync(new AdoS8DetectionLog
|
|
|
{
|
|
|
- TenantId = tenantId, FactoryId = factoryId,
|
|
|
+ TenantId = tenantId, FactoryId = rule.FactoryId,
|
|
|
RuleId = rule.Id, RuleCode = rule.RuleCode, RuleType = ruleType, SceneCode = rule.SceneCode,
|
|
|
SourceObjectType = rule.SourceObjectType,
|
|
|
DetectResult = DetectResultEvaluateFailed,
|
|
|
@@ -937,7 +937,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
List<long> recoveredIds;
|
|
|
try
|
|
|
{
|
|
|
- recoveredIds = await ReconcileRecoveriesForRuleAsync(tenantId, factoryId, rule, ruleType, hits, runId);
|
|
|
+ recoveredIds = await ReconcileRecoveriesForRuleAsync(tenantId, rule, ruleType, hits, runId);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -961,7 +961,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
long matchedId;
|
|
|
try
|
|
|
{
|
|
|
- matchedId = await FindOpenExceptionByDedupKeyAsync(tenantId, factoryId, hit.DedupKey);
|
|
|
+ matchedId = await FindOpenExceptionByDedupKeyAsync(tenantId, hit.DedupKey);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -974,9 +974,9 @@ public class S8WatchSchedulerService : ITransient
|
|
|
try
|
|
|
{
|
|
|
await RefreshDetectionAsync(matchedId, hit);
|
|
|
- if (!await HasRecentRefreshedDetectionLogAsync(tenantId, factoryId, rule.RuleCode, hit.DedupKey, hit.DetectedAt))
|
|
|
+ if (!await HasRecentRefreshedDetectionLogAsync(tenantId, rule.RuleCode, hit.DedupKey, hit.DetectedAt))
|
|
|
{
|
|
|
- await WriteDetectionLogAsync(BuildHitLog(tenantId, factoryId, rule, ruleType, hit, DetectResultRefreshed, matchedId, runId));
|
|
|
+ await WriteDetectionLogAsync(BuildHitLog(tenantId, rule, ruleType, hit, DetectResultRefreshed, matchedId, runId));
|
|
|
}
|
|
|
results.Add(BuildSkippedDuplicate(rule, hit, matchedId));
|
|
|
}
|
|
|
@@ -992,7 +992,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
long compatId;
|
|
|
try
|
|
|
{
|
|
|
- compatId = await FindLegacyOutOfRangeExceptionAsync(tenantId, factoryId, rule.Id, hit.RelatedObjectCode);
|
|
|
+ compatId = await FindLegacyOutOfRangeExceptionAsync(tenantId, rule.Id, hit.RelatedObjectCode);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -1004,9 +1004,9 @@ public class S8WatchSchedulerService : ITransient
|
|
|
try
|
|
|
{
|
|
|
await BackfillLegacyExceptionAsync(compatId, hit);
|
|
|
- if (!await HasRecentRefreshedDetectionLogAsync(tenantId, factoryId, rule.RuleCode, hit.DedupKey, hit.DetectedAt))
|
|
|
+ if (!await HasRecentRefreshedDetectionLogAsync(tenantId, rule.RuleCode, hit.DedupKey, hit.DetectedAt))
|
|
|
{
|
|
|
- await WriteDetectionLogAsync(BuildHitLog(tenantId, factoryId, rule, ruleType, hit, DetectResultRefreshed, compatId, runId));
|
|
|
+ await WriteDetectionLogAsync(BuildHitLog(tenantId, rule, ruleType, hit, DetectResultRefreshed, compatId, runId));
|
|
|
}
|
|
|
results.Add(BuildSkippedDuplicate(rule, hit, compatId));
|
|
|
}
|
|
|
@@ -1022,9 +1022,12 @@ public class S8WatchSchedulerService : ITransient
|
|
|
try
|
|
|
{
|
|
|
typeExists = await _exceptionTypeRep.AsQueryable()
|
|
|
+ // S8-TENANT-ONLY-BATCH5:去掉工厂条件。这是一次**存在性**检查,
|
|
|
+ // 少一个条件只会更宽松:factory_id=0 的供给规则也能看到本租户的工厂级异常类型行,
|
|
|
+ // 否则它只能匹配到平台默认行、找不到就整条规则 exception_type_missing。
|
|
|
+ // 注意 t.TenantId == 0 是平台默认哨兵(S8ConfigScope),不是工厂语义,保留。
|
|
|
.Where(t => t.TypeCode == hit.ExceptionTypeCode
|
|
|
&& (t.TenantId == 0 || t.TenantId == tenantId)
|
|
|
- && (t.FactoryId == 0 || t.FactoryId == factoryId)
|
|
|
&& t.Enabled)
|
|
|
.AnyAsync();
|
|
|
}
|
|
|
@@ -1044,7 +1047,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
AdoS8RuleDetectionState? state;
|
|
|
try
|
|
|
{
|
|
|
- (state, hitCount) = await UpsertDetectionStateOnHitAsync(tenantId, factoryId, rule, hit);
|
|
|
+ (state, hitCount) = await UpsertDetectionStateOnHitAsync(tenantId, rule, hit);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -1064,7 +1067,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- var entity = await _reportDispatcher.CreateFromHitAsync(tenantId, factoryId, hit);
|
|
|
+ var entity = await _reportDispatcher.CreateFromHitAsync(tenantId, rule.FactoryId, hit);
|
|
|
await _exceptionRep.Context.Updateable<AdoS8Exception>()
|
|
|
.SetColumns(x => new AdoS8Exception
|
|
|
{
|
|
|
@@ -1085,7 +1088,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
.Where(x => x.Id == state.Id)
|
|
|
.ExecuteCommandAsync();
|
|
|
}
|
|
|
- await WriteDetectionLogAsync(BuildHitLog(tenantId, factoryId, rule, ruleType, hit, DetectResultCreated, entity.Id, runId));
|
|
|
+ await WriteDetectionLogAsync(BuildHitLog(tenantId, rule, ruleType, hit, DetectResultCreated, entity.Id, runId));
|
|
|
await TryDispatchLayerNotificationAsync(entity);
|
|
|
results.Add(BuildCreatedResult(rule, hit, entity.Id));
|
|
|
}
|
|
|
@@ -1103,7 +1106,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// 必须 WHERE id = lease.RuleId AND lock_token = lease.LockToken;affectedRows = 0 视为 lease 丢失,记录 Warning,不覆盖状态。
|
|
|
/// 失败 ≥ 阈值(默认 3)写 paused_until = NOW + 1h。
|
|
|
/// </summary>
|
|
|
- public async Task OnRuleCompletedAsync(long tenantId, long factoryId, S8RuleLease lease, S8RuleRunResult result, int durationMs)
|
|
|
+ public async Task OnRuleCompletedAsync(long tenantId, S8RuleLease lease, S8RuleRunResult result, int durationMs)
|
|
|
{
|
|
|
var rule = await _ruleRep.AsQueryable().Where(x => x.Id == lease.RuleId).FirstAsync();
|
|
|
if (rule == null)
|
|
|
@@ -1184,7 +1187,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
/// 3) 每条 rule 独立 try/catch 调用 RunSingleRuleAsync + OnRuleCompletedAsync
|
|
|
/// 单条规则失败不影响其他规则;整 tick 不抛异常。
|
|
|
/// </summary>
|
|
|
- public async Task<S8DispatchTickResult> RunDispatchTickAsync(long tenantId, long factoryId, int batchSize, string lockedBy)
|
|
|
+ public async Task<S8DispatchTickResult> RunDispatchTickAsync(long tenantId, int batchSize, string lockedBy)
|
|
|
{
|
|
|
var tickId = Guid.NewGuid().ToString("N").Substring(0, 8);
|
|
|
var runId = Guid.NewGuid().ToString("N").Substring(0, 16);
|
|
|
@@ -1192,7 +1195,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- summary.LeaseReleased = await ResetExpiredLeasesAsync(tenantId, factoryId);
|
|
|
+ summary.LeaseReleased = await ResetExpiredLeasesAsync(tenantId);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -1202,7 +1205,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
List<S8RuleLease> leases;
|
|
|
try
|
|
|
{
|
|
|
- leases = await PickReadyRulesAsync(tenantId, factoryId, batchSize, lockedBy, runId);
|
|
|
+ leases = await PickReadyRulesAsync(tenantId, batchSize, lockedBy, runId);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -1217,7 +1220,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
S8RuleRunResult runResult;
|
|
|
try
|
|
|
{
|
|
|
- runResult = await RunSingleRuleAsync(tenantId, factoryId, lease);
|
|
|
+ runResult = await RunSingleRuleAsync(tenantId, lease);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -1226,7 +1229,7 @@ public class S8WatchSchedulerService : ITransient
|
|
|
sw.Stop();
|
|
|
try
|
|
|
{
|
|
|
- await OnRuleCompletedAsync(tenantId, factoryId, lease, runResult, (int)sw.ElapsedMilliseconds);
|
|
|
+ await OnRuleCompletedAsync(tenantId, lease, runResult, (int)sw.ElapsedMilliseconds);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|