feat: DB 정보 없이 진행

noneDB
박성영 4 weeks ago
parent a6a1b7d14b
commit b15825454a

@ -227,7 +227,7 @@ start "" /B "D:\DEV\.jdks\azul-17.0.14\bin\javaw.exe" -Dspring.profiles.active=p
cd /d d:
D:\VMIS-interface\azul-17.0.14\bin
cd D:\VMIS-interface\azul-17.0.14\bin
java -Dspring.profiles.active=prd -jar D:\VMIS-interface\VMIS-interface-0.0.1-SNAPSHOT.jar

@ -29,11 +29,6 @@ dependencies {
// GPKI JNI local library
implementation files('lib/libgpkiapi_jni_1.5.jar')
// Database & MyBatis
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
implementation 'org.mariadb.jdbc:mariadb-java-client:3.3.3'
// Configuration metadata
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

@ -0,0 +1,474 @@
# 자동차 정보 조회 API RestTemplate 구조
## 1. 자동차기본사항조회 API
### 1.1 Request (BasicRequest)
**엔드포인트**: `POST /api/v1/vehicles/basic`
```json
{
"data": [
{
"INFO_SYS_ID": "41-345", // 정보시스템ID (자동설정)
"INFO_SYS_IP": "105.19.10.135", // 정보시스템IP (자동설정)
"SIGUNGU_CODE": "41460", // 시군구코드 (자동설정)
"CNTC_INFO_CODE": "AC1_FD11_01", // 연계정보코드 (자동설정)
"CHARGER_ID": "", // 담당자ID (자동설정)
"CHARGER_IP": "", // 담당자IP (자동설정)
"CHARGER_NM": "", // 담당자명 (자동설정)
"LEVY_STDDE": "20250101", // 부과기준일
"INQIRE_SE_CODE": "3", // 조회구분코드 (자동설정: VHRNO→"3", VIN→"2")
"VHRNO": "12가3456", // 자동차등록번호
"VIN": null // 차대번호
}
]
}
```
#### 필드 설명
| 필드명 | 타입 | 설명 | 비고 |
|--------|------|------|------|
| INFO_SYS_ID | String | 정보시스템ID | 자동설정 |
| INFO_SYS_IP | String | 정보시스템IP | 자동설정 |
| SIGUNGU_CODE | String | 시군구코드 | 자동설정 |
| CNTC_INFO_CODE | String | 연계정보코드 | 자동설정 |
| CHARGER_ID | String | 담당자ID | 자동설정 |
| CHARGER_IP | String | 담당자IP | 자동설정 |
| CHARGER_NM | String | 담당자명(사용자) | 자동설정 |
| LEVY_STDDE | String | 부과기준일 (YYYYMMDD) | 사용자 입력 |
| INQIRE_SE_CODE | String | 조회구분코드 | 자동설정 (VHRNO→"3", VIN→"2") |
| VHRNO | String | 자동차등록번호 | 사용자 입력 |
| VIN | String | 차대번호 | 사용자 입력 |
---
### 1.2 Response (BasicResponse)
```json
{
"data": [
{
"CNTC_RESULT_CODE": "0",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
// 차량 기본 정보
"VHRNO": "12가3456",
"VIN": "KMHAB812345678901",
"CNM": "제네시스",
"FOM_NM": "GV80",
"COLOR_NM": "검은색",
"FRST_REGIST_DE": "20200115",
"REGIST_DE": "20200115",
"CHANGE_DE": "20200115",
"PRYE": "2020",
"YBL_MD": "202001",
// 차종 정보
"VHCTY_ASORT_CODE": "01",
"VHCTY_ASORT_NM": "승용",
"VHCTY_TY_CODE": "01",
"VHCTY_TY_NM": "일반",
"VHCTY_SE_CODE": "01",
"VHCTY_SE_NM": "대형",
"PRPOS_SE_CODE": "01",
// 차량 제원
"DSPLVL": "2987",
"VHCLE_TOT_WT": "2500",
"MXMM_LDG": "0",
"CBD_LT": "4945",
"CBD_BT": "1975",
"CBD_HG": "1715",
"FRST_MXMM_LDG": "0",
"FUEL_CNSMP_RT": "9.5",
"ELCTY_CMPND_FUEL_CNSMP_RT": "",
"USE_FUEL_CODE": "D",
"MTRS_FOM_NM": "D3.0",
// 소유자 정보
"MBER_SE_CODE": "1",
"MBER_NM": "홍길동",
"MBER_SE_NO": "8801011234567",
"TELNO": "010-1234-5678",
"OWNER_LEGALDONG_CODE": "1111011700",
"OWNER_ADSTRD_CODE": "1111011700",
"OWNER_MNTN": "0",
"OWNER_LNBR": "123",
"OWNER_HO": "1",
"OWNER_ADRES_NM": "서울특별시 종로구",
"OWNER_ROAD_NM_CODE": "1111011700",
"OWNER_UNDGRND_BULD_SE_CODE": "0",
"OWNER_BULD_MAIN_NO": "123",
"OWNER_BULD_SUB_NO": "0",
"OWNER_ADRES_FULL": "서울특별시 종로구 세종대로 123",
// 사용자 정보 (사용본거지)
"USE_STRNGHLD_LEGALDONG_CODE": "1111011700",
"USE_STRNGHLD_ADSTRD_CODE": "1111011700",
"USE_STRNGHLD_MNTN": "0",
"USE_STRNGHLD_LNBR": "123",
"USE_STRNGHLD_HO": "1",
"USE_STRNGHLD_ADRES_NM": "서울특별시 종로구",
"USE_STRNGHLD_ROAD_NM_CODE": "1111011700",
"USGSRHLD_UNDGRND_BULD_SE_CODE": "0",
"USE_STRNGHLD_BULD_MAIN_NO": "123",
"USE_STRNGHLD_BULD_SUB_NO": "0",
"USGSRHLD_ADRES_FULL": "서울특별시 종로구 세종대로 123",
"USE_STRNGHLD_GRC_CODE": "",
// 등록 상태
"REGIST_DETAIL_CODE": "11",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_SE_NM": "",
"ERSR_REGIST_DE": "",
"SPCF_REGIST_STTUS_CODE": "",
"FRNT_VHRNO": "",
"AFTR_VHRNO": "",
// 검사 정보
"INSPT_VALID_PD_BGNDE": "20240115",
"INSPT_VALID_PD_ENDDE": "20260114",
// 취득/압류 정보
"ACQS_DE": "20200115",
"ACQS_END_DE": "",
"ACQS_AMOUNT": "85000000",
"MRTG_CO": "0",
"SEIZR_CO": "0",
"STMD_CO": "0",
// 기타
"CAAG_ENDDE": "",
"TRANSR_REGIST_DE": "",
"ORIGIN_SE_CODE": "1",
"NMPL_STNDRD_CODE": "01",
"NMPL_CSDY_AT": "N",
"NMPL_CSDY_REMNR_DE": "",
"TKCAR_PSCAP_CO": "5",
"SPMNNO": "",
"TRVL_DSTNC": "45000",
"FRST_REGIST_RQRCNO": "",
"VLNT_ERSR_PRVNTC_NTICE_DE": "",
"REGIST_INSTT_NM": "서울특별시청",
"PROCESS_IMPRTY_RESN_CODE": "",
"PROCESS_IMPRTY_RESN_DTLS": ""
}
]
}
]
}
```
#### Response 필드 설명
| 구분 | 필드명 | 타입 | 설명 |
|------|--------|------|------|
| 기본 | CNTC_RESULT_CODE | String | 연계결과코드 |
| 기본 | CNTC_RESULT_DTLS | String | 연계결과상세 |
| 차량기본 | VHRNO | String | 자동차등록번호 |
| 차량기본 | VIN | String | 차대번호 |
| 차량기본 | CNM | String | 차명 |
| 차량기본 | FOM_NM | String | 형식명 |
| 차량기본 | COLOR_NM | String | 색상명 |
| 차량기본 | FRST_REGIST_DE | String | 최초등록일자 |
| 차량기본 | PRYE | String | 연식 |
| 차종 | VHCTY_ASORT_CODE | String | 차종구분코드 |
| 차종 | VHCTY_ASORT_NM | String | 차종구분명 |
| 차종 | VHCTY_TY_CODE | String | 차종유형코드 |
| 차종 | VHCTY_TY_NM | String | 차종유형명 |
| 차종 | VHCTY_SE_CODE | String | 차종세분류코드 |
| 차종 | VHCTY_SE_NM | String | 차종세분류명 |
| 제원 | DSPLVL | String | 배기량(cc) |
| 제원 | VHCLE_TOT_WT | String | 차량총중량(kg) |
| 제원 | MXMM_LDG | String | 최대적재량(kg) |
| 제원 | CBD_LT | String | 길이(mm) |
| 제원 | CBD_BT | String | 너비(mm) |
| 제원 | CBD_HG | String | 높이(mm) |
| 제원 | FUEL_CNSMP_RT | String | 연비(km/l) |
| 제원 | USE_FUEL_CODE | String | 사용연료코드 |
| 소유자 | MBER_SE_CODE | String | 회원구분코드 |
| 소유자 | MBER_NM | String | 회원명 |
| 소유자 | MBER_SE_NO | String | 회원구분번호 |
| 소유자 | TELNO | String | 전화번호 |
| 소유자 | OWNER_ADRES_FULL | String | 소유자주소전체 |
| 사용본거지 | USGSRHLD_ADRES_FULL | String | 사용자주소전체 |
| 검사 | INSPT_VALID_PD_BGNDE | String | 검사유효기간시작 |
| 검사 | INSPT_VALID_PD_ENDDE | String | 검사유효기간종료 |
| 권리 | MRTG_CO | String | 저당권수 |
| 권리 | SEIZR_CO | String | 압류수 |
| 권리 | STMD_CO | String | 체당금수 |
| 취득 | ACQS_AMOUNT | String | 취득가액 |
---
## 2. 자동차등록원부(갑)조회 API
### 2.1 Request (LedgerRequest)
**엔드포인트**: `POST /api/v1/vehicles/ledger`
```json
{
"data": [
{
"INFO_SYS_ID": "41-345", // 정보시스템ID (자동설정)
"INFO_SYS_IP": "105.19.10.135", // 정보시스템IP (자동설정)
"SIGUNGU_CODE": "41460", // 시군구코드 (자동설정)
"CNTC_INFO_CODE": "AC1_FD11_02", // 연계정보코드 (자동설정)
"CHARGER_ID": "", // 담당자ID (자동설정)
"CHARGER_IP": "", // 담당자IP (자동설정)
"CHARGER_NM": "", // 담당자명 (자동설정)
"VHRNO": "12가3456", // 자동차등록번호
"ONES_INFORMATION_OPEN": "1", // 개인정보공개 (자동설정: "1")
"CPTTR_NM": "홍길동", // 민원인성명
"CPTTR_IHIDNUM": "8801011234567", // 민원인주민번호
"CPTTR_LEGALDONG_CODE": "1111011700", // 민원인법정동코드
"ROUTE_SE_CODE": "3", // 경로구분코드 (자동설정: "3")
"DETAIL_EXPRESSION": "1", // 내역표시 (자동설정: "1")
"INQIRE_SE_CODE": "1" // 조회구분코드 (자동설정: "1")
}
]
}
```
#### 필드 설명
| 필드명 | 타입 | 설명 | 비고 |
|--------|------|------|------|
| INFO_SYS_ID | String | 정보시스템ID | 자동설정 |
| INFO_SYS_IP | String | 정보시스템IP | 자동설정 |
| SIGUNGU_CODE | String | 시군구코드 | 자동설정 |
| CNTC_INFO_CODE | String | 연계정보코드 | 자동설정 |
| CHARGER_ID | String | 담당자ID | 자동설정 |
| CHARGER_IP | String | 담당자IP | 자동설정 |
| CHARGER_NM | String | 담당자명(사용자) | 자동설정 |
| VHRNO | String | 자동차등록번호 | 사용자 입력 |
| ONES_INFORMATION_OPEN | String | 개인정보공개 | 자동설정 (1:공개, 2:비공개) |
| CPTTR_NM | String | 민원인성명 | 사용자 입력 |
| CPTTR_IHIDNUM | String | 민원인주민번호 (13자리) | 사용자 입력 |
| CPTTR_LEGALDONG_CODE | String | 민원인법정동코드 | 사용자 입력 |
| ROUTE_SE_CODE | String | 경로구분코드 | 자동설정 ("3" 고정) |
| DETAIL_EXPRESSION | String | 내역표시 | 자동설정 (1:전체, 2:최종) |
| INQIRE_SE_CODE | String | 조회구분코드 | 자동설정 ("1" 열람) |
---
### 2.2 Response (LedgerResponse)
```json
{
"data": [
{
// 연계 결과
"CNTC_RESULT_CODE": "0",
"CNTC_RESULT_DTLS": "정상",
// 원부 기본 정보
"LEDGER_GROUP_NO": "0000000001",
"LEDGER_INDVDLZ_NO": "0000000001",
"VHMNO": "A123456789",
"VHRNO": "12가3456",
"VIN": "KMHAB812345678901",
// 차종 정보
"VHCTY_ASORT_CODE": "01",
"VHCTY_ASORT_NM": "승용",
"CNM": "제네시스",
"COLOR_CODE": "01",
"COLOR_NM": "검은색",
// 등록 정보
"NMPL_STNDRD_CODE": "01",
"NMPL_STNDRD_NM": "일반",
"PRPOS_SE_CODE": "01",
"PRPOS_SE_NM": "자가용",
"MTRS_FOM_NM": "D3.0",
"FOM_NM": "GV80",
"ACQS_AMOUNT": "85000000",
"REGIST_DETAIL_CODE": "11",
"REGIST_DETAIL_NM": "신규등록",
"FRST_REGIST_DE": "20200115",
"CAAG_ENDDE": "",
"PRYE": "2020",
// 표본/운행 정보
"SPMNNO1": "",
"SPMNNO2": "",
"YBL_MD": "202001",
"TRVL_DSTNC": "45000",
// 검사/점검 정보
"INSPT_VALID_PD_BGNDE": "20240115",
"INSPT_VALID_PD_ENDDE": "20260114",
"CHCK_VALID_PD_BGNDE": "",
"CHCK_VALID_PD_ENDDE": "",
"REGIST_REQST_SE_NM": "",
"FRST_REGIST_RQRCNO": "",
// 번호판/말소 정보
"NMPL_CSDY_REMNR_DE": "",
"NMPL_CSDY_AT": "N",
"BSS_USE_PD": "",
"OCTHT_ERSR_PRVNTC_NTICE_DE": "",
"ERSR_REGIST_DE": "",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_SE_NM": "",
// 권리 정보
"MRTGCNT": "0",
"VHCLECNT": "0",
"STMDCNT": "0",
// 주소 정보
"ADRES1": "서울특별시 종로구",
"ADRES_NM1": "세종대로 123",
"ADRES": "서울특별시 종로구",
"ADRES_NM": "세종대로 123",
// 소유자 정보
"INDVDL_BSNM_AT": "N",
"TELNO": "010-1234-5678",
"MBER_NM": "홍길동",
"MBER_SE_CODE": "1",
"MBER_SE_NO": "8801011234567",
// 세제/기타
"TAXXMPT_TRGTER_SE_CODE": "",
"TAXXMPT_TRGTER_SE_CODE_NM": "",
"CNT_MATTER": "",
"EMD_NM": "종로동",
"PRVNTCCNT": "0",
"XPORT_FLFL_AT_STTEMNT_DE": "",
"PARTN_RQRCNO": "",
// 변경 내역
"record": [
{
"MAINCHK": "Y",
"CHANGE_JOB_SE_CODE": "01",
"MAINNO": "1",
"SUBNO": "0",
"DTLS": "신규등록",
"RQRCNO": "20200115001",
"VHMNO": "A123456789",
"LEDGER_GROUP_NO": "0000000001",
"LEDGER_INDVDLZ_NO": "0000000001",
"GUBUN_NM": "등록",
"CHANGE_DE": "20200115",
"DETAIL_SN": "1",
"FLAG": ""
}
]
}
]
}
```
#### Response 필드 설명
| 구분 | 필드명 | 타입 | 설명 |
|------|--------|------|------|
| 기본 | CNTC_RESULT_CODE | String | 연계결과코드 |
| 기본 | CNTC_RESULT_DTLS | String | 연계결과상세 |
| 원부 | LEDGER_GROUP_NO | String | 원부그룹번호 |
| 원부 | LEDGER_INDVDLZ_NO | String | 원부개별화번호 |
| 원부 | VHMNO | String | 자동차검사번호 |
| 차량 | VHRNO | String | 자동차등록번호 |
| 차량 | VIN | String | 차대번호 |
| 차량 | CNM | String | 차명 |
| 차량 | FOM_NM | String | 형식명 |
| 차종 | VHCTY_ASORT_CODE | String | 차종구분코드 |
| 차종 | VHCTY_ASORT_NM | String | 차종구분명 |
| 등록 | FRST_REGIST_DE | String | 최초등록일자 |
| 등록 | REGIST_DETAIL_CODE | String | 등록상세코드 |
| 등록 | REGIST_DETAIL_NM | String | 등록상세명 |
| 등록 | PRPOS_SE_CODE | String | 용도구분코드 |
| 등록 | PRPOS_SE_NM | String | 용도구분명 |
| 검사 | INSPT_VALID_PD_BGNDE | String | 검사유효기간시작 |
| 검사 | INSPT_VALID_PD_ENDDE | String | 검사유효기간종료 |
| 권리 | MRTGCNT | String | 저당권수 |
| 권리 | VHCLECNT | String | 압류수 |
| 권리 | STMDCNT | String | 체당금수 |
| 말소 | ERSR_REGIST_DE | String | 말소등록일자 |
| 말소 | ERSR_REGIST_SE_CODE | String | 말소등록구분코드 |
| 말소 | ERSR_REGIST_SE_NM | String | 말소등록구분명 |
| 소유자 | MBER_NM | String | 회원명 |
| 소유자 | MBER_SE_CODE | String | 회원구분코드 |
| 소유자 | MBER_SE_NO | String | 회원구분번호 |
| 소유자 | TELNO | String | 전화번호 |
| 주소 | ADRES | String | 주소 |
| 주소 | ADRES_NM | String | 주소명 |
| 취득 | ACQS_AMOUNT | String | 취득가액 |
#### Record (변경내역) 필드
| 필드명 | 타입 | 설명 |
|--------|------|------|
| MAINCHK | String | 주체크 |
| CHANGE_JOB_SE_CODE | String | 변경업무구분코드 |
| MAINNO | String | 주번호 |
| SUBNO | String | 부번호 |
| DTLS | String | 상세 |
| RQRCNO | String | 요청번호 |
| GUBUN_NM | String | 구분명 |
| CHANGE_DE | String | 변경일자 |
| DETAIL_SN | String | 상세일련번호 |
---
## 3. 공통 구조
### 3.1 Envelope (요청/응답 래퍼)
```json
{
"data": [ ... ]
}
```
모든 요청과 응답은 `data` 배열로 감싸져 있습니다.
### 3.2 HTTP Headers
| 헤더명 | 설명 | 예시 |
|--------|------|------|
| Content-Type | 콘텐츠 타입 | application/json;charset=UTF-8 |
| Accept | 응답 타입 | application/json |
| gpki_yn | GPKI 암호화 여부 | Y/N |
| tx_id | 트랜잭션 ID | UUID |
| cert_server_id | 인증서 서버 ID | 설정값 |
| api_key | API 키 | 설정값 |
| cvmis_apikey | CVMIS API 키 | 설정값 |
### 3.3 자동설정 필드 요약
| API | 필드 | 설정 방식 |
|-----|------|----------|
| 공통 | INFO_SYS_ID, INFO_SYS_IP, SIGUNGU_CODE | application.yml system 설정 |
| 공통 | CHARGER_ID, CHARGER_IP, CHARGER_NM | application.yml system 설정 |
| 기본사항 | CNTC_INFO_CODE | vmis.gov.services.basic.cntcInfoCode |
| 기본사항 | INQIRE_SE_CODE | VHRNO→"3", VIN→"2" |
| 등록원부 | CNTC_INFO_CODE | vmis.gov.services.ledger.cntcInfoCode |
| 등록원부 | ONES_INFORMATION_OPEN | 기본값 "1" (소유자공개) |
| 등록원부 | ROUTE_SE_CODE | 고정값 "3" |
| 등록원부 | DETAIL_EXPRESSION | 기본값 "1" (전체내역) |
| 등록원부 | INQIRE_SE_CODE | 기본값 "1" (열람) |
---
## 4. 관련 파일 경로
| 구분 | 파일 경로 |
|------|----------|
| 기본정보 Request | `src/main/java/com/vmis/interfaceapp/model/basic/BasicRequest.java` |
| 기본정보 Response | `src/main/java/com/vmis/interfaceapp/model/basic/BasicResponse.java` |
| 등록원부 Request | `src/main/java/com/vmis/interfaceapp/model/ledger/LedgerRequest.java` |
| 등록원부 Response | `src/main/java/com/vmis/interfaceapp/model/ledger/LedgerResponse.java` |
| 기본정보 Service | `src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireService.java` |
| 등록원부 Service | `src/main/java/com/vmis/interfaceapp/service/CarLedgerFrmbkService.java` |
| RestTemplate Client | `src/main/java/com/vmis/interfaceapp/client/GovernmentApiClient.java` |
| Request Enricher | `src/main/java/com/vmis/interfaceapp/service/RequestEnricher.java` |
| Controller | `src/main/java/com/vmis/interfaceapp/controller/VehicleInterfaceController.java` |

@ -1,5 +1,6 @@
package com.vmis.interfaceapp.client;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vmis.interfaceapp.config.properties.VmisProperties;
@ -475,6 +476,8 @@ public class GovernmentApiClient implements GovernmentApi {
// ObjectMapper가 Envelope 객체를 JSON으로 변환
// 날짜, null 값 등의 처리는 ObjectMapper 설정에 따름
String jsonBody = objectMapper.writeValueAsString(envelope);
// 상세 진단: 요청 평문 JSON 전체 로깅 (개인 테스트 요청에 따라 상세 출력)
log.info("[GOV-REQ-BODY] tx_id={}, json={}", txId, jsonBody);
// 4. HTTP 헤더 구성
HttpHeaders headers = buildHeaders(svc, txId);
@ -484,6 +487,8 @@ public class GovernmentApiClient implements GovernmentApi {
if (gpkiService.isEnabled()) {
// JSON 평문을 암호화된 문자열로 변환
bodyToSend = gpkiEncrypt(jsonBody);
// 상세 진단: 암호문 전체 로깅 (개인 테스트 요청에 따라 상세 출력)
log.info("[GOV-REQ-BODY-ENC] tx_id={}, cipher={}", txId, bodyToSend);
}
// 6. HTTP 엔티티 생성 (헤더 + 바디)
@ -491,23 +496,70 @@ public class GovernmentApiClient implements GovernmentApi {
// 7. 요청 로그 기록
log.info("[GOV-REQ] url={}, tx_id={}, gpki={}, length={}", url, txId, gpkiService.isEnabled(), bodyToSend != null ? bodyToSend.length() : 0);
// 상세 진단: 요청 헤더 전체 로깅
log.info("[GOV-REQ-HEADERS] tx_id={}, headers={}", txId, headers);
// 8. 실제 HTTP POST 요청 전송
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
String respBody = response.getBody();
// 상세 진단: 수신 원문 전체 로깅
log.info("[GOV-RES-RAW] tx_id={}, status={}, body={}",
txId, response.getStatusCode(), respBody);
// 상세 진단: 응답 헤더 전체 로깅
log.info("[GOV-RES-HEADERS] tx_id={}, headers={}", txId, response.getHeaders());
// 9-a. 복호화 전 응답 바디 진단 로그(민감정보 노출 방지를 위해 앞부분만 샘플링)
MediaType contentType = response.getHeaders() != null ? response.getHeaders().getContentType() : null;
boolean looksBase64 = looksLikeBase64(respBody);
log.info("[GOV-RES] status={}, contentType={}, len={}, looksBase64={}, head64={}",
response.getStatusCode(),
contentType,
(respBody != null ? respBody.length() : 0),
looksBase64,
head(respBody, 64));
// 9. GPKI 복호화 처리 (성공 응답인 경우만)
if (gpkiService.isEnabled() && response.getStatusCode().is2xxSuccessful()) {
// 암호화된 응답을 평문 JSON으로 복호화
respBody = gpkiDecrypt(respBody);
if (looksBase64) {
try {
// 암호화된 응답을 평문 JSON으로 복호화
respBody = gpkiDecrypt(respBody);
// 상세 진단: 복호화 후 평문 전체 로깅
log.info("[GOV-RES-DEC] tx_id={}, body={}", txId, respBody);
} catch (RuntimeException decEx) {
// 복호화 실패 시 진단 로그(샘플만 노출)
log.warn("[GOV-RES-DECERR] tx_id={}, len={}, sample={}, cause={}",
txId,
(respBody != null ? respBody.length() : 0),
head(respBody, 64),
decEx.getMessage());
throw decEx; // 기존 흐름 유지: 상위에서 전역 예외 처리
}
} else {
// Base64처럼 보이지 않으면 복호화를 생략하고 경고만 남김
log.warn("[GOV-RES] skip decrypt: status2xx=true, gpki=true, looksBase64=false, tx_id={}, ct={}, len={}, head64={}",
txId, contentType, (respBody != null ? respBody.length() : 0), head(respBody, 64));
}
}
// 10. 역직렬화: JSON 문자열 → Java 객체
// TypeReference를 사용하여 제네릭 타입 정보 전달
Envelope<TResp> mapped = objectMapper.readValue(respBody, respType);
try {
Envelope<TResp> mapped = objectMapper.readValue(respBody, respType);
// 정상 매핑 시 반환
return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(mapped);
} catch (JsonProcessingException jpe) {
// 상세 진단: 비JSON/깨진 JSON 전체 출력
log.warn("[GOV-RES-PLAINERR] tx_id={}, ct={}, len={}, body={}",
txId,
contentType,
(respBody != null ? respBody.length() : 0),
respBody);
throw new RuntimeException("정부 API 비정상 응답(비JSON 또는 파싱 실패). tx_id=" + txId + ", ct=" + contentType + ", body=" + respBody, jpe);
}
// 11. 응답 반환 (상태 코드, 헤더, 바디 모두 포함)
return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(mapped);
// 11. 응답 반환 (상태 코드, 헤더, 바디 모두 포함) — 위에서 이미 반환됨
} catch (HttpStatusCodeException ex) {
// HTTP 에러 처리 (4xx, 5xx)
@ -624,4 +676,18 @@ public class GovernmentApiClient implements GovernmentApi {
}
}
// === 내부 유틸리티: 응답 바디 샘플 및 Base64 판별 ===
private static String head(String s, int max) {
if (s == null) return null;
return s.substring(0, Math.min(max, s.length()));
}
private static boolean looksLikeBase64(String s) {
if (s == null || s.isEmpty()) return false;
int len = s.length();
if ((len % 4) != 0) return false; // Base64는 4의 배수 길이
// 허용 문자 집합만 포함하는지 대략 판별(개행 허용)
return s.matches("[A-Za-z0-9+/=\\r\\n]+\\z");
}
}

@ -1,57 +0,0 @@
package com.vmis.interfaceapp.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
/**
*
*
* <p> .
* Spring Boot ,
* TransactionManager .</p>
*
* <ul>
* <li>DataSource: application.yml </li>
* <li>SqlSessionFactory: MyBatis Spring Boot Starter </li>
* <li>TransactionManager: </li>
* <li>MapperScan: com.vmis.interfaceapp.mapper Mapper </li>
* </ul>
*/
@Configuration
@EnableTransactionManagement
@MapperScan("com.vmis.interfaceapp.mapper")
public class DatabaseConfig {
/**
* .
*
* <p>DataSourceTransactionManager JDBC .
* @Transactional .</p>
*
* <p> (Propagation), (Isolation),
* @Transactional .</p>
*
* <p>:</p>
* <pre>
* {@code
* @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
* public void saveData() {
* // 트랜잭션 처리가 필요한 로직
* }
* }
* </pre>
*
* @param dataSource Spring Boot DataSource
* @return PlatformTransactionManager
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}

@ -12,6 +12,10 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
@Configuration
public class HttpClientConfig {
@ -37,8 +41,16 @@ public class HttpClientConfig {
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
return builder
RestTemplate rt = builder
.requestFactory(() -> requestFactory)
.build();
// 개인 테스트 환경: 응답 한글 깨짐 방지를 위해 String 컨버터를 UTF-8로 강제
// 기본 등록된 StringHttpMessageConverter를 제거하고 UTF-8 컨버터를 최상단에 배치
List<HttpMessageConverter<?>> converters = rt.getMessageConverters();
converters.removeIf(c -> c instanceof StringHttpMessageConverter);
converters.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return rt;
}
}

@ -2,11 +2,13 @@ package com.vmis.interfaceapp.gpki;
import com.vmis.interfaceapp.config.properties.VmisProperties;
import com.vmis.interfaceapp.util.GpkiCryptoUtil;
import lombok.extern.slf4j.Slf4j;
/**
* Real GPKI service backed by native GPKI JNI via legacy NewGpkiUtil wrapper.
* Uses YAML-configured paths and options in {@link VmisProperties.GpkiProps}.
*/
@Slf4j
public class RealGpkiService implements GpkiService {
private final VmisProperties props;
@ -25,17 +27,53 @@ public class RealGpkiService implements GpkiService {
public String encrypt(String plain) throws Exception {
String charset = props.getGpki().getCharset();
String targetId = props.getGpki().getTargetServerId();
return crypto.encryptToBase64(plain, targetId, charset);
boolean useSign = props.getGpki().isUseSign();
try {
// 샘플 순서 준수: encrypt(bytes) → (서명) → Base64
return crypto.encryptThenSignToBase64(plain, targetId, charset, useSign);
} catch (Exception e) {
String detail = extractGpkiDetail(e);
if (detail != null) {
log.warn("[GPKI-ENC-ERR] targetId={}, detail={}", targetId, detail);
}
throw e;
}
}
@Override
public String decrypt(String cipher) throws Exception {
String charset = props.getGpki().getCharset();
return crypto.decryptFromBase64(cipher, charset);
boolean useSign = props.getGpki().isUseSign();
try {
// 샘플 순서 준수: Base64 decode → (서명검증) → decrypt → String 변환
return crypto.decodeValidateThenDecrypt(cipher, charset, useSign);
} catch (Exception e) {
String detail = extractGpkiDetail(e);
if (detail != null) {
log.warn("[GPKI-DEC-ERR] detail={}", detail);
}
throw e;
}
}
@Override
public boolean isEnabled() {
return true;
}
private static String extractGpkiDetail(Throwable t) {
if (t == null) return null;
Throwable root = t;
int guard = 0;
while (root.getCause() != null && root.getCause() != root && guard++ < 20) {
root = root.getCause();
}
String msg = root.getMessage();
if (msg == null) return null;
int idx = msg.indexOf("gpkiErrorMessage=");
if (idx >= 0) {
return msg.substring(idx + "gpkiErrorMessage=".length()).trim();
}
return msg;
}
}

@ -1,54 +0,0 @@
package com.vmis.interfaceapp.mapper;
import com.vmis.interfaceapp.model.basic.CarBassMatterInqireVO;
import org.apache.ibatis.annotations.Mapper;
/**
* Mapper
*
* <p>API Mapper .</p>
* <ul>
* <li> : insertCarBassMatterInqire() </li>
* <li> : updateCarBassMatterInqire() </li>
* </ul>
*/
@Mapper
public interface CarBassMatterInqireMapper {
/**
* 퀀 ID .
*
* <p>: CBMI000000000001</p>
*
* @return ID
*/
String selectNextCarBassMatterInqireId();
/**
* API .
*
* <p> , null .</p>
*
* @param carBassMatterInqireVO
* @return
*/
int insertCarBassMatterInqire(CarBassMatterInqireVO carBassMatterInqireVO);
/**
* API .
*
* <p> .</p>
*
* @param carBassMatterInqireVO (carBassMatterInqire )
* @return
*/
int updateCarBassMatterInqire(CarBassMatterInqireVO carBassMatterInqireVO);
/**
* ID .
*
* @param carBassMatterInqire ID
* @return
*/
CarBassMatterInqireVO selectCarBassMatterInqireById(String carBassMatterInqire);
}

@ -1,28 +0,0 @@
package com.vmis.interfaceapp.mapper;
import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkDtlVO;
import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* () Mapper
*/
@Mapper
public interface CarLedgerFrmbkMapper {
// ID 시퀀스
String selectNextCarLedgerFrmbkId();
String selectNextCarLedgerFrmbkDtlId();
// 마스터 INSERT/UPDATE/SELECT
int insertCarLedgerFrmbk(CarLedgerFrmbkVO vo);
int updateCarLedgerFrmbk(CarLedgerFrmbkVO vo);
CarLedgerFrmbkVO selectCarLedgerFrmbkById(String carLedgerFrmbkId);
// 상세 INSERT (단건)
int insertCarLedgerFrmbkDtl(CarLedgerFrmbkDtlVO vo);
// 편의: 상세 일괄 (MyBatis foreach를 XML에서 사용할 수도 있으나, 여기서는 단건 호출을 반복)
}

@ -1,56 +0,0 @@
package com.vmis.interfaceapp.service;
import com.vmis.interfaceapp.mapper.CarBassMatterInqireMapper;
import com.vmis.interfaceapp.model.basic.CarBassMatterInqireVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* .
*
* <p> (REQUIRES_NEW) ,
* .</p>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class CarBassMatterInqireLogService {
private final CarBassMatterInqireMapper carBassMatterInqireMapper;
/**
* API . (REQUIRES_NEW)
* @param request
* @return ID
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public String createInitialRequestNewTx(CarBassMatterInqireVO request) {
String generatedId = carBassMatterInqireMapper.selectNextCarBassMatterInqireId();
request.setCarBassMatterInqireId(generatedId);
int result = carBassMatterInqireMapper.insertCarBassMatterInqire(request);
if (result != 1) {
throw new RuntimeException("자동차 기본 사항 조회 정보 등록 실패");
}
log.info("[BASIC-REQ-LOG] 요청 정보 저장 완료(별도TX) - ID: {}, 차량번호: {}", generatedId, request.getDmndVhrno());
return generatedId;
}
/**
* / . (REQUIRES_NEW)
* @param response
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateResponseNewTx(CarBassMatterInqireVO response) {
if (response.getCarBassMatterInqireId() == null) {
throw new IllegalArgumentException("자동차 기본 사항 조회 ID는 필수입니다.");
}
int result = carBassMatterInqireMapper.updateCarBassMatterInqire(response);
if (result != 1) {
throw new RuntimeException("자동차 기본 사항 조회 정보 업데이트 실패 - ID: " + response.getCarBassMatterInqireId());
}
log.info("[BASIC-RES-LOG] 응답/에러 정보 저장 완료(별도TX) - ID: {}, 결과코드: {}", response.getCarBassMatterInqireId(), response.getCntcResultCode());
}
}

@ -1,17 +1,13 @@
package com.vmis.interfaceapp.service;
import com.vmis.interfaceapp.client.GovernmentApi;
import com.vmis.interfaceapp.config.ApiConstant;
import com.vmis.interfaceapp.util.ExceptionDetailUtil;
import com.vmis.interfaceapp.model.basic.BasicRequest;
import com.vmis.interfaceapp.model.basic.BasicResponse;
import com.vmis.interfaceapp.model.basic.CarBassMatterInqireVO;
import com.vmis.interfaceapp.model.common.Envelope;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
@ -37,7 +33,6 @@ public class CarBassMatterInqireService {
/**
* : -> -> -> .
*/
@Transactional
public ResponseEntity<Envelope<BasicResponse>> basic(Envelope<BasicRequest> envelope) {
// 1) 요청 보강
enricher.enrichBasic(envelope);

@ -1,61 +0,0 @@
package com.vmis.interfaceapp.service;
import com.vmis.interfaceapp.mapper.CarLedgerFrmbkMapper;
import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkDtlVO;
import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* () .
* - (write) (REQUIRES_NEW) .
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class CarLedgerFrmbkLogService {
private final CarLedgerFrmbkMapper mapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public String createInitialRequestNewTx(CarLedgerFrmbkVO request) {
String id = mapper.selectNextCarLedgerFrmbkId();
request.setCarLedgerFrmbkId(id);
int result = mapper.insertCarLedgerFrmbk(request);
if (result != 1) {
throw new RuntimeException("자동차 등록 원부(갑) 최초요청 등록 실패");
}
log.info("[LEDGER-REQ-LOG] 최초 요청 저장(별도TX) - ID: {}, 차량번호: {}", id, request.getDmndVhrno());
return id;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateResponseNewTx(CarLedgerFrmbkVO response) {
if (response.getCarLedgerFrmbkId() == null) {
throw new IllegalArgumentException("자동차 등록 원부(갑) ID는 필수입니다.");
}
int updated = mapper.updateCarLedgerFrmbk(response);
if (updated != 1) {
throw new RuntimeException("자동차 등록 원부(갑) 정보 업데이트 실패 - ID: " + response.getCarLedgerFrmbkId());
}
log.info("[LEDGER-RES-LOG] 마스터 응답 업데이트(별도TX) - ID: {}, 결과코드: {}",
response.getCarLedgerFrmbkId(), response.getCntcResultCode());
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveDetailsNewTx(String masterId, List<CarLedgerFrmbkDtlVO> details) {
if (details == null || details.isEmpty()) return;
for (CarLedgerFrmbkDtlVO dtl : details) {
String dtlId = mapper.selectNextCarLedgerFrmbkDtlId();
dtl.setCarLedgerFrmbkDtlId(dtlId);
dtl.setCarLedgerFrmbkId(masterId);
mapper.insertCarLedgerFrmbkDtl(dtl);
}
log.info("[LEDGER-RES-LOG] 상세 {}건 저장(별도TX) - ID: {}", details.size(), masterId);
}
}

@ -1,20 +1,13 @@
package com.vmis.interfaceapp.service;
import com.vmis.interfaceapp.client.GovernmentApi;
import com.vmis.interfaceapp.config.ApiConstant;
import com.vmis.interfaceapp.model.common.Envelope;
import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkDtlVO;
import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkVO;
import com.vmis.interfaceapp.model.ledger.LedgerRequest;
import com.vmis.interfaceapp.model.ledger.LedgerResponse;
import com.vmis.interfaceapp.util.ExceptionDetailUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* () ()
@ -27,24 +20,16 @@ public class CarLedgerFrmbkService {
private final GovernmentApi governmentApi;
private final RequestEnricher enricher;
//private final CarLedgerFrmbkLogService logService;
/**
* () : -> (TX) -> -> (/, TX) -> (TX).
*/
@Transactional
public ResponseEntity<Envelope<LedgerResponse>> ledger(Envelope<LedgerRequest> envelope) {
// 1) 요청 보강
enricher.enrichLedger(envelope);
String generatedId = null;
try {
// 2) 최초 요청 로그 저장 (첫 번째 데이터 기준)
/*if (envelope.getData() != null && !envelope.getData().isEmpty()) {
LedgerRequest req = envelope.getData().get(0);
CarLedgerFrmbkVO init = CarLedgerFrmbkVO.fromRequest(req);
generatedId = logService.createInitialRequestNewTx(init);
}*/
// 3) 외부 API 호출
ResponseEntity<Envelope<LedgerResponse>> response = governmentApi.callLedger(envelope);

@ -90,6 +90,33 @@ public class GpkiCryptoUtil {
return new String(data, charset);
}
/**
* Encrypt plain text with target server public key, then optionally sign the encrypted bytes,
* then Base64-encode the resulting payload exactly matching the sample implementation order.
*
* Order: encrypt(bytes) (useSign? sign(encryptedBytes) : encryptedBytes) encode(Base64)
*/
public String encryptThenSignToBase64(String plain, String targetServerId, String charset, boolean useSign) throws Exception {
ensureInit();
byte[] encrypted = delegate.encrypt(plain.getBytes(charset), targetServerId, true);
byte[] payload = useSign ? delegate.sign(encrypted) : encrypted;
return delegate.encode(payload);
}
/**
* Decode Base64, then validate signature (when enabled), then decrypt, then convert to string with charset.
*
* <p> :
* Base64 decode (useSign=true) validate decrypt new String(dec, charset)</p>
*/
public String decodeValidateThenDecrypt(String base64, String charset, boolean useSign) throws Exception {
ensureInit();
byte[] decoded = delegate.decode(base64);
byte[] encrypted = useSign ? delegate.validate(decoded) : decoded;
byte[] plain = delegate.decrypt(encrypted);
return new String(plain, charset);
}
private void ensureInit() {
if (delegate == null) {
throw new IllegalStateException("GpkiCryptoUtil is not initialized. Call initialize() or from(props).");

@ -13,6 +13,6 @@ public final class TxIdUtil {
public static String generate() {
String time = new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.KOREA).format(new Date());
int random = 100000 + RANDOM.nextInt(900000);
return time + "_" + random;
return time + "S" + random;
}
}

@ -2,44 +2,6 @@
server:
port: 18080 # 애플리케이션 구동 포트 (운영환경)
spring:
# DataSource 설정 - MariaDB
datasource:
driver-class-name: org.mariadb.jdbc.Driver # MariaDB JDBC 드라이버
url: jdbc:mariadb://211.119.124.117:53306/vips?characterEncoding=UTF-8&allowMultiQueries=true # DB 접속 URL (UTF-8 인코딩, 다중쿼리 허용)
username: # DB 접속 사용자명
password: # DB 접속 비밀번호
hikari:
# 커넥션 풀 크기 설정 (4코어 32GB 서버 기준)
# 동시에 사용할 수 있는 최대 커넥션 수
# 권장값: (코어수 × 2) + (동시사용자 × 0.1) = (4 × 2) + (300 × 0.1) = 38 → 40
maximum-pool-size: 40
# 풀에서 유지할 최소 유휴 커넥션 수
# 권장값: maximum-pool-size의 25% (40 × 0.25 = 10)
minimum-idle: 10
# 커넥션을 얻기 위한 최대 대기 시간 (밀리초)
# 권장값: 30초 - 네트워크 지연이나 데이터베이스 부하 시 적절한 대기 시간
connection-timeout: 30000
# 커넥션 유효성 검사 타임아웃 (밀리초)
validation-timeout: 60000
# 커넥션의 최대 생명 시간 (밀리초)
# 권장값: 30분 - 데이터베이스 연결이 너무 오래 유지되지 않도록 제한
max-lifetime: 1800000
# 유휴 커넥션을 제거하기 위한 최소 대기 시간 (밀리초)
# 권장값: 10분 - 메모리 절약과 커넥션 재사용의 균형점
idle-timeout: 600000
# auto-commit을 false로 설정하여 명시적 트랜잭션 관리
auto-commit: false
# MyBatis 설정
mybatis:
# MyBatis 전역 설정 파일 위치
config-location: classpath:mybatis/mybatis-config.xml
# Mapper XML 파일 위치 (DbType 변수 사용)
mapper-locations: classpath:mybatis/mapper/**/*_${Globals.DbType}.xml
# 타입 별칭 패키지 (하위 패키지 자동 스캔)
type-aliases-package: com.vmis.interfaceapp.model
# 로그 설정 - 운영(PRD) 환경
logging:
config: classpath:logback-spring.xml # Logback 설정 파일 위치
@ -96,6 +58,6 @@ vmis:
cvmisApikey: "014F9215-B6D9A3B6-4CED5225-68408C46" # CVMIS API 키
ledger: # 시군구연계 자동차등록원부(갑) 서비스
path: "/SignguCarLedgerFrmbkService" # 서비스 경로
cntcInfoCode: "AC1_FD11_02" # 연계정보코드
cntcInfoCode: "AC1_FE08_01" # 연계정보코드
apiKey: "1beeb01857c2e7e9b41c002b007ccb9754d9c272f66d4bb64fc45b302c69e529" # API 인증키
cvmisApikey: "63DF159B-7B9C64C5-86CCB15C-5F93E750" # CVMIS API 키

@ -1,148 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vmis.interfaceapp.mapper.CarBassMatterInqireMapper">
<!-- 시퀀스로 새로운 ID 생성 -->
<select id="selectNextCarBassMatterInqireId" resultType="String">
SELECT CONCAT('CBMI', LPAD(NEXTVAL(seq_car_bass_matter_inqire), 16, '0')) AS id
</select>
<!-- 최초 요청 정보 INSERT -->
<insert id="insertCarBassMatterInqire" parameterType="CarBassMatterInqireVO">
INSERT INTO tb_car_bass_matter_inqire (
CAR_BASS_MATTER_INQIRE_ID,
INFO_SYS_ID,
INFO_SYS_IP,
SIGUNGU_CODE,
CNTC_INFO_CODE,
CHARGER_ID,
CHARGER_IP,
CHARGER_NM,
DMND_LEVY_STDDE,
DMND_INQIRE_SE_CODE,
DMND_VHRNO,
DMND_VIN,
REG_DT,
RGTR
) VALUES (
#{carBassMatterInqireId},
#{infoSysId},
#{infoSysIp},
#{sigunguCode},
#{cntcInfoCode},
#{chargerId},
#{chargerIp},
#{chargerNm},
#{dmndLevyStdde},
#{dmndInqireSeCode},
#{dmndVhrno},
#{dmndVin},
NOW(),
#{rgtr}
)
</insert>
<!-- 응답 결과 UPDATE -->
<update id="updateCarBassMatterInqire" parameterType="CarBassMatterInqireVO">
UPDATE tb_car_bass_matter_inqire
<set>
<if test="cntcResultCode != null">CNTC_RESULT_CODE = #{cntcResultCode},</if>
<if test="cntcResultDtls != null">CNTC_RESULT_DTLS = #{cntcResultDtls},</if>
<if test="prye != null">PRYE = #{prye},</if>
<if test="registDe != null">REGIST_DE = #{registDe},</if>
<if test="ersrRegistSeCode != null">ERSR_REGIST_SE_CODE = #{ersrRegistSeCode},</if>
<if test="ersrRegistSeNm != null">ERSR_REGIST_SE_NM = #{ersrRegistSeNm},</if>
<if test="ersrRegistDe != null">ERSR_REGIST_DE = #{ersrRegistDe},</if>
<if test="registDetailCode != null">REGIST_DETAIL_CODE = #{registDetailCode},</if>
<if test="dsplvl != null">DSPLVL = #{dsplvl},</if>
<if test="useStrnghldLegaldongCode != null">USE_STRNGHLD_LEGALDONG_CODE = #{useStrnghldLegaldongCode},</if>
<if test="useStrnghldAdstrdCode != null">USE_STRNGHLD_ADSTRD_CODE = #{useStrnghldAdstrdCode},</if>
<if test="useStrnghldMntn != null">USE_STRNGHLD_MNTN = #{useStrnghldMntn},</if>
<if test="useStrnghldLnbr != null">USE_STRNGHLD_LNBR = #{useStrnghldLnbr},</if>
<if test="useStrnghldHo != null">USE_STRNGHLD_HO = #{useStrnghldHo},</if>
<if test="useStrnghldAdresNm != null">USE_STRNGHLD_ADRES_NM = #{useStrnghldAdresNm},</if>
<if test="useStrnghldRoadNmCode != null">USE_STRNGHLD_ROAD_NM_CODE = #{useStrnghldRoadNmCode},</if>
<if test="usgsrhldUndgrndBuldSeCode != null">USGSRHLD_UNDGRND_BULD_SE_CODE = #{usgsrhldUndgrndBuldSeCode},</if>
<if test="useStrnghldBuldMainNo != null">USE_STRNGHLD_BULD_MAIN_NO = #{useStrnghldBuldMainNo},</if>
<if test="useStrnghldBuldSubNo != null">USE_STRNGHLD_BULD_SUB_NO = #{useStrnghldBuldSubNo},</if>
<if test="usgsrhldAdresFull != null">USGSRHLD_ADRES_FULL = #{usgsrhldAdresFull},</if>
<if test="mberSeCode != null">MBER_SE_CODE = #{mberSeCode},</if>
<if test="mberSeNo != null">MBER_SE_NO = #{mberSeNo},</if>
<if test="telno != null">TELNO = #{telno},</if>
<if test="ownerLegaldongCode != null">OWNER_LEGALDONG_CODE = #{ownerLegaldongCode},</if>
<if test="ownerAdstrdCode != null">OWNER_ADSTRD_CODE = #{ownerAdstrdCode},</if>
<if test="ownerMntn != null">OWNER_MNTN = #{ownerMntn},</if>
<if test="ownerLnbr != null">OWNER_LNBR = #{ownerLnbr},</if>
<if test="ownerHo != null">OWNER_HO = #{ownerHo},</if>
<if test="ownerAdresNm != null">OWNER_ADRES_NM = #{ownerAdresNm},</if>
<if test="ownerRoadNmCode != null">OWNER_ROAD_NM_CODE = #{ownerRoadNmCode},</if>
<if test="ownerUndgrndBuldSeCode != null">OWNER_UNDGRND_BULD_SE_CODE = #{ownerUndgrndBuldSeCode},</if>
<if test="ownerBuldMainNo != null">OWNER_BULD_MAIN_NO = #{ownerBuldMainNo},</if>
<if test="ownerBuldSubNo != null">OWNER_BULD_SUB_NO = #{ownerBuldSubNo},</if>
<if test="ownrWholaddr != null">OWNR_WHOLADDR = #{ownrWholaddr},</if>
<if test="aftrVhrno != null">AFTR_VHRNO = #{aftrVhrno},</if>
<if test="useFuelCode != null">USE_FUEL_CODE = #{useFuelCode},</if>
<if test="prposSeCode != null">PRPOS_SE_CODE = #{prposSeCode},</if>
<if test="mtrsFomNm != null">MTRS_FOM_NM = #{mtrsFomNm},</if>
<if test="frntVhrno != null">FRNT_VHRNO = #{frntVhrno},</if>
<if test="vhrno != null">VHRNO = #{vhrno},</if>
<if test="vin != null">VIN = #{vin},</if>
<if test="cnm != null">CNM = #{cnm},</if>
<if test="vhcleTotWt != null">VHCLE_TOT_WT = #{vhcleTotWt},</if>
<if test="caagEndde != null">CAAG_ENDDE = #{caagEndde},</if>
<if test="changeDe != null">CHANGE_DE = #{changeDe},</if>
<if test="vhctyAsortCode != null">VHCTY_ASORT_CODE = #{vhctyAsortCode},</if>
<if test="vhctyTyCode != null">VHCTY_TY_CODE = #{vhctyTyCode},</if>
<if test="vhctySeCode != null">VHCTY_SE_CODE = #{vhctySeCode},</if>
<if test="mxmmLdg != null">MXMM_LDG = #{mxmmLdg},</if>
<if test="vhctyAsortNm != null">VHCTY_ASORT_NM = #{vhctyAsortNm},</if>
<if test="vhctyTyNm != null">VHCTY_TY_NM = #{vhctyTyNm},</if>
<if test="vhctySeNm != null">VHCTY_SE_NM = #{vhctySeNm},</if>
<if test="frstRegistDe != null">FRST_REGIST_DE = #{frstRegistDe},</if>
<if test="fomNm != null">FOM_NM = #{fomNm},</if>
<if test="acqsDe != null">ACQS_DE = #{acqsDe},</if>
<if test="acqsEndDe != null">ACQS_END_DE = #{acqsEndDe},</if>
<if test="yblMd != null">YBL_MD = #{yblMd},</if>
<if test="transrRegistDe != null">TRANSR_REGIST_DE = #{transrRegistDe},</if>
<if test="spcfRegistSttusCode != null">SPCF_REGIST_STTUS_CODE = #{spcfRegistSttusCode},</if>
<if test="colorNm != null">COLOR_NM = #{colorNm},</if>
<if test="mrtgCo != null">MRTG_CO = #{mrtgCo},</if>
<if test="seizrCo != null">SEIZR_CO = #{seizrCo},</if>
<if test="stmdCo != null">STMD_CO = #{stmdCo},</if>
<if test="nmplCsdyAt != null">NMPL_CSDY_AT = #{nmplCsdyAt},</if>
<if test="nmplCsdyRemnrDe != null">NMPL_CSDY_REMNR_DE = #{nmplCsdyRemnrDe},</if>
<if test="originSeCode != null">ORIGIN_SE_CODE = #{originSeCode},</if>
<if test="nmplStndrdCode != null">NMPL_STNDRD_CODE = #{nmplStndrdCode},</if>
<if test="acqsAmount != null">ACQS_AMOUNT = #{acqsAmount},</if>
<if test="insptValidPdBgnde != null">INSPT_VALID_PD_BGNDE = #{insptValidPdBgnde},</if>
<if test="insptValidPdEndde != null">INSPT_VALID_PD_ENDDE = #{insptValidPdEndde},</if>
<if test="useStrnghldGrcCode != null">USE_STRNGHLD_GRC_CODE = #{useStrnghldGrcCode},</if>
<if test="tkcarPscapCo != null">TKCAR_PSCAP_CO = #{tkcarPscapCo},</if>
<if test="spmnno != null">SPMNNO = #{spmnno},</if>
<if test="trvlDstnc != null">TRVL_DSTNC = #{trvlDstnc},</if>
<if test="frstRegistRqrcno != null">FRST_REGIST_RQRCNO = #{frstRegistRqrcno},</if>
<if test="vlntErsrPrvntcNticeDe != null">VLNT_ERSR_PRVNTC_NTICE_DE = #{vlntErsrPrvntcNticeDe},</if>
<if test="registInsttNm != null">REGIST_INSTT_NM = #{registInsttNm},</if>
<if test="processImprtyResnCode != null">PROCESS_IMPRTY_RESN_CODE = #{processImprtyResnCode},</if>
<if test="processImprtyResnDtls != null">PROCESS_IMPRTY_RESN_DTLS = #{processImprtyResnDtls},</if>
<if test="cbdLt != null">CBD_LT = #{cbdLt},</if>
<if test="cbdBt != null">CBD_BT = #{cbdBt},</if>
<if test="cbdHg != null">CBD_HG = #{cbdHg},</if>
<if test="frstMxmmLdg != null">FRST_MXMM_LDG = #{frstMxmmLdg},</if>
<if test="fuelCnsmpRt != null">FUEL_CNSMP_RT = #{fuelCnsmpRt},</if>
<if test="elctyCmpndFuelCnsmpRt != null">ELCTY_CMPND_FUEL_CNSMP_RT = #{elctyCmpndFuelCnsmpRt},</if>
<if test="mberNm != null">MBER_NM = #{mberNm},</if>
</set>
WHERE CAR_BASS_MATTER_INQIRE_ID = #{carBassMatterInqireId}
</update>
<!-- ID로 조회 -->
<select id="selectCarBassMatterInqireById" parameterType="String" resultType="CarBassMatterInqireVO">
SELECT *
FROM tb_car_bass_matter_inqire
WHERE CAR_BASS_MATTER_INQIRE_ID = #{carBassMatterInqireId}
</select>
</mapper>

@ -1,178 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vmis.interfaceapp.mapper.CarLedgerFrmbkMapper">
<!-- 시퀀스로 새로운 마스터/상세 ID 생성 -->
<select id="selectNextCarLedgerFrmbkId" resultType="String">
SELECT CONCAT('CLFB', LPAD(NEXTVAL(seq_car_ledger_frmbk), 16, '0')) AS id
</select>
<select id="selectNextCarLedgerFrmbkDtlId" resultType="String">
SELECT CONCAT('CLFD', LPAD(NEXTVAL(seq_car_ledger_frmbk_dtl), 16, '0')) AS id
</select>
<!-- 최초 요청 정보 INSERT (마스터) -->
<insert id="insertCarLedgerFrmbk" parameterType="CarLedgerFrmbkVO">
INSERT INTO tb_car_ledger_frmbk (
CAR_LEDGER_FRMBK_ID,
INFO_SYS_ID,
INFO_SYS_IP,
SIGUNGU_CODE,
CNTC_INFO_CODE,
CHARGER_ID,
CHARGER_IP,
CHARGER_NM,
DMND_VHRNO,
DMND_ONES_INFORMATION_OPEN,
DMND_CPTTR_NM,
DMND_CPTTR_IHIDNUM,
DMND_CPTTR_LEGALDONG_CODE,
DMND_ROUTE_SE_CODE,
DMND_DETAIL_EXPRESSION,
DMND_INQIRE_SE_CODE,
REG_DT,
RGTR
) VALUES (
#{carLedgerFrmbkId},
#{infoSysId},
#{infoSysIp},
#{sigunguCode},
#{cntcInfoCode},
#{chargerId},
#{chargerIp},
#{chargerNm},
#{dmndVhrno},
#{dmndOnesInformationOpen},
#{dmndCpttrNm},
#{dmndCpttrIhidnum},
#{dmndCpttrLegaldongCode},
#{dmndRouteSeCode},
#{dmndDetailExpression},
#{dmndInqireSeCode},
NOW(),
#{rgtr}
)
</insert>
<!-- 응답 결과 UPDATE (마스터) -->
<update id="updateCarLedgerFrmbk" parameterType="CarLedgerFrmbkVO">
UPDATE tb_car_ledger_frmbk
<set>
<if test="cntcResultCode != null">CNTC_RESULT_CODE = #{cntcResultCode},</if>
<if test="cntcResultDtls != null">CNTC_RESULT_DTLS = #{cntcResultDtls},</if>
<if test="ledgerGroupNo != null">LEDGER_GROUP_NO = #{ledgerGroupNo},</if>
<if test="ledgerIndvdlzNo != null">LEDGER_INDVDLZ_NO = #{ledgerIndvdlzNo},</if>
<if test="vhmno != null">VHMNO = #{vhmno},</if>
<if test="vhrno != null">VHRNO = #{vhrno},</if>
<if test="vin != null">VIN = #{vin},</if>
<if test="vhctyAsortCode != null">VHCTY_ASORT_CODE = #{vhctyAsortCode},</if>
<if test="vhctyAsortNm != null">VHCTY_ASORT_NM = #{vhctyAsortNm},</if>
<if test="cnm != null">CNM = #{cnm},</if>
<if test="colorCode != null">COLOR_CODE = #{colorCode},</if>
<if test="colorNm != null">COLOR_NM = #{colorNm},</if>
<if test="nmplStndrdCode != null">NMPL_STNDRD_CODE = #{nmplStndrdCode},</if>
<if test="nmplStndrdNm != null">NMPL_STNDRD_NM = #{nmplStndrdNm},</if>
<if test="prposSeCode != null">PRPOS_SE_CODE = #{prposSeCode},</if>
<if test="prposSeNm != null">PRPOS_SE_NM = #{prposSeNm},</if>
<if test="mtrsFomNm != null">MTRS_FOM_NM = #{mtrsFomNm},</if>
<if test="fomNm != null">FOM_NM = #{fomNm},</if>
<if test="acqsAmount != null">ACQS_AMOUNT = #{acqsAmount},</if>
<if test="registDetailCode != null">REGIST_DETAIL_CODE = #{registDetailCode},</if>
<if test="registDetailNm != null">REGIST_DETAIL_NM = #{registDetailNm},</if>
<if test="frstRegistDe != null">FRST_REGIST_DE = #{frstRegistDe},</if>
<if test="caagEndde != null">CAAG_ENDDE = #{caagEndde},</if>
<if test="prye != null">PRYE = #{prye},</if>
<if test="spmnno1 != null">SPMNNO1 = #{spmnno1},</if>
<if test="spmnno2 != null">SPMNNO2 = #{spmnno2},</if>
<if test="yblMd != null">YBL_MD = #{yblMd},</if>
<if test="trvlDstnc != null">TRVL_DSTNC = #{trvlDstnc},</if>
<if test="insptValidPdBgnde != null">INSPT_VALID_PD_BGNDE = #{insptValidPdBgnde},</if>
<if test="insptValidPdEndde != null">INSPT_VALID_PD_ENDDE = #{insptValidPdEndde},</if>
<if test="chckValidPdBgnde != null">CHCK_VALID_PD_BGNDE = #{chckValidPdBgnde},</if>
<if test="chckValidPdEndde != null">CHCK_VALID_PD_ENDDE = #{chckValidPdEndde},</if>
<if test="registReqstSeNm != null">REGIST_REQST_SE_NM = #{registReqstSeNm},</if>
<if test="frstRegistRqrcno != null">FRST_REGIST_RQRCNO = #{frstRegistRqrcno},</if>
<if test="nmplCsdyRemnrDe != null">NMPL_CSDY_REMNR_DE = #{nmplCsdyRemnrDe},</if>
<if test="nmplCsdyAt != null">NMPL_CSDY_AT = #{nmplCsdyAt},</if>
<if test="bssUsePd != null">BSS_USE_PD = #{bssUsePd},</if>
<if test="octhtErsrPrvntcNticeDe != null">OCTHT_ERSR_PRVNTC_NTICE_DE = #{octhtErsrPrvntcNticeDe},</if>
<if test="ersrRegistDe != null">ERSR_REGIST_DE = #{ersrRegistDe},</if>
<if test="ersrRegistSeCode != null">ERSR_REGIST_SE_CODE = #{ersrRegistSeCode},</if>
<if test="ersrRegistSeNm != null">ERSR_REGIST_SE_NM = #{ersrRegistSeNm},</if>
<if test="mrtgcnt != null">MRTGCNT = #{mrtgcnt},</if>
<if test="vhclecnt != null">VHCLECNT = #{vhclecnt},</if>
<if test="stmdcnt != null">STMDCNT = #{stmdcnt},</if>
<if test="adres1 != null">ADRES1 = #{adres1},</if>
<if test="adresNm1 != null">ADRES_NM1 = #{adresNm1},</if>
<if test="adres != null">ADRES = #{adres},</if>
<if test="adresNm != null">ADRES_NM = #{adresNm},</if>
<if test="indvdlBsnmAt != null">INDVDL_BSNM_AT = #{indvdlBsnmAt},</if>
<if test="telno != null">TELNO = #{telno},</if>
<if test="mberNm != null">MBER_NM = #{mberNm},</if>
<if test="mberSeCode != null">MBER_SE_CODE = #{mberSeCode},</if>
<if test="mberSeNo != null">MBER_SE_NO = #{mberSeNo},</if>
<if test="taxxmptTrgterSeCode != null">TAXXMPT_TRGTER_SE_CODE = #{taxxmptTrgterSeCode},</if>
<if test="taxxmptTrgterSeCodeNm != null">TAXXMPT_TRGTER_SE_CODE_NM = #{taxxmptTrgterSeCodeNm},</if>
<if test="cntMatter != null">CNT_MATTER = #{cntMatter},</if>
<if test="emdNm != null">EMD_NM = #{emdNm},</if>
<if test="prvntccnt != null">PRVNTCCNT = #{prvntccnt},</if>
<if test="xportFlflAtSttemntDe != null">XPORT_FLFL_AT_STTEMNT_DE = #{xportFlflAtSttemntDe},</if>
<if test="partnRqrcno != null">PARTN_RQRCNO = #{partnRqrcno},</if>
<if test="frstTrnsfrDe != null">FRST_TRNSFR_DE = #{frstTrnsfrDe},</if>
<if test="processImprtyResnCode != null">PROCESS_IMPRTY_RESN_CODE = #{processImprtyResnCode},</if>
<if test="processImprtyResnDtls != null">PROCESS_IMPRTY_RESN_DTLS = #{processImprtyResnDtls},</if>
</set>
WHERE CAR_LEDGER_FRMBK_ID = #{carLedgerFrmbkId}
</update>
<!-- 상세 INSERT -->
<insert id="insertCarLedgerFrmbkDtl" parameterType="CarLedgerFrmbkDtlVO">
INSERT INTO tb_car_ledger_frmbk_dtl (
CAR_LEDGER_FRMBK_DTL_ID,
CAR_LEDGER_FRMBK_ID,
MAINCHK,
CHANGE_JOB_SE_CODE,
MAINNO,
SUBNO,
DTLS,
RQRCNO,
VHMNO,
LEDGER_GROUP_NO,
LEDGER_INDVDLZ_NO,
GUBUN_NM,
CHANGE_DE,
DETAIL_SN,
FLAG,
REG_DT,
RGTR
) VALUES (
#{carLedgerFrmbkDtlId},
#{carLedgerFrmbkId},
#{mainchk},
#{changeJobSeCode},
#{mainno},
#{subno},
#{dtls},
#{rqrcno},
#{vhmno},
#{ledgerGroupNo},
#{ledgerIndvdlzNo},
#{gubunNm},
#{changeDe},
#{detailSn},
#{flag},
NOW(),
#{rgtr}
)
</insert>
<!-- ID로 조회 (선택) -->
<select id="selectCarLedgerFrmbkById" parameterType="String" resultType="CarLedgerFrmbkVO">
SELECT *
FROM tb_car_ledger_frmbk
WHERE CAR_LEDGER_FRMBK_ID = #{carLedgerFrmbkId}
</select>
</mapper>

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 설정 -->
<settings>
<!-- 카멜케이스 자동 매핑 (DB의 snake_case를 Java의 camelCase로 자동 변환) -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- JDBC 타입이 null일 때의 처리 (NULL 값 허용) -->
<setting name="jdbcTypeForNull" value="NULL"/>
<!-- 쿼리 실행 시 로그 출력 레벨 설정 -->
<setting name="logImpl" value="SLF4J"/>
<!-- 결과가 null일 때도 객체 생성 -->
<setting name="callSettersOnNulls" value="true"/>
<!-- 지연 로딩 설정 (필요시 변경) -->
<setting name="lazyLoadingEnabled" value="false"/>
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 캐시 사용 설정 -->
<setting name="cacheEnabled" value="false"/>
<!-- 자동 생성 키 사용 -->
<setting name="useGeneratedKeys" value="true"/>
<!-- 디폴트 Executor 타입 (SIMPLE, REUSE, BATCH) -->
<setting name="defaultExecutorType" value="REUSE"/>
<!-- 타임아웃 설정 (초 단위, null이면 무제한) -->
<setting name="defaultStatementTimeout" value="25"/>
</settings>
<!-- 타입 별칭 설정 (선택사항) -->
<!-- type-aliases-package로 자동 스캔되므로 여기서는 생략 가능 -->
<typeAliases>
<!-- 필요시 추가 -->
</typeAliases>
<!-- 타입 핸들러 설정 (선택사항) -->
<typeHandlers>
<!-- 필요시 추가 -->
</typeHandlers>
</configuration>
Loading…
Cancel
Save