AidopActionRunLogWriter.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. using System.Text.Json;
  2. using Yitter.IdGenerator;
  3. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  4. /// <summary>统一写入 aidop_action_run_log。</summary>
  5. public class AidopActionRunLogWriter : ITransient
  6. {
  7. private readonly ISqlSugarClient _db;
  8. public AidopActionRunLogWriter(ISqlSugarClient db)
  9. {
  10. _db = db;
  11. }
  12. /// <summary>
  13. /// 同租户同动作是否已有进行中的运行(用于排程单飞等互斥)。
  14. /// </summary>
  15. public async Task<bool> HasRunningAsync(string actionCode, long tenantId)
  16. {
  17. var n = await _db.Ado.GetIntAsync(
  18. """
  19. SELECT COUNT(*) FROM aidop_action_run_log
  20. WHERE tenant_id = @TenantId AND action_code = @ActionCode AND status = 'RUNNING'
  21. """,
  22. new SugarParameter("@TenantId", tenantId),
  23. new SugarParameter("@ActionCode", actionCode));
  24. return n > 0;
  25. }
  26. /// <summary>
  27. /// 取同租户同动作最近一次 SUCCESS 的 id(可作为当前有效 run_id)。
  28. /// </summary>
  29. public async Task<long?> GetLatestSuccessIdAsync(string actionCode, long tenantId)
  30. {
  31. var id = await _db.Ado.GetLongAsync(
  32. """
  33. SELECT id FROM aidop_action_run_log
  34. WHERE tenant_id = @TenantId AND action_code = @ActionCode AND status = 'SUCCESS'
  35. ORDER BY start_time DESC, id DESC
  36. LIMIT 1
  37. """,
  38. new List<SugarParameter>
  39. {
  40. new("@TenantId", tenantId),
  41. new("@ActionCode", actionCode)
  42. });
  43. return id > 0 ? id : null;
  44. }
  45. public async Task<long> StartAsync(
  46. string actionCode,
  47. long tenantId,
  48. string bizType,
  49. long? bizId,
  50. string? bizNo)
  51. {
  52. var id = YitIdHelper.NextId();
  53. var now = DateTime.Now;
  54. await _db.Ado.ExecuteCommandAsync(
  55. """
  56. INSERT INTO aidop_action_run_log
  57. (id, tenant_id, action_code, biz_type, biz_id, biz_no, status, start_time, create_time)
  58. VALUES
  59. (@Id, @TenantId, @ActionCode, @BizType, @BizId, @BizNo, 'RUNNING', @Now, @Now)
  60. """,
  61. new SugarParameter("@Id", id),
  62. new SugarParameter("@TenantId", tenantId),
  63. new SugarParameter("@ActionCode", actionCode),
  64. new SugarParameter("@BizType", bizType),
  65. new SugarParameter("@BizId", bizId ?? (object)DBNull.Value),
  66. new SugarParameter("@BizNo", bizNo ?? string.Empty),
  67. new SugarParameter("@Now", now));
  68. return id;
  69. }
  70. public async Task SuccessAsync(long id, string message, object? detail = null) =>
  71. await FinishAsync(id, "SUCCESS", message, detail);
  72. public async Task FailedAsync(long id, string message, object? detail = null) =>
  73. await FinishAsync(id, "FAILED", message, detail);
  74. /// <summary>运行中更新进度(不改 status,不清 end_time)。</summary>
  75. public async Task UpdateProgressAsync(long id, string message, object? detail = null)
  76. {
  77. await _db.Ado.ExecuteCommandAsync(
  78. """
  79. UPDATE aidop_action_run_log
  80. SET message = @Message, detail_json = @Detail
  81. WHERE id = @Id AND status = 'RUNNING'
  82. """,
  83. new SugarParameter("@Message", Truncate(message, 1000)),
  84. new SugarParameter("@Detail", detail is null ? (object)DBNull.Value : JsonSerializer.Serialize(detail)),
  85. new SugarParameter("@Id", id));
  86. }
  87. /// <summary>
  88. /// 将超时仍为 RUNNING 的记录标为 FAILED,避免进程崩溃后单飞永久堵死。
  89. /// </summary>
  90. public async Task FailStaleRunningAsync(string actionCode, long tenantId, TimeSpan olderThan)
  91. {
  92. var cutoff = DateTime.Now - olderThan;
  93. await _db.Ado.ExecuteCommandAsync(
  94. """
  95. UPDATE aidop_action_run_log
  96. SET status = 'FAILED',
  97. message = CONCAT(IFNULL(message, ''), ' [超时未完成,已自动关闭]'),
  98. end_time = NOW()
  99. WHERE tenant_id = @TenantId AND action_code = @ActionCode
  100. AND status = 'RUNNING' AND start_time < @Cutoff
  101. """,
  102. new SugarParameter("@TenantId", tenantId),
  103. new SugarParameter("@ActionCode", actionCode),
  104. new SugarParameter("@Cutoff", cutoff));
  105. }
  106. private async Task FinishAsync(long id, string status, string message, object? detail)
  107. {
  108. var now = DateTime.Now;
  109. await _db.Ado.ExecuteCommandAsync(
  110. """
  111. UPDATE aidop_action_run_log
  112. SET status = @Status, message = @Message, detail_json = @Detail, end_time = @Now
  113. WHERE id = @Id
  114. """,
  115. new SugarParameter("@Status", status),
  116. new SugarParameter("@Message", Truncate(message, 1000)),
  117. new SugarParameter("@Detail", detail is null ? (object)DBNull.Value : JsonSerializer.Serialize(detail)),
  118. new SugarParameter("@Now", now),
  119. new SugarParameter("@Id", id));
  120. }
  121. private static string Truncate(string? s, int max) =>
  122. string.IsNullOrEmpty(s) || s.Length <= max ? s ?? string.Empty : s[..max];
  123. }