CleanExpiredTasksCommand.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <?php
  2. namespace App\Module\Task\Commands;
  3. use App\Module\Task\Enums\TASK_STATUS;
  4. use App\Module\Task\Models\Task;
  5. use App\Module\Task\Models\TaskUserTask;
  6. use Illuminate\Console\Command;
  7. use Illuminate\Support\Carbon;
  8. use Illuminate\Support\Facades\DB;
  9. use Illuminate\Support\Facades\Log;
  10. /**
  11. * 清理过期任务命令
  12. *
  13. * 用于清理已过期的任务,包括:
  14. * 1. 已过期的任务(任务的结束时间已过)
  15. * 2. 已超时的任务(任务的时间限制已过)
  16. */
  17. class CleanExpiredTasksCommand extends Command
  18. {
  19. /**
  20. * 命令名称
  21. *
  22. * @var string
  23. */
  24. protected $signature = 'task:clean-expired
  25. {--user-id= : 指定用户ID进行清理}
  26. {--task-id= : 指定任务ID进行清理}
  27. {--days=30 : 清理多少天前的过期任务}
  28. {--batch-size=100 : 每批处理的数量}
  29. {--dry-run : 仅检查不执行实际操作}';
  30. /**
  31. * 命令描述
  32. *
  33. * @var string
  34. */
  35. protected $description = '清理过期任务';
  36. /**
  37. * 执行命令
  38. *
  39. * @return int
  40. */
  41. public function handle()
  42. {
  43. $this->info('开始清理过期任务...');
  44. // 获取命令选项
  45. $userId = $this->option('user-id');
  46. $taskId = $this->option('task-id');
  47. $days = $this->option('days');
  48. $batchSize = $this->option('batch-size');
  49. $dryRun = $this->option('dry-run');
  50. // 清理已过期的任务(任务的结束时间已过)
  51. $this->cleanExpiredByEndTime($userId, $taskId, $days, $batchSize, $dryRun);
  52. // 清理已超时的任务(任务的时间限制已过)
  53. $this->cleanExpiredByTimeLimit($userId, $taskId, $days, $batchSize, $dryRun);
  54. $this->info('过期任务清理完成');
  55. return 0;
  56. }
  57. /**
  58. * 清理已过期的任务(任务的结束时间已过)
  59. *
  60. * @param int|null $userId 用户ID
  61. * @param int|null $taskId 任务ID
  62. * @param int $days 天数
  63. * @param int $batchSize 批处理大小
  64. * @param bool $dryRun 是否仅检查
  65. * @return void
  66. */
  67. protected function cleanExpiredByEndTime(?int $userId, ?int $taskId, int $days, int $batchSize, bool $dryRun): void
  68. {
  69. $this->info('清理已过期的任务(任务的结束时间已过)...');
  70. // 获取已过期的任务
  71. $expiredTasks = Task::where('end_time', '<', Carbon::now()->subDays($days))
  72. ->where('is_active', true)
  73. ->get();
  74. if ($expiredTasks->isEmpty()) {
  75. $this->info('没有找到已过期的任务');
  76. return;
  77. }
  78. $this->info("找到 {$expiredTasks->count()} 个已过期的任务");
  79. if ($dryRun) {
  80. $this->warn('仅检查模式,不执行实际操作');
  81. return;
  82. }
  83. // 创建进度条
  84. $bar = $this->output->createProgressBar($expiredTasks->count());
  85. $bar->start();
  86. // 处理每个过期任务
  87. foreach ($expiredTasks as $task) {
  88. try {
  89. // 构建查询
  90. $query = TaskUserTask::query()
  91. ->where('task_id', $task->id)
  92. ->whereIn('status', [TASK_STATUS::NOT_ACCEPTED->value, TASK_STATUS::IN_PROGRESS->value]);
  93. // 应用过滤条件
  94. if ($userId) {
  95. $query->where('user_id', $userId);
  96. }
  97. // 分批处理
  98. $query->chunk($batchSize, function ($userTasks) use ($task) {
  99. foreach ($userTasks as $userTask) {
  100. DB::beginTransaction();
  101. try {
  102. // 记录日志
  103. Log::info('清理过期任务', [
  104. 'user_id' => $userTask->user_id,
  105. 'task_id' => $userTask->task_id,
  106. 'reason' => '任务结束时间已过',
  107. 'end_time' => $task->end_time,
  108. ]);
  109. // 删除用户任务
  110. $userTask->delete();
  111. DB::commit();
  112. } catch (\Exception $e) {
  113. DB::rollBack();
  114. Log::error('清理过期任务失败', [
  115. 'user_id' => $userTask->user_id,
  116. 'task_id' => $userTask->task_id,
  117. 'error' => $e->getMessage(),
  118. ]);
  119. }
  120. }
  121. });
  122. // 更新任务状态
  123. $task->is_active = false;
  124. $task->save();
  125. } catch (\Exception $e) {
  126. $this->error("处理任务异常: 任务ID={$task->id}, 错误: {$e->getMessage()}");
  127. }
  128. $bar->advance();
  129. }
  130. $bar->finish();
  131. $this->newLine(2);
  132. }
  133. /**
  134. * 清理已超时的任务(任务的时间限制已过)
  135. *
  136. * @param int|null $userId 用户ID
  137. * @param int|null $taskId 任务ID
  138. * @param int $days 天数
  139. * @param int $batchSize 批处理大小
  140. * @param bool $dryRun 是否仅检查
  141. * @return void
  142. */
  143. protected function cleanExpiredByTimeLimit(?int $userId, ?int $taskId, int $days, int $batchSize, bool $dryRun): void
  144. {
  145. $this->info('清理已超时的任务(任务的时间限制已过)...');
  146. // 构建查询
  147. $query = TaskUserTask::query()
  148. ->where('status', TASK_STATUS::IN_PROGRESS->value)
  149. ->where('accepted_at', '<', Carbon::now()->subDays($days))
  150. ->whereHas('task', function ($query) {
  151. $query->whereNotNull('time_limit');
  152. });
  153. // 应用过滤条件
  154. if ($userId) {
  155. $query->where('user_id', $userId);
  156. }
  157. if ($taskId) {
  158. $query->where('task_id', $taskId);
  159. }
  160. // 获取符合条件的任务数量
  161. $totalTasks = $query->count();
  162. if ($totalTasks === 0) {
  163. $this->info('没有找到已超时的任务');
  164. return;
  165. }
  166. $this->info("找到 {$totalTasks} 个已超时的任务");
  167. if ($dryRun) {
  168. $this->warn('仅检查模式,不执行实际操作');
  169. return;
  170. }
  171. // 创建进度条
  172. $bar = $this->output->createProgressBar($totalTasks);
  173. $bar->start();
  174. // 分批处理
  175. $query->with('task')->chunk($batchSize, function ($userTasks) use ($bar) {
  176. foreach ($userTasks as $userTask) {
  177. DB::beginTransaction();
  178. try {
  179. // 计算任务是否超时
  180. $acceptedAt = $userTask->accepted_at;
  181. $timeLimit = $userTask->task->time_limit;
  182. $expireAt = $acceptedAt->addSeconds($timeLimit);
  183. if (Carbon::now()->greaterThan($expireAt)) {
  184. // 记录日志
  185. Log::info('清理超时任务', [
  186. 'user_id' => $userTask->user_id,
  187. 'task_id' => $userTask->task_id,
  188. 'reason' => '任务时间限制已过',
  189. 'accepted_at' => $acceptedAt,
  190. 'time_limit' => $timeLimit,
  191. 'expire_at' => $expireAt,
  192. ]);
  193. // 删除用户任务
  194. $userTask->delete();
  195. }
  196. DB::commit();
  197. } catch (\Exception $e) {
  198. DB::rollBack();
  199. Log::error('清理超时任务失败', [
  200. 'user_id' => $userTask->user_id,
  201. 'task_id' => $userTask->task_id,
  202. 'error' => $e->getMessage(),
  203. ]);
  204. $this->error("处理任务异常: 用户ID={$userTask->user_id}, 任务ID={$userTask->task_id}, 错误: {$e->getMessage()}");
  205. }
  206. $bar->advance();
  207. }
  208. });
  209. $bar->finish();
  210. $this->newLine(2);
  211. }
  212. }