S1DashboardRebuildStore.cs 9.2 KB

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