| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- using Admin.NET.Core;
- using Admin.NET.Plugin.AiDOP.Entity;
- using SqlSugar;
- namespace Admin.NET.Plugin.AiDOP.SmartOps;
- /// <summary>分发结果:引擎、是否应写 KPI 值、指标值、分母状态。</summary>
- public sealed class KpiCalcDispatchResult
- {
- public string EngineType { get; set; } = "LEGACY_CODE";
- /// <summary>是否写 KPI 日值。FAILED 时 false → 不写、保留上一成功值。</summary>
- public bool ShouldUpsert { get; set; } = true;
- public decimal? MetricValue { get; set; }
- /// <summary>分母/结果状态:OK / NO_DATA / NO_NUMERATOR / FAILED。</summary>
- public string DenominatorStatus { get; set; } = "OK";
- }
- /// <summary>
- /// KPI 计算运行时分发器:解析当前生效配置 → LEGACY_CODE(用旧代码值) / CONFIG_SQL(只读执行 SQL) / LEGACY_TVF(用旧代码值)。
- /// 无生效配置 → 回落 LEGACY_CODE(向后兼容)。CONFIG_SQL 失败**不 fallback**、不写值、返回 FAILED(保留上一成功值)。
- /// 每次执行落 ado_smart_ops_kpi_calc_run_log(含引擎/版本/SQL 指纹/分子分母/耗时/状态)。
- /// </summary>
- public sealed class KpiCalcDispatcher : ITransient
- {
- private readonly ISqlSugarClient _db;
- private readonly AdoSmartOpsKpiCalcConfigService _configService;
- private readonly KpiSqlReadOnlyExecutor _executor;
- public KpiCalcDispatcher(ISqlSugarClient db, AdoSmartOpsKpiCalcConfigService configService, KpiSqlReadOnlyExecutor executor)
- {
- _db = db;
- _configService = configService;
- _executor = executor;
- }
- public async Task<KpiCalcDispatchResult> DispatchAsync(
- string metricCode, string moduleCode, long tenantId, long factoryId,
- DateTime bizDate, DateTime periodStart, DateTime periodEnd, string sourceZtid,
- string batchId, string triggerType,
- decimal? legacyValue, string legacyDenomStatus, CancellationToken ct)
- {
- var startedAt = DateTime.Now;
- var config = await _configService.GetActiveAsync(tenantId, metricCode);
- var engine = config?.CalcEngineType ?? "LEGACY_CODE";
- var result = new KpiCalcDispatchResult { EngineType = engine };
- // 无配置 / LEGACY_CODE / LEGACY_TVF → 用旧代码算出的值(旧 Build 已计算并传入)
- if (config == null || engine == "LEGACY_CODE" || engine == "LEGACY_TVF")
- {
- result.MetricValue = legacyValue;
- result.DenominatorStatus = legacyDenomStatus;
- result.ShouldUpsert = true;
- await WriteRunLogAsync(config, metricCode, moduleCode, tenantId, batchId, triggerType, engine, bizDate,
- startedAt, legacyValue == null ? "NO_DATA" : "SUCCESS", legacyValue, null, null, 0, null, null, null);
- return result;
- }
- // CONFIG_SQL:只读执行已发布 SQL
- var pars = new KpiSqlRunParams
- {
- TenantId = tenantId, FactoryId = factoryId, ModuleCode = moduleCode, MetricCode = metricCode,
- BizDate = bizDate, PeriodStart = periodStart, PeriodEnd = periodEnd, SourceZtid = sourceZtid,
- };
- var exec = await _executor.ExecuteAsync(config.DataSourceCode, config.SqlScript ?? "", config.TimeoutSeconds, false, pars, ct);
- switch (exec.Status)
- {
- case "SUCCESS":
- result.MetricValue = exec.MetricValue;
- result.DenominatorStatus = "OK";
- result.ShouldUpsert = true;
- break;
- case "NO_DATA":
- result.MetricValue = null;
- result.DenominatorStatus = "NO_DATA";
- result.ShouldUpsert = true; // 遵循现有契约:NO_DATA 写 null,不伪造 0
- break;
- default: // FAILED
- result.MetricValue = null;
- result.DenominatorStatus = "FAILED";
- result.ShouldUpsert = false; // 不 fallback、不写错误新值、保留上一成功值
- break;
- }
- await WriteRunLogAsync(config, metricCode, moduleCode, tenantId, batchId, triggerType, engine, bizDate,
- startedAt, exec.Status, exec.MetricValue, exec.NumeratorValue, exec.DenominatorValue,
- exec.RowCount, exec.ErrorCode, exec.ErrorMessage, exec.SqlHash);
- return result;
- }
- private async Task WriteRunLogAsync(
- AdoSmartOpsKpiCalcConfig? config, string metricCode, string moduleCode, long tenantId,
- string batchId, string triggerType, string engine, DateTime bizDate, DateTime startedAt,
- string status, decimal? metricValue, decimal? numerator, decimal? denominator, int rowCount,
- string? errorCode, string? errorMessage, string? sqlHash)
- {
- try
- {
- var finishedAt = DateTime.Now;
- var log = new AdoSmartOpsKpiCalcRunLog
- {
- TenantId = tenantId,
- BatchId = batchId,
- ModuleCode = moduleCode,
- MetricCode = metricCode,
- ConfigId = config?.Id,
- VersionNo = config?.VersionNo,
- EngineType = engine,
- DataSourceCode = config?.DataSourceCode,
- BizDate = bizDate.Date,
- StartedAt = startedAt,
- FinishedAt = finishedAt,
- DurationMs = (long)(finishedAt - startedAt).TotalMilliseconds,
- Status = status,
- RowCount = rowCount,
- MetricValue = metricValue,
- NumeratorValue = numerator,
- DenominatorValue = denominator,
- ErrorCode = errorCode,
- ErrorMessage = errorMessage,
- SqlHash = sqlHash,
- ParameterSnapshot = $"{{\"tenant_id\":{tenantId},\"metric_code\":\"{metricCode}\",\"biz_date\":\"{bizDate:yyyy-MM-dd}\"}}",
- TriggerType = triggerType,
- CreateTime = finishedAt,
- };
- // 同批次同指标唯一:存在则更新、否则插入(避免撞唯一键 uk_kpi_calc_run_metric_batch)
- var existing = await _db.Queryable<AdoSmartOpsKpiCalcRunLog>().ClearFilter<ITenantIdFilter>()
- .Where(x => x.MetricCode == metricCode && x.BatchId == batchId).FirstAsync();
- if (existing != null)
- {
- log.Id = existing.Id;
- await _db.Updateable(log).ExecuteCommandAsync();
- }
- else
- {
- await _db.Insertable(log).ExecuteCommandAsync();
- }
- }
- catch
- {
- // 运行日志失败不影响主计算链路
- }
- }
- }
|