네이버 매물 정보받기
This commit is contained in:
@@ -9,42 +9,99 @@ 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';
|
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)
|
public function run(array $params)
|
||||||
{
|
{
|
||||||
$redis = new \Redis();
|
$redis = new \Redis();
|
||||||
$redis->connect('redis', 6379);
|
// 환경 변수를 사용하도록 변경 (php_worker service의 env 설정 활용)
|
||||||
|
$redisHost = getenv('REDIS_HOST') ?: 'redis';
|
||||||
|
$redisPort = getenv('REDIS_PORT') ?: 6379;
|
||||||
|
|
||||||
CLI::write('Worker started...');
|
$redis->connect($redisHost, $redisPort);
|
||||||
|
|
||||||
|
CLI::write('Worker started. Listening on naver:queue...');
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// 큐에서 데이터 꺼내기 (blocking pop)
|
// 메인 큐 및 재시도 큐에서 데이터 꺼내기 (Blocking Pop, 5초 타임아웃)
|
||||||
|
// LIFO (lPush/brPop) 사용 시: ['naver:queue:retry', 'naver:queue']
|
||||||
$item = $redis->brPop(['naver:queue'], 5);
|
$item = $redis->brPop(['naver:queue'], 5);
|
||||||
|
|
||||||
if ($item) {
|
if ($item) {
|
||||||
$payload = json_decode($item[1], true);
|
$payload = json_decode($item[1], true);
|
||||||
$this->process($payload);
|
$this->process($redis, $payload); // Redis 객체를 process에 전달
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function process($payload)
|
private function process(\Redis $redis, $payload)
|
||||||
{
|
{
|
||||||
$articleNum = $payload['articleNumbr'];
|
$articleNum = $payload['articleNumbr'];
|
||||||
$requestType = $payload['reqeustType'];
|
$requestType = $payload['reqeustType'];
|
||||||
|
$retryCount = $payload['retry_count'] ?? 0;
|
||||||
|
|
||||||
if (in_array($requestType, ['REG','MOD'])) {
|
CLI::write("Processing {$requestType} for {$articleNum} (Attempt: " . ($retryCount + 1) . ")");
|
||||||
$client = \Config\Services::curlrequest();
|
|
||||||
$url = "https://네이버CP/kiso/center/verification-article/{$articleNum}";
|
|
||||||
|
|
||||||
try {
|
if (!in_array($requestType, ['REG', 'MOD'])) {
|
||||||
$response = $client->get($url);
|
CLI::write("Skipping non-verification request {$requestType} for {$articleNum}");
|
||||||
CLI::write("Processed {$requestType} for {$articleNum}: " . $response->getStatusCode());
|
return;
|
||||||
} catch (\Exception $e) {
|
}
|
||||||
CLI::error("Error processing {$articleNum}: " . $e->getMessage());
|
|
||||||
|
// 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 {
|
} else {
|
||||||
CLI::write("Skipping {$requestType} for {$articleNum}");
|
// 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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ class Cache extends BaseConfig
|
|||||||
* The name of the preferred handler that should be used. If for some reason
|
* The name of the preferred handler that should be used. If for some reason
|
||||||
* it is not available, the $backupHandler will be used in its place.
|
* it is not available, the $backupHandler will be used in its place.
|
||||||
*/
|
*/
|
||||||
public string $handler = 'file';
|
public string $handler = 'redis';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
@@ -114,13 +114,7 @@ class Cache extends BaseConfig
|
|||||||
*
|
*
|
||||||
* @var array{host?: string, password?: string|null, port?: int, timeout?: int, database?: int}
|
* @var array{host?: string, password?: string|null, port?: int, timeout?: int, database?: int}
|
||||||
*/
|
*/
|
||||||
public array $redis = [
|
public array $redis = [];
|
||||||
'host' => '127.0.0.1',
|
|
||||||
'password' => null,
|
|
||||||
'port' => 6379,
|
|
||||||
'timeout' => 0,
|
|
||||||
'database' => 0,
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
@@ -159,4 +153,22 @@ class Cache extends BaseConfig
|
|||||||
* @var bool|list<string>
|
* @var bool|list<string>
|
||||||
*/
|
*/
|
||||||
public $cacheQueryString = false;
|
public $cacheQueryString = false;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// Redis 설정에 .env 값을 할당 (이전 논의된 Docker 호스트 이름 'redis' 사용)
|
||||||
|
$this->redis = [
|
||||||
|
'host' => env('redis.default.host', '127.0.0.1'),
|
||||||
|
'password' => env('redis.default.password', null),
|
||||||
|
'port' => (int)env('redis.default.port', 6379),
|
||||||
|
'timeout' => (int)env('redis.default.timeout', 0),
|
||||||
|
'database' => (int)env('redis.default.database', 0)
|
||||||
|
];
|
||||||
|
|
||||||
|
// 필요하다면, 이 생성자에서 $handler나 $backupHandler 같은 다른 설정도
|
||||||
|
// 환경 변수에 따라 동적으로 설정할 수 있습니다.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ use CodeIgniter\Router\RouteCollection;
|
|||||||
/** @var RouteCollection $routes */
|
/** @var RouteCollection $routes */
|
||||||
|
|
||||||
$routes->group('kiso', function(RouteCollection $routes) {
|
$routes->group('kiso', function(RouteCollection $routes) {
|
||||||
$routes->get('api/vrfcReq', 'KisoController::vrfcReq');
|
$routes->match(['get', 'post'], 'api/vrfcReq', 'KisoController::vrfcReq');
|
||||||
});
|
});
|
||||||
@@ -2,28 +2,81 @@
|
|||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
|
use CodeIgniter\HTTP\Response;
|
||||||
|
|
||||||
class KisoController extends BaseController
|
class KisoController extends BaseController
|
||||||
{
|
{
|
||||||
|
/** @var RequestInterface */
|
||||||
|
protected $request;
|
||||||
|
|
||||||
|
/** @var ResponseInterface */
|
||||||
|
protected $response;
|
||||||
|
|
||||||
public function vrfcReq()
|
public function vrfcReq()
|
||||||
{
|
{
|
||||||
$data = $this->request->getJSON(true);
|
// 1. 요청 방식에 따라 데이터 파싱
|
||||||
|
if ($this->request->getMethod() === 'post') {
|
||||||
$articleNum = $data['articleNumbr'] ?? null;
|
// POST 방식: JSON Body에서 데이터 가져오기
|
||||||
$requestType = $data['reqeustType'] ?? null;
|
$data = $this->request->getJSON(true);
|
||||||
|
} elseif ($this->request->getMethod() === 'get') {
|
||||||
if (!$articleNum || !$requestType) {
|
// GET 방식: Query Parameter에서 데이터 가져오기
|
||||||
return $this->response->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST)
|
$data = $this->request->getGet();
|
||||||
->setJSON(['error' => 'Invalid request']);
|
} else {
|
||||||
|
// 지원하지 않는 메소드 처리 (예: PUT, DELETE 등)
|
||||||
|
return $this->response->setStatusCode(Response::HTTP_METHOD_NOT_ALLOWED)
|
||||||
|
->setJSON(['resultCode' => 'E005', 'resultMessage' => 'Method not allowed. Use GET or POST.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redis 연결
|
// 2. 필수 항목 검증
|
||||||
$redis = new \Redis();
|
$requiredKeys = ['articleNumbr', 'reqeustType', 'requestDatetime'];
|
||||||
$redis->connect('redis', 6379);
|
|
||||||
|
|
||||||
// 큐에 push
|
foreach ($requiredKeys as $key) {
|
||||||
$redis->lPush('naver:queue', json_encode($data));
|
// 파싱된 데이터($data) 내에 키가 없거나 값이 비어있는지 확인
|
||||||
|
if (empty($data[$key])) {
|
||||||
|
return $this->response->setStatusCode(Response::HTTP_BAD_REQUEST)
|
||||||
|
->setJSON([
|
||||||
|
'resultCode' => 'E001',
|
||||||
|
'resultMessage' => "Missing required parameter: {$key}"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this->response->setJSON(['status' => 'queued']);
|
// 3. Redis 연결 및 예외 처리
|
||||||
|
try {
|
||||||
|
// CI4 Cache Service 인스턴스를 가져옴. 기본 핸들러가 Redis여야 함.
|
||||||
|
// (또는 Services::cache('redis')를 사용해 RedisHandler를 명시적으로 요청)
|
||||||
|
$redis = \Config\Services::cache();
|
||||||
|
|
||||||
|
// Redis 핸들러가 맞는지 확인 (선택 사항)
|
||||||
|
if (!($redis->getHandler() instanceof RedisHandler)) {
|
||||||
|
throw new \Exception('Cache handler is not Redis. Check Config/Cache.php $handler setting.');
|
||||||
|
}
|
||||||
|
// 요청에 재시도 횟수 초기화
|
||||||
|
$data['retry_count'] = 0;
|
||||||
|
|
||||||
|
// RedisHandler의 getClient()를 사용하여 원본 \Redis 객체를 가져옵니다.
|
||||||
|
// 주의: 모든 RedisHandler가 getClient()를 제공하는 것은 아니지만, CI4 내장 RedisHandler는 제공합니다.
|
||||||
|
$client = $redis->getHandler()->getClient();
|
||||||
|
|
||||||
|
// 큐에 push
|
||||||
|
$client->lPush('naver:queue', json_encode($data));
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
log_message('error', 'Redis connection failed: ' . $e->getMessage());
|
||||||
|
return $this->response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR)
|
||||||
|
->setJSON([
|
||||||
|
'resultCode' => 'E999',
|
||||||
|
'resultMessage' => 'Internal server error (Queue system unavailable)'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 성공 응답
|
||||||
|
return $this->response->setStatusCode(Response::HTTP_ACCEPTED) // 202 Accepted
|
||||||
|
->setJSON([
|
||||||
|
'resultCode' => 'S000',
|
||||||
|
'resultMessage' => 'Request successfully queued for processing',
|
||||||
|
'articleNumbr' => $data['articleNumbr']
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user