HistoryCleanupService.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. using SWRIS.Core;
  2. using SWRIS.Models;
  3. using SWRIS.Repository;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using System.Windows;
  10. namespace SWRIS.Services
  11. {
  12. public class HistoryCleanupService : IDisposable
  13. {
  14. private Timer _cleanupTimer;
  15. private readonly IRecordRepository _recordRepository;
  16. private bool _isRunning = false;
  17. private bool _disposed = false;
  18. private int _consecutiveFailures = 0;
  19. public event EventHandler<DeleteResultModel> OnCleanupCompleted;
  20. public event EventHandler<Exception> OnCleanupFailed;
  21. public bool IsRunning => _isRunning;
  22. public DateTime? NextScheduledTime { get; private set; }
  23. public DateTime? LastCleanupTime { get; private set; }
  24. public DeleteResultModel LastCleanupResult { get; private set; }
  25. public HistoryCleanupService()
  26. {
  27. _recordRepository = new RecordRepository();
  28. ValidateConfiguration();
  29. }
  30. public void StartScheduledCleanup()
  31. {
  32. if (_isRunning)
  33. {
  34. LogHelper.Warn("清理服务已在运行中");
  35. return;
  36. }
  37. try
  38. {
  39. ValidateConfiguration();
  40. var now = DateTime.Now;
  41. var scheduledTime = CalculateNextScheduledTime(now);
  42. var initialDelay = scheduledTime - now;
  43. _cleanupTimer = new Timer(
  44. callback: _ => ExecuteCleanupAsync().ConfigureAwait(false),
  45. state: null,
  46. dueTime: initialDelay,
  47. period: TimeSpan.FromHours(24)
  48. );
  49. _isRunning = true;
  50. NextScheduledTime = scheduledTime;
  51. _consecutiveFailures = 0;
  52. LogHelper.Info($"计划清理已启动。下次执行: {scheduledTime:yyyy-MM-dd HH:mm:ss}");
  53. }
  54. catch (Exception ex)
  55. {
  56. LogHelper.Error($"启动计划任务失败: {ex.Message}");
  57. _isRunning = false;
  58. OnCleanupFailed?.Invoke(this, ex);
  59. // 延迟重试
  60. Task.Delay(TimeSpan.FromMinutes(5)).ContinueWith(_ => RetryStartup());
  61. }
  62. }
  63. private DateTime CalculateNextScheduledTime(DateTime now)
  64. {
  65. var timeSpan = App.Config.CleanScheduledTime;
  66. var scheduledTime = new DateTime(now.Year, now.Month, now.Day,
  67. timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
  68. if (now > scheduledTime)
  69. {
  70. scheduledTime = scheduledTime.AddDays(1);
  71. }
  72. return scheduledTime;
  73. }
  74. private void RetryStartup(int retryCount = 0)
  75. {
  76. if (retryCount >= 3)
  77. {
  78. LogHelper.Error("启动重试次数超过限制,请检查系统配置");
  79. return;
  80. }
  81. LogHelper.Info($"尝试重新启动清理服务 (重试 {retryCount + 1}/3)");
  82. StartScheduledCleanup();
  83. }
  84. private async Task ExecuteCleanupAsync()
  85. {
  86. try
  87. {
  88. LastCleanupTime = DateTime.Now;
  89. LogHelper.Info($"开始执行计划清理任务 ({LastCleanupTime:yyyy-MM-dd HH:mm:ss})");
  90. var result = await Task.Run(() =>
  91. CleanupHistoryOlderThanDays(App.Config.DataSaveDays));
  92. // 更新下一次执行时间
  93. NextScheduledTime = CalculateNextScheduledTime(DateTime.Now);
  94. // 重置连续失败计数
  95. _consecutiveFailures = 0;
  96. // 在UI线程更新结果
  97. await Application.Current.Dispatcher.InvokeAsync(() =>
  98. {
  99. LastCleanupResult = result;
  100. if (result.IsSuccess)
  101. {
  102. LogHelper.Info($"{result.Message} (执行时间: {result.OperationTime:HH:mm:ss})");
  103. LogHelper.Info($"删除记录: {result.DeletedRecords} 条, 删除文件: {result.DeletedFiles} 个");
  104. }
  105. else
  106. {
  107. LogHelper.Warn($"清理任务完成但有警告: {result.Message}");
  108. }
  109. OnCleanupCompleted?.Invoke(this, result);
  110. });
  111. }
  112. catch (Exception ex)
  113. {
  114. _consecutiveFailures++;
  115. LogHelper.Error($"清理执行失败 (失败次数: {_consecutiveFailures}): {ex.Message}", ex);
  116. // 连续失败告警
  117. if (_consecutiveFailures >= 3)
  118. {
  119. LogHelper.Warn("连续清理失败次数过多,请检查系统状态");
  120. }
  121. OnCleanupFailed?.Invoke(this, ex);
  122. }
  123. }
  124. public DeleteResultModel CleanupHistoryOlderThanDays(int days)
  125. {
  126. var result = new DeleteResultModel
  127. {
  128. OperationTime = DateTime.Now,
  129. DataSaveDays = days
  130. };
  131. try
  132. {
  133. if (days <= 0)
  134. {
  135. throw new ArgumentException("数据保存天数必须大于0", nameof(days));
  136. }
  137. var endTime = DateTime.Today.AddDays(-days);
  138. LogHelper.Info($"开始清理 {endTime:yyyy-MM-dd} 之前的历史数据");
  139. var (effect, filePaths) = _recordRepository.DeleteRecords(endTime);
  140. result.DeletedRecords = effect;
  141. result.DeletedFiles = DeleteAssociatedFiles(filePaths);
  142. result.IsSuccess = true;
  143. result.Message = $"清理完成,删除了 {result.DeletedRecords} 条记录和 {result.DeletedFiles} 个文件。";
  144. LogHelper.Info($"数据库记录清理: {result.DeletedRecords} 条");
  145. LogHelper.Info($"关联文件清理: {result.DeletedFiles} 个");
  146. }
  147. catch (Exception ex)
  148. {
  149. result.IsSuccess = false;
  150. result.Message = $"清理过程中发生错误: {ex.Message}";
  151. LogHelper.Error($"Cleanup Error: {ex}", ex);
  152. }
  153. return result;
  154. }
  155. private int DeleteAssociatedFiles(List<string> filePaths)
  156. {
  157. int deletedCount = 0;
  158. int errorCount = 0;
  159. if (filePaths == null || filePaths.Count == 0)
  160. {
  161. return 0;
  162. }
  163. LogHelper.Info($"开始清理 {filePaths.Count} 个关联文件");
  164. foreach (var filePath in filePaths)
  165. {
  166. try
  167. {
  168. if (File.Exists(filePath))
  169. {
  170. File.Delete(filePath);
  171. deletedCount++;
  172. }
  173. else
  174. {
  175. LogHelper.Warn($"文件不存在: {filePath}");
  176. }
  177. }
  178. catch (Exception ex)
  179. {
  180. errorCount++;
  181. LogHelper.Error($"删除文件失败 {filePath}: {ex.Message}");
  182. }
  183. }
  184. if (errorCount > 0)
  185. {
  186. LogHelper.Warn($"{errorCount} 个文件删除失败");
  187. }
  188. return deletedCount;
  189. }
  190. public void StopScheduledCleanup()
  191. {
  192. _cleanupTimer?.Change(Timeout.Infinite, Timeout.Infinite);
  193. _isRunning = false;
  194. NextScheduledTime = null;
  195. LogHelper.Info("计划清理已停止");
  196. }
  197. public void TriggerManualCleanup()
  198. {
  199. LogHelper.Info("手动触发清理任务");
  200. ExecuteCleanupAsync().ConfigureAwait(false);
  201. }
  202. private void ValidateConfiguration()
  203. {
  204. if (App.Config?.CleanScheduledTime == null)
  205. {
  206. throw new InvalidOperationException("清理计划时间未配置");
  207. }
  208. if (App.Config.DataSaveDays <= 0)
  209. {
  210. throw new InvalidOperationException("数据保存天数必须大于0");
  211. }
  212. }
  213. public void Dispose()
  214. {
  215. Dispose(true);
  216. GC.SuppressFinalize(this);
  217. }
  218. protected virtual void Dispose(bool disposing)
  219. {
  220. if (!_disposed)
  221. {
  222. if (disposing)
  223. {
  224. StopScheduledCleanup();
  225. _cleanupTimer?.Dispose();
  226. _cleanupTimer = null;
  227. }
  228. _disposed = true;
  229. }
  230. }
  231. ~HistoryCleanupService()
  232. {
  233. Dispose(false);
  234. }
  235. }
  236. }