비교로직 정리

main
박성영 4 weeks ago
parent 0cd1b5c68f
commit 708544506f

@ -1,958 +0,0 @@
# VMIS API 통합 아키텍처 문서
## 📋 목차
- [1. 개요](#1-개요)
- [2. 패키지 구조](#2-패키지-구조)
- [3. 아키텍처 패턴](#3-아키텍처-패턴)
- [4. Internal Mode 상세](#4-internal-mode-상세)
- [5. External Mode 상세](#5-external-mode-상세)
- [6. 공통 구성요소](#6-공통-구성요소)
- [7. 데이터 흐름](#7-데이터-흐름)
- [8. 설정 가이드](#8-설정-가이드)
- [9. 사용 예제](#9-사용-예제)
---
## 1. 개요
### 1.1 목적
VMIS(차량관리정보시스템) API 통합 모듈은 차량 정보 조회 기능을 제공하며, 두 가지 운영 모드를 지원합니다:
- **Internal Mode**: 내부 VMIS 모듈을 직접 호출 (성능 우선)
- **External Mode**: 외부 REST API 서버 호출 (분리된 아키텍처)
### 1.2 설계 원칙
- **Strategy Pattern**: 런타임에 알고리즘(구현체) 전환
- **Dependency Inversion**: 인터페이스에 의존, 구현체는 교체 가능
- **Single Responsibility**: 각 클래스는 단일 책임만 수행
- **Open-Closed**: 확장에는 열려있고 수정에는 닫혀있음
---
## 2. 패키지 구조
```
src/main/java/go/kr/project/api/
├── VehicleInfoService.java # 공통 인터페이스
├── config/ # 공통 설정
│ ├── ApiConstant.java # API 상수
│ ├── ApiMapperConfig.java # API Mapper 스캔 설정
│ ├── VmisIntegrationConfig.java # 통합 설정 및 모니터링
│ └── properties/
│ └── VmisProperties.java # 설정 바인딩 클래스
├── vo/ # 공통 응답 VO (공유)
│ ├── VehicleApiResponseVO.java # 최상위 응답 객체
│ ├── VehicleBasicInfoVO.java # 차량 기본정보 VO
│ ├── VehicleLedgerVO.java # 차량 등록원부 VO
│ ├── VehicleBasicRequestVO.java # 기본정보 요청 VO
│ └── VehicleLedgerRequestVO.java # 등록원부 요청 VO
├── internal/ # Internal Mode (내부 모듈)
│ ├── service/
│ │ ├── InternalVehicleInfoServiceImpl.java # 내부 모드 구현체 ⭐
│ │ ├── VmisCarBassMatterInqireService.java # 기본정보 조회 서비스
│ │ ├── VmisCarBassMatterInqireLogService.java # 기본정보 로그 서비스
│ │ ├── VmisCarLedgerFrmbkService.java # 등록원부 조회 서비스
│ │ └── VmisCarLedgerFrmbkLogService.java # 등록원부 로그 서비스
│ │
│ ├── client/
│ │ ├── GovernmentApi.java # 정부 API 인터페이스
│ │ └── GovernmentApiClient.java # 정부 API 클라이언트
│ │
│ ├── mapper/
│ │ ├── VmisCarBassMatterInqireMapper.java # 기본정보 Mapper
│ │ └── VmisCarLedgerFrmbkMapper.java # 등록원부 Mapper
│ │
│ ├── model/ # Internal 전용 모델
│ │ ├── basic/
│ │ │ ├── BasicRequest.java # 기본정보 요청
│ │ │ ├── BasicResponse.java # 기본정보 응답
│ │ │ └── VmisCarBassMatterInqireVO.java
│ │ ├── ledger/
│ │ │ ├── LedgerRequest.java # 등록원부 요청
│ │ │ ├── LedgerResponse.java # 등록원부 응답
│ │ │ ├── VmisCarLedgerFrmbkVO.java
│ │ │ └── VmisCarLedgerFrmbkDtlVO.java
│ │ └── common/
│ │ └── Envelope.java # 공통 Envelope
│ │
│ ├── config/
│ │ ├── HttpClientConfig.java # RestTemplate 설정
│ │ ├── OpenApiConfig.java # OpenAPI 설정
│ │ ├── PropertiesConfig.java # Properties 빈 등록
│ │ ├── GpkiConfig.java # GPKI 설정
│ │ └── Globals.java # 전역 상수
│ │
│ ├── controller/
│ │ └── VehicleInterfaceController.java # REST API 컨트롤러
│ │
│ ├── enricher/
│ │ └── VmisRequestEnricher.java # 요청 데이터 자동 보강
│ │
│ ├── gpki/
│ │ ├── GpkiService.java # GPKI 인터페이스
│ │ ├── RealGpkiService.java # 실제 GPKI 구현
│ │ └── NoopGpkiService.java # GPKI 비활성화 구현
│ │
│ └── util/
│ └── VehicleResponseMapper.java # 내부↔외부 VO 변환
└── external/ # External Mode (외부 API)
└── service/
├── ExternalVehicleInfoServiceImpl.java # 외부 모드 구현체 ⭐
└── ExternalVehicleApiService.java # 외부 API 호출 서비스
```
---
## 3. 아키텍처 패턴
### 3.1 Strategy Pattern (전략 패턴)
```java
// 공통 인터페이스
public interface VehicleInfoService {
VehicleApiResponseVO getVehicleInfo(String vehicleNumber);
List<VehicleApiResponseVO> getVehiclesInfo(List<String> vehicleNumbers);
}
// Internal Mode 구현체
@Service
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "internal")
public class InternalVehicleInfoServiceImpl implements VehicleInfoService {
// 내부 VMIS 모듈 직접 호출
}
// External Mode 구현체
@Service
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "external", matchIfMissing = true)
public class ExternalVehicleInfoServiceImpl implements VehicleInfoService {
// 외부 REST API 호출
}
```
**장점:**
- 런타임에 구현체 전환 가능
- 클라이언트 코드는 인터페이스만 의존
- 새로운 전략 추가 시 기존 코드 수정 불필요
### 3.2 의존성 주입 (Dependency Injection)
```yaml
# application.yml
vmis:
integration:
mode: internal # 또는 external
```
Spring의 `@ConditionalOnProperty`를 통해 설정값에 따라 자동으로 적절한 구현체가 Bean으로 등록됩니다.
```java
@Autowired
private VehicleInfoService vehicleInfoService;
// 설정에 따라 InternalVehicleInfoServiceImpl 또는
// ExternalVehicleInfoServiceImpl이 주입됨
```
### 3.3 계층화 아키텍처
```
┌─────────────────────────────────────┐
│ Controller Layer │ ← REST API 엔드포인트
├─────────────────────────────────────┤
│ Service Layer (VehicleInfoService)│ ← 비즈니스 로직
├─────────────────────────────────────┤
│ Client/Mapper Layer │ ← 데이터 접근
├─────────────────────────────────────┤
│ External System (정부 API/DB) │ ← 외부 시스템
└─────────────────────────────────────┘
```
---
## 4. Internal Mode 상세
### 4.1 개요
내부 VMIS 모듈을 직접 사용하여 정부 시스템(MOLIT)과 통신합니다.
**장점:**
- 네트워크 오버헤드 감소 (외부 서버 호출 불필요)
- 빠른 응답 속도
- 단일 애플리케이션으로 운영 가능
**단점:**
- GPKI 인증서 관리 필요
- 정부 API 연동 복잡도 증가
### 4.2 주요 구성요소
#### 4.2.1 InternalVehicleInfoServiceImpl
```java
@Service
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "internal")
public class InternalVehicleInfoServiceImpl implements VehicleInfoService {
private final VmisCarBassMatterInqireService carBassMatterInqireService;
private final VmisCarLedgerFrmbkService carLedgerFrmbkService;
@Override
public VehicleApiResponseVO getVehicleInfo(String vehicleNumber) {
// 1. 기본정보 조회
VehicleBasicInfoVO basicInfo = getBasicInfo(vehicleNumber);
// 2. 등록원부 조회
VehicleLedgerVO ledgerInfo = getLedgerInfo(vehicleNumber);
// 3. 결과 통합
return VehicleApiResponseVO.builder()
.vhrno(vehicleNumber)
.basicInfo(basicInfo)
.ledgerInfo(ledgerInfo)
.build();
}
}
```
#### 4.2.2 GovernmentApiClient
정부 API와 실제 HTTP 통신을 수행합니다.
```java
@Component
@RequiredArgsConstructor
public class GovernmentApiClient implements GovernmentApi {
@Qualifier("vmisRestTemplate")
private final RestTemplate restTemplate;
@Override
public ResponseEntity<String> basic(String requestBody) {
String url = baseUrl + basicServicePath;
return restTemplate.postForEntity(url, requestBody, String.class);
}
}
```
#### 4.2.3 VmisRequestEnricher
요청 데이터를 자동으로 보강합니다.
```java
@Component
public class VmisRequestEnricher {
public void enrichBasicRequest(BasicRequest request) {
// 시스템 정보 자동 설정
request.setInfoSysId(vmisProperties.getSystem().getInfoSysId());
request.setInfoSysIp(vmisProperties.getSystem().getInfoSysIp());
request.setRegionCode(vmisProperties.getSystem().getRegionCode());
// ...
}
}
```
#### 4.2.4 GPKI 암호화
운영 환경에서 정부 API 통신 시 GPKI 암호화를 사용합니다.
```java
public interface GpkiService {
String encrypt(String plainText);
String decrypt(String encryptedText);
}
// 개발: NoopGpkiService (암호화 없음)
// 운영: RealGpkiService (실제 GPKI 암호화)
```
### 4.3 데이터 흐름
```
[Client]
[InternalVehicleInfoServiceImpl]
├─► [VmisCarBassMatterInqireService]
│ │
│ ├─► [VmisRequestEnricher] ──► Request 보강
│ ├─► [GovernmentApiClient] ──► HTTP POST
│ │ │
│ │ ▼
│ │ [정부 API - 기본정보]
│ │ │
│ │ ▼
│ ├─► [GpkiService] ──► 암호화/복호화
│ ├─► [VmisCarBassMatterInqireMapper] ──► DB 저장
│ └─► BasicResponse
├─► [VmisCarLedgerFrmbkService]
│ │
│ └─► (기본정보와 동일한 흐름)
└─► [VehicleResponseMapper] ──► 내부 VO → 외부 VO 변환
VehicleApiResponseVO
```
### 4.4 설정 예시
```yaml
vmis:
integration:
mode: internal
system:
infoSysId: "41-345"
infoSysIp: "105.19.10.135"
regionCode: "41460"
gov:
scheme: "http"
host: "10.188.225.94:29001"
basePath: "/piss/api/molit"
services:
basic:
path: "/SignguCarBassMatterInqireService"
apiKey: "${GOV_API_KEY_BASIC}"
ledger:
path: "/SignguCarLedgerFrmbkService"
apiKey: "${GOV_API_KEY_LEDGER}"
gpki:
enabled: "Y" # 운영: Y, 개발: N
certServerId: "SVR5640020001"
targetServerId: "SVR1500000015"
```
---
## 5. External Mode 상세
### 5.1 개요
외부 VMIS-interface 서버의 REST API를 호출하여 차량 정보를 조회합니다.
**장점:**
- 마이크로서비스 아키텍처 지원
- GPKI 관리를 외부 서버에 위임
- 간단한 설정
**단점:**
- 네트워크 오버헤드 증가
- 외부 서버 의존성
### 5.2 주요 구성요소
#### 5.2.1 ExternalVehicleInfoServiceImpl
```java
@Service
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "external", matchIfMissing = true)
public class ExternalVehicleInfoServiceImpl implements VehicleInfoService {
private final ExternalVehicleApiService externalVehicleApiService;
@Override
public VehicleApiResponseVO getVehicleInfo(String vehicleNumber) {
// 외부 API 서비스에 위임
return externalVehicleApiService.getVehicleInfo(vehicleNumber);
}
}
```
#### 5.2.2 ExternalVehicleApiService
외부 REST API를 호출합니다.
```java
@Service
@RequiredArgsConstructor
public class ExternalVehicleApiService {
private final RestTemplate restTemplate;
public VehicleApiResponseVO getVehicleInfo(String vehicleNumber) {
String url = vmisProperties.getExternal().getApi().getUrl();
VehicleBasicRequestVO request = new VehicleBasicRequestVO();
request.setVhrno(vehicleNumber);
return restTemplate.postForObject(url + "/basic", request, VehicleApiResponseVO.class);
}
}
```
### 5.3 데이터 흐름
```
[Client]
[ExternalVehicleInfoServiceImpl]
[ExternalVehicleApiService]
[RestTemplate] ──► HTTP POST
[VMIS-interface Server] (외부)
├─► 정부 API 호출
├─► GPKI 암호화/복호화
├─► DB 저장
└─► VehicleApiResponseVO 반환
[Client]
```
### 5.4 설정 예시
```yaml
vmis:
integration:
mode: external
external:
api:
url: "http://localhost:8081/api/v1/vehicles"
connectTimeoutMillis: 5000
readTimeoutMillis: 10000
```
---
## 6. 공통 구성요소
### 6.1 VehicleInfoService 인터페이스
```java
public interface VehicleInfoService {
// 단일 차량 조회
VehicleApiResponseVO getVehicleInfo(String vehicleNumber);
// 일괄 조회
List<VehicleApiResponseVO> getVehiclesInfo(List<String> vehicleNumbers);
}
```
### 6.2 공통 VO (vo 패키지)
#### VehicleApiResponseVO
```java
@Data
@Builder
public class VehicleApiResponseVO {
private String vhrno; // 차량번호
private boolean success; // 성공 여부
private String message; // 메시지
private VehicleBasicInfoVO basicInfo; // 기본정보
private VehicleLedgerVO ledgerInfo; // 등록원부 정보
}
```
#### VehicleBasicInfoVO
차량 기본정보 (소유자, 차종, 연식 등)
#### VehicleLedgerVO
자동차 등록원부 정보 (등록일, 변경이력 등)
### 6.3 VmisProperties
application.yml의 설정을 바인딩하는 클래스입니다.
```java
@Data
@Component
@ConfigurationProperties(prefix = "vmis")
public class VmisProperties {
private Integration integration;
private System system;
private Gov gov;
private Gpki gpki;
private External external;
}
```
### 6.4 ApiMapperConfig
API 전용 MyBatis Mapper를 스캔합니다.
```java
@Configuration
@MapperScan(basePackages = "go.kr.project.api.internal.mapper")
public class ApiMapperConfig {
}
```
---
## 7. 데이터 흐름
### 7.1 Internal Mode 전체 흐름
```
┌─────────────────────────────────────────────────────────────────────┐
│ 1. Client Request │
│ vehicleInfoService.getVehicleInfo("12가3456") │
└────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 2. InternalVehicleInfoServiceImpl │
│ - BasicRequest 생성 (vhrno="12가3456") │
│ - LedgerRequest 생성 (vhrno="12가3456") │
└────────────────────────┬────────────────────────────────────────────┘
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ 3-1. Basic Service │ │ 3-2. Ledger Service │
│ - VmisRequestEnricher│ │ - VmisRequestEnricher│
│ (시스템 정보 자동 설정)│ │ (시스템 정보 자동 설정)│
└──────┬───────────────┘ └──────┬───────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ 4-1. 정부 API 호출 │ │ 4-2. 정부 API 호출 │
│ - GovernmentApiClient│ │ - GovernmentApiClient│
│ - GPKI 암호화 (운영) │ │ - GPKI 암호화 (운영) │
│ - HTTP POST │ │ - HTTP POST │
└──────┬───────────────┘ └──────┬───────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ 5-1. 응답 처리 │ │ 5-2. 응답 처리 │
│ - GPKI 복호화 │ │ - GPKI 복호화 │
│ - JSON 파싱 │ │ - JSON 파싱 │
│ - DB 저장 (Mapper) │ │ - DB 저장 (Mapper) │
└──────┬───────────────┘ └──────┬───────────────┘
│ │
└───────────────┬───────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 6. VehicleResponseMapper │
│ - BasicResponse → VehicleBasicInfoVO 변환 │
│ - LedgerResponse → VehicleLedgerVO 변환 │
└────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 7. Response │
│ VehicleApiResponseVO { │
│ vhrno: "12가3456", │
│ success: true, │
│ basicInfo: { ... }, │
│ ledgerInfo: { ... } │
│ } │
└─────────────────────────────────────────────────────────────────────┘
```
### 7.2 External Mode 전체 흐름
```
┌─────────────────────────────────────────────────────────────────────┐
│ 1. Client Request │
│ vehicleInfoService.getVehicleInfo("12가3456") │
└────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 2. ExternalVehicleInfoServiceImpl │
│ - ExternalVehicleApiService에 위임 │
└────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 3. ExternalVehicleApiService │
│ - VehicleBasicRequestVO 생성 │
│ - RestTemplate으로 외부 API 호출 │
└────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 4. HTTP Request to External Server │
│ POST http://localhost:8081/api/v1/vehicles/basic │
│ Body: { vhrno: "12가3456" } │
└────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 5. VMIS-interface Server (외부) │
│ - 정부 API 호출 │
│ - GPKI 처리 │
│ - DB 저장 │
│ - VehicleApiResponseVO 반환 │
└────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 6. Response │
│ VehicleApiResponseVO { │
│ vhrno: "12가3456", │
│ success: true, │
│ basicInfo: { ... }, │
│ ledgerInfo: { ... } │
│ } │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 8. 설정 가이드
### 8.1 Internal Mode 설정
```yaml
vmis:
integration:
mode: internal # ⭐ Internal Mode 활성화
# 시스템 정보 (요청 헤더에 자동 포함)
system:
infoSysId: "41-345"
infoSysIp: "${SERVER_IP:105.19.10.135}"
regionCode: "41460"
departmentCode: ""
chargerId: ""
chargerIp: ""
chargerNm: ""
# 정부 API 연동 설정
gov:
scheme: "http"
host: "10.188.225.94:29001"
basePath: "/piss/api/molit"
connectTimeoutMillis: 5000
readTimeoutMillis: 10000
services:
basic:
path: "/SignguCarBassMatterInqireService"
cntcInfoCode: "AC1_FD11_01"
apiKey: "${GOV_API_KEY_BASIC}"
ledger:
path: "/SignguCarLedgerFrmbkService"
cntcInfoCode: "AC1_FD11_02"
apiKey: "${GOV_API_KEY_LEDGER}"
# GPKI 암호화 (운영 환경 필수)
gpki:
enabled: "Y" # Y: 암호화 사용, N: 암호화 미사용
certServerId: "SVR5640020001"
targetServerId: "SVR1500000015"
envCertFilePathName: "src/GPKI/certs/SVR5640020001_env.cer"
sigCertFilePathName: "src/GPKI/certs/SVR5640020001_sig.cer"
```
### 8.2 External Mode 설정
```yaml
vmis:
integration:
mode: external # ⭐ External Mode 활성화
# 외부 API 서버 설정
external:
api:
url: "http://localhost:8081/api/v1/vehicles"
connectTimeoutMillis: 5000
readTimeoutMillis: 10000
```
### 8.3 개발 환경 설정 (GPKI 비활성화)
```yaml
vmis:
integration:
mode: internal
gpki:
enabled: "N" # ⭐ GPKI 비활성화
```
---
## 9. 사용 예제
### 9.1 기본 사용법
```java
@RestController
@RequestMapping("/api/vehicles")
@RequiredArgsConstructor
public class VehicleController {
private final VehicleInfoService vehicleInfoService;
/**
* 단일 차량 조회
*/
@GetMapping("/{vehicleNumber}")
public ResponseEntity<VehicleApiResponseVO> getVehicle(
@PathVariable String vehicleNumber) {
VehicleApiResponseVO response = vehicleInfoService.getVehicleInfo(vehicleNumber);
if (response.isSuccess()) {
return ResponseEntity.ok(response);
} else {
return ResponseEntity.badRequest().body(response);
}
}
/**
* 일괄 조회
*/
@PostMapping("/batch")
public ResponseEntity<List<VehicleApiResponseVO>> getVehicles(
@RequestBody List<String> vehicleNumbers) {
List<VehicleApiResponseVO> responses =
vehicleInfoService.getVehiclesInfo(vehicleNumbers);
return ResponseEntity.ok(responses);
}
}
```
### 9.2 Service Layer에서 사용
```java
@Service
@RequiredArgsConstructor
public class InspectionService {
private final VehicleInfoService vehicleInfoService;
public void performInspection(String vehicleNumber) {
// 1. 차량 정보 조회
VehicleApiResponseVO vehicleInfo =
vehicleInfoService.getVehicleInfo(vehicleNumber);
if (!vehicleInfo.isSuccess()) {
throw new VehicleNotFoundException(vehicleNumber);
}
// 2. 차량 정보를 사용한 비즈니스 로직
VehicleBasicInfoVO basicInfo = vehicleInfo.getBasicInfo();
String ownerName = basicInfo.getVhrNm(); // 소유자명
String modelYear = basicInfo.getYridnw(); // 연식
// 3. 검사 수행
// ...
}
}
```
### 9.3 응답 데이터 활용
```java
VehicleApiResponseVO response = vehicleInfoService.getVehicleInfo("12가3456");
if (response.isSuccess()) {
// 기본정보 활용
VehicleBasicInfoVO basic = response.getBasicInfo();
System.out.println("차량번호: " + basic.getVhrno());
System.out.println("소유자: " + basic.getVhrNm());
System.out.println("차종: " + basic.getVhctyNm());
System.out.println("용도: " + basic.getVhclPrposNm());
// 등록원부 활용
VehicleLedgerVO ledger = response.getLedgerInfo();
System.out.println("최초등록일: " + ledger.getFrstRegistDe());
System.out.println("등록상태: " + ledger.getRegistSttusNm());
} else {
System.err.println("조회 실패: " + response.getMessage());
}
```
### 9.4 일괄 조회 예제
```java
List<String> vehicleNumbers = Arrays.asList(
"12가3456",
"34나5678",
"56다7890"
);
List<VehicleApiResponseVO> responses =
vehicleInfoService.getVehiclesInfo(vehicleNumbers);
// 성공/실패 분류
List<VehicleApiResponseVO> successful = responses.stream()
.filter(VehicleApiResponseVO::isSuccess)
.collect(Collectors.toList());
List<VehicleApiResponseVO> failed = responses.stream()
.filter(r -> !r.isSuccess())
.collect(Collectors.toList());
System.out.println("성공: " + successful.size());
System.out.println("실패: " + failed.size());
```
---
## 10. 모드 전환 시나리오
### 10.1 개발 → 운영 전환
```yaml
# 개발 환경 (application-local.yml)
vmis:
integration:
mode: internal
gpki:
enabled: "N" # GPKI 비활성화
```
```yaml
# 운영 환경 (application-prod.yml)
vmis:
integration:
mode: internal
gpki:
enabled: "Y" # GPKI 활성화
system:
infoSysIp: "${SERVER_IP}" # 실제 서버 IP
```
### 10.2 Monolithic → MSA 전환
```yaml
# Phase 1: 단일 애플리케이션 (Internal Mode)
vmis:
integration:
mode: internal
```
```yaml
# Phase 2: 마이크로서비스 (External Mode)
vmis:
integration:
mode: external
external:
api:
url: "http://vmis-service:8080/api/v1/vehicles"
```
**코드 변경 없이 설정만으로 전환 가능!**
---
## 11. 트러블슈팅
### 11.1 Bean 충돌 오류
**증상:**
```
expected single matching bean but found 2:
internalVehicleInfoServiceImpl, externalVehicleInfoServiceImpl
```
**원인:**
`vmis.integration.mode` 설정이 잘못되었거나 누락됨
**해결:**
```yaml
vmis:
integration:
mode: internal # 또는 external (반드시 지정)
```
### 11.2 Mapper 중복 스캔 경고
**증상:**
```
Skipping MapperFactoryBean with name 'vmisCarBassMatterInqireMapper'
Bean already defined with the same name!
```
**원인:**
여러 `@MapperScan`에서 같은 패키지를 중복 스캔
**해결:**
`ApiMapperConfig`에서 API 전용 Mapper만 스캔하도록 분리되어 있음
### 11.3 GPKI 오류 (운영 환경)
**증상:**
```
GPKI 초기화 실패: 인증서 파일을 찾을 수 없습니다
```
**해결:**
1. GPKI 인증서 파일 경로 확인
2. 개발 환경에서는 `gpki.enabled: "N"` 설정
---
## 12. 성능 최적화
### 12.1 Internal Mode 최적화
- RestTemplate Connection Pool 설정 활용
- GPKI 세션 재사용
- DB 커넥션 풀 튜닝
### 12.2 External Mode 최적화
- 외부 API 서버와 동일 네트워크 배치
- Circuit Breaker 패턴 적용 (향후)
- 캐싱 전략 (향후)
---
## 13. 보안 고려사항
### 13.1 GPKI 인증서 관리
- 인증서 파일 접근 권한 제한
- 패스워드는 환경변수로 관리
- 인증서 만료 모니터링
### 13.2 API Key 관리
```yaml
vmis:
gov:
services:
basic:
apiKey: "${GOV_API_KEY_BASIC}" # 환경변수 사용
```
### 13.3 로깅 주의사항
- 차량번호 등 개인정보는 마스킹 처리
- 요청/응답 전문은 DEBUG 레벨로 출력
---
## 14. 확장 가능성
### 14.1 새로운 모드 추가
Strategy Pattern을 사용하므로 새로운 구현체 추가 가능:
```java
@Service
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "cache")
public class CachedVehicleInfoServiceImpl implements VehicleInfoService {
// 캐시 기반 구현
}
```
### 14.2 새로운 차량 정보 조회 API 추가
1. 정부 API 인터페이스 메서드 추가
2. Service 계층에 메서드 추가
3. Mapper 및 Model 추가
4. VehicleInfoService 인터페이스 확장
---
## 15. 참고 자료
### 15.1 관련 설정 파일
- `src/main/resources/application.yml`
- `src/main/resources/mybatis/mapper/api-internal/`
### 15.2 관련 문서
- Spring Boot Conditional Beans
- Strategy Pattern
- MyBatis Integration
---
**문서 버전:** 1.0
**최종 수정일:** 2025-11-07
**작성자:** VIPS Development Team

@ -1,4 +1,13 @@
- yml flag 값에 따라, 차량기본정보를 구 or 신 api 호출할지 결정
- 구 or 신 api 통일한 model object 값으로 처리될수 있도록 통합 model 필요 및 자동변환 처리
1. 자동차기본정보 api 호출 [차량번호, 부과일자:검사일] -> response.차대번호
2. 자동차기본정보 api 호출 [1.response.차대번호, 부과일자:오늘일자] -> response.차량번호, response.성명, response.민원인주민번호, response.민원인법정동코드
3. 자동차등록원본(갑) api 호출 [2.response.차량번호, 2.response.성명, 2.response.민원인주민번호, 2.response.민원인법정동코드]
-- 비교로직에 사용될 api response 정보
* 순서 중요!!
* 조건에 걸리는 순간 다음 차량번호 비교 진행
* 현재는 하나의 api 를 호출해서 비교로직 진행 ->
@ -8,18 +17,24 @@
-- 상품용 [차량기본정보 필요]
-- 상품용 [자동차등록원부(갑) 필요]
----- 필요한 api 정보
1. 자동차기본정보 api 호출 [차량번호, 부과일자:검사일] -> response.차대번호
2. 자동차기본정보 api 호출 [1.response.차대번호, 부과일자:오늘일자] -> response.차량번호, response.성명, response.민원인주민번호, response.민원인법정동코드
3. 자동차등록원본(갑) api 호출 [2.response.차량번호, 2.response.성명, 2.response.민원인주민번호, 2.response.민원인법정동코드]
-- 비교로직에 사용될 api response 정보
----------------------------------
1. TB_CAR_FFNLG_TRGT.검사일 기준 api 호출
2. api.MBER_NM like %상품용%
3. (갑부 상세(LedgerRecord) List.CHG_YMD between TB_CAR_FFNLG_TRGT.유효기간만료일 and TB_CAR_FFNLG_TRGT.검사종료일자) and (갑부 상세 List.CHANGE_JOB_SE_CODE = '11' --명의이전 코드)
4. TB_CAR_FFNLG_TRGT 비고 : 조건에 걸린 - "[상품용] 갑부정보"
-- 이첩-1 [차량등록원부(갑) 필요]
-- 이첩-1 [차동차기본정보]
1. TB_CAR_FFNLG_TRGT.검사일 기준 api 호출
2. 아래 로직 참조
/**
* 이첩 조건1: 법정동코드 불일치
* 이첩 조건1: 1. TB_CAR_FFNLG_TRGT.DAYCNT(textFile 일수) <= 115 이내, 법정동코드 불일치
* 사용본거지법정동코드 앞 4자리 != 사용자 조직코드 앞 4자리
*/
private boolean checkTransferCondition1_LegalDongMismatch(BasicResponse.Record basicInfo, String userId, String vhclno) {
@ -63,3 +78,17 @@ private boolean checkTransferCondition1_LegalDongMismatch(BasicResponse.Record b
4. 2 api<->3 api 자동차기본정보 : 사용본거지법정동코드 앞 4자리 다를경우
5. TB_CAR_FFNLG_TRGT 비고 : 조건에 걸린 - "전라남도 순천시 / 김정대, 115일 도래지, [2개의 api 법정동코드 및 법정동명]"
차량번호
if(TB_CAR_FFNLG_TRGT.DAYCNT(textFile 일수) > 115)
부과기준일 = (TB_CAR_FFNLG_TRGT.검사종료일자 + 115일)
TB_CAR_FFNLG_TRGT 비고 : 조건에 걸린 - "전라남도 순천시 / 김정대, 115일 도래지, [2개의 api 법정동코드 및 법정동명]"
이첩-2
else{
부과기준일 = (TB_CAR_FFNLG_TRGT.검사일자)
table 비고 : 조건에 걸린 - "서울시 용산구/ 이경호, 검사일사용본거지, [검사대상, 사용자 조직코드 앞 4자리 및 법정동명]"
이첩-1
}
call api

Loading…
Cancel
Save