From 8338df57c9474b9e8c0e46af07dec357630c679d Mon Sep 17 00:00:00 2001 From: jjstyle Date: Fri, 2 Jan 2026 14:50:27 +0900 Subject: [PATCH] =?UTF-8?q?httpd=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20202->=20200?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Commands/NaverWorker.php | 201 ++++++++++++++++++-- app/Models/Entities/NaverWorkerLogModel.php | 26 +++ app/Models/{ => Entities}/ReceiptModel.php | 0 app/Models/{ => Entities}/VrfcReqModel.php | 0 app/Services/NaverService.php | 104 +++++++++- worker/api_receiver.php | 2 +- 6 files changed, 306 insertions(+), 27 deletions(-) create mode 100644 app/Models/Entities/NaverWorkerLogModel.php rename app/Models/{ => Entities}/ReceiptModel.php (100%) rename app/Models/{ => Entities}/VrfcReqModel.php (100%) diff --git a/app/Commands/NaverWorker.php b/app/Commands/NaverWorker.php index 757c5ea..1105b26 100644 --- a/app/Commands/NaverWorker.php +++ b/app/Commands/NaverWorker.php @@ -5,6 +5,8 @@ namespace App\Commands; use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; +use App\Models\Entities\NaverWorkerLogModel; // 새로 만든 테이블용 모델 + // 헬퍼 로드 (app/Helpers/log_helper.php 가 있어야 함 autoload 설정 넣어놓았음) class NaverWorker extends BaseCommand @@ -16,6 +18,9 @@ class NaverWorker extends BaseCommand public function run(array $params) { helper('log'); // 여기서 로드 완료! + + $logModel = model(NaverWorkerLogModel::class); + $naverService = new \App\Services\NaverService(); // 서비스 생성 $redis = new \Redis(); try { @@ -27,32 +32,188 @@ class NaverWorker extends BaseCommand return; } - $naverService = new \App\Services\NaverService(); // 서비스 생성 + - while (true) { - $result = $redis->brPop(['naver:raw_queue'], 30); - - if ($result) { - try { - $responseJson = json_decode($result[1], true); - $payload = $responseJson['request_data'] ?? []; + while (true) { + $result = $redis->brPop(['naver:raw_queue'], 30); - if (empty($payload)) { - throw new \Exception("빈 페이로드 데이터"); - } + if (!$result) { + // 데이터가 없어서 타임아웃 난 경우. + // 굳이 sleep 안 해도 바로 다음 brPop이 다시 30초 대기를 시작함. + continue; + } - // 서비스의 함수 하나로 모든 처리 완료 - $insertId = $naverService->processArticle($payload); - - CLI::write("✅ Success! DB ID: $insertId", 'cyan'); + if ($result) { + $rawData = $result[1]; + // [1] 꺼내자마자 DB에 원문 저장 (2차 임시 저장) + $logId = $logModel->insert([ + 'raw_payload' => $rawData, + 'status' => 'INIT' + ]); - } catch (\Exception $e) { - CLI::error("❌ Task Failed: " . $e->getMessage()); - // 실패 로그는 여기서 남김 - helper('log'); - write_custom_log("FAILED_DATA | Error: " . $e->getMessage(), 'ERROR', 'failed'); + try { + $responseJson = json_decode($result[1], true); + $payload = $responseJson['request_data'] ?? []; + + if (empty($payload)) { + throw new \Exception("빈 페이로드 데이터"); } + + // 서비스의 함수 하나로 모든 처리 완료 + $insertId = $naverService->processArticle($payload); + + // [3] 성공 시 로그 업데이트 + $logModel->update($logId, [ + 'atcl_no' => $payload['articleNumber'] ?? null, + 'status' => 'SUCCESS', + 'target_db_id' => $insertId + ]); + + CLI::write("✅ Success! DB ID: $insertId", 'cyan'); + + } catch (\Exception $e) { + CLI::error("❌ Task Failed: " . $e->getMessage()); + // 실패 로그는 여기서 남김 + helper('log'); + write_custom_log("FAILED_DATA | Error: " . $e->getMessage(), 'ERROR', 'failed'); } } } + } + + private function insertVrfc($payload) + { + // 1. 필수 데이터 검증 + if (empty($payload['articleNumber']) || empty($payload['requestType'])) { + throw new \Exception("필수 파라미터 누락"); + } + + $articleNumber = $payload['articleNumber']; + $requestType = $payload['requestType']; + $requestDatetime = date('Y-m-d H:i:s', strtotime($payload['requestDatetime'])); + + $vrfcModel = model(\App\Models\VrfcReqModel::class); + // 중복 요청 체크 + $existing = $vrfcModel->where('atcl_no', $articleNumber) + ->where('req_type', $requestType) + ->first(); + if ($existing) { + throw new \Exception("중복 요청: " . $articleNumber . " / " . $requestType); + } + + // 2. 네이버 API 클라이언트 초기화 + $naverClient = new \App\Libraries\NaverApiClient(); + // 매물 정보 조회 + $articleInfojson = $naverClient->getArticleInfo($articleNumber); + // if (!$articleInfojson || !isset($articleInfojson['data']) || empty($articleInfojson['code'] !== 'success')) { + // throw new \Exception("매물 정보 조회 실패: $articleNumber ::: message : " . ($articleInfojson['message'] ?? 'No response')); + // } + + if (!$articleInfojson || !isset($articleInfojson['data']) || $articleInfojson['code'] !== 'success') { + $msg = $articleInfojson['message'] ?? 'No message'; + throw new \Exception("네이버 API 응답 에러: $articleNumber | 메시지: $msg"); + } + + $articleInfo = $articleInfojson['data']; + + // 받아온 정보 로그 기록 + CLI::write("DEBUG: write_custom_log 호출 직전"); + write_custom_log("ARTICLE_INFO | ArticleNumber: $articleNumber | Info: " . json_encode($articleInfo , JSON_UNESCAPED_UNICODE), 'INFO', 'service'); + CLI::write("DEBUG: write_custom_log 호출 완료"); + + /** + * $articleInfo['verificationTypeCode'] + * S : 현장확인매물 + * D : 홍보확인서 + * N : 신홍보확인서 + * M : 모바일 + * T : 전화 + * O : 모바일확인V2 + * + * S - reciept , result 테이블 사용 + * D,N,M,T,O - v2_vrfc_req , v2_article_info , v2_article_info_etc , v2_article_fail 테이블 사용 + */ + + // {"code":"success","message":"","data":{"articleNumber":"2500000001","realEstateType":"빌라/연립","realEstateTypeCode":"C02","cpId":"toad","cpArticleNumber":"15882606","tradeType":"전세","tradeTypeCode":"B1","statusTypeCode":"E12","verificationTypeCode":"M","isUnregisteredVerificationRequested":false,"isBuildingRegisterAreaCheckRequested":false,"isAutoVerificationRequested":false,"exposureStartDateTime":"2025-01-02 09:12:02","facilities":{"roomCount":2,"bathroomCount":1},"address":{"legalDivision":{"cityNumber":"1100000000","divisionNumber":"1168000000","sectorNumber":"1168010700","legalDivisionAddress":"서울특별시 강남구 신사동"},"isVirtualAddress":false,"correspondenceFloorCount":2,"longitude":0.0,"latitude":0.0},"space":{"totalSpace":34.5,"groundSpace":34.5,"buildingSpace":34.5,"supplySpace":34.5,"exclusiveSpace":34.5},"price":{"dealAmount":777777777,"warrantyAmount":2999999999,"leaseAmount":7777777},"floor":{"correspondenceFloorCount":2,"totalFloorCount":3,"undergroundFloorCount":null}}} + $comp_sq = '2'; + $rcpt_rating = '3'; + + $files = $articleInfo['files'] ?? []; + $certRegister = []; + $confirm_doc_img_url = []; + $referenceFileUrl = []; + foreach ($files as $file) { + // 파일 처리 로직 + $fileTypeCode = $file['fileTypeCode']; + if ( $fileTypeCode == 'RCDOC' ) { + $certRegister[] = $file['fileUrl']; + } elseif ( $fileTypeCode == 'ADDOC' ) { + $confirm_doc_img_url[] = $file['fileUrl']; + } elseif ( $fileTypeCode == 'REFER') { + $referenceFileUrl[] = $file['fileUrl']; + } + } + + $vrfc_params = [ + 'reqSeq' => '', + 'atcl_no' => $articleInfo['articleNumber'], + 'step' => '', + 'cpid' => $articleInfo['cpId'], + 'cp_atcl_id' => $articleInfo['cpArticleNumber'], + 'trade_type' => $articleInfo['tradeTypeCode'], + 'realtor_nm' => $articleInfo['realtor']['realtorName'], + 'realtor_tel_no' => $articleInfo['realtor']['representativeCellphoneNumber'], + 'seller_tel_no' => $articleInfo['seller']['sellerTelephoneNumber'], + 'vrfc_type' => $articleInfo['verificationTypeCode'], + 'rgbk_confirm' => $articleInfo['isUnregisteredVerificationRequested'] ? 'Y' : 'N', + 'req_type' => '', + 'rdate' => $requestDatetime ?? db_now('Y-m-d H:i:s'), + 'cpTelNo' => $articleInfo['seller']['sellerTelephoneNumber'], + 'stat_cd' => '10', + 'try_cnt' => '0', + 'insert_user' => 'admin', + 'insert_tm' => db_now(), + 'memo' => '', + 'contact_fail_cnt' => '0', + 'sync_yn' => 'N', + 'reg_try_cnt' => '0', + 'tel_fail_cause' => null, + 'rgbk_confirm_owner_nm' => $articleInfo['seller']['ownerName'] ?? null, + 'direct_trad_yn' => $articleInfo['seller']['isDirectTrade'] === true ? 'Y' : 'N', + 'confirm_doc_img_url' => json_encode($confirm_doc_img_url, JSON_UNESCAPED_UNICODE), + 'confirm_doc_owner_check_yn' => '', + 'owner_verifiable' => null, + 'vrfc_cmpl_type' => null, + 'rgbk_doc_img_url' => null, + 'certRegister' => json_encode($certRegister, JSON_UNESCAPED_UNICODE), + 'referenceFileUrl' => json_encode($referenceFileUrl, JSON_UNESCAPED_UNICODE), + ]; + + write_custom_log("VRFC_PARAMS | " . json_encode($vrfc_params , JSON_UNESCAPED_UNICODE), 'INFO', 'service'); + + + // if ($articleInfo['verificationTypeCode'] == 'S') { // 현장확인매물 + // // 현장확인매물 처리 로직 + + // } else { // 그외 일반매물 + // // V2 매물 처리 로직 + // $v2ArticleModel = model(\App\Models\V2ArticleModel::class); + // // 매물 번호로 중복 체크 + // $existingV2 = $v2ArticleModel->where('atcl_no', $articleInfo['articleNumber'])->first(); + // if ($existingV2) { + // throw new \Exception("중복 매물 번호: " . $articleInfo['articleNumber']); + // } + + + // $v2ArticleModel->insert($vrfc_params); + // $vr_sq = $v2ArticleModel->getInsertID(); // 방금 삽입한 vr_sq 값 가져오기 + // CLI::write("Inserted V2 Article with vr_sq: " . $vr_sq); + // // 오류 처리 + // if ($v2ArticleModel->errors()) { + // $errorMessages = implode(', ', $v2ArticleModel->errors()); + // throw new \Exception("V2 매물 삽입 오류: " . $errorMessages); + // } + + // } + } } \ No newline at end of file diff --git a/app/Models/Entities/NaverWorkerLogModel.php b/app/Models/Entities/NaverWorkerLogModel.php new file mode 100644 index 0000000..4a4d8fb --- /dev/null +++ b/app/Models/Entities/NaverWorkerLogModel.php @@ -0,0 +1,26 @@ +naverClient->getArticleInfo($articleNumber); @@ -35,16 +44,41 @@ class NaverService } $articleInfo = $response['data']; - // 로그 기록 write_custom_log("ARTICLE_INFO | ArticleNumber: $articleNumber", 'INFO', 'service'); - // 2. 가공 로직 (insertVrfc에 있던 긴 배열 생성 로직) $vrfcParams = $this->mapToDatabaseParams($articleInfo, $payload); - - // write_custom_log("VRFC_PARAMS | " . json_encode($vrfcParams, JSON_UNESCAPED_UNICODE), 'INFO', 'service'); + switch ($requestType) { + case 'REG': + $vrfcParams['req_type'] = '10'; + return $this->handleRegistration($articleNumber, $vrfcParams); + break; + case 'MOD': + $vrfcParams['req_type'] = '20'; + return $this->handleModification($articleNumber, $vrfcParams); + break; + case 'CNC': + $vrfcParams['req_type'] = '30'; + return $this->handleStatusChange($articleNumber, $vrfcParams); + break; + case 'FIN': + $vrfcParams['req_type'] = '40'; + return $this->handleStatusChange($articleNumber, $vrfcParams); + break; + default: + throw new \Exception("알 수 없는 requestType: " . $requestType); + } + + // 중복 체크 + $existing = $this->VrfcReqModel->where('atcl_no', $articleNumber) + + ->first(); + if ($existing) { + throw new \Exception("중복 요청: " . $articleNumber . " / " . $vrfcParams['req_type']); + } + // 3. DB 저장 if (!$this->VrfcReqModel->insert($vrfcParams)) { $errorMessages = implode(', ', $this->VrfcReqModel->errors()); @@ -113,4 +147,62 @@ class NaverService return $vrfc_params; } + + /** + * [REG] 신규 등록 처리 + */ + private function handleRegistration($articleNumber, $params) + { + $existing = $this->VrfcReqModel->where('atcl_no', $articleNumber)->first(); + if ($existing) { + // 이미 존재한다면 업데이트로 돌리거나 예외 처리 + return $this->handleModification($articleNumber, $params); + } + + if (!$this->VrfcReqModel->insert($params)) { + throw new \Exception("등록 실패: " . implode(', ', $this->VrfcReqModel->errors())); + } + return $this->VrfcReqModel->getInsertID(); + } + + /** + * [MOD] 수정 처리 + */ + private function handleModification($articleNumber, $params) + { + // 기존 기록이 있는지 확인 + $existing = $this->VrfcReqModel->where('atcl_no', $articleNumber)->first(); + + if (!$existing) { + // 수정 요청인데 데이터가 없으면 새로 등록 + return $this->handleRegistration($articleNumber, $params); + } + + // 기존 데이터 업데이트 (PK인 reqSeq 기준으로 업데이트) + if (!$this->VrfcReqModel->update($existing['reqSeq'], $params)) { + throw new \Exception("수정 실패: " . implode(', ', $this->VrfcReqModel->errors())); + } + return $existing['reqSeq']; + } + + /** + * [CNC / FIN] 상태값 변경 처리 + */ + private function handleStatusChange($articleNumber, $statusCode, $memo) + { + $existing = $this->VrfcReqModel->where('atcl_no', $articleNumber)->first(); + if (!$existing) { + throw new \Exception("상태 변경 실패: 해당 매물 없음 ($articleNumber)"); + } + + $updateData = [ + 'stat_cd' => $statusCode, + 'memo' => $existing['memo'] . " | " . $memo . "(" . date('Y-m-d H:i:s') . ")" + ]; + + if (!$this->VrfcReqModel->update($existing['reqSeq'], $updateData)) { + throw new \Exception("상태 업데이트 실패: " . $articleNumber); + } + return $existing['reqSeq']; + } } \ No newline at end of file diff --git a/worker/api_receiver.php b/worker/api_receiver.php index 3e16c47..e3efbd4 100644 --- a/worker/api_receiver.php +++ b/worker/api_receiver.php @@ -57,7 +57,7 @@ try { // -------------------------------------- // 6. 네이버측에 성공 응답 (202 Accepted) // 처리가 완료된 것은 아니지만, 접수는 완료되었음을 의미 - http_response_code(202); + http_response_code(200); echo apiResponse([ 'code' => 'success', 'message' => ''