CleanupLogsCommand.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. <?php
  2. namespace App\Module\ThirdParty\Commands;
  3. use Illuminate\Console\Command;
  4. use App\Module\ThirdParty\Models\ThirdPartyLog;
  5. use App\Module\ThirdParty\Models\ThirdPartyMonitor;
  6. /**
  7. * 第三方服务日志清理命令
  8. */
  9. class CleanupLogsCommand extends Command
  10. {
  11. /**
  12. * 命令签名
  13. *
  14. * @var string
  15. */
  16. protected $signature = 'thirdparty:cleanup-logs
  17. {--days=30 : 保留天数}
  18. {--type=all : 清理类型 (logs|monitors|all)}
  19. {--service= : 指定服务ID}
  20. {--dry-run : 只显示将要删除的记录数,不实际执行}
  21. {--force : 强制执行,不询问确认}';
  22. /**
  23. * 命令描述
  24. *
  25. * @var string
  26. */
  27. protected $description = '清理第三方服务的旧日志和监控记录';
  28. /**
  29. * 执行命令
  30. *
  31. * @return int
  32. */
  33. public function handle(): int
  34. {
  35. $days = (int)$this->option('days');
  36. $type = $this->option('type');
  37. $serviceId = $this->option('service');
  38. $dryRun = $this->option('dry-run');
  39. $force = $this->option('force');
  40. if ($days < 1) {
  41. $this->error('保留天数必须大于0');
  42. return Command::FAILURE;
  43. }
  44. if (!in_array($type, ['logs', 'monitors', 'all'])) {
  45. $this->error('清理类型必须是 logs、monitors 或 all');
  46. return Command::FAILURE;
  47. }
  48. $this->info("开始清理 {$days} 天前的记录...");
  49. try {
  50. $cutoffDate = now()->subDays($days);
  51. $results = [];
  52. if ($type === 'logs' || $type === 'all') {
  53. $results['logs'] = $this->cleanupLogs($cutoffDate, $serviceId, $dryRun);
  54. }
  55. if ($type === 'monitors' || $type === 'all') {
  56. $results['monitors'] = $this->cleanupMonitors($cutoffDate, $serviceId, $dryRun);
  57. }
  58. $this->displayResults($results, $dryRun);
  59. if ($dryRun) {
  60. $this->info('这是预览模式,没有实际删除记录');
  61. return Command::SUCCESS;
  62. }
  63. if (!$force && !$this->confirmDeletion($results)) {
  64. $this->info('操作已取消');
  65. return Command::SUCCESS;
  66. }
  67. // 实际执行删除
  68. $actualResults = [];
  69. if ($type === 'logs' || $type === 'all') {
  70. $actualResults['logs'] = $this->cleanupLogs($cutoffDate, $serviceId, false);
  71. }
  72. if ($type === 'monitors' || $type === 'all') {
  73. $actualResults['monitors'] = $this->cleanupMonitors($cutoffDate, $serviceId, false);
  74. }
  75. $this->displayResults($actualResults, false);
  76. $this->info('清理完成');
  77. return Command::SUCCESS;
  78. } catch (\Exception $e) {
  79. $this->error("清理失败: {$e->getMessage()}");
  80. return Command::FAILURE;
  81. }
  82. }
  83. /**
  84. * 清理调用日志
  85. *
  86. * @param \Carbon\Carbon $cutoffDate
  87. * @param int|null $serviceId
  88. * @param bool $dryRun
  89. * @return array
  90. */
  91. protected function cleanupLogs($cutoffDate, $serviceId, bool $dryRun): array
  92. {
  93. $query = ThirdPartyLog::where('created_at', '<', $cutoffDate);
  94. if ($serviceId) {
  95. $query->where('service_id', $serviceId);
  96. }
  97. $count = $query->count();
  98. if (!$dryRun && $count > 0) {
  99. $deleted = $query->delete();
  100. return [
  101. 'type' => '调用日志',
  102. 'count' => $deleted,
  103. 'size' => $this->estimateLogSize($deleted),
  104. ];
  105. }
  106. return [
  107. 'type' => '调用日志',
  108. 'count' => $count,
  109. 'size' => $this->estimateLogSize($count),
  110. ];
  111. }
  112. /**
  113. * 清理监控记录
  114. *
  115. * @param \Carbon\Carbon $cutoffDate
  116. * @param int|null $serviceId
  117. * @param bool $dryRun
  118. * @return array
  119. */
  120. protected function cleanupMonitors($cutoffDate, $serviceId, bool $dryRun): array
  121. {
  122. $query = ThirdPartyMonitor::where('checked_at', '<', $cutoffDate);
  123. if ($serviceId) {
  124. $query->where('service_id', $serviceId);
  125. }
  126. $count = $query->count();
  127. if (!$dryRun && $count > 0) {
  128. $deleted = $query->delete();
  129. return [
  130. 'type' => '监控记录',
  131. 'count' => $deleted,
  132. 'size' => $this->estimateMonitorSize($deleted),
  133. ];
  134. }
  135. return [
  136. 'type' => '监控记录',
  137. 'count' => $count,
  138. 'size' => $this->estimateMonitorSize($count),
  139. ];
  140. }
  141. /**
  142. * 估算日志大小
  143. *
  144. * @param int $count
  145. * @return string
  146. */
  147. protected function estimateLogSize(int $count): string
  148. {
  149. // 估算每条日志记录约2KB
  150. $sizeInBytes = $count * 2048;
  151. return $this->formatBytes($sizeInBytes);
  152. }
  153. /**
  154. * 估算监控记录大小
  155. *
  156. * @param int $count
  157. * @return string
  158. */
  159. protected function estimateMonitorSize(int $count): string
  160. {
  161. // 估算每条监控记录约512字节
  162. $sizeInBytes = $count * 512;
  163. return $this->formatBytes($sizeInBytes);
  164. }
  165. /**
  166. * 格式化字节大小
  167. *
  168. * @param int $bytes
  169. * @return string
  170. */
  171. protected function formatBytes(int $bytes): string
  172. {
  173. $units = ['B', 'KB', 'MB', 'GB'];
  174. $unitIndex = 0;
  175. while ($bytes >= 1024 && $unitIndex < count($units) - 1) {
  176. $bytes /= 1024;
  177. $unitIndex++;
  178. }
  179. return round($bytes, 2) . ' ' . $units[$unitIndex];
  180. }
  181. /**
  182. * 显示清理结果
  183. *
  184. * @param array $results
  185. * @param bool $dryRun
  186. * @return void
  187. */
  188. protected function displayResults(array $results, bool $dryRun): void
  189. {
  190. if (empty($results)) {
  191. return;
  192. }
  193. $action = $dryRun ? '将要删除' : '已删除';
  194. $this->info("\n清理结果:");
  195. $this->line(str_repeat('-', 60));
  196. $totalCount = 0;
  197. $totalSize = 0;
  198. foreach ($results as $result) {
  199. $this->line("{$action} {$result['type']}: {$result['count']} 条记录 ({$result['size']})");
  200. $totalCount += $result['count'];
  201. // 简单累加字节数(这里简化处理)
  202. $sizeInBytes = $this->parseSizeToBytes($result['size']);
  203. $totalSize += $sizeInBytes;
  204. }
  205. $this->line(str_repeat('-', 60));
  206. $this->line("总计: {$totalCount} 条记录 ({$this->formatBytes($totalSize)})");
  207. if ($totalCount === 0) {
  208. $this->info('没有找到需要清理的记录');
  209. }
  210. }
  211. /**
  212. * 解析大小字符串为字节数
  213. *
  214. * @param string $size
  215. * @return int
  216. */
  217. protected function parseSizeToBytes(string $size): int
  218. {
  219. $units = ['B' => 1, 'KB' => 1024, 'MB' => 1024*1024, 'GB' => 1024*1024*1024];
  220. foreach ($units as $unit => $multiplier) {
  221. if (str_contains($size, $unit)) {
  222. $value = (float)str_replace(' ' . $unit, '', $size);
  223. return (int)($value * $multiplier);
  224. }
  225. }
  226. return 0;
  227. }
  228. /**
  229. * 确认删除操作
  230. *
  231. * @param array $results
  232. * @return bool
  233. */
  234. protected function confirmDeletion(array $results): bool
  235. {
  236. $totalCount = array_sum(array_column($results, 'count'));
  237. if ($totalCount === 0) {
  238. return false;
  239. }
  240. return $this->confirm("确认要删除 {$totalCount} 条记录吗?此操作不可恢复!");
  241. }
  242. /**
  243. * 显示清理统计信息
  244. *
  245. * @return void
  246. */
  247. protected function displayCleanupStats(): void
  248. {
  249. $logCount = ThirdPartyLog::count();
  250. $monitorCount = ThirdPartyMonitor::count();
  251. $oldLogCount = ThirdPartyLog::where('created_at', '<', now()->subDays(30))->count();
  252. $oldMonitorCount = ThirdPartyMonitor::where('checked_at', '<', now()->subDays(30))->count();
  253. $this->info("\n当前统计:");
  254. $this->line("调用日志总数: {$logCount} (30天前: {$oldLogCount})");
  255. $this->line("监控记录总数: {$monitorCount} (30天前: {$oldMonitorCount})");
  256. if ($oldLogCount > 0 || $oldMonitorCount > 0) {
  257. $this->warn("建议定期清理旧记录以节省存储空间");
  258. }
  259. }
  260. }