# VIPS - Vehicle Information Processing System ## 프로젝트 개요 VIPS는 자동차 정보 조회 시스템으로, 정부 VMIS(Vehicle Management Information System)와 연계하여 차량 기본정보 및 등록원부 정보를 조회하는 Spring Boot 기반 웹 애플리케이션입니다. ### 주요 기능 - **차량 기본정보 조회**: 차량번호로 기본사항 조회 (차대번호, 소유자 정보, 차종 등) - **차량 등록원부 조회**: 자동차등록원부(갑) 정보 조회 - **통합 조회**: 기본정보 + 등록원부 정보를 한 번에 조회 - **GPKI 인증**: 정부 시스템과의 안전한 통신을 위한 GPKI 암호화 지원 - **이력 관리**: 모든 조회 요청/응답 이력을 데이터베이스에 저장 --- ## 아키텍처 ### 동작 모드 VIPS는 두 가지 모드로 동작하며, 설정을 통해 전환할 수 있습니다: #### 1. **Internal Mode** (내부 모듈 직접 호출) - VMIS 모듈을 애플리케이션 내부에 직접 통합하여 정부 API 호출 - 네트워크 오버헤드 없이 직접 호출로 성능 향상 - 단일 서버 환경에 적합 ``` Client → VehicleInterfaceController → InternalVehicleInfoServiceImpl → VmisCarBassMatterInqireService / VmisCarLedgerFrmbkService → GovernmentApiClient → 정부 VMIS API ``` #### 2. **External Mode** (외부 REST API 호출) - 별도로 실행 중인 VIPS 서버의 REST API를 호출 - 서비스 분리를 통한 확장성 및 유지보수성 향상 - MSA(Microservices Architecture) 환경에 적합 ``` Client → VehicleInterfaceController → ExternalVehicleInfoServiceImpl → ExternalVehicleApiServiceImpl (RestTemplate) → VIPS 서버 (별도 실행) → 정부 VMIS API ``` ### 모드 설정 방법 `application.yml` 또는 환경별 설정 파일에서 설정: ```yaml vmis: integration: mode: internal # 또는 external ``` ### 모드 분기 메커니즘 VIPS는 Spring Boot의 `@ConditionalOnProperty` 어노테이션을 활용하여 설정값에 따라 자동으로 적절한 구현체를 선택합니다. #### 핵심 컴포넌트 **1. VehicleInfoService (인터페이스)** ```java public interface VehicleInfoService { VehicleApiResponseVO getVehicleInfo(String vehicleNumber); VehicleApiResponseVO getVehicleInfo(BasicRequest basicRequest); BasicResponse getBasicInfo(BasicRequest request); LedgerResponse getLedgerInfo(LedgerRequest request); // ... } ``` **2. InternalVehicleInfoServiceImpl (Internal 모드 구현체)** ```java @Service @ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "internal") public class InternalVehicleInfoServiceImpl implements VehicleInfoService { // 내부 VMIS 모듈 직접 호출 로직 } ``` - `vmis.integration.mode=internal` 일 때만 Bean으로 등록 - 내부 VMIS 서비스를 직접 호출 - GPKI 암호화 처리 - 데이터베이스에 이력 저장 **3. ExternalVehicleInfoServiceImpl (External 모드 구현체)** ```java @Service @ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "external", matchIfMissing = true) public class ExternalVehicleInfoServiceImpl implements VehicleInfoService { // 외부 REST API 호출 로직 } ``` - `vmis.integration.mode=external` 일 때만 Bean으로 등록 - `matchIfMissing = true`: 설정값이 없으면 External 모드가 기본값 - ExternalVehicleApiService를 통해 외부 서버 REST API 호출 **4. VmisIntegrationConfig (통합 설정 및 모니터링)** ```java @Configuration public class VmisIntegrationConfig { @Bean public CommandLineRunner vmisIntegrationModeLogger(VehicleInfoService vehicleInfoService) { // 애플리케이션 시작 시 현재 모드 및 설정 정보 출력 } } ``` #### 동작 원리 1. **애플리케이션 시작 시** - Spring Boot가 `vmis.integration.mode` 설정값 확인 - 조건에 맞는 VehicleInfoService 구현체를 Bean으로 등록 - 다른 구현체는 Bean으로 등록되지 않음 2. **컨트롤러에서 서비스 호출 시** ```java @RestController public class VehicleInterfaceController { // Spring이 자동으로 적절한 구현체 주입 private final VehicleInfoService vehicleInfoService; @PostMapping("/info.ajax") public ResponseEntity> info(...) { // 설정에 따라 Internal 또는 External 구현체가 실행됨 VehicleApiResponseVO resp = vehicleInfoService.getVehicleInfo(request); return ResponseEntity.ok(new Envelope<>(resp)); } } ``` 3. **모드 확인 로그** 애플리케이션 시작 시 콘솔에 다음과 같은 로그가 출력됩니다: **Internal 모드:** ``` ======================================== VMIS Integration Mode: internal Active Implementation: InternalVehicleInfoServiceImpl ======================================== [Internal Mode] 내부 VMIS 모듈을 직접 사용합니다 - 정부 API 호스트: https://gov-api-url - 기본사항 조회 경로: /api/basic - 등록원부 조회 경로: /api/ledger - GPKI 암호화: Y - 연결 타임아웃: 30000ms - 읽기 타임아웃: 60000ms ``` **External 모드:** ``` ======================================== VMIS Integration Mode: external Active Implementation: ExternalVehicleInfoServiceImpl ======================================== [External Mode] 외부 REST API를 사용합니다 - 외부 API Base URL: http://localhost:8081/api/v1/vehicles - 연결 타임아웃: 10000ms - 읽기 타임아웃: 30000ms ``` #### 모드 전환 시 주의사항 **Internal → External 전환 시:** 1. 외부 VIPS 서버가 실행 중이어야 함 2. `vmis.external.api.url.base` 설정 필요 3. 네트워크 방화벽 설정 확인 4. GPKI 암호화는 외부 서버에서 처리됨 **External → Internal 전환 시:** 1. GPKI 인증서 설정 필요 2. 정부 API 직접 호출을 위한 네트워크 설정 3. `vmis.gov.host`, `vmis.gov.services` 설정 필요 4. 데이터베이스 이력 저장용 테이블 필요 #### 관련 소스 파일 **모드 분기 관련 핵심 파일:** ``` src/main/java/go/kr/project/api/ ├── config/ │ └── VmisIntegrationConfig.java # 통합 모드 설정 및 로깅 │ ├── service/ │ └── VehicleInfoService.java # 공통 인터페이스 │ ├── internal/service/impl/ │ └── InternalVehicleInfoServiceImpl.java # Internal 모드 구현체 │ # @ConditionalOnProperty(havingValue = "internal") │ └── external/service/impl/ └── ExternalVehicleInfoServiceImpl.java # External 모드 구현체 # @ConditionalOnProperty(havingValue = "external") ``` **설정 파일:** ``` src/main/resources/ ├── application.yml # 공통 설정 ├── application-local.yml # 로컬 환경 (Internal 모드 권장) ├── application-dev.yml # 개발 환경 └── application-prd.yml # 운영 환경 ``` --- ## 프로젝트 구조 ``` src/main/java/go/kr/project/api/ ├── config/ # 설정 클래스 │ ├── ApiConstant.java # API 상수 정의 (결과 코드 등) │ ├── VmisIntegrationConfig.java # VMIS 통합 모드 설정 │ ├── RestTemplateConfig.java # RestTemplate 빈 설정 │ └── properties/ │ └── VmisProperties.java # VMIS 설정 프로퍼티 │ ├── controller/ # REST 컨트롤러 │ └── VehicleInterfaceController.java # 차량 정보 조회 API 엔드포인트 │ ├── service/ # 서비스 인터페이스 │ └── VehicleInfoService.java # 차량 정보 조회 서비스 인터페이스 │ ├── internal/ # Internal 모드 관련 클래스 │ ├── service/ │ │ ├── impl/ │ │ │ └── InternalVehicleInfoServiceImpl.java # Internal 모드 구현체 │ │ ├── VmisCarBassMatterInqireService.java # 기본정보 조회 서비스 │ │ ├── VmisCarLedgerFrmbkService.java # 등록원부 조회 서비스 │ │ └── VmisRequestEnricher.java # 요청 데이터 자동 설정 │ ├── client/ │ │ ├── GovernmentApi.java # 정부 API Feign 인터페이스 │ │ └── GovernmentApiClient.java # 정부 API 호출 클라이언트 │ ├── gpki/ # GPKI 암호화 관련 │ │ ├── GpkiService.java │ │ ├── RealGpkiService.java │ │ └── NoopGpkiService.java │ └── mapper/ # MyBatis 매퍼 │ ├── VmisCarBassMatterInqireMapper.java │ └── VmisCarLedgerFrmbkMapper.java │ ├── external/ # External 모드 관련 클래스 │ └── service/ │ ├── ExternalVehicleApiService.java # 외부 API 호출 인터페이스 │ └── impl/ │ ├── ExternalVehicleInfoServiceImpl.java # External 모드 구현체 │ └── ExternalVehicleApiServiceImpl.java # REST API 호출 구현체 │ └── model/ # 데이터 모델 ├── request/ │ ├── BasicRequest.java # 기본정보 조회 요청 │ └── LedgerRequest.java # 등록원부 조회 요청 ├── response/ │ ├── BasicResponse.java # 기본정보 조회 응답 │ └── LedgerResponse.java # 등록원부 조회 응답 ├── VehicleApiResponseVO.java # 통합 조회 응답 └── Envelope.java # 공통 메시지 래퍼 ``` --- ## 기술 스택 - **Backend Framework**: Spring Boot 2.7.x - **Language**: Java 1.8 - **Database**: MariaDB - **ORM**: MyBatis 3.x - **API Documentation**: Springdoc OpenAPI (Swagger) - **Build Tool**: Gradle - **Security**: GPKI (Government Public Key Infrastructure) --- ## 환경 설정 ### 필수 설정 파일 프로젝트는 환경별로 다음 설정 파일들을 사용합니다: - `application.yml`: 공통 설정 - `application-local.yml`: 로컬 개발 환경 - `application-dev.yml`: 개발 환경 - `application-prd.yml`: 운영 환경 ### VMIS 통합 설정 예시 ```yaml vmis: integration: mode: internal # internal 또는 external # Internal 모드 설정 gov: api: url: https://gov-api-url services: basic: cntcInfoCode: AC1_FD11_01 ledger: cntcInfoCode: AC1_FD11_02 # System 정보 (공통) system: infoSysId: "41-345" infoSysIp: "105.19.10.135" sigunguCode: "41460" chargerId: "" chargerIp: "" chargerNm: "" # External 모드 설정 external: api: url: base: http://localhost:8081/api/v1/vehicles ``` --- ## API 엔드포인트 모든 API는 `/api/v1/vehicles` 경로 하위에 정의되어 있습니다. ### 1. 통합 조회 (기본정보 + 등록원부) **POST** `/api/v1/vehicles/info.ajax` 요청 예시: ```json { "data": [{ "VHRNO": "12가3456", "LEVY_STDDE": "20250101", "INQIRE_SE_CODE": "1", "VIN": "KMHAB812345678901" }] } ``` 응답 예시: ```json { "data": [{ "vhrno": "12가3456", "success": true, "message": "조회 성공", "basicInfo": { /* 기본정보 */ }, "ledgerInfo": { /* 등록원부 정보 */ } }] } ``` ### 2. 기본정보만 조회 **POST** `/api/v1/vehicles/basic.ajax` 요청 예시: ```json { "data": [{ "VHRNO": "12가3456", "LEVY_STDDE": "20250101", "INQIRE_SE_CODE": "1", "VIN": "KMHAB812345678901" }] } ``` ### 3. 등록원부만 조회 **POST** `/api/v1/vehicles/ledger.ajax` 요청 예시: ```json { "data": [{ "VHRNO": "12가3456", "ONES_INFORMATION_OPEN": "1", "CPTTR_NM": "홍길동", "CPTTR_IHIDNUM": "8801011234567", "ROUTE_SE_CODE": "3", "DETAIL_EXPRESSION": "1", "INQIRE_SE_CODE": "1" }] } ``` --- ## 주요 클래스 및 컴포넌트 ### 1. VehicleInfoService (인터페이스) 차량 정보 조회의 핵심 인터페이스로, Internal/External 모드에 따라 다른 구현체가 자동으로 주입됩니다. **구현체:** - `InternalVehicleInfoServiceImpl`: Internal 모드 - `ExternalVehicleInfoServiceImpl`: External 모드 ### 2. VmisRequestEnricher 요청 객체에 시스템 정보를 자동으로 채워주는 컴포넌트입니다. **자동 설정 항목:** - INFO_SYS_ID, INFO_SYS_IP, SIGUNGU_CODE - CNTC_INFO_CODE (서비스별) - CHARGER_ID, CHARGER_IP, CHARGER_NM **고정값 설정 (LedgerRequest):** - ONES_INFORMATION_OPEN: "1" (소유자 공개) - ROUTE_SE_CODE: "3" - DETAIL_EXPRESSION: "1" (전체내역) - INQIRE_SE_CODE: "1" (열람) ### 3. ApiConstant API 응답 코드 및 시스템 상수를 관리합니다. **주요 상수:** - `CNTC_RESULT_CODE_SUCCESS`: "MSG50000" (성공) - `CNTC_RESULT_CODE_ERROR`: "ERROR" (에러) - `DEFAULT_REGISTRANT`: "SYSTEM" --- ## 데이터베이스 ### 주요 테이블 - `tb_car_bass_matter_inqire`: 차량 기본정보 조회 이력 - `tb_car_ledger_frmbk`: 차량 등록원부 조회 이력 ### MyBatis 매퍼 위치 ``` src/main/resources/mybatis/mapper/api-internal/ ├── CarBassMatterInqireMapper_maria.xml └── CarLedgerFrmbkMapper_maria.xml ``` --- ## 빌드 및 실행 ### 빌드 ```bash # 클린 빌드 ./gradlew clean build # 컴파일만 (테스트 제외) ./gradlew clean compileJava ``` ### 실행 ```bash # 기본 실행 (local 프로파일) ./gradlew bootRun # 특정 프로파일로 실행 ./gradlew bootRun --args='--spring.profiles.active=dev' ``` ### 포트 설정 기본 포트: `8080` 변경 방법: ```yaml server: port: 8080 ``` #### 방법 2: JAR 파일 실행 (배포 권장) **개발 환경** ```bash # Linux/Mac java -Dspring.profiles.active=dev -jar build/libs/VIPS-BOOT.war # Windows PowerShell java -Dspring.profiles.active=dev -jar build/libs/VIPS-BOOT.war # 또는 환경변수 사용 (Windows PowerShell) $env:SPRING_PROFILES_ACTIVE = "dev" java -jar build/libs/VIPS-BOOT.war ``` **운영 환경 (환경변수 포함)** ```bash # Linux/Mac export SPRING_PROFILES_ACTIVE=prd export VMIS_SYSTEM_INFO_SYS_ID="41-345" export VMIS_SYSTEM_SIGUNGU_CODE="41460" java -jar build/libs/VIPS-BOOT.war # Windows PowerShell $env:SPRING_PROFILES_ACTIVE = "prd" $env:VMIS_SYSTEM_INFO_SYS_ID = "41-345" $env:VMIS_SYSTEM_SIGUNGU_CODE = "41460" java -jar build/libs/VIPS-BOOT.war ``` #### 백그라운드 실행 (Linux 서버) ```bash # nohup 사용 (기본 java 경로, 콘솔 출력을 app.log에 저장) nohup java -Dspring.profiles.active=prd -jar build/libs/VIPS-BOOT.war > app.log 2>&1 & # Java 경로를 직접 지정하는 방법 (Java 17 이상) nohup /usr/bin/java -Dspring.profiles.active=prd -jar build/libs/VIPS-BOOT.war > app.log 2>&1 & # 또는 사용자 지정 Java 경로 사용 nohup /opt/jdk-17/bin/java -Dspring.profiles.active=prd -jar build/libs/VIPS-BOOT.war > app.log 2>&1 & # 콘솔 출력을 남기지 않을 때 (애플리케이션 자체 로그는 logback-spring.xml 설정에 따라 별도 저장됨) # 개발 환경 nohup /usr/bin/java -Dspring.profiles.active=dev -jar build/libs/VIPS-BOOT.war > /dev/null 2>&1 & # 운영 환경 (애플리케이션 로그: d:/data/VIPS/logs/vips.log) nohup /usr/bin/java -Dspring.profiles.active=prd -jar build/libs/VIPS-BOOT.war > /dev/null 2>&1 & # Java 경로 확인 방법 which java # 또는 readlink -f $(which java) # 프로세스 확인 ps aux | grep VIPS # 종료 kill -9 ``` **참고:** `/dev/null` 사용 시 콘솔 출력만 버려지며, 애플리케이션 자체 로그는 `logging.file.path` 설정에 따라 정상적으로 기록됩니다. - 개발: `d:/data/VIPS/logs/vips.log` - 운영: `d:/data/VIPS/logs/vips.log` #### 백그라운드 실행 (Windows 서버) **PowerShell - 백그라운드 프로세스로 실행** ```powershell # 개발 환경 (콘솔 출력을 app.log에 저장) Start-Process -NoNewWindow -FilePath "java" -ArgumentList "-Dspring.profiles.active=dev","-jar","build\libs\VIPS-BOOT.war" -RedirectStandardOutput "app.log" -RedirectStandardError "app.log" # 운영 환경 (콘솔 출력을 app.log에 저장) Start-Process -NoNewWindow -FilePath "java" -ArgumentList "-Dspring.profiles.active=prd","-jar","build\libs\VIPS-BOOT.war" -RedirectStandardOutput "app.log" -RedirectStandardError "app.log" # Java 경로를 직접 지정하는 방법 Start-Process -NoNewWindow -FilePath "D:\DEV\.jdks\jdk1.8.0_271\bin\java.exe" -ArgumentList "-Dspring.profiles.active=prd","-jar","build\libs\VIPS-BOOT.war" -RedirectStandardOutput "app.log" -RedirectStandardError "app.log" # 콘솔 출력을 남기지 않을 때 (NUL로 리다이렉트) Start-Process -NoNewWindow -FilePath "java" -ArgumentList "-Dspring.profiles.active=prd","-jar","build\libs\VIPS-BOOT.war" -RedirectStandardOutput "NUL" -RedirectStandardError "NUL" ``` **CMD - javaw 사용 (콘솔 창 없이 실행)** ```cmd # 개발 환경 start /B javaw -Dspring.profiles.active=dev -jar build\libs\VIPS-BOOT.war # 운영 환경 start /B javaw -Dspring.profiles.active=prd -jar build\libs\VIPS-BOOT.war # Java 경로를 직접 지정 (경로에 공백이 있을 경우 빈 창제목 "" 필요) start "" /B "D:\DEV\.jdks\jdk1.8.0_271\bin\javaw.exe" -Dspring.profiles.active=prd -jar build\libs\VIPS-BOOT.war # 또는 절대 경로 사용 start "" /B "D:\DEV\.jdks\jdk1.8.0_271\bin\javaw.exe" -Dspring.profiles.active=prd -jar D:\workspace\git\VIPS\build\libs\VIPS-BOOT.war cd /d d: D:\VIPS\azul-17.0.14\bin java -Dspring.profiles.active=prd -jar D:\VIPS\VIPS-BOOT.war "D:\VIPS\azul-17.0.14\bin\javaw.exe" -Dspring.profiles.active=prd -jar D:\VIPS\VIPS-BOOT.war start "" /B "D:\VIPS\azul-17.0.14\bin\javaw.exe" -Dspring.profiles.active=prd -jar D:\VIPS\VIPS-BOOT.war ``` **참고:** `start` 명령어는 첫 번째 따옴표 문자열을 창 제목으로 인식하므로, 경로를 따옴표로 묶을 때는 앞에 빈 창제목 `""`을 추가해야 합니다. **프로세스 관리** ```powershell # 프로세스 확인 Get-Process | Where-Object {$_.ProcessName -like "*java*"} | Select-Object Id, ProcessName, Path # 또는 특정 포트로 확인 netstat -ano | findstr :18080 # 프로세스 종료 (PID로) Stop-Process -Id -Force # 또는 taskkill 사용 taskkill /F /PID ``` **Java 경로 확인 (Windows)** ```powershell # PowerShell where.exe java # 또는 (Get-Command java).Source # Java 버전 확인 java -version ``` **참고:** Windows 환경에서도 콘솔 출력을 `NUL`로 리다이렉트하면, 애플리케이션 자체 로그는 `d:/data/VIPS/logs/vips.log`에 정상적으로 기록됩니다. ### 4) API 문서(Swagger UI) - 개발 환경: `http://localhost:8080/swagger-ui/index.html` - 운영 환경: `http://localhost:18080/swagger-ui/index.html` --- --- ## Swagger API 문서 애플리케이션 실행 후 다음 URL에서 API 문서를 확인할 수 있습니다: ``` http://localhost:8080/swagger-ui/index.html ``` --- ## 개발 가이드 ### 새로운 API 추가 시 1. **Request/Response 모델 생성** - `model/request/` 또는 `model/response/` 패키지에 DTO 클래스 생성 - `@JsonProperty` 어노테이션으로 JSON 필드 매핑 2. **서비스 인터페이스에 메서드 추가** - `VehicleInfoService` 인터페이스에 메서드 정의 3. **구현체에 로직 작성** - `InternalVehicleInfoServiceImpl`: Internal 모드 - `ExternalVehicleApiServiceImpl`: External 모드 4. **컨트롤러에 엔드포인트 추가** - `VehicleInterfaceController`에 `@PostMapping` 추가 - Swagger 문서화를 위한 `@Operation` 어노테이션 추가 ### LedgerRequest 필수 설정 값 등록원부 조회 시 다음 값들이 자동으로 설정됩니다: ```java // BasicInfo에서 자동 매핑 ledgerRequest.setCpttrNm(basicInfo.getMberNm()); // 민원인성명 ledgerRequest.setCpttrIhidnum(basicInfo.getMberSeNo()); // 민원인주민번호 // 고정값 ledgerRequest.setOnesInformationOpen("1"); // 개인정보공개 ledgerRequest.setRouteSeCode("3"); // 경로구분코드 ledgerRequest.setDetailExpression("2"); // 내역표시 ledgerRequest.setInqireSeCode("1"); // 조회구분코드 ``` ### 성공 코드 체크 응답 검증 시 반드시 상수를 사용하세요: ```java // ❌ 하드코딩 if ("00".equals(response.getCntcResultCode())) { ... } // ✅ 상수 사용 if (ApiConstant.CNTC_RESULT_CODE_SUCCESS.equals(response.getCntcResultCode())) { ... } ``` --- ## 응답 코드 정부 API 응답 코드는 `ApiConstant.java`의 주석을 참고하세요. **주요 응답 코드:** | 코드 | 설명 | |------|------| | MSG50000 | EAI 연계 호출을 성공적으로 처리하였습니다. | | MSG50001 | 요청하신 자료가 존재하지 않습니다. | | MSG50002 | 기준치보다 많은 결과조회로 인해 열람이 불가합니다. | | MSG50110 | EAI 호출을 위한 Message(Header) 구성에 실패하였습니다. | | MSG50250 | EAI-HUB 구간 통신장애 오류 | --- ## 트러블슈팅 ### 1. 모드가 전환되지 않을 때 `@ConditionalOnProperty` 어노테이션이 적용된 구현체가 제대로 로드되는지 확인: ```yaml vmis: integration: mode: internal # 또는 external ``` ### 2. GPKI 오류 - GPKI 인증서 경로 확인 - `GpkiConfig` 설정 확인 - 로컬 개발 시 `NoopGpkiService` 사용 고려 ### 3. 데이터베이스 연결 오류 - HikariCP 설정 확인 - 데이터베이스 URL, 사용자명, 비밀번호 확인 - 방화벽/네트워크 설정 확인 --- --- ## 라이선스 Copyright (c) 2025 XIT Co., Ltd. --- ## 연락처 프로젝트 관련 문의사항은 개발팀에 문의하시기 바랍니다.