worker 프로젝트에 추가
This commit is contained in:
@@ -9,99 +9,32 @@ class NaverWorker extends BaseCommand
|
|||||||
{
|
{
|
||||||
protected $group = 'Workers';
|
protected $group = 'Workers';
|
||||||
protected $name = 'naver:worker';
|
protected $name = 'naver:worker';
|
||||||
protected $description = 'Process Naver verification requests from Redis queue with retry logic.';
|
protected $description = 'Redis에서 데이터를 꺼내 DB에 저장하고 네이버 API를 호출합니다.';
|
||||||
|
|
||||||
// 최대 재시도 횟수 정의
|
|
||||||
private const MAX_RETRIES = 3;
|
|
||||||
private const RETRY_DELAY = 60; // 초 단위 (60초 지연 후 재시도)
|
|
||||||
|
|
||||||
public function run(array $params)
|
public function run(array $params)
|
||||||
{
|
{
|
||||||
$redis = new \Redis();
|
$redis = new \Redis();
|
||||||
// 환경 변수를 사용하도록 변경 (php_worker service의 env 설정 활용)
|
$redis->connect('redis', 6379);
|
||||||
$redisHost = getenv('REDIS_HOST') ?: 'redis';
|
$redis->select(10);
|
||||||
$redisPort = getenv('REDIS_PORT') ?: 6379;
|
|
||||||
|
|
||||||
$redis->connect($redisHost, $redisPort);
|
|
||||||
|
|
||||||
CLI::write('Worker started. Listening on naver:queue...');
|
CLI::write('🟢 Naver Worker running...');
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// 메인 큐 및 재시도 큐에서 데이터 꺼내기 (Blocking Pop, 5초 타임아웃)
|
// Redis에서 데이터가 들어올 때까지 대기 (Blocking Pop)
|
||||||
// LIFO (lPush/brPop) 사용 시: ['naver:queue:retry', 'naver:queue']
|
// 'naver:raw_queue'는 api_receiver.php에서 lPush한 이름과 같아야 함
|
||||||
$item = $redis->brPop(['naver:worker_queue'], 5);
|
$result = $redis->brPop(['naver:raw_queue'], 30);
|
||||||
|
|
||||||
if ($item) {
|
if ($result) {
|
||||||
$payload = json_decode($item[1], true);
|
$payload = json_decode($result[1], true);
|
||||||
$this->process($redis, $payload); // Redis 객체를 process에 전달
|
$this->processTask($payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function process(\Redis $redis, $payload)
|
private function processTask($payload)
|
||||||
{
|
{
|
||||||
$articleNum = $payload['articleNumbr'];
|
// 여기서 DB 모델(ConfirmModel)을 불러와 저장하고
|
||||||
$requestType = $payload['reqeustType'];
|
// CURL을 사용하여 네이버 API를 호출하는 로직을 작성합니다.
|
||||||
$retryCount = $payload['retry_count'] ?? 0;
|
CLI::write("Processing data received at: " . $payload['received_at']);
|
||||||
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
77
worker/api_receiver.php
Normal file
77
worker/api_receiver.php
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* [A 작업] 네이버 검증 요청 실시간 수신 리시버
|
||||||
|
* - 프레임워크를 로드하지 않아 매우 빠르고 안전함
|
||||||
|
* - 받은 데이터를 Redis 큐에 넣고 즉시 응답
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 1. 응답 헤더 설정 (JSON)
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
// 2. 보안 키 체크 (URL 파라미터 key=값)
|
||||||
|
$configKey = "YOUR_SECRET_KEY_HERE"; // 실제 사용할 키값으로 변경하세요
|
||||||
|
$receivedKey = $_GET['key'] ?? '';
|
||||||
|
|
||||||
|
if ($receivedKey !== $configKey) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode([
|
||||||
|
'resultCode' => '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()
|
||||||
|
]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user