using Admin.NET.Plugin.AiDOP.Infrastructure; using Admin.NET.Plugin.AiDOP.Order; using Admin.NET.Plugin.AiDOP.ProcurementExecution; using Admin.NET.Plugin.AiDOP.Production; using Admin.NET.Plugin.AiDOP.Supply; using Furion.Schedule; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SqlSugar; using System.Text.Json; namespace Admin.NET.Plugin.AiDOP.Job; /// /// 应用启动后按 S1→S2→S3→S4 顺序执行 MDP 全量同步,构建模块 KPI 汇总与 KPI 原子聚合层。 /// 新环境发布后无需等待 hourly 任务即可在看板筛选中看到数据。 /// [JobDetail("job_smart_ops_kpi_mdp_bootstrap", Description = "智慧运营KPI:启动时按序执行S1→S2→S3→S4 MDP同步(含原子聚合层)", GroupName = "default", Concurrent = false)] [PeriodSeconds(1, TriggerId = "trigger_smart_ops_kpi_mdp_bootstrap", Description = "应用启动后立即执行一次", MaxNumberOfRuns = 1, RunOnStart = true)] public class SmartOpsKpiMdpBootstrapJob : IJob { private const string EnvBootstrapEnabled = "AIDOP_MDP_BOOTSTRAP_ENABLED"; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; public SmartOpsKpiMdpBootstrapJob(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory) { _scopeFactory = scopeFactory; _logger = loggerFactory.CreateLogger(nameof(SmartOpsKpiMdpBootstrapJob)); } public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) { if (!IsBootstrapEnabled(Environment.GetEnvironmentVariable(EnvBootstrapEnabled))) { _logger.LogInformation( "SmartOpsKpiMdpBootstrapJob 被环境变量 {EnvironmentVariable}=false 禁用", EnvBootstrapEnabled); return; } using var scope = _scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); try { AidopTenantMigration.EnsureKpiAtomicTable(db); _logger.LogInformation("SmartOpsKpiMdpBootstrapJob: ado_smart_ops_kpi_atomic_day 已就绪"); } catch (Exception ex) { _logger.LogError(ex, "SmartOpsKpiMdpBootstrapJob: 原子聚合表初始化失败"); } await RunStepAsync("S1", stoppingToken, async sp => { var catalog = sp.GetRequiredService(); var rebuild = sp.GetRequiredService(); var scopes = await catalog.ListEnabledScopesAsync(stoppingToken); var accepted = 0; foreach (var runScope in scopes) { var (status, _) = await rebuild.EnqueueAsync(runScope.TenantId, runScope.FactoryId, null, "BOOTSTRAP", stoppingToken); if (status == 202) accepted++; } return new { scopes = scopes.Count, accepted }; }); await RunStepAsync("S2", stoppingToken, async sp => { var payload = await Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildJobRunner .EnqueueEnabledScopesAsync(sp, "S2", "BOOTSTRAP", stoppingToken, _logger); return payload; }); await RunStepAsync("S3", stoppingToken, async sp => { var payload = await Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildJobRunner .EnqueueEnabledScopesAsync(sp, "S3", "BOOTSTRAP", stoppingToken, _logger); return payload; }); await RunStepAsync("S4", stoppingToken, async sp => { var payload = await Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildJobRunner .EnqueueEnabledScopesAsync(sp, "S4", "BOOTSTRAP", stoppingToken, _logger); return payload; }); _logger.LogInformation("SmartOpsKpiMdpBootstrapJob: 全部 MDP 引导同步已完成"); } internal static bool IsBootstrapEnabled(string? raw) => !bool.TryParse(raw, out var enabled) || enabled; private async Task RunStepAsync(string step, CancellationToken stoppingToken, Func> action) { using var scope = _scopeFactory.CreateScope(); try { stoppingToken.ThrowIfCancellationRequested(); var payload = await action(scope.ServiceProvider); _logger.LogInformation("SmartOpsKpiMdpBootstrapJob {Step} 完成 {Payload}", step, JsonSerializer.Serialize(payload)); } catch (Admin.NET.Plugin.AiDOP.DataPlatform.S1Refresh.S1MdpAlreadyRunningException) { _logger.LogInformation("SmartOpsKpiMdpBootstrapJob {Step} 跳过:已有全量任务在执行", step); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { _logger.LogInformation("SmartOpsKpiMdpBootstrapJob {Step} 收到停止信号", step); throw; } catch (Exception ex) { _logger.LogError(ex, "SmartOpsKpiMdpBootstrapJob {Step} 执行失败,继续后续步骤", step); } } }