MdpNightlyFullRebuildJob.cs 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. using Furion.Schedule;
  2. using Microsoft.Extensions.DependencyInjection;
  3. using Microsoft.Extensions.Logging;
  4. using System.Text.Json;
  5. using Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild;
  6. namespace Admin.NET.Plugin.AiDOP.Job;
  7. /// <summary>
  8. /// S1–S7 夜间串行全量兜底。triggerType 用 <see cref="ModuleRebuildTriggerType.AutoNightly"/>,
  9. /// 穿透 AUTO 冷却窗口;串行靠 <c>GlobalMaxParallelScopes = 1</c>,本 Job 一次性入队全部 scope。
  10. ///
  11. /// <para><b>扇出前先清自己上一轮未开跑的入队</b>:日度全量重算的排队记录过了当天就没有价值,
  12. /// 今天这一轮天然取代昨天那一轮。不清的后果实测过——2026-09-28 有 28 条 03:40 入队的
  13. /// <c>AUTO_NIGHTLY</c> 因执行机指派丢失而无人可领,一直挡着同 scope 的后续入队。
  14. /// 只清 <c>QUEUED</c> 且无 <c>requested_by</c> 的行,RUNNING 与页面手工任务一律不碰。</para>
  15. [JobDetail("job_mdp_nightly_full_rebuild",
  16. Description = "S1–S7 夜间串行全量兜底(穿透 AUTO 冷却窗口)",
  17. GroupName = "default",
  18. Concurrent = false)]
  19. [Cron("40 3 * * *",
  20. TriggerId = "trigger_mdp_nightly_full_rebuild",
  21. Description = "每日 03:40 触发(5 字段:分 时 日 月 周)")]
  22. public class MdpNightlyFullRebuildJob : IJob
  23. {
  24. private static readonly string[] Modules = ["S1", "S2", "S3", "S4", "S5", "S6", "S7"];
  25. private readonly IServiceScopeFactory _scopeFactory;
  26. private readonly ILogger _logger;
  27. public MdpNightlyFullRebuildJob(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory)
  28. {
  29. _scopeFactory = scopeFactory;
  30. _logger = loggerFactory.CreateLogger(nameof(MdpNightlyFullRebuildJob));
  31. }
  32. public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
  33. {
  34. if (!AidopJobGate.ShouldRun(nameof(MdpNightlyFullRebuildJob), _logger)) return;
  35. using var scope = _scopeFactory.CreateScope();
  36. try
  37. {
  38. var stale = await scope.ServiceProvider.GetRequiredService<IModuleRebuildJobStore>()
  39. .CancelUnstartedByTriggerAsync(
  40. ModuleRebuildTriggerType.AutoNightly,
  41. TimeSpan.Zero,
  42. "已被今日夜间全量取代(上一轮未开跑)",
  43. stoppingToken);
  44. if (stale > 0)
  45. _logger.LogWarning(
  46. "MdpNightlyFullRebuildJob 清理上一轮未开跑的 AUTO_NIGHTLY {Count} 条。"
  47. + "条数偏高说明队列此前长期无人消费,请查 MDP 运行监控页的执行机指派",
  48. stale);
  49. foreach (var module in Modules)
  50. {
  51. stoppingToken.ThrowIfCancellationRequested();
  52. var payload = await ModuleRebuildJobRunner.EnqueueEnabledScopesAsync(
  53. scope.ServiceProvider, module, ModuleRebuildTriggerType.AutoNightly, stoppingToken, _logger);
  54. _logger.LogInformation("MdpNightlyFullRebuildJob {Module} 入队 {Payload}", module, JsonSerializer.Serialize(payload));
  55. }
  56. }
  57. catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
  58. {
  59. _logger.LogInformation("MdpNightlyFullRebuildJob 收到停止信号,本轮结束");
  60. throw;
  61. }
  62. catch (Exception ex)
  63. {
  64. _logger.LogError(ex, "MdpNightlyFullRebuildJob 入队失败");
  65. }
  66. }
  67. }