'재처리할 특정 로그 ID (선택사항, 없으면 모든 실패 건 재처리)' ]; protected $options = [ '--limit' => '재처리할 최대 개수 (기본: 10)', '--dry-run' => '실제 실행 없이 목록만 확인', '--force' => '재시도 횟수 제한 무시 (3회 이상 실패한 건도 재처리)' ]; public function run(array $params) { helper(['log']); $logId = $params[0] ?? null; $limit = CLI::getOption('limit') ?? 10; $isDryRun = CLI::getOption('dry-run') !== null; $isForce = CLI::getOption('force') !== null; $logModel = model(NaverWorkerLogModel::class); $naverService = new \App\Services\NaverService(); // 1. 특정 ID 재처리 if ($logId) { $this->retryOne($logModel, $naverService, $logId, $isDryRun, $isForce); return; } // 2. 실패한 모든 작업 재처리 $this->retryFailed($logModel, $naverService, $limit, $isDryRun, $isForce); } /** * 특정 로그 ID 재처리 */ protected function retryOne($logModel, $naverService, $logId, $isDryRun, $isForce) { $log = $logModel->find($logId); if (!$log) { CLI::error("❌ Log ID {$logId} not found"); return; } CLI::write(CLI::color("📋 Log ID: {$logId}", 'cyan')); CLI::write(" Status: {$log['status']}"); CLI::write(" Retry Count: " . ($log['retry_cnt'] ?? 0)); CLI::write(" Error: {$log['error_msg']}"); CLI::write(" Created: {$log['created_at']}"); // 재시도 횟수 체크 if (!$isForce && ($log['retry_cnt'] ?? 0) >= 3) { CLI::write(CLI::color('⚠️ 이미 3회 이상 재시도했습니다. --force 옵션으로 강제 실행 가능', 'yellow')); return; } // 재시도 불가능한 오류 체크 if (!$isForce && $this->isNonRetryableError($log['error_msg'])) { CLI::write(CLI::color('⚠️ 재시도 불가능한 오류입니다. 데이터를 먼저 수정해주세요.', 'yellow')); CLI::write(CLI::color(' (--force 옵션으로 강제 실행 가능)', 'yellow')); return; } if ($isDryRun) { CLI::write(CLI::color('🔍 DRY RUN - 재처리하지 않음', 'yellow')); return; } $this->processLog($logModel, $naverService, $log); } /** * 재시도 불가능한 오류인지 확인 */ protected function isNonRetryableError($errorMsg) { if (!$errorMsg) return false; // 데이터 수정이 필요한 오류 패턴 $nonRetryablePatterns = [ 'foreign key constraint', 'Duplicate entry', '빈 페이로드', 'usr_sq.*users 테이블에 없음', // 이미 폴백 적용된 건은 재시도 가능 ]; foreach ($nonRetryablePatterns as $pattern) { if (stripos($errorMsg, $pattern) !== false) { return true; } } return false; } /** * 실패한 모든 작업 재처리 */ protected function retryFailed($logModel, $naverService, $limit, $isDryRun, $isForce) { $query = $logModel->where('status', 'FAIL'); // force가 아닌 경우 재시도 횟수 3회 미만만 조회 if (!$isForce) { $query->groupStart() ->where('retry_cnt IS NULL') ->orWhere('retry_cnt <', 3) ->groupEnd(); } $failedLogs = $query->orderBy('created_at', 'ASC')->findAll($limit); if (empty($failedLogs)) { CLI::write(CLI::color('✅ 재처리할 실패 작업이 없습니다.', 'green')); return; } CLI::write(CLI::color("📋 실패한 작업 {count}건 발견", 'cyan') ->replace('{count}', count($failedLogs))); if ($isDryRun) { foreach ($failedLogs as $log) { CLI::write(" - ID: {$log['seq']} | Atcl: {$log['atcl_no']} | Error: {$log['error_msg']}"); } CLI::write(CLI::color('🔍 DRY RUN - 재처리하지 않음', 'yellow')); return; } // 실제 재처리 $successCount = 0; $failCount = 0; foreach ($failedLogs as $log) { CLI::write(CLI::color("\n🔄 Retrying Log ID: {$log['seq']}", 'yellow')); $result = $this->processLog($logModel, $naverService, $log); if ($result) { $successCount++; } else { $failCount++; } // 과부하 방지 sleep(1); } CLI::write(CLI::color("\n✅ 재처리 완료: 성공 {$successCount}건, 실패 {$failCount}건", 'green')); } /** * 로그 데이터 재처리 */ protected function processLog($logModel, $naverService, $log) { try { // 상태를 RETRY로 변경 $logModel->update($log['seq'], ['status' => 'RETRY']); // JSON 파싱 $responseJson = json_decode($log['raw_payload'], true); $payload = $responseJson['request_data'] ?? []; if (empty($payload)) { throw new \Exception("빈 페이로드 데이터"); } CLI::write(" Article: {$payload['articleNumber']}"); // 재처리 $insertId = $naverService->processArticle($payload); // 성공 시 로그 업데이트 $logModel->update($log['seq'], [ 'atcl_no' => $payload['articleNumber'] ?? null, 'status' => 'SUCCESS', 'target_db_id' => $insertId, 'error_msg' => null ]); CLI::write(CLI::color(" ✅ Success! DB ID: {$insertId}", 'green')); return true; } catch (\Exception $e) { // 실패 시 로그 업데이트 $logModel->update($log['seq'], [ 'status' => 'FAIL', 'error_msg' => $e->getMessage() ]); CLI::error(" ❌ Failed: " . $e->getMessage()); return false; } } }