TestModelCleanupCommand.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <?php
  2. namespace App\Module\Cleanup\Commands;
  3. use App\Module\Cleanup\Logics\CleanupExecutorLogic;
  4. use App\Module\Cleanup\Models\CleanupPlanContent;
  5. use App\Module\Cleanup\Enums\CLEANUP_TYPE;
  6. use Illuminate\Console\Command;
  7. /**
  8. * 测试基于Model的清理功能
  9. */
  10. class TestModelCleanupCommand extends Command
  11. {
  12. /**
  13. * 命令签名
  14. */
  15. protected $signature = 'cleanup:test-model
  16. {model_class : Model类名}
  17. {--type=3 : 清理类型(1=TRUNCATE,2=DELETE_ALL,3=DELETE_BY_TIME)}
  18. {--dry-run : 预览模式,不实际执行}';
  19. /**
  20. * 命令描述
  21. */
  22. protected $description = '测试基于Model类的清理功能';
  23. /**
  24. * 执行命令
  25. */
  26. public function handle(): int
  27. {
  28. $modelClass = $this->argument('model_class');
  29. $cleanupType = (int)$this->option('type');
  30. $dryRun = $this->option('dry-run');
  31. $this->info("测试Model清理功能");
  32. $this->info("Model类: {$modelClass}");
  33. $this->info("清理类型: " . CLEANUP_TYPE::from($cleanupType)->getDescription());
  34. $this->info("模式: " . ($dryRun ? '预览模式' : '实际执行'));
  35. $this->newLine();
  36. try {
  37. // 检查Model类是否存在
  38. if (!class_exists($modelClass)) {
  39. $this->error("Model类不存在: {$modelClass}");
  40. return Command::FAILURE;
  41. }
  42. $model = new $modelClass();
  43. $tableName = $model->getTable();
  44. $currentCount = $modelClass::count();
  45. $this->info("表名: {$tableName}");
  46. $this->info("当前记录数: {$currentCount}");
  47. $this->newLine();
  48. // 创建测试的计划内容
  49. $content = new CleanupPlanContent();
  50. $content->model_class = $modelClass;
  51. $content->table_name = $tableName;
  52. $content->cleanup_type = $cleanupType;
  53. $content->batch_size = 100;
  54. $content->is_enabled = true;
  55. // 设置清理条件
  56. switch ($cleanupType) {
  57. case CLEANUP_TYPE::DELETE_BY_TIME->value:
  58. $content->conditions = [
  59. 'time_field' => 'created_at',
  60. 'before' => '30_days_ago'
  61. ];
  62. break;
  63. case CLEANUP_TYPE::DELETE_BY_USER->value:
  64. $content->conditions = [
  65. 'user_field' => 'user_id',
  66. 'user_condition' => 'in',
  67. 'user_values' => [999999] // 不存在的用户ID
  68. ];
  69. break;
  70. default:
  71. $content->conditions = [];
  72. }
  73. if ($dryRun) {
  74. $this->info("🔍 预览模式 - 计算将要删除的记录数...");
  75. // 这里可以添加预览逻辑
  76. $affectedRecords = $this->calculateAffectedRecords($modelClass, $cleanupType, $content->conditions);
  77. $this->table(
  78. ['项目', '数值'],
  79. [
  80. ['当前记录数', $currentCount],
  81. ['将删除记录数', $affectedRecords],
  82. ['剩余记录数', $currentCount - $affectedRecords],
  83. ]
  84. );
  85. } else {
  86. $this->warn("⚠️ 即将执行实际清理操作!");
  87. if (!$this->confirm('确定要继续吗?')) {
  88. $this->info('操作已取消');
  89. return Command::SUCCESS;
  90. }
  91. $this->info("🚀 开始执行清理...");
  92. // 使用反射调用私有方法进行测试
  93. $reflection = new \ReflectionClass(CleanupExecutorLogic::class);
  94. $method = $reflection->getMethod('executeModelCleanup');
  95. $method->setAccessible(true);
  96. $startTime = microtime(true);
  97. $result = $method->invoke(null, $content, 0, $startTime);
  98. if ($result['success']) {
  99. $this->info("✅ 清理成功!");
  100. $this->table(
  101. ['项目', '数值'],
  102. [
  103. ['删除记录数', $result['deleted_records']],
  104. ['执行时间', $result['execution_time'] . 's'],
  105. ['当前记录数', $modelClass::count()],
  106. ]
  107. );
  108. } else {
  109. $this->error("❌ 清理失败: " . $result['message']);
  110. }
  111. }
  112. return Command::SUCCESS;
  113. } catch (\Exception $e) {
  114. $this->error("❌ 执行失败: " . $e->getMessage());
  115. $this->error("详细错误: " . $e->getTraceAsString());
  116. return Command::FAILURE;
  117. }
  118. }
  119. /**
  120. * 计算受影响的记录数
  121. */
  122. private function calculateAffectedRecords(string $modelClass, int $cleanupType, array $conditions): int
  123. {
  124. switch ($cleanupType) {
  125. case CLEANUP_TYPE::TRUNCATE->value:
  126. case CLEANUP_TYPE::DELETE_ALL->value:
  127. return $modelClass::count();
  128. case CLEANUP_TYPE::DELETE_BY_TIME->value:
  129. if (empty($conditions['time_field']) || empty($conditions['before'])) {
  130. return 0;
  131. }
  132. $timeField = $conditions['time_field'];
  133. $beforeTime = $this->parseTimeCondition($conditions['before']);
  134. return $modelClass::where($timeField, '<', $beforeTime)->count();
  135. case CLEANUP_TYPE::DELETE_BY_USER->value:
  136. if (empty($conditions['user_field']) || empty($conditions['user_values'])) {
  137. return 0;
  138. }
  139. $userField = $conditions['user_field'];
  140. $userCondition = $conditions['user_condition'] ?? 'in';
  141. $userValues = $conditions['user_values'];
  142. $query = $modelClass::query();
  143. switch ($userCondition) {
  144. case 'in':
  145. $query->whereIn($userField, $userValues);
  146. break;
  147. case 'not_in':
  148. $query->whereNotIn($userField, $userValues);
  149. break;
  150. case 'null':
  151. $query->whereNull($userField);
  152. break;
  153. case 'not_null':
  154. $query->whereNotNull($userField);
  155. break;
  156. }
  157. return $query->count();
  158. default:
  159. return 0;
  160. }
  161. }
  162. /**
  163. * 解析时间条件
  164. */
  165. private function parseTimeCondition(string $timeCondition): string
  166. {
  167. if (preg_match('/(\d+)_days?_ago/', $timeCondition, $matches)) {
  168. return now()->subDays((int)$matches[1])->toDateTimeString();
  169. }
  170. if (preg_match('/(\d+)_months?_ago/', $timeCondition, $matches)) {
  171. return now()->subMonths((int)$matches[1])->toDateTimeString();
  172. }
  173. if (preg_match('/(\d+)_years?_ago/', $timeCondition, $matches)) {
  174. return now()->subYears((int)$matches[1])->toDateTimeString();
  175. }
  176. return $timeCondition;
  177. }
  178. }