diff --git a/app/Commands/NaverWorker.php b/app/Commands/NaverWorker.php index 580e25b..b002ae6 100644 --- a/app/Commands/NaverWorker.php +++ b/app/Commands/NaverWorker.php @@ -9,99 +9,32 @@ 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초 지연 후 재시도) + protected $description = 'Redis에서 데이터를 꺼내 DB에 저장하고 네이버 API를 호출합니다.'; 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); + $redis->connect('redis', 6379); + $redis->select(10); - CLI::write('Worker started. Listening on naver:queue...'); + CLI::write('🟢 Naver Worker running...'); 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에 전달 + // Redis에서 데이터가 들어올 때까지 대기 (Blocking Pop) + // 'naver:raw_queue'는 api_receiver.php에서 lPush한 이름과 같아야 함 + $result = $redis->brPop(['naver:raw_queue'], 30); + + if ($result) { + $payload = json_decode($result[1], true); + $this->processTask($payload); } } } - private function process(\Redis $redis, $payload) + private function processTask($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."); - } + // 여기서 DB 모델(ConfirmModel)을 불러와 저장하고 + // CURL을 사용하여 네이버 API를 호출하는 로직을 작성합니다. + CLI::write("Processing data received at: " . $payload['received_at']); } } \ No newline at end of file diff --git a/worker/api_receiver.php b/worker/api_receiver.php new file mode 100644 index 0000000..eba91fe --- /dev/null +++ b/worker/api_receiver.php @@ -0,0 +1,77 @@ + 'E003', + 'resultMessage' => 'Invalid API Key' + ]); + exit; +} + +try { + // 3. 데이터 수신 (POST JSON 또는 GET 파라미터) + $rawData = file_get_contents('php://input'); + $data = json_decode($rawData, true); + + // JSON 데이터가 비어있다면 GET 파라미터 사용 (테스트용) + if (empty($data)) { + $data = $_GET; + } + + if (empty($data)) { + throw new Exception("Empty data received"); + } + + // 4. Redis 연결 + $redis = new Redis(); + // Docker 서비스 이름인 'redis' 사용 + $success = $redis->connect('redis', 6379); + + if (!$success) { + throw new Exception("Could not connect to Redis"); + } + + $redis->select(10); // 10번 DB 사용 + + // 5. 큐에 넣을 데이터 포맷팅 + $payload = [ + 'request_data' => $data, + 'received_at' => date('Y-m-d H:i:s'), + 'client_ip' => $_SERVER['REMOTE_ADDR'] + ]; + + // 'naver:raw_queue'라는 이름의 리스트에 저장 + $redis->lPush('naver:raw_queue', json_encode($payload)); + + // 6. 네이버측에 성공 응답 (202 Accepted) + // 처리가 완료된 것은 아니지만, 접수는 완료되었음을 의미 + http_response_code(202); + echo json_encode([ + 'resultCode' => 'S000', + 'resultMessage' => 'Request accepted and queued', + 'articleNumber' => $data['articleNumber'] ?? 'N/A' + ]); + +} catch (Exception $e) { + // 7. 장애 발생 시 로그 기록 (시스템 로그) + error_log("[API_RECEIVER_ERROR] " . $e->getMessage()); + + http_response_code(500); + echo json_encode([ + 'resultCode' => 'E999', + 'resultMessage' => 'Internal System Error: ' . $e->getMessage() + ]); +} \ No newline at end of file