ModuleRebuildStore.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. using System.Threading.Channels;
  2. using Admin.NET.Plugin.AiDOP.Entity.SmartOps;
  3. namespace Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild;
  4. public sealed class ModuleRebuildQueue : ISingleton
  5. {
  6. private readonly Channel<byte> _channel = Channel.CreateBounded<byte>(new BoundedChannelOptions(8)
  7. {
  8. FullMode = BoundedChannelFullMode.DropOldest,
  9. SingleReader = true,
  10. SingleWriter = false
  11. });
  12. public void Pulse() => _channel.Writer.TryWrite(0);
  13. public ChannelReader<byte> Reader => _channel.Reader;
  14. }
  15. public sealed class ModuleRebuildJobAccepted
  16. {
  17. public bool Ok { get; set; }
  18. public string ModuleCode { get; set; } = string.Empty;
  19. public long? JobId { get; set; }
  20. public string Status { get; set; }
  21. public string Message { get; set; }
  22. }
  23. public sealed class ModuleRebuildJobDto
  24. {
  25. public bool Ok { get; set; } = true;
  26. public string ModuleCode { get; set; } = string.Empty;
  27. public long JobId { get; set; }
  28. public string Status { get; set; }
  29. public string CurrentStage { get; set; }
  30. public int StageIndex { get; set; }
  31. public int StageTotal { get; set; }
  32. public int ProgressPercent { get; set; }
  33. public string ProgressMessage { get; set; }
  34. public DateTime? LastProgressAt { get; set; }
  35. public DateTime? HeartbeatAt { get; set; }
  36. public string FailedStage { get; set; }
  37. public DateTime SubmittedAt { get; set; }
  38. public DateTime? StartedAt { get; set; }
  39. public DateTime? FinishedAt { get; set; }
  40. public int? DurationMs { get; set; }
  41. public string BatchId { get; set; }
  42. public int StageRows { get; set; }
  43. public int StandardRows { get; set; }
  44. public int DwdRows { get; set; }
  45. public int KpiRows { get; set; }
  46. public int AtomicRows { get; set; }
  47. public string DetailJson { get; set; }
  48. public string ErrorMessage { get; set; }
  49. public string Message { get; set; }
  50. }
  51. public interface IModuleRebuildJobStore
  52. {
  53. Task<AdoModuleDashboardRebuildJob> InsertQueuedAsync(AdoModuleDashboardRebuildJob row, CancellationToken ct = default);
  54. Task<AdoModuleDashboardRebuildJob> FindActiveAsync(string moduleCode, long tenantId, long factoryId, CancellationToken ct = default);
  55. Task<AdoModuleDashboardRebuildJob> GetByIdAsync(string moduleCode, long id, long tenantId, long factoryId, CancellationToken ct = default);
  56. Task<AdoModuleDashboardRebuildJob> GetLatestAsync(string moduleCode, long tenantId, long factoryId, CancellationToken ct = default);
  57. Task<AdoModuleDashboardRebuildJob> ClaimNextQueuedAsync(IReadOnlyCollection<string> enabledModules, CancellationToken ct = default);
  58. Task UpdateAsync(AdoModuleDashboardRebuildJob row, CancellationToken ct = default);
  59. Task UpdateProgressAsync(long jobId, string currentStage, int stageIndex, int progressPercent, string message, DateTime now, CancellationToken ct = default);
  60. Task UpdateStageResultAsync(long jobId, string completedStage, int rows, int progressPercent, string nextMessage, string detailJson, DateTime now, CancellationToken ct = default);
  61. Task TouchHeartbeatAsync(long jobId, DateTime now, CancellationToken ct = default);
  62. Task FailStaleRunningAsync(TimeSpan staleAfter, CancellationToken ct = default);
  63. }
  64. public sealed class ModuleRebuildJobStore : IModuleRebuildJobStore, ITransient
  65. {
  66. private readonly ISqlSugarClient _db;
  67. public ModuleRebuildJobStore(ISqlSugarClient db) => _db = db;
  68. public async Task<AdoModuleDashboardRebuildJob> InsertQueuedAsync(AdoModuleDashboardRebuildJob row, CancellationToken ct = default)
  69. {
  70. var id = await _db.Insertable(row).ExecuteReturnIdentityAsync();
  71. row.Id = id;
  72. return row;
  73. }
  74. public Task<AdoModuleDashboardRebuildJob> FindActiveAsync(string moduleCode, long tenantId, long factoryId, CancellationToken ct = default) =>
  75. _db.Queryable<AdoModuleDashboardRebuildJob>()
  76. .Where(x => x.ModuleCode == moduleCode && x.TenantId == tenantId && x.FactoryId == factoryId
  77. && (x.Status == ModuleRebuildStatus.Queued || x.Status == ModuleRebuildStatus.Running))
  78. .OrderBy(x => x.Id)
  79. .FirstAsync(ct);
  80. public Task<AdoModuleDashboardRebuildJob> GetByIdAsync(string moduleCode, long id, long tenantId, long factoryId, CancellationToken ct = default) =>
  81. _db.Queryable<AdoModuleDashboardRebuildJob>()
  82. .FirstAsync(x => x.Id == id && x.ModuleCode == moduleCode && x.TenantId == tenantId && x.FactoryId == factoryId, ct);
  83. public Task<AdoModuleDashboardRebuildJob> GetLatestAsync(string moduleCode, long tenantId, long factoryId, CancellationToken ct = default) =>
  84. _db.Queryable<AdoModuleDashboardRebuildJob>()
  85. .Where(x => x.ModuleCode == moduleCode && x.TenantId == tenantId && x.FactoryId == factoryId)
  86. .OrderBy(x => x.Id, OrderByType.Desc)
  87. .FirstAsync(ct);
  88. public async Task<AdoModuleDashboardRebuildJob> ClaimNextQueuedAsync(IReadOnlyCollection<string> enabledModules, CancellationToken ct = default)
  89. {
  90. if (enabledModules == null || enabledModules.Count == 0)
  91. return null;
  92. var enabled = enabledModules.ToList();
  93. var row = await _db.Queryable<AdoModuleDashboardRebuildJob>()
  94. .Where(x => x.Status == ModuleRebuildStatus.Queued && enabled.Contains(x.ModuleCode))
  95. .Where(x => SqlFunc.Subqueryable<AdoModuleDashboardRebuildJob>()
  96. .Where(y => y.ModuleCode == x.ModuleCode && y.TenantId == x.TenantId && y.FactoryId == x.FactoryId
  97. && y.Status == ModuleRebuildStatus.Running)
  98. .NotAny())
  99. .OrderBy(x => x.Id)
  100. .FirstAsync(ct);
  101. if (row == null)
  102. return null;
  103. var now = DateTime.Now;
  104. var n = await _db.Updateable<AdoModuleDashboardRebuildJob>()
  105. .SetColumns(x => new AdoModuleDashboardRebuildJob
  106. {
  107. Status = ModuleRebuildStatus.Running,
  108. CurrentStage = ModuleRebuildStages.AcquiringLock,
  109. StageIndex = 0,
  110. ProgressPercent = 2,
  111. ProgressMessage = $"等待现有 {row.ModuleCode} 全量任务完成",
  112. LastProgressAt = now,
  113. StartedAt = now,
  114. HeartbeatAt = now,
  115. UpdateTime = now
  116. })
  117. .Where(x => x.Id == row.Id && x.Status == ModuleRebuildStatus.Queued)
  118. .ExecuteCommandAsync(ct);
  119. if (n <= 0)
  120. return null;
  121. row.Status = ModuleRebuildStatus.Running;
  122. row.CurrentStage = ModuleRebuildStages.AcquiringLock;
  123. row.ProgressPercent = 2;
  124. row.StartedAt = now;
  125. row.HeartbeatAt = now;
  126. row.UpdateTime = now;
  127. return row;
  128. }
  129. public Task UpdateAsync(AdoModuleDashboardRebuildJob row, CancellationToken ct = default) =>
  130. _db.Updateable(row).ExecuteCommandAsync(ct);
  131. public Task UpdateProgressAsync(long jobId, string currentStage, int stageIndex, int progressPercent, string message, DateTime now, CancellationToken ct = default) =>
  132. _db.Updateable<AdoModuleDashboardRebuildJob>()
  133. .SetColumns(x => new AdoModuleDashboardRebuildJob
  134. {
  135. CurrentStage = currentStage,
  136. StageIndex = stageIndex,
  137. ProgressPercent = progressPercent,
  138. ProgressMessage = message,
  139. LastProgressAt = now,
  140. HeartbeatAt = now,
  141. UpdateTime = now
  142. })
  143. .Where(x => x.Id == jobId && x.Status == ModuleRebuildStatus.Running)
  144. .ExecuteCommandAsync(ct);
  145. public Task UpdateStageResultAsync(long jobId, string completedStage, int rows, int progressPercent, string nextMessage, string detailJson, DateTime now, CancellationToken ct = default)
  146. {
  147. var column = completedStage switch
  148. {
  149. ModuleRebuildStages.Staging or ModuleRebuildStages.T8Inbound => "stage_rows",
  150. ModuleRebuildStages.Standard or ModuleRebuildStages.KpiPreparing => "standard_rows",
  151. ModuleRebuildStages.Dwd => "dwd_rows",
  152. ModuleRebuildStages.Kpi or ModuleRebuildStages.KpiCalculating => "kpi_rows",
  153. ModuleRebuildStages.Atomic => "atomic_rows",
  154. _ => null
  155. };
  156. if (column == null)
  157. return UpdateProgressAsync(jobId, completedStage, 0, progressPercent, nextMessage, now, ct);
  158. return _db.Ado.ExecuteCommandAsync(
  159. $"""
  160. UPDATE ado_module_dashboard_rebuild_job
  161. SET `{column}`=@Rows,
  162. progress_percent=@Pct,
  163. progress_message=@Msg,
  164. detail_json=IFNULL(@Detail, detail_json),
  165. last_progress_at=@Now,
  166. heartbeat_at=@Now,
  167. update_time=@Now
  168. WHERE id=@Id AND status='RUNNING'
  169. """,
  170. new SugarParameter("@Rows", rows),
  171. new SugarParameter("@Pct", progressPercent),
  172. new SugarParameter("@Msg", nextMessage),
  173. new SugarParameter("@Detail", (object?)detailJson ?? DBNull.Value),
  174. new SugarParameter("@Now", now),
  175. new SugarParameter("@Id", jobId));
  176. }
  177. public Task TouchHeartbeatAsync(long jobId, DateTime now, CancellationToken ct = default) =>
  178. _db.Updateable<AdoModuleDashboardRebuildJob>()
  179. .SetColumns(x => new AdoModuleDashboardRebuildJob { HeartbeatAt = now, UpdateTime = now })
  180. .Where(x => x.Id == jobId && x.Status == ModuleRebuildStatus.Running)
  181. .ExecuteCommandAsync(ct);
  182. public Task FailStaleRunningAsync(TimeSpan staleAfter, CancellationToken ct = default)
  183. {
  184. var cutoff = DateTime.Now - staleAfter;
  185. var now = DateTime.Now;
  186. return _db.Updateable<AdoModuleDashboardRebuildJob>()
  187. .SetColumns(x => new AdoModuleDashboardRebuildJob
  188. {
  189. Status = ModuleRebuildStatus.Failed,
  190. CurrentStage = ModuleRebuildStages.Failed,
  191. FinishedAt = now,
  192. LastProgressAt = now,
  193. UpdateTime = now,
  194. ErrorMessage = "服务中断,任务未正常结束"
  195. })
  196. .Where(x => x.Status == ModuleRebuildStatus.Running && (x.HeartbeatAt == null || x.HeartbeatAt < cutoff))
  197. .ExecuteCommandAsync(ct);
  198. }
  199. }