CleanupExecutorLogic.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. <?php
  2. namespace App\Module\Cleanup\Logics;
  3. use App\Module\Cleanup\Models\CleanupTask;
  4. use App\Module\Cleanup\Models\CleanupPlan;
  5. use App\Module\Cleanup\Models\CleanupPlanContent;
  6. use App\Module\Cleanup\Models\CleanupLog;
  7. use App\Module\Cleanup\Enums\TASK_STATUS;
  8. use App\Module\Cleanup\Enums\CLEANUP_TYPE;
  9. use Illuminate\Support\Facades\DB;
  10. use Illuminate\Support\Facades\Log;
  11. use Illuminate\Support\Facades\Schema;
  12. use Illuminate\Database\Eloquent\Model;
  13. use Illuminate\Database\Eloquent\SoftDeletes;
  14. /**
  15. * 清理执行逻辑类
  16. *
  17. * 负责实际的数据清理执行操作
  18. */
  19. class CleanupExecutorLogic
  20. {
  21. /**
  22. * 预览计划的清理结果
  23. *
  24. * @param int $planId 计划ID
  25. * @return array 预览结果
  26. */
  27. public static function previewPlanCleanup(int $planId): array
  28. {
  29. try {
  30. $plan = CleanupPlan::with('contents')->findOrFail($planId);
  31. $previewData = [];
  32. $totalRecords = 0;
  33. $totalTables = 0;
  34. foreach ($plan->contents->where('is_enabled', true) as $content) {
  35. $tablePreview = static::previewTableCleanup($content);
  36. $previewData[] = $tablePreview;
  37. $totalRecords += $tablePreview['affected_records'];
  38. $totalTables++;
  39. }
  40. return [
  41. 'success' => true,
  42. 'data' => [
  43. 'plan' => [
  44. 'id' => $plan->id,
  45. 'plan_name' => $plan->plan_name,
  46. 'description' => $plan->description,
  47. ],
  48. 'summary' => [
  49. 'total_tables' => $totalTables,
  50. 'total_records' => $totalRecords,
  51. 'estimated_time' => static::estimateExecutionTime($totalRecords),
  52. ],
  53. 'tables' => $previewData,
  54. ]
  55. ];
  56. } catch (\Exception $e) {
  57. Log::error('预览计划清理失败', [
  58. 'plan_id' => $planId,
  59. 'error' => $e->getMessage()
  60. ]);
  61. return [
  62. 'success' => false,
  63. 'message' => '预览计划清理失败: ' . $e->getMessage(),
  64. 'data' => null
  65. ];
  66. }
  67. }
  68. /**
  69. * 预览任务的清理结果
  70. *
  71. * @param int $taskId 任务ID
  72. * @return array 预览结果
  73. */
  74. public static function previewTaskCleanup(int $taskId): array
  75. {
  76. try {
  77. $task = CleanupTask::with('plan.contents')->findOrFail($taskId);
  78. if (!$task->plan) {
  79. throw new \Exception('任务关联的计划不存在');
  80. }
  81. return static::previewPlanCleanup($task->plan->id);
  82. } catch (\Exception $e) {
  83. Log::error('预览任务清理失败', [
  84. 'task_id' => $taskId,
  85. 'error' => $e->getMessage()
  86. ]);
  87. return [
  88. 'success' => false,
  89. 'message' => '预览任务清理失败: ' . $e->getMessage(),
  90. 'data' => null
  91. ];
  92. }
  93. }
  94. /**
  95. * 执行清理任务
  96. *
  97. * @param int $taskId 任务ID
  98. * @param bool $dryRun 是否为预演模式
  99. * @return array 执行结果
  100. */
  101. public static function executeTask(int $taskId, bool $dryRun = false): array
  102. {
  103. try {
  104. $task = CleanupTask::with('plan.contents')->findOrFail($taskId);
  105. if (!$task->plan) {
  106. throw new \Exception('任务关联的计划不存在');
  107. }
  108. // 检查任务状态
  109. $currentStatus = TASK_STATUS::from($task->status);
  110. if ($currentStatus !== TASK_STATUS::PENDING) {
  111. throw new \Exception('任务状态不正确,无法执行');
  112. }
  113. if ($dryRun) {
  114. return static::previewTaskCleanup($taskId);
  115. }
  116. // 开始执行任务
  117. return static::executeTaskInternal($task);
  118. } catch (\Exception $e) {
  119. Log::error('执行清理任务失败', [
  120. 'task_id' => $taskId,
  121. 'dry_run' => $dryRun,
  122. 'error' => $e->getMessage(),
  123. 'trace' => $e->getTraceAsString()
  124. ]);
  125. return [
  126. 'success' => false,
  127. 'message' => '执行清理任务失败: ' . $e->getMessage(),
  128. 'data' => null
  129. ];
  130. }
  131. }
  132. /**
  133. * 内部执行任务逻辑
  134. *
  135. * @param CleanupTask $task 任务对象
  136. * @return array 执行结果
  137. */
  138. private static function executeTaskInternal(CleanupTask $task): array
  139. {
  140. $startTime = microtime(true);
  141. $totalDeleted = 0;
  142. $processedTables = 0;
  143. $errors = [];
  144. try {
  145. // 更新任务状态为执行中
  146. CleanupTaskLogic::updateTaskStatus($task->id, TASK_STATUS::RUNNING, [
  147. 'current_step' => '开始执行清理',
  148. ]);
  149. // 获取启用的内容配置
  150. $enabledContents = $task->plan->contents->where('is_enabled', true)->sortBy('priority');
  151. foreach ($enabledContents as $content) {
  152. try {
  153. // 更新当前步骤
  154. $targetName = $content->model_class ?: $content->table_name;
  155. CleanupTaskLogic::updateTaskProgress(
  156. $task->id,
  157. $processedTables,
  158. $totalDeleted,
  159. "正在清理: {$targetName}"
  160. );
  161. // 执行清理(支持Model类和表名两种模式)
  162. $result = static::executeCleanup($content, $task->id);
  163. if ($result['success']) {
  164. $totalDeleted += $result['deleted_records'];
  165. $processedTables++;
  166. } else {
  167. $targetName = $content->model_class ?: $content->table_name;
  168. $errors[] = "{$targetName}: " . $result['message'];
  169. }
  170. } catch (\Exception $e) {
  171. $targetName = $content->model_class ?: $content->table_name;
  172. $errors[] = "{$targetName}: " . $e->getMessage();
  173. Log::error("清理失败", [
  174. 'task_id' => $task->id,
  175. 'model_class' => $content->model_class,
  176. 'table_name' => $content->table_name,
  177. 'error' => $e->getMessage()
  178. ]);
  179. }
  180. }
  181. $executionTime = round(microtime(true) - $startTime, 3);
  182. // 更新任务完成状态
  183. $finalStatus = empty($errors) ? TASK_STATUS::COMPLETED : TASK_STATUS::FAILED;
  184. CleanupTaskLogic::updateTaskStatus($task->id, $finalStatus, [
  185. 'total_records' => $totalDeleted,
  186. 'deleted_records' => $totalDeleted,
  187. 'execution_time' => $executionTime,
  188. 'error_message' => empty($errors) ? null : implode('; ', $errors),
  189. 'current_step' => $finalStatus === TASK_STATUS::COMPLETED ? '清理完成' : '清理失败',
  190. ]);
  191. return [
  192. 'success' => $finalStatus === TASK_STATUS::COMPLETED,
  193. 'message' => $finalStatus === TASK_STATUS::COMPLETED ? '清理任务执行成功' : '清理任务执行完成,但有错误',
  194. 'data' => [
  195. 'task_id' => $task->id,
  196. 'processed_tables' => $processedTables,
  197. 'total_tables' => $enabledContents->count(),
  198. 'deleted_records' => $totalDeleted,
  199. 'execution_time' => $executionTime,
  200. 'errors' => $errors,
  201. ]
  202. ];
  203. } catch (\Exception $e) {
  204. // 更新任务失败状态
  205. CleanupTaskLogic::updateTaskStatus($task->id, TASK_STATUS::FAILED, [
  206. 'error_message' => $e->getMessage(),
  207. 'execution_time' => round(microtime(true) - $startTime, 3),
  208. 'current_step' => '执行失败',
  209. ]);
  210. throw $e;
  211. }
  212. }
  213. /**
  214. * 预览表清理
  215. *
  216. * @param CleanupPlanContent $content 计划内容
  217. * @return array 预览结果
  218. */
  219. private static function previewTableCleanup(CleanupPlanContent $content): array
  220. {
  221. try {
  222. $tableName = $content->table_name;
  223. $cleanupType = CLEANUP_TYPE::from($content->cleanup_type);
  224. // 检查表是否存在
  225. if (!Schema::hasTable($tableName)) {
  226. return [
  227. 'table_name' => $tableName,
  228. 'cleanup_type' => $cleanupType->getDescription(),
  229. 'affected_records' => 0,
  230. 'current_records' => 0,
  231. 'error' => '表不存在',
  232. ];
  233. }
  234. $currentRecords = DB::table($tableName)->count();
  235. $affectedRecords = static::calculateAffectedRecords($tableName, $cleanupType, $content->conditions);
  236. return [
  237. 'table_name' => $tableName,
  238. 'cleanup_type' => $cleanupType->getDescription(),
  239. 'affected_records' => $affectedRecords,
  240. 'current_records' => $currentRecords,
  241. 'remaining_records' => $currentRecords - $affectedRecords,
  242. 'conditions' => $content->conditions,
  243. 'batch_size' => $content->batch_size,
  244. 'backup_enabled' => $content->backup_enabled,
  245. ];
  246. } catch (\Exception $e) {
  247. return [
  248. 'table_name' => $content->table_name,
  249. 'cleanup_type' => CLEANUP_TYPE::from($content->cleanup_type)->getDescription(),
  250. 'affected_records' => 0,
  251. 'current_records' => 0,
  252. 'error' => $e->getMessage(),
  253. ];
  254. }
  255. }
  256. /**
  257. * 计算受影响的记录数
  258. *
  259. * @param string $tableName 表名
  260. * @param CLEANUP_TYPE $cleanupType 清理类型
  261. * @param array $conditions 清理条件
  262. * @return int 受影响的记录数
  263. */
  264. private static function calculateAffectedRecords(string $tableName, CLEANUP_TYPE $cleanupType, array $conditions): int
  265. {
  266. switch ($cleanupType) {
  267. case CLEANUP_TYPE::TRUNCATE:
  268. case CLEANUP_TYPE::DELETE_ALL:
  269. return DB::table($tableName)->count();
  270. case CLEANUP_TYPE::DELETE_BY_TIME:
  271. return static::countByTimeCondition($tableName, $conditions);
  272. case CLEANUP_TYPE::DELETE_BY_USER:
  273. return static::countByUserCondition($tableName, $conditions);
  274. case CLEANUP_TYPE::DELETE_BY_CONDITION:
  275. return static::countByCustomCondition($tableName, $conditions);
  276. default:
  277. return 0;
  278. }
  279. }
  280. /**
  281. * 按时间条件统计记录数
  282. *
  283. * @param string $tableName 表名
  284. * @param array $conditions 条件
  285. * @return int 记录数
  286. */
  287. private static function countByTimeCondition(string $tableName, array $conditions): int
  288. {
  289. $query = DB::table($tableName);
  290. if (!empty($conditions['time_field']) && !empty($conditions['before'])) {
  291. $timeField = $conditions['time_field'];
  292. $beforeTime = static::parseTimeCondition($conditions['before']);
  293. $query->where($timeField, '<', $beforeTime);
  294. }
  295. return $query->count();
  296. }
  297. /**
  298. * 按用户条件统计记录数
  299. *
  300. * @param string $tableName 表名
  301. * @param array $conditions 条件
  302. * @return int 记录数
  303. */
  304. private static function countByUserCondition(string $tableName, array $conditions): int
  305. {
  306. $query = DB::table($tableName);
  307. if (!empty($conditions['user_field']) && !empty($conditions['user_ids'])) {
  308. $userField = $conditions['user_field'];
  309. $userIds = is_array($conditions['user_ids']) ? $conditions['user_ids'] : [$conditions['user_ids']];
  310. $query->whereIn($userField, $userIds);
  311. }
  312. return $query->count();
  313. }
  314. /**
  315. * 按自定义条件统计记录数
  316. *
  317. * @param string $tableName 表名
  318. * @param array $conditions 条件
  319. * @return int 记录数
  320. */
  321. private static function countByCustomCondition(string $tableName, array $conditions): int
  322. {
  323. $query = DB::table($tableName);
  324. if (!empty($conditions['where'])) {
  325. foreach ($conditions['where'] as $condition) {
  326. if (isset($condition['field'], $condition['operator'], $condition['value'])) {
  327. $query->where($condition['field'], $condition['operator'], $condition['value']);
  328. }
  329. }
  330. }
  331. return $query->count();
  332. }
  333. /**
  334. * 检查Model是否支持软删除
  335. *
  336. * @param Model $model Model实例
  337. * @return bool 是否支持软删除
  338. */
  339. private static function modelSupportsSoftDeletes(Model $model): bool
  340. {
  341. return in_array(SoftDeletes::class, class_uses_recursive($model));
  342. }
  343. /**
  344. * 解析时间条件
  345. *
  346. * @param string $timeCondition 时间条件
  347. * @return string 解析后的时间
  348. */
  349. private static function parseTimeCondition(string $timeCondition): string
  350. {
  351. // 支持格式:30_days_ago, 1_month_ago, 2024-01-01, 等
  352. if (preg_match('/(\d+)_days?_ago/', $timeCondition, $matches)) {
  353. return now()->subDays((int)$matches[1])->toDateTimeString();
  354. }
  355. if (preg_match('/(\d+)_months?_ago/', $timeCondition, $matches)) {
  356. return now()->subMonths((int)$matches[1])->toDateTimeString();
  357. }
  358. if (preg_match('/(\d+)_years?_ago/', $timeCondition, $matches)) {
  359. return now()->subYears((int)$matches[1])->toDateTimeString();
  360. }
  361. // 直接返回时间字符串
  362. return $timeCondition;
  363. }
  364. /**
  365. * 估算执行时间
  366. *
  367. * @param int $totalRecords 总记录数
  368. * @return string 估算时间
  369. */
  370. private static function estimateExecutionTime(int $totalRecords): string
  371. {
  372. // 简单估算:每秒处理1000条记录
  373. $seconds = ceil($totalRecords / 1000);
  374. if ($seconds < 60) {
  375. return "{$seconds}秒";
  376. } elseif ($seconds < 3600) {
  377. $minutes = ceil($seconds / 60);
  378. return "{$minutes}分钟";
  379. } else {
  380. $hours = ceil($seconds / 3600);
  381. return "{$hours}小时";
  382. }
  383. }
  384. /**
  385. * 执行清理(支持Model类和表名两种模式)
  386. *
  387. * @param CleanupPlanContent $content 计划内容
  388. * @param int $taskId 任务ID
  389. * @return array 执行结果
  390. */
  391. private static function executeCleanup(CleanupPlanContent $content, int $taskId): array
  392. {
  393. $startTime = microtime(true);
  394. $cleanupType = CLEANUP_TYPE::from($content->cleanup_type);
  395. try {
  396. // 优先使用Model类,如果没有则使用表名
  397. if (!empty($content->model_class)) {
  398. return static::executeModelCleanup($content, $taskId, $startTime);
  399. } else {
  400. return static::executeTableCleanup($content, $taskId, $startTime);
  401. }
  402. } catch (\Exception $e) {
  403. $executionTime = round(microtime(true) - $startTime, 3);
  404. $targetName = $content->model_class ?: $content->table_name;
  405. // 记录错误日志
  406. static::logCleanupOperation($taskId, $content, $cleanupType, [
  407. 'error' => $e->getMessage(),
  408. 'execution_time' => $executionTime,
  409. 'conditions' => $content->conditions,
  410. ]);
  411. return [
  412. 'success' => false,
  413. 'message' => $e->getMessage(),
  414. 'deleted_records' => 0,
  415. 'execution_time' => $executionTime,
  416. ];
  417. }
  418. }
  419. /**
  420. * 执行基于Model类的清理
  421. *
  422. * @param CleanupPlanContent $content 计划内容
  423. * @param int $taskId 任务ID
  424. * @param float $startTime 开始时间
  425. * @return array 执行结果
  426. */
  427. private static function executeModelCleanup(CleanupPlanContent $content, int $taskId, float $startTime): array
  428. {
  429. $modelClass = $content->model_class;
  430. $cleanupType = CLEANUP_TYPE::from($content->cleanup_type);
  431. // 检查Model类是否存在
  432. if (!class_exists($modelClass)) {
  433. throw new \Exception("Model类不存在: {$modelClass}");
  434. }
  435. $model = new $modelClass();
  436. $tableName = $model->getTable();
  437. // 记录清理前的记录数
  438. $beforeCount = $modelClass::count();
  439. // 执行清理
  440. $deletedRecords = static::performModelCleanup($modelClass, $cleanupType, $content->conditions, $content->batch_size);
  441. // 记录清理后的记录数
  442. $afterCount = $modelClass::count();
  443. $actualDeleted = $beforeCount - $afterCount;
  444. $executionTime = round(microtime(true) - $startTime, 3);
  445. // 记录清理日志
  446. static::logCleanupOperation($taskId, $content, $cleanupType, [
  447. 'before_count' => $beforeCount,
  448. 'after_count' => $afterCount,
  449. 'deleted_records' => $actualDeleted,
  450. 'execution_time' => $executionTime,
  451. 'conditions' => $content->conditions,
  452. 'batch_size' => $content->batch_size,
  453. ]);
  454. return [
  455. 'success' => true,
  456. 'message' => "Model {$modelClass} 清理成功",
  457. 'deleted_records' => $actualDeleted,
  458. 'execution_time' => $executionTime,
  459. ];
  460. }
  461. /**
  462. * 执行基于表名的清理(向后兼容)
  463. *
  464. * @param CleanupPlanContent $content 计划内容
  465. * @param int $taskId 任务ID
  466. * @param float $startTime 开始时间
  467. * @return array 执行结果
  468. */
  469. private static function executeTableCleanup(CleanupPlanContent $content, int $taskId, float $startTime): array
  470. {
  471. $tableName = $content->table_name;
  472. $cleanupType = CLEANUP_TYPE::from($content->cleanup_type);
  473. // 检查表是否存在
  474. if (!Schema::hasTable($tableName)) {
  475. throw new \Exception("表 {$tableName} 不存在");
  476. }
  477. // 记录清理前的记录数
  478. $beforeCount = DB::table($tableName)->count();
  479. // 执行清理
  480. $deletedRecords = static::performTableCleanup($tableName, $cleanupType, $content->conditions, $content->batch_size);
  481. // 记录清理后的记录数
  482. $afterCount = DB::table($tableName)->count();
  483. $actualDeleted = $beforeCount - $afterCount;
  484. $executionTime = round(microtime(true) - $startTime, 3);
  485. // 记录清理日志
  486. static::logCleanupOperation($taskId, $content, $cleanupType, [
  487. 'before_count' => $beforeCount,
  488. 'after_count' => $afterCount,
  489. 'deleted_records' => $actualDeleted,
  490. 'execution_time' => $executionTime,
  491. 'conditions' => $content->conditions,
  492. 'batch_size' => $content->batch_size,
  493. ]);
  494. return [
  495. 'success' => true,
  496. 'message' => "表 {$tableName} 清理成功",
  497. 'deleted_records' => $actualDeleted,
  498. 'execution_time' => $executionTime,
  499. ];
  500. }
  501. /**
  502. * 执行基于Model类的清理操作
  503. *
  504. * @param string $modelClass Model类名
  505. * @param CLEANUP_TYPE $cleanupType 清理类型
  506. * @param array $conditions 清理条件
  507. * @param int $batchSize 批处理大小
  508. * @return int 删除的记录数
  509. */
  510. private static function performModelCleanup(string $modelClass, CLEANUP_TYPE $cleanupType, array $conditions, int $batchSize): int
  511. {
  512. switch ($cleanupType) {
  513. case CLEANUP_TYPE::TRUNCATE:
  514. return static::performModelTruncate($modelClass);
  515. case CLEANUP_TYPE::DELETE_ALL:
  516. return static::performModelDeleteAll($modelClass, $batchSize);
  517. case CLEANUP_TYPE::DELETE_BY_TIME:
  518. return static::performModelDeleteByTime($modelClass, $conditions, $batchSize);
  519. case CLEANUP_TYPE::DELETE_BY_USER:
  520. return static::performModelDeleteByUser($modelClass, $conditions, $batchSize);
  521. case CLEANUP_TYPE::DELETE_BY_CONDITION:
  522. return static::performModelDeleteByCondition($modelClass, $conditions, $batchSize);
  523. default:
  524. throw new \Exception("不支持的清理类型: {$cleanupType->value}");
  525. }
  526. }
  527. /**
  528. * 执行基于表名的清理操作(向后兼容)
  529. *
  530. * @param string $tableName 表名
  531. * @param CLEANUP_TYPE $cleanupType 清理类型
  532. * @param array $conditions 清理条件
  533. * @param int $batchSize 批处理大小
  534. * @return int 删除的记录数
  535. */
  536. private static function performTableCleanup(string $tableName, CLEANUP_TYPE $cleanupType, array $conditions, int $batchSize): int
  537. {
  538. switch ($cleanupType) {
  539. case CLEANUP_TYPE::TRUNCATE:
  540. return static::performTableTruncate($tableName);
  541. case CLEANUP_TYPE::DELETE_ALL:
  542. return static::performTableDeleteAll($tableName, $batchSize);
  543. case CLEANUP_TYPE::DELETE_BY_TIME:
  544. return static::performTableDeleteByTime($tableName, $conditions, $batchSize);
  545. case CLEANUP_TYPE::DELETE_BY_USER:
  546. return static::performTableDeleteByUser($tableName, $conditions, $batchSize);
  547. case CLEANUP_TYPE::DELETE_BY_CONDITION:
  548. return static::performTableDeleteByCondition($tableName, $conditions, $batchSize);
  549. default:
  550. throw new \Exception("不支持的清理类型: {$cleanupType->value}");
  551. }
  552. }
  553. /**
  554. * 执行Model的TRUNCATE操作
  555. *
  556. * @param string $modelClass Model类名
  557. * @return int 删除的记录数
  558. */
  559. private static function performModelTruncate(string $modelClass): int
  560. {
  561. $beforeCount = $modelClass::count();
  562. $modelClass::truncate();
  563. return $beforeCount;
  564. }
  565. /**
  566. * 执行表的TRUNCATE操作(向后兼容)
  567. *
  568. * @param string $tableName 表名
  569. * @return int 删除的记录数
  570. */
  571. private static function performTableTruncate(string $tableName): int
  572. {
  573. $beforeCount = DB::table($tableName)->count();
  574. DB::statement("TRUNCATE TABLE `{$tableName}`");
  575. return $beforeCount;
  576. }
  577. /**
  578. * 执行Model的DELETE ALL操作
  579. *
  580. * @param string $modelClass Model类名
  581. * @param int $batchSize 批处理大小
  582. * @return int 删除的记录数
  583. */
  584. private static function performModelDeleteAll(string $modelClass, int $batchSize): int
  585. {
  586. $totalDeleted = 0;
  587. do {
  588. $deleted = $modelClass::limit($batchSize)->delete();
  589. $totalDeleted += $deleted;
  590. } while ($deleted > 0);
  591. return $totalDeleted;
  592. }
  593. /**
  594. * 执行表的DELETE ALL操作(向后兼容)
  595. *
  596. * @param string $tableName 表名
  597. * @param int $batchSize 批处理大小
  598. * @return int 删除的记录数
  599. */
  600. private static function performTableDeleteAll(string $tableName, int $batchSize): int
  601. {
  602. $totalDeleted = 0;
  603. do {
  604. $deleted = DB::table($tableName)->limit($batchSize)->delete();
  605. $totalDeleted += $deleted;
  606. } while ($deleted > 0);
  607. return $totalDeleted;
  608. }
  609. /**
  610. * 执行Model的按时间删除操作
  611. *
  612. * @param string $modelClass Model类名
  613. * @param array $conditions 条件
  614. * @param int $batchSize 批处理大小
  615. * @return int 删除的记录数
  616. */
  617. private static function performModelDeleteByTime(string $modelClass, array $conditions, int $batchSize): int
  618. {
  619. if (empty($conditions['time_field']) || empty($conditions['before'])) {
  620. throw new \Exception('时间删除条件不完整');
  621. }
  622. $timeField = $conditions['time_field'];
  623. $beforeTime = static::parseTimeCondition($conditions['before']);
  624. $totalDeleted = 0;
  625. // 检查Model是否支持软删除
  626. $model = new $modelClass();
  627. $supportsSoftDeletes = static::modelSupportsSoftDeletes($model);
  628. $forceDelete = $conditions['force_delete'] ?? false;
  629. do {
  630. $query = $modelClass::where($timeField, '<', $beforeTime)->limit($batchSize);
  631. if ($supportsSoftDeletes && $forceDelete) {
  632. $deleted = $query->forceDelete();
  633. } else {
  634. $deleted = $query->delete();
  635. }
  636. $totalDeleted += $deleted;
  637. } while ($deleted > 0);
  638. return $totalDeleted;
  639. }
  640. /**
  641. * 执行表的按时间删除操作(向后兼容)
  642. *
  643. * @param string $tableName 表名
  644. * @param array $conditions 条件
  645. * @param int $batchSize 批处理大小
  646. * @return int 删除的记录数
  647. */
  648. private static function performTableDeleteByTime(string $tableName, array $conditions, int $batchSize): int
  649. {
  650. if (empty($conditions['time_field']) || empty($conditions['before'])) {
  651. throw new \Exception('时间删除条件不完整');
  652. }
  653. $timeField = $conditions['time_field'];
  654. $beforeTime = static::parseTimeCondition($conditions['before']);
  655. $totalDeleted = 0;
  656. do {
  657. $deleted = DB::table($tableName)
  658. ->where($timeField, '<', $beforeTime)
  659. ->limit($batchSize)
  660. ->delete();
  661. $totalDeleted += $deleted;
  662. } while ($deleted > 0);
  663. return $totalDeleted;
  664. }
  665. /**
  666. * 执行Model的按用户删除操作
  667. *
  668. * @param string $modelClass Model类名
  669. * @param array $conditions 条件
  670. * @param int $batchSize 批处理大小
  671. * @return int 删除的记录数
  672. */
  673. private static function performModelDeleteByUser(string $modelClass, array $conditions, int $batchSize): int
  674. {
  675. if (empty($conditions['user_field']) || empty($conditions['user_values'])) {
  676. throw new \Exception('用户删除条件不完整');
  677. }
  678. $userField = $conditions['user_field'];
  679. $userCondition = $conditions['user_condition'] ?? 'in';
  680. $userValues = is_array($conditions['user_values']) ? $conditions['user_values'] : [$conditions['user_values']];
  681. $totalDeleted = 0;
  682. // 检查Model是否支持软删除
  683. $model = new $modelClass();
  684. $supportsSoftDeletes = static::modelSupportsSoftDeletes($model);
  685. $forceDelete = $conditions['force_delete'] ?? false;
  686. do {
  687. $query = $modelClass::query();
  688. // 应用用户条件
  689. switch ($userCondition) {
  690. case 'in':
  691. $query->whereIn($userField, $userValues);
  692. break;
  693. case 'not_in':
  694. $query->whereNotIn($userField, $userValues);
  695. break;
  696. case 'null':
  697. $query->whereNull($userField);
  698. break;
  699. case 'not_null':
  700. $query->whereNotNull($userField);
  701. break;
  702. default:
  703. throw new \Exception("不支持的用户条件: {$userCondition}");
  704. }
  705. $query->limit($batchSize);
  706. if ($supportsSoftDeletes && $forceDelete) {
  707. $deleted = $query->forceDelete();
  708. } else {
  709. $deleted = $query->delete();
  710. }
  711. $totalDeleted += $deleted;
  712. } while ($deleted > 0);
  713. return $totalDeleted;
  714. }
  715. /**
  716. * 执行表的按用户删除操作(向后兼容)
  717. *
  718. * @param string $tableName 表名
  719. * @param array $conditions 条件
  720. * @param int $batchSize 批处理大小
  721. * @return int 删除的记录数
  722. */
  723. private static function performTableDeleteByUser(string $tableName, array $conditions, int $batchSize): int
  724. {
  725. if (empty($conditions['user_field']) || empty($conditions['user_ids'])) {
  726. throw new \Exception('用户删除条件不完整');
  727. }
  728. $userField = $conditions['user_field'];
  729. $userIds = is_array($conditions['user_ids']) ? $conditions['user_ids'] : [$conditions['user_ids']];
  730. $totalDeleted = 0;
  731. do {
  732. $deleted = DB::table($tableName)
  733. ->whereIn($userField, $userIds)
  734. ->limit($batchSize)
  735. ->delete();
  736. $totalDeleted += $deleted;
  737. } while ($deleted > 0);
  738. return $totalDeleted;
  739. }
  740. /**
  741. * 执行Model的按条件删除操作
  742. *
  743. * @param string $modelClass Model类名
  744. * @param array $conditions 条件
  745. * @param int $batchSize 批处理大小
  746. * @return int 删除的记录数
  747. */
  748. private static function performModelDeleteByCondition(string $modelClass, array $conditions, int $batchSize): int
  749. {
  750. if (empty($conditions['where'])) {
  751. throw new \Exception('自定义删除条件不完整');
  752. }
  753. $totalDeleted = 0;
  754. // 检查Model是否支持软删除
  755. $model = new $modelClass();
  756. $supportsSoftDeletes = static::modelSupportsSoftDeletes($model);
  757. $forceDelete = $conditions['force_delete'] ?? false;
  758. do {
  759. $query = $modelClass::query();
  760. foreach ($conditions['where'] as $condition) {
  761. if (isset($condition['field'], $condition['operator'], $condition['value'])) {
  762. $query->where($condition['field'], $condition['operator'], $condition['value']);
  763. }
  764. }
  765. $query->limit($batchSize);
  766. if ($supportsSoftDeletes && $forceDelete) {
  767. $deleted = $query->forceDelete();
  768. } else {
  769. $deleted = $query->delete();
  770. }
  771. $totalDeleted += $deleted;
  772. } while ($deleted > 0);
  773. return $totalDeleted;
  774. }
  775. /**
  776. * 执行表的按条件删除操作(向后兼容)
  777. *
  778. * @param string $tableName 表名
  779. * @param array $conditions 条件
  780. * @param int $batchSize 批处理大小
  781. * @return int 删除的记录数
  782. */
  783. private static function performTableDeleteByCondition(string $tableName, array $conditions, int $batchSize): int
  784. {
  785. if (empty($conditions['where'])) {
  786. throw new \Exception('自定义删除条件不完整');
  787. }
  788. $totalDeleted = 0;
  789. do {
  790. $query = DB::table($tableName);
  791. foreach ($conditions['where'] as $condition) {
  792. if (isset($condition['field'], $condition['operator'], $condition['value'])) {
  793. $query->where($condition['field'], $condition['operator'], $condition['value']);
  794. }
  795. }
  796. $deleted = $query->limit($batchSize)->delete();
  797. $totalDeleted += $deleted;
  798. } while ($deleted > 0);
  799. return $totalDeleted;
  800. }
  801. /**
  802. * 记录清理操作日志
  803. *
  804. * @param int $taskId 任务ID
  805. * @param CleanupPlanContent $content 计划内容
  806. * @param CLEANUP_TYPE $cleanupType 清理类型
  807. * @param array $details 详细信息
  808. */
  809. private static function logCleanupOperation(int $taskId, CleanupPlanContent $content, CLEANUP_TYPE $cleanupType, array $details): void
  810. {
  811. try {
  812. $tableName = $content->table_name;
  813. if (!empty($content->model_class)) {
  814. // 如果有Model类,从Model获取表名
  815. $modelClass = $content->model_class;
  816. if (class_exists($modelClass)) {
  817. $model = new $modelClass();
  818. $tableName = $model->getTable();
  819. }
  820. }
  821. CleanupLog::create([
  822. 'task_id' => $taskId,
  823. 'table_name' => $tableName,
  824. 'cleanup_type' => $cleanupType->value,
  825. 'before_count' => $details['before_count'] ?? 0,
  826. 'after_count' => $details['after_count'] ?? 0,
  827. 'deleted_records' => $details['deleted_records'] ?? 0,
  828. 'execution_time' => $details['execution_time'] ?? 0,
  829. 'conditions' => $details['conditions'] ?? [],
  830. 'error_message' => $details['error'] ?? null,
  831. 'model_class' => $content->model_class,
  832. 'created_at' => now(),
  833. ]);
  834. } catch (\Exception $e) {
  835. Log::error('记录清理日志失败', [
  836. 'task_id' => $taskId,
  837. 'model_class' => $content->model_class ?? null,
  838. 'table_name' => $content->table_name ?? null,
  839. 'error' => $e->getMessage()
  840. ]);
  841. }
  842. }
  843. }