107 lines
4.4 KiB
PHP
107 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace App\Commands;
|
|
|
|
use CodeIgniter\CLI\BaseCommand;
|
|
use CodeIgniter\CLI\CLI;
|
|
|
|
class NaverWorker extends BaseCommand
|
|
{
|
|
protected $group = 'Workers';
|
|
protected $name = 'naver:worker';
|
|
protected $description = 'Process Naver verification requests from Redis queue with retry logic.';
|
|
|
|
// 최대 재시도 횟수 정의
|
|
private const MAX_RETRIES = 3;
|
|
private const RETRY_DELAY = 60; // 초 단위 (60초 지연 후 재시도)
|
|
|
|
public function run(array $params)
|
|
{
|
|
$redis = new \Redis();
|
|
// 환경 변수를 사용하도록 변경 (php_worker service의 env 설정 활용)
|
|
$redisHost = getenv('REDIS_HOST') ?: 'redis';
|
|
$redisPort = getenv('REDIS_PORT') ?: 6379;
|
|
|
|
$redis->connect($redisHost, $redisPort);
|
|
|
|
CLI::write('Worker started. Listening on naver:queue...');
|
|
|
|
while (true) {
|
|
// 메인 큐 및 재시도 큐에서 데이터 꺼내기 (Blocking Pop, 5초 타임아웃)
|
|
// LIFO (lPush/brPop) 사용 시: ['naver:queue:retry', 'naver:queue']
|
|
$item = $redis->brPop(['naver:worker_queue'], 5);
|
|
|
|
if ($item) {
|
|
$payload = json_decode($item[1], true);
|
|
$this->process($redis, $payload); // Redis 객체를 process에 전달
|
|
}
|
|
}
|
|
}
|
|
|
|
private function process(\Redis $redis, $payload)
|
|
{
|
|
$articleNum = $payload['articleNumbr'];
|
|
$requestType = $payload['reqeustType'];
|
|
$retryCount = $payload['retry_count'] ?? 0;
|
|
|
|
CLI::write("Processing {$requestType} for {$articleNum} (Attempt: " . ($retryCount + 1) . ")");
|
|
|
|
if (!in_array($requestType, ['REG', 'MOD'])) {
|
|
CLI::write("Skipping non-verification request {$requestType} for {$articleNum}");
|
|
return;
|
|
}
|
|
|
|
// 1. 네이버 CP API 호출
|
|
$client = \Config\Services::curlrequest([
|
|
'timeout' => 10,
|
|
// ... (인증 헤더 설정 등 이전 답변의 보안 관련 항목 추가)
|
|
]);
|
|
$url = "https://네이버CP/kiso/center/verification-article/{$articleNum}";
|
|
|
|
try {
|
|
$response = $client->get($url);
|
|
$statusCode = $response->getStatusCode();
|
|
|
|
if ($statusCode >= 200 && $statusCode < 300) {
|
|
// 2. 성공 처리
|
|
CLI::write("✅ SUCCESS: {$requestType} for {$articleNum} (Status: {$statusCode})");
|
|
// 성공 시 DB에 결과 업데이트 등 후속 작업 진행
|
|
} else {
|
|
// 3. API 실패 (4xx, 5xx 에러)
|
|
$this->handleFailure($redis, $payload, $retryCount, "API returned Status: {$statusCode}");
|
|
}
|
|
} catch (\Exception $e) {
|
|
// 4. 네트워크/타임아웃 오류
|
|
$this->handleFailure($redis, $payload, $retryCount, "Network Error: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
private function handleFailure(\Redis $redis, $payload, $retryCount, $reason)
|
|
{
|
|
$articleNum = $payload['articleNumbr'];
|
|
|
|
if ($retryCount < self::MAX_RETRIES) {
|
|
// 5. 재시도 로직: 재시도 횟수 증가 및 큐에 다시 푸시
|
|
$payload['retry_count'] = $retryCount + 1;
|
|
|
|
// 지연된 재시도를 위해 Sorted Set (ZADD) 또는 별도의 큐 사용을 고려할 수 있으나,
|
|
// 여기서는 단순성을 위해 즉시 재시도 큐에 넣고 sleep을 사용하는 방식을 가정합니다.
|
|
// **주의: 실제 프로덕션 환경에서는 `Redis ZSET`을 사용하여 지연된 작업을 처리하는 것이 더 일반적입니다.**
|
|
|
|
// 단순 재시도 큐 (ZSET을 사용하지 않는 경우)
|
|
$redis->lPush('naver:queue:retry', json_encode($payload));
|
|
|
|
// CLI 환경에서 재시도 간 지연 시간을 강제 (Blocking pop을 쓰므로 실제 지연은 다음 worker 실행 시 발생)
|
|
CLI::error("Attempt {$retryCount} FAILED for {$articleNum}. Reason: {$reason}. Retrying later.");
|
|
|
|
} else {
|
|
// 6. 최종 실패 처리: DLQ로 이동
|
|
$payload['fail_reason'] = $reason;
|
|
$payload['fail_time'] = date('Y-m-d H:i:s');
|
|
|
|
$redis->lPush('naver:queue:dlq', json_encode($payload));
|
|
|
|
CLI::error("❌ CRITICAL FAIL: {$articleNum} moved to DLQ after " . self::MAX_RETRIES . " attempts.");
|
|
}
|
|
}
|
|
} |