210 lines
6.9 KiB
PHP
210 lines
6.9 KiB
PHP
<?php
|
|
|
|
namespace App\Commands;
|
|
|
|
use CodeIgniter\CLI\BaseCommand;
|
|
use CodeIgniter\CLI\CLI;
|
|
use App\Models\Entities\NaverWorkerLogModel;
|
|
|
|
class NaverRetry extends BaseCommand
|
|
{
|
|
protected $group = 'Workers';
|
|
protected $name = 'naver:retry';
|
|
protected $description = '실패한 Naver Worker 작업을 재처리합니다.';
|
|
|
|
protected $usage = 'naver:retry [log_id] [options]';
|
|
protected $arguments = [
|
|
'log_id' => '재처리할 특정 로그 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;
|
|
}
|
|
}
|
|
}
|