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);
}
}
}