**chore: 과태료 비교 로직 및 서비스 관련 문서 삭제**

- `README.md`, `COMPARISON_GUIDE.md`, `이첩조건_추가방법.md`, `VMIS_INTEGRATION_COMPLETE.md`, `VMIS_INTEGRATION_STRATEGY_DESIGN.md` 파일 삭제.
  - Chain of Responsibility 패턴 비교 규칙 관련 문서.
  - 서비스 및 구현 방법에 대한 가이드 문서 포함.
  - 이첩 조건 추가 방법 및 통합 완료 가이드 문서.
- 조회구분코드 : 자동차기본정보조회, 갑부에 따라 구분코드 다르게 설정하는 부분 기본셋팅값으로 추가 함
internalApi
박성영 4 weeks ago
parent 732a977b67
commit 6c085ec7c0

@ -1,349 +0,0 @@
# VMIS 통합 완료 및 사용 가이드
## 📋 작업 완료 요약
VMIS-interface 프로젝트의 모든 코드를 VIPS 내부로 통합하고, YAML 설정을 통해 내부/외부 모드를 자유롭게 전환할 수 있는 기능을 구현했습니다.
### ✅ 완료된 작업
#### 1단계: 코드 마이그레이션 (완료)
- ✅ VMIS-interface 전체 코드 이식 (33개 Java 파일, 2개 XML)
- ✅ 패키지 변경: `com.vmis.interfaceapp``go.kr.project.vmis`
- ✅ Spring Boot 3 → 2 호환: `jakarta``javax`
- ✅ Java 17 → 8 호환: Text Blocks, List.of() 제거
- ✅ HttpClient 5 → 4 변환
- ✅ GPKI 라이브러리 추가
- ✅ application.yml에 VMIS 설정 통합
#### 2단계: Strategy Pattern 구현 (완료)
- ✅ VehicleInfoService 인터페이스 생성
- ✅ InternalVehicleInfoServiceImpl: 내부 VMIS 모듈 직접 호출
- ✅ ExternalVehicleInfoServiceImpl: 외부 REST API 호출
- ✅ VmisIntegrationConfig: 모드 모니터링 및 로깅
- ✅ VehicleResponseMapper: 모델 변환 유틸리티
#### 3단계: Bean 충돌 해결 (완료)
- ✅ `restTemplate``vmisRestTemplate`으로 변경
- ✅ @Qualifier 어노테이션 추가
- ✅ 빌드 성공 확인
---
## 🎯 주요 기능
### 1. 모드 전환 (YAML 설정)
**Internal Mode: 내부 VMIS 모듈 직접 호출**
```yaml
# application.yml
vmis:
integration:
mode: internal # 내부 모듈 사용
```
**External Mode: 외부 REST API 호출**
```yaml
# application.yml
vmis:
integration:
mode: external # 외부 API 사용
external:
api:
url: http://localhost:8081/api/v1/vehicles
```
### 2. 자동 Bean 선택
Spring Boot의 `@ConditionalOnProperty` 어노테이션을 사용하여 설정에 따라 자동으로 적절한 구현체를 선택합니다:
- **mode: internal**`InternalVehicleInfoServiceImpl` 활성화
- **mode: external**`ExternalVehicleInfoServiceImpl` 활성화 (기본값)
### 3. 통합 인터페이스
```java
@Autowired
private VehicleInfoService vehicleInfoService;
// 단일 차량 조회
VehicleApiResponseVO response = vehicleInfoService.getVehicleInfo("12가3456");
// 여러 차량 일괄 조회
List<VehicleApiResponseVO> responses = vehicleInfoService.getVehiclesInfo(
Arrays.asList("12가3456", "34나5678")
);
```
---
## 📁 생성/수정된 파일
### 새로 생성된 파일
1. **공통 인터페이스**
- `src/main/java/go/kr/project/common/service/VehicleInfoService.java`
2. **Strategy Pattern 구현체**
- `src/main/java/go/kr/project/vmis/service/InternalVehicleInfoServiceImpl.java`
- `src/main/java/go/kr/project/externalApi/service/ExternalVehicleInfoServiceImpl.java`
3. **설정 및 유틸리티**
- `src/main/java/go/kr/project/vmis/config/VmisIntegrationConfig.java`
- `src/main/java/go/kr/project/vmis/util/VehicleResponseMapper.java`
### 수정된 파일
1. `src/main/java/go/kr/project/vmis/config/HttpClientConfig.java`
- Bean 이름 변경: `restTemplate``vmisRestTemplate`
2. `src/main/java/go/kr/project/vmis/client/GovernmentApiClient.java`
- @Qualifier 추가: `@Qualifier("vmisRestTemplate")`
3. `src/main/java/go/kr/project/vmis/config/properties/VmisProperties.java`
- IntegrationProps, ExternalProps 추가
---
## 🔧 설정 상세
### application.yml 전체 구조
```yaml
vmis:
# ===== 모드 선택 =====
integration:
mode: internal # internal 또는 external
# ===== Internal Mode 설정 =====
system:
infoSysId: "41-345"
infoSysIp: "${SERVER_IP:105.19.10.135}"
regionCode: "41460"
departmentCode: ""
chargerId: ""
chargerIp: ""
chargerNm: ""
gpki:
enabled: "N" # Y: 암호화 사용, N: 암호화 미사용
useSign: true
charset: "UTF-8"
certServerId: "SVR5640020001"
targetServerId: "SVR1500000015"
# ... (기타 GPKI 설정)
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:...}"
cvmisApikey: "${GOV_CVMIS_API_KEY_BASIC:...}"
ledger:
path: "/SignguCarLedgerFrmbkService"
cntcInfoCode: "AC1_FD11_02"
apiKey: "${GOV_API_KEY_LEDGER:...}"
cvmisApikey: "${GOV_CVMIS_API_KEY_LEDGER:...}"
# ===== External Mode 설정 =====
external:
api:
url: "http://localhost:8081/api/v1/vehicles"
connectTimeoutMillis: 5000
readTimeoutMillis: 10000
```
---
## 📊 모드별 특징
### Internal Mode (내부 모듈 직접 호출)
**장점:**
- ✅ 네트워크 오버헤드 없음 (더 빠른 성능)
- ✅ 외부 서버 불필요
- ✅ 단일 애플리케이션 배포
- ✅ 직접 정부 시스템과 통신
**요구사항:**
- 정부 API 접근 권한 필요
- GPKI 인증서 설정 (운영 환경)
- 방화벽 정책 설정
**사용 시나리오:**
- 운영 환경 배포
- 단일 서버 구성
- 높은 성능이 요구되는 경우
### External Mode (외부 REST API 호출)
**장점:**
- ✅ 마이크로서비스 아키텍처 지원
- ✅ VMIS 모듈 독립 배포 가능
- ✅ 여러 시스템에서 공유 가능
- ✅ 부하 분산 가능
**요구사항:**
- VMIS-interface 서버가 실행 중이어야 함
- 네트워크 연결 필요
**사용 시나리오:**
- 개발 환경 (VMIS-interface 서버 별도 실행)
- 마이크로서비스 구조
- 여러 클라이언트가 VMIS 기능을 공유하는 경우
---
## 🚀 시작 로그 예시
### Internal Mode 시작 시
```
========================================
VMIS Integration Mode: internal
Active Implementation: InternalVehicleInfoServiceImpl
========================================
[Internal Mode] 내부 VMIS 모듈을 직접 사용합니다
- 정부 API 호스트: http://10.188.225.94:29001
- 기본사항 조회 경로: /SignguCarBassMatterInqireService
- 등록원부 조회 경로: /SignguCarLedgerFrmbkService
- GPKI 암호화: N
- 연결 타임아웃: 5000ms
- 읽기 타임아웃: 10000ms
- GPKI 암호화가 비활성화되어 있습니다. 개발 환경에서만 사용하세요.
```
### External Mode 시작 시
```
========================================
VMIS Integration Mode: external
Active Implementation: ExternalVehicleInfoServiceImpl
========================================
[External Mode] 외부 REST API를 사용합니다
- 외부 API URL: http://localhost:8081/api/v1/vehicles
- 연결 타임아웃: 5000ms
- 읽기 타임아웃: 10000ms
- 외부 VMIS-interface 서버가 실행 중이어야 합니다.
- 기본사항 조회: POST http://localhost:8081/api/v1/vehicles/basic
- 등록원부 조회: POST http://localhost:8081/api/v1/vehicles/ledger
```
---
## 🧪 테스트 방법
### 1. Internal Mode 테스트
```yaml
# application.yml
vmis:
integration:
mode: internal
```
```bash
# 애플리케이션 시작
./gradlew bootRun
# 로그에서 "VMIS Integration Mode: internal" 확인
```
### 2. External Mode 테스트
```yaml
# application.yml
vmis:
integration:
mode: external
```
```bash
# 1. VMIS-interface 서버 시작 (별도 터미널)
cd D:\workspace\git\VMIS-interface
./gradlew bootRun
# 2. VIPS 애플리케이션 시작
cd D:\workspace\git\VIPS
./gradlew bootRun
# 로그에서 "VMIS Integration Mode: external" 확인
```
---
## 🔍 코드 사용 예시
### 기존 코드 수정 불필요
기존에 `ExternalVehicleApiService`를 사용하던 코드는 수정 없이 `VehicleInfoService`로 변경하면 됩니다:
**변경 전:**
```java
@Autowired
private ExternalVehicleApiService externalVehicleApiService;
VehicleApiResponseVO response = externalVehicleApiService.getVehicleInfo("12가3456");
```
**변경 후:**
```java
@Autowired
private VehicleInfoService vehicleInfoService;
VehicleApiResponseVO response = vehicleInfoService.getVehicleInfo("12가3456");
```
설정 파일만 변경하면 자동으로 내부/외부 모드가 전환됩니다!
---
## 📝 커밋 내역
### 1차 커밋: 기본 통합
```
feat: VMIS-interface 통합 (Spring Boot 2.7 호환)
- VMIS-interface 전체 코드 이식 (33개 Java 파일, 2개 XML)
- 패키지 변경, Spring Boot 호환, Java 호환
- 빌드 성공 확인 ✅
```
### 2차 커밋: Strategy Pattern
```
feat: VMIS 통합 모드 전환 기능 구현 (Strategy Pattern)
- VehicleInfoService 인터페이스 및 구현체
- Bean 충돌 해결
- 설정 확장 및 모니터링
- 빌드 성공 확인 ✅
```
---
## ⚠️ 주의사항
### Bean 이름 변경
- 기존 `restTemplate` → 새로 추가된 `vmisRestTemplate`
- 기존 RestTemplateConfig의 restTemplate은 그대로 유지
- VMIS 내부 모듈은 vmisRestTemplate 사용
### 기본 모드
- 설정이 없거나 `mode`가 지정되지 않으면 **external 모드**가 기본값
- Internal 모드를 사용하려면 명시적으로 `mode: internal` 설정 필요
### GPKI 인증서
- 운영 환경에서는 반드시 `gpki.enabled: Y` 설정
- 개발 환경에서는 `gpki.enabled: N` 사용 가능
---
## 🎉 완료!
모든 작업이 성공적으로 완료되었습니다. YAML 설정만으로 내부/외부 모드를 자유롭게 전환할 수 있습니다.
**질문이나 문제가 있으면 다음 문서를 참조하세요:**
- `VMIS_INTERFACE_INTEGRATION_ANALYSIS.md`: 통합 분석 문서
- `VMIS_INTERFACE_MIGRATION_PLAN.md`: 마이그레이션 계획
- `VMIS_INTEGRATION_STRATEGY_DESIGN.md`: Strategy Pattern 설계

@ -1,802 +0,0 @@
# VMIS 통합 전략 설계 문서
**작성일**: 2025-11-06
**목적**: 설정 기반으로 내부/외부 차량 정보 조회 방식을 선택할 수 있는 아키텍처 설계
---
## 목차
1. [요구사항](#1-요구사항)
2. [아키텍처 설계](#2-아키텍처-설계)
3. [구현 가이드](#3-구현-가이드)
4. [설정 예시](#4-설정-예시)
5. [테스트 방법](#5-테스트-방법)
---
## 1. 요구사항
### 1.1 핵심 요구사항
**application.yml 설정에 따라 두 가지 방식 중 선택 가능**:
1. **Internal Mode (내부 모드)**
- 신규 통합된 VMIS 모듈을 직접 호출
- 네트워크 오버헤드 없음
- 단일 트랜잭션 처리 가능
- 높은 성능
2. **External Mode (외부 모드)**
- 기존 ExternalVehicleApiService를 통해 외부 REST API 호출
- 독립적인 VMIS-interface 서버와 통신
- 기존 방식 유지 (하위 호환성)
### 1.2 전환 시나리오
- **개발 단계**: External 모드로 테스트 (기존 방식)
- **통합 테스트**: Internal 모드로 전환하여 성능 검증
- **점진적 배포**: 일부 서버는 External, 일부는 Internal
- **롤백 대비**: 문제 발생 시 External 모드로 즉시 전환
---
## 2. 아키텍처 설계
### 2.1 전략 패턴 (Strategy Pattern) 적용
```
┌─────────────────────────────────────────┐
│ VehicleInfoServiceFacade │ (클라이언트 코드)
│ - getBasicInfo(vhrno) │
│ - getLedgerInfo(vhrno) │
└─────────────────┬───────────────────────┘
│ 의존
┌─────────────────────────────────────────┐
<<interface>> │
│ VehicleInfoService │ (전략 인터페이스)
│ + getBasicInfo(vhrno): ResponseDTO │
│ + getLedgerInfo(vhrno): ResponseDTO │
└─────────────────┬───────────────────────┘
┌────────┴─────────┐
↓ ↓
┌──────────────────┐ ┌─────────────────────────┐
│ Internal모드 │ │ External모드 │
│ │ │ │
│ Internal │ │ External │
│ VehicleInfo │ │ VehicleInfo │
│ ServiceImpl │ │ ServiceImpl │
│ │ │ │
@Conditional │ │ @Conditional
│ OnProperty │ │ OnProperty │
│ (internal) │ │ (external) │
│ │ │ │
│ ↓ │ │ ↓ │
│ VMIS 모듈 │ │ RestTemplate │
│ 직접 호출 │ │ (HTTP 호출) │
└──────────────────┘ └─────────────────────────┘
```
### 2.2 컴포넌트 설계
#### 2.2.1 인터페이스
**VehicleInfoService.java** (전략 인터페이스)
```java
package go.kr.project.common.service;
import go.kr.project.common.vo.VehicleBasicInfoResponseVO;
import go.kr.project.common.vo.VehicleLedgerResponseVO;
public interface VehicleInfoService {
/**
* 차량 기본정보 조회
* @param vhrno 차량번호
* @return 차량 기본정보 응답
*/
VehicleBasicInfoResponseVO getBasicInfo(String vhrno);
/**
* 차량 등록원부 조회
* @param vhrno 차량번호
* @return 차량 등록원부 응답
*/
VehicleLedgerResponseVO getLedgerInfo(String vhrno);
}
```
#### 2.2.2 구현체 1: Internal Mode
**InternalVehicleInfoServiceImpl.java**
```java
package go.kr.project.vmis.service;
import go.kr.project.common.service.VehicleInfoService;
import go.kr.project.common.vo.*;
import go.kr.project.vmis.service.CarBassMatterInqireService;
import go.kr.project.vmis.service.CarLedgerFrmbkService;
import go.kr.project.vmis.model.vo.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "internal")
public class InternalVehicleInfoServiceImpl implements VehicleInfoService {
private final CarBassMatterInqireService carBassMatterInqireService;
private final CarLedgerFrmbkService carLedgerFrmbkService;
@Override
public VehicleBasicInfoResponseVO getBasicInfo(String vhrno) {
log.info("[INTERNAL MODE] 차량 기본정보 조회 - 차량번호: {}", vhrno);
try {
// 내부 VMIS 모듈 직접 호출
CarBassMatterInqireRequestVO request = new CarBassMatterInqireRequestVO();
request.setVhrno(vhrno);
Envelope<VehicleBasicInfoDTO> response =
carBassMatterInqireService.getBasicInfo(request);
// DTO → VO 변환
return convertToBasicInfoResponse(response);
} catch (Exception e) {
log.error("[INTERNAL MODE] 차량 기본정보 조회 실패 - 차량번호: {}", vhrno, e);
return createErrorResponse(vhrno, e.getMessage());
}
}
@Override
public VehicleLedgerResponseVO getLedgerInfo(String vhrno) {
log.info("[INTERNAL MODE] 차량 등록원부 조회 - 차량번호: {}", vhrno);
try {
// 내부 VMIS 모듈 직접 호출
CarLedgerFrmbkRequestVO request = new CarLedgerFrmbkRequestVO();
request.setVhrno(vhrno);
Envelope<VehicleLedgerDTO> response =
carLedgerFrmbkService.getLedgerInfo(request);
// DTO → VO 변환
return convertToLedgerResponse(response);
} catch (Exception e) {
log.error("[INTERNAL MODE] 차량 등록원부 조회 실패 - 차량번호: {}", vhrno, e);
return createLedgerErrorResponse(vhrno, e.getMessage());
}
}
// DTO → VO 변환 메서드들...
private VehicleBasicInfoResponseVO convertToBasicInfoResponse(Envelope<VehicleBasicInfoDTO> envelope) {
// 구현...
}
private VehicleLedgerResponseVO convertToLedgerResponse(Envelope<VehicleLedgerDTO> envelope) {
// 구현...
}
}
```
#### 2.2.3 구현체 2: External Mode
**ExternalVehicleInfoServiceImpl.java**
```java
package go.kr.project.externalApi.service;
import go.kr.project.common.service.VehicleInfoService;
import go.kr.project.common.vo.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "external", matchIfMissing = true)
public class ExternalVehicleInfoServiceImpl implements VehicleInfoService {
private final RestTemplate restTemplate;
@Value("${vmis.external.api.url:http://localhost:8081/api/v1/vehicles}")
private String vmisApiUrl;
@Override
public VehicleBasicInfoResponseVO getBasicInfo(String vhrno) {
log.info("[EXTERNAL MODE] 차량 기본정보 조회 - 차량번호: {}", vhrno);
try {
String url = vmisApiUrl + "/basic";
// 기존 ExternalVehicleApiService 로직 활용
// RestTemplate으로 외부 API 호출
VehicleBasicRequestVO request = new VehicleBasicRequestVO();
request.setVhrno(vhrno);
VehicleBasicInfoResponseVO response = restTemplate.postForObject(
url,
request,
VehicleBasicInfoResponseVO.class
);
return response;
} catch (Exception e) {
log.error("[EXTERNAL MODE] 차량 기본정보 조회 실패 - 차량번호: {}", vhrno, e);
return createErrorResponse(vhrno, e.getMessage());
}
}
@Override
public VehicleLedgerResponseVO getLedgerInfo(String vhrno) {
log.info("[EXTERNAL MODE] 차량 등록원부 조회 - 차량번호: {}", vhrno);
try {
String url = vmisApiUrl + "/ledger";
VehicleLedgerRequestVO request = new VehicleLedgerRequestVO();
request.setVhrno(vhrno);
VehicleLedgerResponseVO response = restTemplate.postForObject(
url,
request,
VehicleLedgerResponseVO.class
);
return response;
} catch (Exception e) {
log.error("[EXTERNAL MODE] 차량 등록원부 조회 실패 - 차량번호: {}", vhrno, e);
return createLedgerErrorResponse(vhrno, e.getMessage());
}
}
}
```
#### 2.2.4 설정 클래스
**VmisIntegrationConfig.java**
```java
package go.kr.project.vmis.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Slf4j
@Configuration
@EnableConfigurationProperties(VmisProperties.class)
public class VmisIntegrationConfig {
@Configuration
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "internal")
static class InternalModeConfig {
@PostConstruct
public void init() {
log.info("=================================================");
log.info("VMIS Integration Mode: INTERNAL");
log.info("차량 정보 조회: 내부 VMIS 모듈 직접 호출");
log.info("=================================================");
}
}
@Configuration
@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "external", matchIfMissing = true)
static class ExternalModeConfig {
@PostConstruct
public void init() {
log.info("=================================================");
log.info("VMIS Integration Mode: EXTERNAL");
log.info("차량 정보 조회: 외부 REST API 호출");
log.info("=================================================");
}
}
}
```
---
## 3. 구현 가이드
### 3.1 파일 구조
```
go/kr/project/
├── common/
│ ├── service/
│ │ └── VehicleInfoService.java (인터페이스)
│ └── vo/
│ ├── VehicleBasicInfoResponseVO.java
│ └── VehicleLedgerResponseVO.java
├── vmis/
│ ├── config/
│ │ ├── VmisProperties.java
│ │ └── VmisIntegrationConfig.java (설정)
│ └── service/
│ └── InternalVehicleInfoServiceImpl.java (내부 모드 구현)
└── externalApi/
└── service/
└── ExternalVehicleInfoServiceImpl.java (외부 모드 구현)
```
### 3.2 기존 코드 리팩토링
#### Before (기존 ExternalVehicleApiService)
```java
@Service
public class ExternalVehicleApiService {
private final RestTemplate restTemplate;
public VehicleApiResponseVO getBasicInfo(String vhrno) {
// RestTemplate으로 외부 API 호출
}
}
```
#### After (전략 패턴 적용)
```java
// 기존 ExternalVehicleApiService는 ExternalVehicleInfoServiceImpl로 대체
// 클라이언트 코드는 VehicleInfoService 인터페이스에 의존
@Service
@RequiredArgsConstructor
public class CarInspectionService {
private final VehicleInfoService vehicleInfoService; // 인터페이스 주입
public void processInspection(String vhrno) {
// 설정에 따라 자동으로 내부/외부 구현체가 주입됨
VehicleBasicInfoResponseVO info = vehicleInfoService.getBasicInfo(vhrno);
// ...
}
}
```
### 3.3 구현 순서
1. **VehicleInfoService 인터페이스 생성**
- 위치: `go/kr/project/common/service/`
- 메서드: getBasicInfo, getLedgerInfo
2. **InternalVehicleInfoServiceImpl 구현**
- 위치: `go/kr/project/vmis/service/`
- VMIS 모듈 직접 호출
3. **ExternalVehicleInfoServiceImpl 구현**
- 위치: `go/kr/project/externalApi/service/`
- 기존 ExternalVehicleApiService 로직 이전
4. **VmisIntegrationConfig 생성**
- 설정 로깅 및 검증
5. **기존 클라이언트 코드 수정**
- ExternalVehicleApiService → VehicleInfoService 의존성 변경
---
## 4. 설정 예시
### 4.1 application.yml (공통)
```yaml
# VMIS 통합 모드 설정
vmis:
integration:
mode: internal # internal | external
# Internal 모드 설정
system:
info-sys-id: "VMIS001"
info-sys-ip: "${SERVER_IP:192.168.1.100}"
manager-id: "admin"
manager-name: "관리자"
manager-tel: "02-1234-5678"
gpki:
enabled: false
cert-path: "${GPKI_CERT_PATH:/path/to/cert.der}"
private-key-path: "${GPKI_PRIVATE_KEY_PATH:/path/to/private.key}"
private-key-password: "${GPKI_PASSWORD:}"
government:
host: "https://www.vemanet.com"
base-path: "/openapi"
connect-timeout: 10000
read-timeout: 15000
services:
basic:
path: "/carBassMatterInqire"
api-key: "${GOV_API_KEY_BASIC:}"
ledger:
path: "/carLedgerFrmbk"
api-key: "${GOV_API_KEY_LEDGER:}"
# External 모드 설정
external:
api:
url: "http://localhost:8081/api/v1/vehicles"
connect-timeout: 5000
read-timeout: 10000
```
### 4.2 application-local.yml (개발 환경)
```yaml
vmis:
integration:
mode: external # 개발 중에는 외부 API 사용
external:
api:
url: "http://localhost:8081/api/v1/vehicles"
```
### 4.3 application-dev.yml (개발 서버)
```yaml
vmis:
integration:
mode: internal # 개발 서버에서는 내부 모듈 테스트
gpki:
enabled: false # 개발 서버는 암호화 비활성화
```
### 4.4 application-prd.yml (운영 환경)
```yaml
vmis:
integration:
mode: internal # 운영 환경은 내부 모듈 사용
gpki:
enabled: true # 운영 환경은 암호화 활성화
cert-path: "${GPKI_CERT_PATH}"
private-key-path: "${GPKI_PRIVATE_KEY_PATH}"
private-key-password: "${GPKI_PASSWORD}"
```
### 4.5 환경변수 설정 예시
```bash
# Internal 모드 (운영)
export VMIS_INTEGRATION_MODE=internal
export GOV_API_KEY_BASIC=your-api-key-basic
export GOV_API_KEY_LEDGER=your-api-key-ledger
export GPKI_PASSWORD=your-gpki-password
# External 모드 (롤백 시)
export VMIS_INTEGRATION_MODE=external
export VMIS_EXTERNAL_API_URL=http://vmis-interface-server:8081/api/v1/vehicles
```
---
## 5. 테스트 방법
### 5.1 단위 테스트
#### InternalVehicleInfoServiceImplTest.java
```java
@SpringBootTest
@TestPropertySource(properties = {
"vmis.integration.mode=internal"
})
class InternalVehicleInfoServiceImplTest {
@Autowired
private VehicleInfoService vehicleInfoService;
@Test
void testGetBasicInfo() {
// given
String vhrno = "12가3456";
// when
VehicleBasicInfoResponseVO response = vehicleInfoService.getBasicInfo(vhrno);
// then
assertNotNull(response);
assertEquals(vhrno, response.getVhrno());
assertTrue(vehicleInfoService instanceof InternalVehicleInfoServiceImpl);
}
}
```
#### ExternalVehicleInfoServiceImplTest.java
```java
@SpringBootTest
@TestPropertySource(properties = {
"vmis.integration.mode=external",
"vmis.external.api.url=http://localhost:8081/api/v1/vehicles"
})
class ExternalVehicleInfoServiceImplTest {
@Autowired
private VehicleInfoService vehicleInfoService;
@Test
void testGetBasicInfo() {
// given
String vhrno = "12가3456";
// when
VehicleBasicInfoResponseVO response = vehicleInfoService.getBasicInfo(vhrno);
// then
assertNotNull(response);
assertTrue(vehicleInfoService instanceof ExternalVehicleInfoServiceImpl);
}
}
```
### 5.2 통합 테스트
#### 1. Internal 모드 테스트
```bash
# application-local.yml 수정
vmis:
integration:
mode: internal
# 애플리케이션 실행
./gradlew bootRun --args='--spring.profiles.active=local'
# 로그 확인
# [INTERNAL MODE] 차량 기본정보 조회 - 차량번호: 12가3456
# API 호출
curl -X POST http://localhost:8080/api/inspection \
-H "Content-Type: application/json" \
-d '{"vhrno":"12가3456"}'
```
#### 2. External 모드 테스트
```bash
# 1. VMIS-interface 서버 실행 (8081 포트)
cd D:\workspace\git\VMIS-interface
./gradlew bootRun
# 2. application-local.yml 수정
vmis:
integration:
mode: external
external:
api:
url: "http://localhost:8081/api/v1/vehicles"
# 3. VIPS 실행
cd D:\workspace\git\VIPS
./gradlew bootRun --args='--spring.profiles.active=local'
# 로그 확인
# [EXTERNAL MODE] 차량 기본정보 조회 - 차량번호: 12가3456
# API 호출
curl -X POST http://localhost:8080/api/inspection \
-H "Content-Type: application/json" \
-d '{"vhrno":"12가3456"}'
```
### 5.3 성능 비교 테스트
```java
@SpringBootTest
class VehicleInfoServicePerformanceTest {
@Autowired
private VehicleInfoService vehicleInfoService;
@Test
void comparePerformance() {
String vhrno = "12가3456";
int iterations = 100;
// Warmup
for (int i = 0; i < 10; i++) {
vehicleInfoService.getBasicInfo(vhrno);
}
// Performance test
long startTime = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
vehicleInfoService.getBasicInfo(vhrno);
}
long endTime = System.currentTimeMillis();
long avgTime = (endTime - startTime) / iterations;
log.info("평균 응답 시간: {}ms (모드: {})", avgTime,
vehicleInfoService.getClass().getSimpleName());
// Internal 모드가 External 모드보다 빠를 것으로 예상
assertTrue(avgTime < 100); // 100ms
}
}
```
### 5.4 장애 상황 테스트
#### 시나리오 1: External 모드에서 외부 API 장애
```bash
# VMIS-interface 서버 중지
# → External 모드는 에러 반환
# → 설정을 Internal 모드로 변경하여 복구
```
#### 시나리오 2: Internal 모드에서 DB 장애
```bash
# DB 연결 차단
# → Internal 모드는 에러 반환
# → 설정을 External 모드로 변경하여 복구
```
---
## 6. 운영 시나리오
### 6.1 점진적 배포 (Blue-Green)
**1단계: 준비**
```bash
# 모든 서버: External 모드
vmis.integration.mode=external
```
**2단계: 일부 서버 전환**
```bash
# 서버 A, B: External 모드
vmis.integration.mode=external
# 서버 C, D: Internal 모드 (테스트)
vmis.integration.mode=internal
```
**3단계: 모니터링 후 전체 전환**
```bash
# 모든 서버: Internal 모드
vmis.integration.mode=internal
```
### 6.2 롤백 절차
```bash
# 1. application-prd.yml 또는 환경변수 수정
vmis.integration.mode=external
# 2. 애플리케이션 재시작 (또는 설정 리로드)
# 3. 로그 확인
# [EXTERNAL MODE] ...
# 4. VMIS-interface 외부 서버가 실행 중인지 확인
curl http://vmis-interface-server:8081/actuator/health
```
### 6.3 모니터링 지표
**Prometheus/Grafana 메트릭**:
```java
@Component
public class VehicleInfoServiceMetrics {
private final Counter internalCalls;
private final Counter externalCalls;
private final Timer internalTimer;
private final Timer externalTimer;
// 메트릭 수집...
}
```
**로그 분석**:
```bash
# Internal 모드 호출 건수
grep "\[INTERNAL MODE\]" application.log | wc -l
# External 모드 호출 건수
grep "\[EXTERNAL MODE\]" application.log | wc -l
# 평균 응답 시간
grep "응답시간" application.log | awk '{sum+=$NF; count++} END {print sum/count}'
```
---
## 7. 주의사항
### 7.1 설정 검증
**애플리케이션 시작 시 체크**:
```java
@Component
@RequiredArgsConstructor
public class VmisIntegrationValidator implements ApplicationRunner {
@Value("${vmis.integration.mode}")
private String integrationMode;
@Autowired(required = false)
private VehicleInfoService vehicleInfoService;
@Override
public void run(ApplicationArguments args) {
if (vehicleInfoService == null) {
throw new IllegalStateException(
"VehicleInfoService 빈이 생성되지 않았습니다. " +
"vmis.integration.mode 설정을 확인하세요: " + integrationMode
);
}
log.info("VMIS 통합 모드 검증 완료: {} (구현체: {})",
integrationMode,
vehicleInfoService.getClass().getSimpleName());
}
}
```
### 7.2 External 모드 사용 시 주의점
1. **외부 서버 가용성 확인**
- VMIS-interface 서버가 실행 중이어야 함
- Health check 엔드포인트 모니터링
2. **네트워크 타임아웃 설정**
- connect-timeout, read-timeout 적절히 설정
- Circuit Breaker 패턴 고려
3. **API 버전 호환성**
- External 서버와 클라이언트의 API 계약 일치 확인
### 7.3 Internal 모드 사용 시 주의점
1. **트랜잭션 범위**
- @Transactional 설정 주의
- DB 연결 풀 크기 조정
2. **GPKI 설정**
- 운영 환경에서는 gpki.enabled=true
- 인증서 경로 및 비밀번호 확인
3. **API 키 보안**
- 정부 API 키는 환경변수로 관리
- 코드에 하드코딩 금지
---
## 8. FAQ
### Q1: 설정을 변경하면 재시작이 필요한가요?
**A**: 네, @ConditionalOnProperty는 애플리케이션 시작 시에만 평가됩니다. 설정 변경 후 재시작이 필요합니다.
### Q2: 두 모드를 동시에 사용할 수 있나요?
**A**: 아니오, 한 번에 하나의 모드만 활성화됩니다. 설정에 따라 하나의 구현체만 빈으로 등록됩니다.
### Q3: External 모드에서 VMIS-interface 서버가 다운되면?
**A**: RestTemplate 타임아웃이 발생하고 에러가 반환됩니다. Circuit Breaker 패턴을 추가로 구현하면 장애 전파를 방지할 수 있습니다.
### Q4: Internal 모드가 External 모드보다 얼마나 빠른가요?
**A**: 네트워크 오버헤드가 없으므로 평균 50-100ms 정도 빠를 것으로 예상됩니다. 실제 성능은 환경에 따라 다릅니다.
### Q5: 개발 중에는 어떤 모드를 사용하나요?
**A**: 개발 초기에는 External 모드를 사용하여 기존 방식으로 개발하고, 통합 테스트 단계에서 Internal 모드로 전환하여 검증합니다.
---
**문서 버전**: 1.0
**최종 수정**: 2025-11-06

@ -1,765 +0,0 @@
# VMIS-interface → VIPS 통합 프로젝트 분석 문서
**작성일**: 2025-11-06
**작업 방식**: 방법 2 - 직접 통합 (Spring Boot 2.7 다운그레이드)
---
## 목차
1. [프로젝트 개요](#1-프로젝트-개요)
2. [VIPS 프로젝트 구조](#2-vips-프로젝트-구조)
3. [VMIS-interface 프로젝트 구조](#3-vmis-interface-프로젝트-구조)
4. [기술 스택 비교](#4-기술-스택-비교)
5. [통합 방식 선택 이유](#5-통합-방식-선택-이유)
6. [주요 차이점 및 해결 방안](#6-주요-차이점-및-해결-방안)
---
## 1. 프로젝트 개요
### VIPS (차량 점검 페널티 시스템)
- **위치**: D:\workspace\git\VIPS
- **목적**: 자동차 점검 페널티 관리 업무 시스템
- **형태**: 단일 모듈 Spring Boot 웹 애플리케이션
### VMIS-interface (차량 정보 조회 API)
- **위치**: D:\workspace\git\VMIS-interface
- **목적**: 도로교통공단 Open API 연동하여 차량 정보 조회
- **형태**: REST API 서버
### 통합 목표
VMIS-interface의 모든 설정, 로직, 데이터베이스를 VIPS 내부로 이식하여 외부 API 호출 없이 내부 모듈로 작동
---
## 2. VIPS 프로젝트 구조
### 2.1 기본 정보
```
프로젝트명: VIPS
Spring Boot: 2.7.18
Java: 1.8
빌드: Gradle
패키징: WAR
데이터베이스: MariaDB
ORM: MyBatis
```
### 2.2 디렉토리 구조
```
D:\workspace\git\VIPS\
├── src/main/java/
│ ├── egovframework/ (전자정부 프레임워크)
│ │ ├── config/ (설정 클래스)
│ │ │ ├── RestTemplateConfig.java
│ │ │ ├── DataSourceProxyConfig.java
│ │ │ ├── EgovConfigCommon.java
│ │ │ ├── EgovConfigWeb.java
│ │ │ ├── SqlLoggingInterceptor.java
│ │ │ └── SwaggerConfig.java
│ │ ├── configProperties/
│ │ │ └── RestTemplateProperties.java
│ │ ├── filter/
│ │ │ └── XssFilterConfig.java
│ │ └── util/
│ │ ├── ApiResponseEntity.java
│ │ └── ApiResponseUtil.java
│ │
│ └── go/kr/project/
│ ├── externalApi/ (외부 API 통합 모듈) ★
│ │ ├── service/
│ │ │ └── ExternalVehicleApiService.java
│ │ └── vo/
│ │ ├── Envelope.java
│ │ ├── VehicleApiResponseVO.java
│ │ ├── VehicleBasicInfoVO.java
│ │ ├── VehicleBasicRequestVO.java
│ │ ├── VehicleLedgerRequestVO.java
│ │ └── VehicleLedgerVO.java
│ ├── carInspectionPenalty/
│ ├── common/
│ ├── login/
│ ├── main/
│ ├── mypage/
│ └── system/
├── src/main/resources/
│ ├── application.yml
│ ├── application-local.yml
│ ├── application-dev.yml
│ ├── application-prd.yml
│ ├── logback-spring.xml
│ └── mybatis/
│ ├── mybatis-config.xml
│ └── mapper/
└── build.gradle
```
### 2.3 주요 설정 (application.yml)
```yaml
server:
port: 8080
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/**/*_${Globals.DbType}.xml
rest-template:
timeout:
connect-timeout-millis: 10000
read-timeout-millis: 12000
connection-pool:
max-total: 100
max-per-route: 20
rate-limit:
permits-per-second: 5.0
```
### 2.4 기존 외부 API 통합 구조
**ExternalVehicleApiService.java**:
- VMIS-interface를 REST API로 호출 (http://localhost:8081)
- 배치 처리 지원
- 에러 처리 및 로깅
**RestTemplateConfig.java**:
- Apache HttpClient 4 기반
- 연결 풀 관리 (PoolingHttpClientConnectionManager)
- Rate Limiting (Guava RateLimiter)
### 2.5 주요 의존성
```gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:2.7.18'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1'
implementation 'org.mariadb.jdbc:mariadb-java-client'
implementation 'org.apache.httpcomponents:httpclient'
implementation 'com.google.guava:guava:32.1.3-jre'
implementation 'org.egovframe.rte:org.egovframe.rte.fdl.cmmn:4.3.0'
implementation 'org.egovframe.rte:org.egovframe.rte.ptl.mvc:4.3.0'
}
```
---
## 3. VMIS-interface 프로젝트 구조
### 3.1 기본 정보
```
프로젝트명: VMIS-interface
Spring Boot: 3.3.4
Java: 17
빌드: Gradle
패키징: JAR
데이터베이스: MariaDB
ORM: MyBatis
```
### 3.2 디렉토리 구조
```
D:\workspace\git\VMIS-interface\
├── src/main/java/com/vmis/interfaceapp/
│ ├── InterfaceApplication.java (메인 클래스)
│ │
│ ├── config/ (설정 계층)
│ │ ├── MyBatisConfig.java
│ │ ├── OpenApiConfig.java
│ │ └── VmisProperties.java (핵심 설정)
│ │
│ ├── controller/ (API 진입점)
│ │ └── VehicleInterfaceController.java
│ │
│ ├── service/ (비즈니스 로직)
│ │ ├── CarBassMatterInqireService.java (기본사항 조회)
│ │ ├── CarLedgerFrmbkService.java (등록원부 조회)
│ │ ├── CarBassMatterInqireLogService.java (로그)
│ │ └── CarLedgerFrmbkLogService.java (로그)
│ │
│ ├── client/ (외부 API 호출)
│ │ └── GovernmentApiClient.java (도로교통공단 API)
│ │
│ ├── enricher/ (데이터 보강)
│ │ └── RequestEnricher.java
│ │
│ ├── model/ (도메인 모델)
│ │ ├── entity/ (DB 엔티티 - 7개)
│ │ ├── vo/ (VO - 6개)
│ │ └── dto/ (DTO - 2개)
│ │
│ ├── mapper/ (MyBatis Mapper)
│ │ ├── CarBassMatterInqireLogMapper.java
│ │ └── CarLedgerFrmbkLogMapper.java
│ │
│ └── util/ (유틸리티)
│ ├── EncryptionUtil.java (GPKI 암호화)
│ └── TransactionIdGenerator.java
├── src/main/resources/
│ ├── application.yml
│ ├── application-dev.yml
│ ├── application-prd.yml
│ ├── logback-spring.xml
│ └── mybatis/
│ ├── mybatis-config.xml
│ └── mapper/
│ ├── CarBassMatterInqireLogMapper.xml
│ └── CarLedgerFrmbkLogMapper.xml
├── lib/
│ └── libgpkiapi_jni_1.5.jar (GPKI 라이브러리)
├── ddl/vips/ (데이터베이스 스키마)
│ ├── tb_car_bass_matter_inqire.sql
│ ├── seq_car_bass_matter_inqire.sql
│ ├── tb_car_ledger_frmbk.sql
│ ├── seq_car_ledger_frmbk.sql
│ ├── tb_car_ledger_frmbk_dtl.sql
│ └── seq_car_ledger_frmbk_dtl.sql
└── build.gradle
```
### 3.3 핵심 파일 목록 (34개 Java 파일)
#### 설정 (3개)
1. `MyBatisConfig.java`
2. `OpenApiConfig.java`
3. `VmisProperties.java` ⭐ (핵심 설정)
#### 컨트롤러 (1개)
4. `VehicleInterfaceController.java`
#### 서비스 (4개)
5. `CarBassMatterInqireService.java`
6. `CarLedgerFrmbkService.java`
7. `CarBassMatterInqireLogService.java`
8. `CarLedgerFrmbkLogService.java`
#### 클라이언트 (1개)
9. `GovernmentApiClient.java` ⭐ (가장 복잡)
#### 데이터 보강 (1개)
10. `RequestEnricher.java`
#### 엔티티 (7개)
11. `CarBassMatterInqire.java`
12. `CarBassMatterInqireLog.java`
13. `CarLedgerFrmbk.java`
14. `CarLedgerFrmbkDtl.java`
15. `CarLedgerFrmbkLog.java`
16. `VehicleBasicInfo.java`
17. `VehicleLedger.java`
#### VO (6개)
18. `CarBassMatterInqireRequestVO.java`
19. `CarLedgerFrmbkRequestVO.java`
20. `Envelope.java`
21. `GovApiBasicRequest.java`
22. `GovApiBasicResponse.java`
23. `GovApiLedgerRequest.java`
24. `GovApiLedgerResponse.java`
#### DTO (2개)
25. `VehicleBasicInfoDTO.java`
26. `VehicleLedgerDTO.java`
#### Mapper (2개)
27. `CarBassMatterInqireLogMapper.java`
28. `CarLedgerFrmbkLogMapper.java`
#### 유틸리티 (2개)
29. `EncryptionUtil.java`
30. `TransactionIdGenerator.java`
#### 메인 클래스 (1개)
31. `InterfaceApplication.java`
**총 31개 핵심 파일 + 3개 추가 클래스**
### 3.4 VmisProperties 구조 (핵심)
```java
@ConfigurationProperties(prefix = "vmis")
public class VmisProperties {
private SystemProps system; // 시스템 정보
private GpkiProps gpki; // GPKI 암호화 설정
private GovProps government; // 정부 API 설정
public static class SystemProps {
private String infoSysId; // 정보시스템 ID
private String infoSysIp; // 시스템 IP
private String managerId; // 담당자 ID
private String managerName; // 담당자명
private String managerTel; // 담당자 전화번호
}
public static class GpkiProps {
private boolean enabled; // 암호화 사용 여부
private String certPath; // 인증서 경로
private String privateKeyPath; // 개인키 경로
private String privateKeyPassword; // 개인키 비밀번호
}
public static class GovProps {
private String host; // API 호스트
private String basePath; // 기본 경로
private int connectTimeout; // 연결 타임아웃
private int readTimeout; // 읽기 타임아웃
private Services services; // 서비스별 설정
public static class Services {
private ServiceConfig basic; // 기본사항 API
private ServiceConfig ledger; // 등록원부 API
public static class ServiceConfig {
private String path; // API 경로
private String apiKey; // API 키
}
}
}
}
```
### 3.5 application.yml 설정
```yaml
vmis:
system:
info-sys-id: "VMIS001"
info-sys-ip: "192.168.1.100"
manager-id: "admin"
manager-name: "관리자"
manager-tel: "02-1234-5678"
gpki:
enabled: false # 개발환경에서는 false
cert-path: "/path/to/cert.der"
private-key-path: "/path/to/private.key"
private-key-password: "${GPKI_PASSWORD}"
government:
host: "https://www.vemanet.com"
base-path: "/openapi"
connect-timeout: 10000
read-timeout: 15000
services:
basic:
path: "/carBassMatterInqire"
api-key: "${GOV_API_KEY_BASIC}"
ledger:
path: "/carLedgerFrmbk"
api-key: "${GOV_API_KEY_LEDGER}"
```
### 3.6 API 엔드포인트
#### 1. 자동차 기본사항 조회
```
POST /api/v1/vehicles/basic
Content-Type: application/json
Request:
{
"vhrno": "12가3456"
}
Response:
{
"data": [
{
"vhrno": "12가3456",
"success": true,
"message": "조회 성공",
"basicInfo": {
"vhrno": "12가3456",
"vhcleNm": "소나타",
...
}
}
]
}
```
#### 2. 자동차 등록원부 조회
```
POST /api/v1/vehicles/ledger
Content-Type: application/json
Request:
{
"vhrno": "12가3456"
}
Response:
{
"data": [
{
"vhrno": "12가3456",
"success": true,
"message": "조회 성공",
"ledgerInfo": {
"master": {...},
"details": [...]
}
}
]
}
```
### 3.7 데이터베이스 테이블 (6개)
1. **tb_car_bass_matter_inqire** (기본사항 로그)
- seq_car_bass_matter_inqire (시퀀스)
2. **tb_car_ledger_frmbk** (등록원부 마스터)
- seq_car_ledger_frmbk (시퀀스)
3. **tb_car_ledger_frmbk_dtl** (등록원부 상세)
- seq_car_ledger_frmbk_dtl (시퀀스)
### 3.8 주요 의존성
```gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:3.3.4'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
implementation 'org.mariadb.jdbc:mariadb-java-client:3.3.3'
implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.3'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'
implementation 'org.projectlombok:lombok:1.18.34'
implementation files('lib/libgpkiapi_jni_1.5.jar')
}
```
---
## 4. 기술 스택 비교
| 항목 | VIPS | VMIS-interface | 비고 |
|------|------|----------------|------|
| **Spring Boot** | 2.7.18 | 3.3.4 | 메이저 버전 차이 |
| **Java** | 1.8 | 17 | 9개 버전 차이 |
| **패키지** | javax.* | jakarta.* | 네임스페이스 변경 |
| **HttpClient** | Apache 4 | Apache 5 | API 변경 |
| **MyBatis** | 2.3.1 | 3.0.3 | 호환 가능 |
| **MariaDB** | 기본 | 3.3.3 | 호환 가능 |
| **Swagger** | springdoc 1.7.0 | springdoc 2.6.0 | 버전 차이 |
| **Lombok** | 기본 | 1.18.34 | 호환 가능 |
### 4.1 주요 호환성 이슈
#### Spring Boot 2.x → 3.x 주요 변경사항
1. **패키지 변경**: `javax.*``jakarta.*`
2. **최소 Java 버전**: Java 8 → Java 17
3. **Spring Security**: 아키텍처 변경
4. **Configuration Properties**: 일부 속성명 변경
#### HttpClient 4 → 5 주요 변경사항
1. **패키지명**: `org.apache.http.*``org.apache.hc.client5.*`
2. **API 재설계**: 빌더 패턴 강화
3. **클래스명 변경**: `HttpClientBuilder``HttpClients`
---
## 5. 통합 방식 선택 이유
### 방법 2: 직접 통합 (Spring Boot 2.7 다운그레이드)
#### 선택 이유
1. **내부 시스템 자체 완결**: 외부 API 호출 없이 내부 메서드 호출로 처리
2. **트랜잭션 일관성**: 단일 애플리케이션 내 DB 트랜잭션 관리
3. **배포 단순화**: 하나의 WAR 파일로 배포
4. **성능 최적화**: 네트워크 오버헤드 제거
#### 트레이드오프
- Spring Boot 3 → 2 다운그레이드 필요
- Java 17 → Java 8 호환성 작업 필요
- 최신 기술 스택 사용 불가
---
## 6. 주요 차이점 및 해결 방안
### 6.1 Spring Boot 버전 차이
#### 문제
- Spring Boot 3.3.4 → 2.7.18 다운그레이드
#### 해결 방안
```gradle
// VMIS-interface build.gradle 수정
plugins {
id 'org.springframework.boot' version '2.7.18'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
```
### 6.2 패키지 네임스페이스 변경
#### 문제
- `jakarta.*``javax.*` 전체 변경 필요
#### 영향 범위
```
jakarta.servlet.* → javax.servlet.*
jakarta.validation.* → javax.validation.*
jakarta.persistence.* → javax.persistence.*
jakarta.annotation.* → javax.annotation.*
```
#### 해결 방안
- 전체 파일에서 `jakarta``javax` 일괄 교체
- IDE의 Find & Replace 기능 활용
### 6.3 HttpClient 버전 차이
#### 문제
- Apache HttpClient 5 → 4 다운그레이드
#### GovernmentApiClient.java 수정 필요
```java
// Before (HttpClient 5)
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
// After (HttpClient 4)
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
```
### 6.4 Java 17 → Java 8 호환성
#### 주요 체크 포인트
1. **Text Blocks**: Java 15+ 기능 사용 시 일반 문자열로 변경
2. **Records**: Java 14+ 기능 사용 시 일반 클래스로 변경
3. **Switch Expressions**: Java 14+ 기능 사용 시 전통적 switch로 변경
4. **var 키워드**: Java 10+ 기능 (사용 가능하나 명시적 타입 권장)
#### 현재 VMIS-interface 코드 검토 필요
- 대부분 전통적 Java 문법 사용 예상
- Lombok 적극 활용 중 (호환 가능)
### 6.5 MyBatis 설정 차이
#### VMIS-interface mybatis-config.xml
```xml
<?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>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="callSettersOnNulls" value="true"/>
</settings>
</configuration>
```
#### VIPS 설정과 통합
- VIPS의 기존 mybatis-config.xml에 설정 병합
- mapper 위치 추가: `mybatis/mapper/vmis/**/*_maria.xml`
### 6.6 패키지 구조 변경
#### Before (VMIS-interface)
```
com.vmis.interfaceapp.*
```
#### After (VIPS 통합)
```
go.kr.project.vmis.*
```
#### 변경 계획
```
com.vmis.interfaceapp.config → go.kr.project.vmis.config
com.vmis.interfaceapp.controller → go.kr.project.vmis.controller
com.vmis.interfaceapp.service → go.kr.project.vmis.service
com.vmis.interfaceapp.client → go.kr.project.vmis.client
com.vmis.interfaceapp.model → go.kr.project.vmis.model
com.vmis.interfaceapp.mapper → go.kr.project.vmis.mapper
com.vmis.interfaceapp.util → go.kr.project.vmis.util
```
### 6.7 설정 파일 통합
#### VmisProperties 통합 방안
**Option 1**: VIPS의 application.yml에 vmis 섹션 추가
```yaml
# VIPS application.yml
vmis:
system:
info-sys-id: "VMIS001"
...
government:
...
```
**Option 2**: application-vmis.yml 별도 파일 생성
```yaml
# application-vmis.yml
vmis:
...
```
**권장**: Option 1 (단일 설정 파일 유지)
### 6.8 GPKI 라이브러리 통합
#### 파일 위치
```
D:\workspace\git\VMIS-interface\lib\libgpkiapi_jni_1.5.jar
D:\workspace\git\VIPS\lib\libgpkiapi_jni_1.5.jar
```
#### build.gradle 수정
```gradle
dependencies {
implementation files('lib/libgpkiapi_jni_1.5.jar')
}
```
---
## 7. 마이그레이션 체크리스트
### Phase 1: 환경 준비
- [ ] 현재 브랜치 상태 확인 (이미 생성됨)
- [ ] VIPS 프로젝트 백업 (선택사항)
- [ ] GPKI 라이브러리 복사
### Phase 2: 코드 이식
- [ ] VmisProperties 이식 및 패키지 변경
- [ ] 설정 클래스 이식 (MyBatisConfig, OpenApiConfig)
- [ ] GovernmentApiClient 이식 (HttpClient 4 변경)
- [ ] 모델 클래스 이식 (entity, vo, dto)
- [ ] 서비스 클래스 이식
- [ ] 컨트롤러 이식
- [ ] Mapper 인터페이스 및 XML 이식
- [ ] 유틸리티 클래스 이식
### Phase 3: 설정 통합
- [ ] application.yml 설정 통합
- [ ] mybatis-config.xml 설정 병합
- [ ] logback-spring.xml 로그 설정 추가
- [ ] build.gradle 의존성 추가
### Phase 4: 패키지 변경
- [ ] jakarta → javax 일괄 변경
- [ ] com.vmis.interfaceapp → go.kr.project.vmis 변경
- [ ] import 문 정리
### Phase 5: 데이터베이스
- [ ] 데이터베이스 연결 테스트 (테이블은 이미 존재)
### Phase 6: 빌드 및 테스트
- [ ] Gradle 빌드 성공 확인
- [ ] 애플리케이션 구동 확인
- [ ] API 엔드포인트 테스트
- [ ] 정부 API 연동 테스트
- [ ] 로그 저장 확인
### Phase 7: 전략 패턴 구현 (내부/외부 통신 분기)
- [ ] VehicleInfoService 인터페이스 생성
- [ ] Internal/External 모드 구현체 작성
- [ ] VmisIntegrationConfig 설정 클래스
- [ ] application.yml 설정 추가 (vmis.integration.mode)
- [ ] 기존 클라이언트 코드 인터페이스 의존으로 변경
### Phase 8: 기존 코드 정리
- [ ] ExternalVehicleApiService 코드 리팩토링
- [ ] 불필요한 REST 호출 로직 제거
- [ ] 내부 메서드 호출로 변경
### Phase 9: 문서화 및 배포
- [ ] API 문서 업데이트
- [ ] 운영 가이드 작성
- [ ] 코드 리뷰
- [ ] main 브랜치 머지
---
## 8. 주요 파일 절대 경로
### VIPS
```
D:\workspace\git\VIPS\src\main\java\egovframework\config\RestTemplateConfig.java
D:\workspace\git\VIPS\src\main\java\go\kr\project\externalApi\service\ExternalVehicleApiService.java
D:\workspace\git\VIPS\src\main\resources\application.yml
D:\workspace\git\VIPS\build.gradle
```
### VMIS-interface
```
D:\workspace\git\VMIS-interface\src\main\java\com\vmis\interfaceapp\config\VmisProperties.java
D:\workspace\git\VMIS-interface\src\main\java\com\vmis\interfaceapp\client\GovernmentApiClient.java
D:\workspace\git\VMIS-interface\src\main\resources\application.yml
D:\workspace\git\VMIS-interface\lib\libgpkiapi_jni_1.5.jar
D:\workspace\git\VMIS-interface\ddl\vips\*.sql
```
---
## 9. 위험 요소 및 대응 방안
### 9.1 Spring Boot 다운그레이드 실패
**위험**: 일부 Spring Boot 3 전용 기능 사용 시 빌드 실패
**대응**: 코드 리뷰 후 대체 코드 작성
### 9.2 GPKI 암호화 라이브러리 호환성
**위험**: GPKI JNI 라이브러리가 특정 Java 버전에서만 작동
**대응**: 개발환경(gpki.enabled=false)에서 먼저 테스트
### 9.3 HttpClient 버전 차이
**위험**: API 차이로 인한 런타임 에러
**대응**: GovernmentApiClient 전면 재작성 고려
### 9.4 데이터베이스 트랜잭션
**위험**: 기존 VIPS와 새로운 VMIS 로직의 트랜잭션 충돌
**대응**: @Transactional 범위 명확히 설정
---
## 10. 예상 작업 시간
| 단계 | 예상 시간 | 비고 |
|------|----------|------|
| Phase 1: 환경 준비 | 20분 | 디렉토리 생성, 라이브러리 복사 |
| Phase 2: 코드 이식 | 3시간 | 34개 파일 이식 |
| Phase 3: 설정 통합 | 1시간 | YAML, XML 병합 |
| Phase 4: 패키지 변경 | 1시간 | 일괄 변경 |
| Phase 5: 데이터베이스 | 10분 | 연결 테스트 (테이블 기존재) |
| Phase 6: 빌드/테스트 | 2시간 | 디버깅 포함 |
| Phase 7: 전략 패턴 구현 | 1.5시간 | 내부/외부 통신 분기 처리 |
| Phase 8: 기존 코드 정리 | 1시간 | 리팩토링 |
| Phase 9: 문서화 | 1시간 | 문서 작성 |
| **총 예상 시간** | **10시간 50분** | 순수 작업 시간 |
---
## 11. 다음 단계
상세한 마이그레이션 계획은 `VMIS_INTERFACE_MIGRATION_PLAN.md` 파일을 참조하세요.
**작업 진행 방법**:
브랜치는 이미 생성되어 있으므로, `VMIS_INTERFACE_MIGRATION_PLAN.md`의 Phase 1부터 시작하세요.
```bash
cd D:\workspace\git\VIPS
git status # 현재 브랜치 확인
```
---
**문서 버전**: 1.2
**최종 수정**: 2025-11-06
**주요 변경사항**:
- 브랜치 생성 관련 내용 제거 (이미 생성됨)
- 데이터베이스 테이블 생성 제거 (이미 존재)
- Phase 7 추가: 전략 패턴 구현 (내부/외부 통신 분기)

File diff suppressed because it is too large Load Diff

@ -64,7 +64,7 @@ public class VehicleInterfaceController {
mediaType = MediaType.APPLICATION_JSON_VALUE,
examples = @ExampleObject(
name = "통합 조회 예제",
value = "{\"data\": [{\"VHRNO\": \"12가3456\",\"LEVY_STDDE\": \"20250101\",\"INQIRE_SE_CODE\": \"1\",\"VIN\": \"KMHAB812345678901\"}]}"
value = "{\"data\": [{\"VHRNO\": \"12가3456\",\"LEVY_STDDE\": \"20250101\"}]}"
)
)
)
@ -97,8 +97,8 @@ public class VehicleInterfaceController {
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
examples = @ExampleObject(
name = "기본사항조회 예제",
value = "{\"data\": [{\"VHRNO\": \"12가3456\",\"LEVY_STDDE\": \"20250101\",\"INQIRE_SE_CODE\": \"1\",\"VIN\": \"KMHAB812345678901\"}]}"
name = "기본사항조회 예제 (자동차번호)",
value = "{\"data\": [{\"VHRNO\": \"12가3456\",\"LEVY_STDDE\": \"20250101\"}]}"
)
)
)

@ -38,6 +38,15 @@ public class VmisRequestEnricher {
req.setChargerId(sys.getChargerId());
req.setChargerIp(sys.getChargerIp());
req.setChargerNm(sys.getChargerNm());
// 조회구분코드 자동 설정: VHRNO가 있으면 "3" (자동차번호), VIN이 있으면 "2" (차대번호)
if (req.getInqireSeCode() == null) {
if (req.getVhrno() != null && !req.getVhrno().trim().isEmpty()) {
req.setInqireSeCode("3"); // 자동차번호로 조회
} else if (req.getVin() != null && !req.getVin().trim().isEmpty()) {
req.setInqireSeCode("2"); // 차대번호로 조회
}
}
}
log.debug("[ENRICH] basic: applied INFO_SYS_ID={}, INFO_SYS_IP={}, SIGUNGU_CODE={}, CNTC_INFO_CODE={}",
sys.getInfoSysId(), sys.getInfoSysIp(), sys.getSigunguCode(), cntc);

@ -47,7 +47,7 @@ public class BasicRequest {
@JsonProperty("LEVY_STDDE")
private String levyStdde;
@Schema(description = "조회구분코드 {1:열람, 2:발급}")
@Schema(description = "조회구분코드 {2:차대번호(VIN), 3:자동차번호(VHRNO)} (VmisRequestEnricher에서 자동설정)")
@JsonProperty("INQIRE_SE_CODE")
private String inqireSeCode;

@ -1,261 +0,0 @@
# 과태료 대상 비교 규칙 모듈
## 개요
이 모듈은 과태료 대상 차량을 API 응답 데이터와 비교하여 자동으로 분류하는 기능을 제공합니다.
**Chain of Responsibility 패턴**을 사용하여 각 비교 규칙을 독립적으로 관리하고 확장할 수 있습니다.
## 아키텍처
```
comparison/
├── ComparisonRule.java # 비교 규칙 인터페이스
├── ComparisonContext.java # 비교에 필요한 데이터 컨테이너
├── ComparisonResult.java # 비교 결과 객체
├── ComparisonRuleProcessor.java # 규칙 실행 체인 관리자
└── rules/ # 개별 규칙 구현체
├── ProductUseComparisonRule.java # 상품용 규칙
└── TransferComparisonRule.java # 이첩 규칙
```
## 동작 방식
1. **ComparisonRuleProcessor**가 모든 `@Component` 규칙을 자동으로 찾아서 등록
2. 규칙들을 `getOrder()` 순서대로 정렬 (낮을수록 먼저 실행)
3. 각 규칙을 순차적으로 실행
4. 규칙이 `applied=true`를 반환하면 즉시 중단
5. 모든 규칙이 `applied=false`를 반환하면 "정상" 처리
## 새로운 비교 규칙 추가 방법
### 1. 규칙 클래스 생성
`rules` 패키지에 새로운 규칙 클래스를 생성합니다.
```java
package go.kr.project.carInspectionPenalty.registration.comparison.rules;
import go.kr.project.carInspectionPenalty.registration.comparison.*;
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* 내사종결 비교 규칙
*
* 적용 조건:
* - 예시: 차량이 말소된 경우
*
* 처리 내용:
* - TASK_PRCS_STTS_CD = 04 (내사종결)
* - TASK_PRCS_YMD = 현재 날짜
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class InvestigationClosedComparisonRule implements ComparisonRule {
private static final String STATUS_CODE = "04"; // 내사종결
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private final CarFfnlgTrgtMapper mapper;
@Override
public ComparisonResult execute(ComparisonContext context) {
String vhclno = context.getVhclno();
// 1. API 응답 데이터 유효성 검사
if (context.getApiResponse().getBasicInfo() == null ||
context.getApiResponse().getBasicInfo().getRecord() == null ||
context.getApiResponse().getBasicInfo().getRecord().isEmpty()) {
log.debug("[{}] API 응답 데이터가 없어 규칙을 적용할 수 없습니다. 차량번호: {}",
getRuleName(), vhclno);
return ComparisonResult.notApplied();
}
// 2. 필요한 데이터 추출
var basicInfo = context.getApiResponse().getBasicInfo().getRecord().get(0);
String ersrRegistSeCode = basicInfo.getErsrRegistSeCode(); // 말소등록구분코드
// 3. 비교 로직 (말소된 차량인지 확인)
if (ersrRegistSeCode == null || ersrRegistSeCode.isEmpty()) {
log.debug("[{}] 말소되지 않은 차량입니다. 차량번호: {}", getRuleName(), vhclno);
return ComparisonResult.notApplied();
}
log.info("[{}] 내사종결 감지! 차량번호: {}, 말소구분: {}",
getRuleName(), vhclno, ersrRegistSeCode);
// 4. DB 업데이트
CarFfnlgTrgtVO updateData = context.getExistingData();
updateData.setTaskPrcsSttsCd(STATUS_CODE);
updateData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
// 필요시 추가 필드 설정
int updateResult = mapper.update(updateData);
// 5. 결과 반환
if (updateResult > 0) {
log.info("[{}] 처리 완료! 차량번호: {}", getRuleName(), vhclno);
return ComparisonResult.applied(STATUS_CODE, "내사종결로 처리되었습니다.");
} else {
log.error("[{}] 업데이트 실패! 차량번호: {}", getRuleName(), vhclno);
throw new RuntimeException(String.format("내사종결 업데이트 실패: %s", vhclno));
}
}
@Override
public String getRuleName() {
return "내사종결";
}
@Override
public int getOrder() {
return 30; // 상품용(10), 이첩(20) 다음으로 실행
}
}
```
### 2. 자동 등록
Spring이 자동으로 `@Component` 어노테이션이 붙은 클래스를 찾아서 등록합니다.
**별도의 설정 파일 수정이 필요 없습니다!**
### 3. 실행 순서 제어
`getOrder()` 메서드로 실행 순서를 제어할 수 있습니다.
```java
@Override
public int getOrder() {
return 30; // 숫자가 낮을수록 먼저 실행
}
```
**현재 순서:**
- 상품용: 10
- 이첩: 20
- (향후 추가): 30, 40, 50...
## 기존 규칙 예제
### 상품용 규칙 (ProductUseComparisonRule)
```java
// 대표소유자성명에 "상품용" 문자열이 포함되어 있는지 확인
String mberNm = basicInfo.getMberNm();
if (mberNm != null && mberNm.contains("상품용")) {
// 상품용으로 처리
return ComparisonResult.applied("02", "상품용으로 처리되었습니다.");
}
```
### 이첩 규칙 (TransferComparisonRule)
```java
// 법정동코드 앞 4자리와 사용자 조직코드 앞 4자리 비교
String legalDong4 = useStrnghldLegaldongCode.substring(0, 4);
String userOrg4 = userOrgCd.substring(0, 4);
if (!legalDong4.equals(userOrg4)) {
// 이첩으로 처리
return ComparisonResult.applied("03", "이첩으로 처리되었습니다.");
}
```
## API 응답 데이터 활용
`ComparisonContext`를 통해 다음 데이터에 접근할 수 있습니다:
```java
// 기존 과태료 대상 데이터
CarFfnlgTrgtVO existingData = context.getExistingData();
// API 응답 - 기본 정보
var basicInfo = context.getApiResponse().getBasicInfo().getRecord().get(0);
String mberNm = basicInfo.getMberNm(); // 대표소유자성명
String vhrno = basicInfo.getVhrno(); // 차량번호
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode(); // 사용본거지법정동코드
String ersrRegistSeCode = basicInfo.getErsrRegistSeCode(); // 말소등록구분코드
// ... 기타 필드들
// API 응답 - 등록원부
var ledgerInfo = context.getApiResponse().getLedgerInfo();
// 사용자 ID
String userId = context.getUserId();
```
## 의존성 주입
규칙 클래스에서 필요한 빈을 자유롭게 주입받을 수 있습니다.
```java
@Component
@RequiredArgsConstructor
public class MyComparisonRule implements ComparisonRule {
private final CarFfnlgTrgtMapper mapper; // Mapper
private final UserMapper userMapper; // User 정보 필요 시
private final SomeOtherService someService; // 다른 서비스
// ...
}
```
## 로깅
규칙 실행 중 상세한 로그가 자동으로 출력됩니다.
```
[상품용] 규칙 실행 중... 차량번호: 12가3456
[상품용] 상품용 감지! 차량번호: 12가3456, 소유자명: 상품용차량
[상품용] 처리 완료! 차량번호: 12가3456
```
## 테스트 방법
규칙을 독립적으로 테스트할 수 있습니다.
```java
@SpringBootTest
class InvestigationClosedComparisonRuleTest {
@Autowired
private InvestigationClosedComparisonRule rule;
@Test
void 말소된_차량은_내사종결로_처리된다() {
// Given
ComparisonContext context = ComparisonContext.builder()
.existingData(existingData)
.apiResponse(apiResponse)
.userId("USER001")
.build();
// When
ComparisonResult result = rule.execute(context);
// Then
assertTrue(result.isApplied());
assertEquals("04", result.getStatusCode());
}
}
```
## 주의사항
1. **순서 관리**: `getOrder()` 값이 중복되지 않도록 주의
2. **트랜잭션**: 각 규칙 내에서 DB 업데이트 수행 (전체는 Service에서 @Transactional)
3. **예외 처리**: 업데이트 실패 시 RuntimeException 발생 → 전체 롤백
4. **null 체크**: API 응답 데이터는 항상 null 체크 필수
5. **로깅**: DEBUG 레벨로 상세한 로그 남기기
## 문의
추가 비교 규칙이 필요하거나 질문이 있으면 개발팀에 문의하세요.

@ -1,234 +0,0 @@
# 비교 로직 구현 방법 가이드
## 📌 두 가지 구현 방법 제공
현재 프로젝트는 두 가지 비교 로직 구현 방법을 제공합니다.
프로젝트 상황에 맞게 선택하여 사용하세요.
---
## 방법1: 일반 Service/Impl 패턴 ⭐ **추천**
### 특징
- ✅ **간단하고 직관적** - 학습 곡선 낮음
- ✅ **명확한 코드 흐름** - 순차적 실행으로 이해하기 쉬움
- ✅ **빠른 개발** - 메서드 하나만 추가하면 됨
- ✅ **디버깅 쉬움** - 코드 추적이 직관적
### 구조
```
service/
├── ComparisonService.java # 인터페이스
└── impl/
└── ComparisonServiceImpl.java # 구현체
```
### 새로운 비교 조건 추가 방법
#### 1단계: ComparisonServiceImpl에 private 메서드 추가
```java
/**
* 4. 새로운 비교 조건 (예: 장기미검차량)
*
* <p>조건: 검사 유효기간 종료일로부터 1년 이상 경과</p>
* <p>처리: TASK_PRCS_STTS_CD = 05</p>
*/
private String checkLongTermUninspected(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo) {
String vhclno = existingData.getVhclno();
String insptValidPdEndde = basicInfo.getInsptValidPdEndde(); // 검사유효기간종료일자
// 조건 체크 로직
if (insptValidPdEndde == null || insptValidPdEndde.isEmpty()) {
log.debug("[장기미검] 조건 미충족. 차량번호: {}", vhclno);
return null;
}
// 1년 경과 여부 확인 로직
LocalDate endDate = LocalDate.parse(insptValidPdEndde, DATE_FORMATTER);
LocalDate oneYearAgo = LocalDate.now().minusYears(1);
if (endDate.isAfter(oneYearAgo)) {
log.debug("[장기미검] 조건 미충족. 차량번호: {}, 종료일: {}", vhclno, insptValidPdEndde);
return null;
}
log.info("[장기미검] 조건 충족! 차량번호: {}, 종료일: {}", vhclno, insptValidPdEndde);
// DB 업데이트
existingData.setTaskPrcsSttsCd("05"); // 장기미검
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {
throw new RuntimeException(String.format("[장기미검] 업데이트 실패: %s", vhclno));
}
log.info("[장기미검] 처리 완료! 차량번호: {}", vhclno);
return "05";
}
```
#### 2단계: executeComparison() 메서드에 호출 추가
```java
@Override
public String executeComparison(CarFfnlgTrgtVO existingData, VehicleApiResponseVO apiResponse, String userId) {
// ... 기존 코드 ...
// ========== 3. 향후 추가될 비교 로직들 ==========
String longTermResult = checkLongTermUninspected(existingData, basicInfo);
if (longTermResult != null) {
log.info("========== 비교 로직 종료 (장기미검): {} ==========", vhclno);
return longTermResult;
}
// ... 나머지 코드 ...
}
```
#### 끝! 매우 간단합니다.
### 파일 위치
- `service/ComparisonService.java`
- `service/impl/ComparisonServiceImpl.java`
### 실행 순서
1. 상품용 체크 → 2. 이첩 체크 → 3. 새로운 조건들...
- **순서대로 실행**되며, 하나라도 조건이 맞으면 즉시 종료
---
## 방법2: Chain of Responsibility 패턴
### 특징
- ✅ **확장성 우수** - 규칙 추가/삭제가 독립적
- ✅ **테스트 용이** - 각 규칙을 독립적으로 테스트 가능
- ✅ **순서 제어** - getOrder()로 실행 순서 명확히 제어
- ❌ **복잡한 구조** - 학습 곡선 높음
- ❌ **파일 분산** - 여러 파일을 확인해야 함
### 구조
```
comparison/
├── ComparisonRule.java # 규칙 인터페이스
├── ComparisonContext.java # 데이터 컨테이너
├── ComparisonResult.java # 결과 객체
├── ComparisonRuleProcessor.java # 체인 관리자
└── rules/ # 규칙 구현체들
├── ProductUseComparisonRule.java
├── TransferComparisonRule.java
└── ... (새 규칙 파일들)
```
### 새로운 비교 조건 추가 방법
#### 1단계: rules/ 폴더에 새 클래스 생성
```java
@Component
@RequiredArgsConstructor
public class LongTermUninspectedComparisonRule implements ComparisonRule {
@Override
public ComparisonResult execute(ComparisonContext context) {
// 비교 로직 작성
if (/* 조건 충족 */) {
// DB 업데이트
return ComparisonResult.applied("05", "장기미검으로 처리");
}
return ComparisonResult.notApplied();
}
@Override
public String getRuleName() { return "장기미검"; }
@Override
public int getOrder() { return 40; } // 실행 순서
}
```
#### 끝! 자동으로 등록됩니다.
### 파일 위치
- `comparison/` 패키지 전체
---
## 🎯 어떤 방법을 선택해야 할까?
### 방법1 선택 (Service/Impl) - 다음과 같은 경우 추천
- ✅ **간단한 프로젝트** - 비교 조건이 10개 이하
- ✅ **빠른 개발 필요** - 당장 구현해야 할 때
- ✅ **팀원 경험 부족** - 디자인 패턴에 익숙하지 않은 경우
- ✅ **유지보수 단순** - 한 파일에서 모든 로직 확인 가능
### 방법2 선택 (Chain of Responsibility) - 다음과 같은 경우 추천
- ✅ **복잡한 프로젝트** - 비교 조건이 10개 이상
- ✅ **장기 유지보수** - 규칙이 자주 추가/변경될 것으로 예상
- ✅ **팀 규모 큽** - 여러 개발자가 동시에 작업
- ✅ **테스트 중요** - 각 규칙을 독립적으로 테스트해야 함
---
## 💡 현재 설정 변경 방법
`CarFfnlgTrgtServiceImpl.java``executeComparisonLogic()` 메서드에서 주석을 변경하면 됩니다.
```java
private String executeComparisonLogic(...) {
// 방법1 사용하려면: (현재 설정)
return executeWithServicePattern(existingData, apiResponse, userId);
// 방법2 사용하려면:
// return executeWithChainPattern(existingData, apiResponse, userId);
}
```
---
## 📊 비교표
| 항목 | 방법1 (Service/Impl) | 방법2 (Chain of Responsibility) |
|------|---------------------|--------------------------------|
| **구현 난이도** | ⭐ 쉬움 | ⭐⭐⭐ 어려움 |
| **학습 시간** | 10분 | 1시간+ |
| **추가 시간** | 5분 | 15분 |
| **코드 가독성** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| **확장성** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| **테스트 용이성** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| **파일 개수** | 2개 | N+4개 |
| **디버깅** | ⭐⭐⭐⭐⭐ 쉬움 | ⭐⭐⭐ 보통 |
---
## 🚀 빠른 시작
### 방법1로 시작하기 (추천)
1. `ComparisonServiceImpl.java` 열기
2. 기존 메서드 참고하여 새 메서드 추가
3. `executeComparison()`에서 호출
4. 끝!
### 방법2로 시작하기
1. `comparison/README.md` 읽기
2. `rules/` 폴더에 새 규칙 클래스 생성
3. `@Component` 붙이기
4. 끝!
---
## ⚠️ 주의사항
1. **두 방법을 동시에 사용하지 마세요** - 하나만 선택
2. **트랜잭션 관리** - 각 비교 메서드는 DB 업데이트를 직접 수행
3. **예외 처리** - 업데이트 실패 시 RuntimeException 발생 → 전체 롤백
4. **로깅** - 각 조건의 충족/미충족 여부를 명확히 로깅
---
## 📞 문의
추가 질문이나 제안사항은 개발팀에 문의하세요.

@ -959,7 +959,7 @@ public class CarFfnlgTrgtServiceImpl implements CarFfnlgTrgtService {
BasicRequest apiRequest = new BasicRequest();
apiRequest.setVhrno(vhclno);
apiRequest.setLevyStdde(inspYmd != null ? inspYmd.replace("-", "") : "");
apiRequest.setInqireSeCode("01"); // 조회구분코드 기본값
// INQIRE_SE_CODE는 VmisRequestEnricher에서 자동 설정됨 (VHRNO 있으면 "3")
VehicleApiResponseVO apiResponse = vehicleInfoService.getVehicleInfo(apiRequest);

@ -1,263 +0,0 @@
# 이첩 조건 추가 방법 (OR 구조)
## 📌 현재 구조
이첩 비교 로직은 **OR 구조**로 되어 있습니다.
- **여러 조건 중 하나라도 만족하면** 이첩으로 처리
- 조건을 순차적으로 체크하다가 **하나라도 true가 나오면 즉시 이첩 처리**
---
## 🎯 새로운 이첩 조건 추가하기
### 방법1: Service/Impl 패턴 (간단) ⭐ 추천
`ComparisonServiceImpl.java` 파일 수정
#### 1단계: 조건 체크 메서드 추가
```java
/**
* 이첩 조건2: 차량 소유자 주소 불일치 (예시)
* 조건: 차량 등록지와 소유자 실거주지가 다른 경우
*/
private boolean checkTransferCondition2_AddressMismatch(BasicResponse.Record basicInfo, String userId, String vhclno) {
String useStrnghldAdresNm = basicInfo.getUseStrnghldAdresNm(); // 사용본거지주소명
String ownerAdresNm = basicInfo.getOwnerAdresNm(); // 소유자주소명
// 주소 유효성 검사
if (useStrnghldAdresNm == null || ownerAdresNm == null) {
log.debug("[이첩][조건2] 주소 정보 없음. 차량번호: {}", vhclno);
return false;
}
// 주소 일치 여부 확인 (시/도 단위 비교 등)
if (useStrnghldAdresNm.equals(ownerAdresNm)) {
log.debug("[이첩][조건2] 주소 일치. 차량번호: {}", vhclno);
return false;
}
log.info("[이첩][조건2] 주소 불일치! 차량번호: {}, 사용본거지: {}, 소유자주소: {}",
vhclno, useStrnghldAdresNm, ownerAdresNm);
return true;
}
```
#### 2단계: checkTransfer() 메서드에 조건 추가
```java
private String checkTransfer(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo, String userId) {
String vhclno = existingData.getVhclno();
// ========== 이첩 조건들 (OR 로직: 하나라도 만족하면 이첩) ==========
// 조건1: 법정동코드 불일치
if (checkTransferCondition1_LegalDongMismatch(basicInfo, userId, vhclno)) {
return processTransfer(existingData, basicInfo, vhclno, "법정동코드 불일치");
}
// 조건2: 주소 불일치 (새로 추가!)
if (checkTransferCondition2_AddressMismatch(basicInfo, userId, vhclno)) {
return processTransfer(existingData, basicInfo, vhclno, "주소 불일치");
}
// 조건3: 향후 추가될 조건들...
// if (checkTransferCondition3_XXX(basicInfo, userId, vhclno)) {
// return processTransfer(existingData, basicInfo, vhclno, "XXX");
// }
return null;
}
```
#### 끝! 매우 간단합니다.
---
### 방법2: Chain of Responsibility 패턴
`TransferComparisonRule.java` 파일 수정
#### 1단계: 조건 체크 메서드 추가
```java
/**
* 조건2: 주소 불일치 체크
*/
private boolean checkAddressMismatch(ComparisonContext context,
go.kr.project.api.model.response.BasicResponse.Record basicInfo,
String vhclno) {
String useStrnghldAdresNm = basicInfo.getUseStrnghldAdresNm();
String ownerAdresNm = basicInfo.getOwnerAdresNm();
if (useStrnghldAdresNm == null || ownerAdresNm == null) {
log.debug("[{}][조건2] 주소 정보 없음. 차량번호: {}", getRuleName(), vhclno);
return false;
}
if (useStrnghldAdresNm.equals(ownerAdresNm)) {
log.debug("[{}][조건2] 주소 일치. 차량번호: {}", getRuleName(), vhclno);
return false;
}
log.info("[{}][조건2] 주소 불일치! 차량번호: {}", getRuleName(), vhclno);
return true;
}
```
#### 2단계: execute() 메서드에 조건 추가
```java
@Override
public ComparisonResult execute(ComparisonContext context) {
String vhclno = context.getVhclno();
// ... API 유효성 검사 ...
var basicInfo = context.getApiResponse().getBasicInfo().getRecord().get(0);
// ========== 이첩 조건들 (OR 로직) ==========
// 조건1: 법정동코드 불일치
if (checkLegalDongCodeMismatch(context, basicInfo, vhclno)) {
return processTransfer(context, basicInfo, vhclno, "법정동코드 불일치");
}
// 조건2: 주소 불일치 (새로 추가!)
if (checkAddressMismatch(context, basicInfo, vhclno)) {
return processTransfer(context, basicInfo, vhclno, "주소 불일치");
}
return ComparisonResult.notApplied();
}
```
---
## 📊 현재 이첩 조건 목록
### 조건1: 법정동코드 불일치 ✅ 구현됨
- **체크 항목**: 사용본거지법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리
- **이첩 기준**: 두 값이 다르면 이첩
### 조건2: (예시) 주소 불일치
- **체크 항목**: 사용본거지주소 vs 소유자주소
- **이첩 기준**: 주소가 다르면 이첩
### 조건3: (예시) 기타 조건들...
- 필요에 따라 추가
---
## 💡 동작 방식
```java
// 이첩 체크 시작
checkTransfer(...)
조건1 체크 → TRUE → 즉시 이첩 처리 후 return "03"
↓ FALSE
조건2 체크 → TRUE → 즉시 이첩 처리 후 return "03"
↓ FALSE
조건3 체크 → TRUE → 즉시 이첩 처리 후 return "03"
↓ FALSE
모든 조건 FALSE → return null (이첩 아님)
```
---
## 🔍 로그 예시
### 조건1에 걸린 경우
```
[이첩][조건1] 법정동코드 불일치! 차량번호: 12가3456, 법정동: 1100, 조직: 4100
[이첩] 조건 충족! 차량번호: 12가3456, 사유: 법정동코드 불일치
[이첩] 처리 완료! 차량번호: 12가3456, 시군구: 서울특별시(11000), 사유: 법정동코드 불일치
```
### 조건2에 걸린 경우
```
[이첩][조건1] 법정동코드 일치. 차량번호: 12가3456, 법정동: 1100, 조직: 1100
[이첩][조건2] 주소 불일치! 차량번호: 12가3456
[이첩] 조건 충족! 차량번호: 12가3456, 사유: 주소 불일치
[이첩] 처리 완료! 차량번호: 12가3456, 시군구: 서울특별시(11000), 사유: 주소 불일치
```
### 모든 조건에 안 걸린 경우
```
[이첩][조건1] 법정동코드 일치. 차량번호: 12가3456
[이첩][조건2] 주소 일치. 차량번호: 12가3456
[이첩] 모든 조건 미충족. 차량번호: 12가3456
```
---
## ⚠️ 주의사항
1. **조건 순서**: 위에서부터 순차적으로 체크됩니다
- 자주 걸리는 조건을 위쪽에 배치하면 성능 향상
2. **조건 메서드 네이밍**: `checkTransferConditionN_XXX` 형식 권장
- 예: `checkTransferCondition1_LegalDongMismatch`
- 예: `checkTransferCondition2_AddressMismatch`
3. **return 값**:
- `true`: 이 조건에 **해당함** → 이첩 처리
- `false`: 이 조건에 **해당 안함** → 다음 조건 체크
4. **processTransfer() 공통 사용**:
- DB 업데이트는 `processTransfer()` 메서드에서 공통으로 처리
- 각 조건에서는 `true/false`만 반환
---
## 📝 체크리스트
새로운 이첩 조건 추가 시 확인사항:
- [ ] 조건 메서드 작성 완료
- [ ] checkTransfer()에 조건 추가 완료
- [ ] 로그 메시지에 조건 번호 포함 (`[조건N]`)
- [ ] null 체크 처리 완료
- [ ] 컴파일 테스트 완료
- [ ] 실제 데이터로 테스트 완료
---
## 🚀 빠른 템플릿
```java
/**
* 이첩 조건N: XXX
* 조건 설명
*/
private boolean checkTransferConditionN_XXX(BasicResponse.Record basicInfo, String userId, String vhclno) {
// 1. 데이터 추출
String data1 = basicInfo.getXXX();
String data2 = basicInfo.getYYY();
// 2. 유효성 검사
if (data1 == null || data2 == null) {
log.debug("[이첩][조건N] 데이터 없음. 차량번호: {}", vhclno);
return false;
}
// 3. 조건 체크
if (조건_만족) {
log.info("[이첩][조건N] 조건 충족! 차량번호: {}, 상세정보...", vhclno);
return true; // 이첩!
}
log.debug("[이첩][조건N] 조건 미충족. 차량번호: {}", vhclno);
return false;
}
// checkTransfer()에 추가
if (checkTransferConditionN_XXX(basicInfo, userId, vhclno)) {
return processTransfer(existingData, basicInfo, vhclno, "XXX");
}
```
---
끝!

@ -0,0 +1,788 @@
# 과태료 대상 비교 로직 구현 가이드
## 📌 목차
1. [개요](#개요)
2. [두 가지 구현 방법 비교](#두-가지-구현-방법-비교)
3. [방법1: Service/Impl 패턴 (추천)](#방법1-serviceimpl-패턴-추천)
4. [방법2: Chain of Responsibility 패턴](#방법2-chain-of-responsibility-패턴)
5. [어떤 방법을 선택해야 할까?](#어떤-방법을-선택해야-할까)
6. [참고사항](#참고사항)
---
## 개요
이 모듈은 과태료 대상 차량을 API 응답 데이터와 비교하여 자동으로 분류하는 기능을 제공합니다.
현재 프로젝트는 **두 가지 비교 로직 구현 방법**을 제공하며, 프로젝트 상황에 맞게 선택하여 사용할 수 있습니다.
### 비교 로직 처리 흐름
```
API 응답 데이터 수신
비교 로직 실행
조건1 (상품용) 체크 → 충족 → TASK_PRCS_STTS_CD = 02 → 종료
↓ 미충족
조건2 (이첩) 체크 → 충족 → TASK_PRCS_STTS_CD = 03 → 종료
↓ 미충족
조건3 (내사종결) 체크 → 충족 → TASK_PRCS_STTS_CD = 04 → 종료
↓ 미충족
모든 조건 미충족 → 정상 처리 (null 반환)
```
---
## 두 가지 구현 방법 비교
### 비교표
| 항목 | 방법1 (Service/Impl) ⭐ | 방법2 (Chain of Responsibility) |
|------|---------------------|--------------------------------|
| **구현 난이도** | ⭐ 쉬움 | ⭐⭐⭐ 어려움 |
| **학습 시간** | 10분 | 1시간+ |
| **조건 추가 시간** | 5분 | 15분 |
| **코드 가독성** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| **확장성** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| **테스트 용이성** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| **파일 개수** | 2개 | N+4개 |
| **디버깅** | ⭐⭐⭐⭐⭐ 쉬움 | ⭐⭐⭐ 보통 |
| **추천 조건 개수** | ~10개 | 10개 이상 |
---
## 방법1: Service/Impl 패턴 (추천)
### 🌟 특징
- ✅ **간단하고 직관적** - 학습 곡선 낮음
- ✅ **명확한 코드 흐름** - 순차적 실행으로 이해하기 쉬움
- ✅ **빠른 개발** - 메서드 하나만 추가하면 됨
- ✅ **디버깅 쉬움** - 코드 추적이 직관적
- ✅ **한 파일에서 관리** - 모든 비교 로직을 한눈에 확인
### 📁 디렉토리 구조
```
service/
├── ComparisonService.java # 인터페이스
└── impl/
└── ComparisonServiceImpl.java # 구현체 (모든 비교 로직 포함)
```
### 🚀 새로운 비교 조건 추가 방법
#### 예제 1: 일반 비교 조건 추가
**1단계: ComparisonServiceImpl에 private 메서드 추가**
```java
/**
* 4. 장기미검차량 체크
*
* <p>조건: 검사 유효기간 종료일로부터 1년 이상 경과</p>
* <p>처리: TASK_PRCS_STTS_CD = 05</p>
*
* @return 05 (적용됨) 또는 null (미적용)
*/
private String checkLongTermUninspected(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo) {
String vhclno = existingData.getVhclno();
String insptValidPdEndde = basicInfo.getInsptValidPdEndde(); // 검사유효기간종료일자
// 조건 체크 로직
if (insptValidPdEndde == null || insptValidPdEndde.isEmpty()) {
log.debug("[장기미검] 조건 미충족. 차량번호: {}", vhclno);
return null;
}
// 1년 경과 여부 확인 로직
LocalDate endDate = LocalDate.parse(insptValidPdEndde, DATE_FORMATTER);
LocalDate oneYearAgo = LocalDate.now().minusYears(1);
if (endDate.isAfter(oneYearAgo)) {
log.debug("[장기미검] 조건 미충족. 차량번호: {}, 종료일: {}", vhclno, insptValidPdEndde);
return null;
}
log.info("[장기미검] 조건 충족! 차량번호: {}, 종료일: {}", vhclno, insptValidPdEndde);
// DB 업데이트
existingData.setTaskPrcsSttsCd("05"); // 장기미검
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {
throw new RuntimeException(String.format("[장기미검] 업데이트 실패: %s", vhclno));
}
log.info("[장기미검] 처리 완료! 차량번호: {}", vhclno);
return "05";
}
```
**2단계: executeComparison() 메서드에 호출 추가**
```java
@Override
public String executeComparison(CarFfnlgTrgtVO existingData, VehicleApiResponseVO apiResponse, String userId) {
String vhclno = existingData.getVhclno();
log.info("========== 비교 로직 시작: {} ==========", vhclno);
// API 응답 데이터 유효성 검사
if (!isValidApiResponse(apiResponse)) {
log.warn("API 응답 데이터가 유효하지 않습니다. 차량번호: {}", vhclno);
return null;
}
BasicResponse.Record basicInfo = apiResponse.getBasicInfo().getRecord().get(0);
// ========== 1. 상품용 체크 ==========
String productUseResult = checkProductUse(existingData, basicInfo);
if (productUseResult != null) {
log.info("========== 비교 로직 종료 (상품용): {} ==========", vhclno);
return productUseResult;
}
// ========== 2. 이첩 체크 ==========
String transferResult = checkTransfer(existingData, basicInfo, userId);
if (transferResult != null) {
log.info("========== 비교 로직 종료 (이첩): {} ==========", vhclno);
return transferResult;
}
// ========== 3. 장기미검 체크 (새로 추가!) ==========
String longTermResult = checkLongTermUninspected(existingData, basicInfo);
if (longTermResult != null) {
log.info("========== 비교 로직 종료 (장기미검): {} ==========", vhclno);
return longTermResult;
}
// 모든 비교 로직에 해당하지 않음
log.info("========== 비교 로직 종료 (정상): {} ==========", vhclno);
return null;
}
```
**끝! 매우 간단합니다.**
---
#### 예제 2: 이첩 조건 추가 (OR 구조)
이첩 조건은 **OR 로직**으로 구성되어 있습니다. **여러 조건 중 하나라도 만족하면** 이첩으로 처리됩니다.
**현재 이첩 구조:**
```java
private String checkTransfer(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo, String userId) {
String vhclno = existingData.getVhclno();
// ========== 이첩 조건들 (OR 로직: 하나라도 만족하면 이첩) ==========
// 조건1: 법정동코드 불일치
if (checkTransferCondition1_LegalDongMismatch(basicInfo, userId, vhclno)) {
return processTransfer(existingData, basicInfo, vhclno, "법정동코드 불일치");
}
// 조건2: 향후 추가될 이첩 조건들...
// if (checkTransferCondition2_XXX(basicInfo, userId, vhclno)) {
// return processTransfer(existingData, basicInfo, vhclno, "XXX");
// }
// 모든 이첩 조건에 해당하지 않음
log.debug("[이첩] 모든 조건 미충족. 차량번호: {}", vhclno);
return null;
}
```
**새로운 이첩 조건 추가:**
**1단계: 조건 체크 메서드 추가**
```java
/**
* 이첩 조건2: 차량 소유자 주소 불일치 (예시)
* 조건: 차량 등록지와 소유자 실거주지가 다른 경우
*/
private boolean checkTransferCondition2_AddressMismatch(BasicResponse.Record basicInfo, String userId, String vhclno) {
String useStrnghldAdresNm = basicInfo.getUseStrnghldAdresNm(); // 사용본거지주소명
String ownerAdresNm = basicInfo.getOwnerAdresNm(); // 소유자주소명
// 주소 유효성 검사
if (useStrnghldAdresNm == null || ownerAdresNm == null) {
log.debug("[이첩][조건2] 주소 정보 없음. 차량번호: {}", vhclno);
return false;
}
// 주소 일치 여부 확인 (시/도 단위 비교 등)
if (useStrnghldAdresNm.equals(ownerAdresNm)) {
log.debug("[이첩][조건2] 주소 일치. 차량번호: {}", vhclno);
return false;
}
log.info("[이첩][조건2] 주소 불일치! 차량번호: {}, 사용본거지: {}, 소유자주소: {}",
vhclno, useStrnghldAdresNm, ownerAdresNm);
return true;
}
```
**2단계: checkTransfer() 메서드에 조건 추가**
```java
private String checkTransfer(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo, String userId) {
String vhclno = existingData.getVhclno();
// ========== 이첩 조건들 (OR 로직: 하나라도 만족하면 이첩) ==========
// 조건1: 법정동코드 불일치
if (checkTransferCondition1_LegalDongMismatch(basicInfo, userId, vhclno)) {
return processTransfer(existingData, basicInfo, vhclno, "법정동코드 불일치");
}
// 조건2: 주소 불일치 (새로 추가!)
if (checkTransferCondition2_AddressMismatch(basicInfo, userId, vhclno)) {
return processTransfer(existingData, basicInfo, vhclno, "주소 불일치");
}
// 조건3: 향후 추가될 조건들...
// if (checkTransferCondition3_XXX(basicInfo, userId, vhclno)) {
// return processTransfer(existingData, basicInfo, vhclno, "XXX");
// }
return null;
}
```
**끝! 매우 간단합니다.**
---
### 📊 이첩 조건 OR 로직 동작 방식
```java
// 이첩 체크 시작
checkTransfer(...)
조건1 체크 → TRUE → 즉시 이첩 처리 후 return "03"
↓ FALSE
조건2 체크 → TRUE → 즉시 이첩 처리 후 return "03"
↓ FALSE
조건3 체크 → TRUE → 즉시 이첩 처리 후 return "03"
↓ FALSE
모든 조건 FALSE → return null (이첩 아님)
```
### 🔍 로그 예시
#### 조건1에 걸린 경우
```
[이첩][조건1] 법정동코드 불일치! 차량번호: 12가3456, 법정동: 1100, 조직: 4100
[이첩] 조건 충족! 차량번호: 12가3456, 사유: 법정동코드 불일치
[이첩] 처리 완료! 차량번호: 12가3456, 시군구: 서울특별시(11000), 사유: 법정동코드 불일치
```
#### 조건2에 걸린 경우
```
[이첩][조건1] 법정동코드 일치. 차량번호: 12가3456, 법정동: 1100, 조직: 1100
[이첩][조건2] 주소 불일치! 차량번호: 12가3456
[이첩] 조건 충족! 차량번호: 12가3456, 사유: 주소 불일치
[이첩] 처리 완료! 차량번호: 12가3456, 시군구: 서울특별시(11000), 사유: 주소 불일치
```
#### 모든 조건에 안 걸린 경우
```
[이첩][조건1] 법정동코드 일치. 차량번호: 12가3456
[이첩][조건2] 주소 일치. 차량번호: 12가3456
[이첩] 모든 조건 미충족. 차량번호: 12가3456
```
---
### 🚀 빠른 템플릿
#### 일반 비교 조건 템플릿
```java
/**
* N. 비교 조건명
*
* <p>조건: 조건 설명</p>
* <p>처리: TASK_PRCS_STTS_CD = XX</p>
*
* @return XX (적용됨) 또는 null (미적용)
*/
private String checkXXX(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo) {
String vhclno = existingData.getVhclno();
// 1. 데이터 추출
String data1 = basicInfo.getXXX();
String data2 = basicInfo.getYYY();
// 2. 유효성 검사
if (data1 == null || data2 == null) {
log.debug("[비교조건명] 조건 미충족. 차량번호: {}", vhclno);
return null;
}
// 3. 조건 체크
if (조건_만족) {
log.info("[비교조건명] 조건 충족! 차량번호: {}, 상세정보...", vhclno);
// 4. DB 업데이트
existingData.setTaskPrcsSttsCd("XX");
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
// 필요한 필드 추가 설정
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {
throw new RuntimeException(String.format("[비교조건명] 업데이트 실패: %s", vhclno));
}
log.info("[비교조건명] 처리 완료! 차량번호: {}", vhclno);
return "XX";
}
log.debug("[비교조건명] 조건 미충족. 차량번호: {}", vhclno);
return null;
}
```
#### 이첩 조건 템플릿
```java
/**
* 이첩 조건N: XXX
* 조건 설명
*/
private boolean checkTransferConditionN_XXX(BasicResponse.Record basicInfo, String userId, String vhclno) {
// 1. 데이터 추출
String data1 = basicInfo.getXXX();
String data2 = basicInfo.getYYY();
// 2. 유효성 검사
if (data1 == null || data2 == null) {
log.debug("[이첩][조건N] 데이터 없음. 차량번호: {}", vhclno);
return false;
}
// 3. 조건 체크
if (조건_만족) {
log.info("[이첩][조건N] 조건 충족! 차량번호: {}, 상세정보...", vhclno);
return true; // 이첩!
}
log.debug("[이첩][조건N] 조건 미충족. 차량번호: {}", vhclno);
return false;
}
// checkTransfer()에 추가
if (checkTransferConditionN_XXX(basicInfo, userId, vhclno)) {
return processTransfer(existingData, basicInfo, vhclno, "XXX");
}
```
---
### ⚠️ 주의사항
1. **조건 순서**: 위에서부터 순차적으로 체크됩니다
- 자주 걸리는 조건을 위쪽에 배치하면 성능 향상
2. **조건 메서드 네이밍**:
- 일반: `checkXXX`
- 이첩: `checkTransferConditionN_XXX` 형식 권장
3. **return 값**:
- 일반 조건: `"상태코드"` (적용됨) 또는 `null` (미적용)
- 이첩 조건: `true` (해당함) 또는 `false` (해당 안함)
4. **processTransfer() 공통 사용**:
- DB 업데이트는 `processTransfer()` 메서드에서 공통으로 처리
- 각 조건에서는 `true/false`만 반환
---
### 📝 체크리스트
새로운 비교 조건 추가 시 확인사항:
- [ ] 조건 메서드 작성 완료
- [ ] executeComparison()에 조건 추가 완료 (일반 조건)
- [ ] checkTransfer()에 조건 추가 완료 (이첩 조건)
- [ ] 로그 메시지에 조건 정보 포함
- [ ] null 체크 처리 완료
- [ ] 컴파일 테스트 완료
- [ ] 실제 데이터로 테스트 완료
---
## 방법2: Chain of Responsibility 패턴
### 🌟 특징
- ✅ **확장성 우수** - 규칙 추가/삭제가 독립적
- ✅ **테스트 용이** - 각 규칙을 독립적으로 테스트 가능
- ✅ **순서 제어** - getOrder()로 실행 순서 명확히 제어
- ✅ **관심사 분리** - 각 규칙이 독립된 파일로 관리
- ❌ **복잡한 구조** - 학습 곡선 높음
- ❌ **파일 분산** - 여러 파일을 확인해야 함
### 📁 디렉토리 구조
```
comparison/
├── ComparisonRule.java # 비교 규칙 인터페이스
├── ComparisonContext.java # 비교에 필요한 데이터 컨테이너
├── ComparisonResult.java # 비교 결과 객체
├── ComparisonRuleProcessor.java # 규칙 실행 체인 관리자
└── rules/ # 개별 규칙 구현체
├── ProductUseComparisonRule.java # 상품용 규칙
└── TransferComparisonRule.java # 이첩 규칙
```
### 🔄 동작 방식
1. **ComparisonRuleProcessor**가 모든 `@Component` 규칙을 자동으로 찾아서 등록
2. 규칙들을 `getOrder()` 순서대로 정렬 (낮을수록 먼저 실행)
3. 각 규칙을 순차적으로 실행
4. 규칙이 `applied=true`를 반환하면 즉시 중단
5. 모든 규칙이 `applied=false`를 반환하면 "정상" 처리
```
ComparisonRuleProcessor
규칙 자동 스캔 (@Component)
순서대로 정렬 (getOrder())
ProductUseComparisonRule (order=10) → 적용됨? → 종료
↓ 미적용
TransferComparisonRule (order=20) → 적용됨? → 종료
↓ 미적용
YourNewRule (order=30) → 적용됨? → 종료
↓ 미적용
정상 처리
```
---
### 🚀 새로운 비교 규칙 추가 방법
#### 1단계: rules/ 폴더에 새 클래스 생성
```java
package go.kr.project.carInspectionPenalty.registration.comparison.rules;
import go.kr.project.carInspectionPenalty.registration.comparison.*;
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* 내사종결 비교 규칙
*
* 적용 조건:
* - 예시: 차량이 말소된 경우
*
* 처리 내용:
* - TASK_PRCS_STTS_CD = 04 (내사종결)
* - TASK_PRCS_YMD = 현재 날짜
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class InvestigationClosedComparisonRule implements ComparisonRule {
private static final String STATUS_CODE = "04"; // 내사종결
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private final CarFfnlgTrgtMapper mapper;
@Override
public ComparisonResult execute(ComparisonContext context) {
String vhclno = context.getVhclno();
// 1. API 응답 데이터 유효성 검사
if (context.getApiResponse().getBasicInfo() == null ||
context.getApiResponse().getBasicInfo().getRecord() == null ||
context.getApiResponse().getBasicInfo().getRecord().isEmpty()) {
log.debug("[{}] API 응답 데이터가 없어 규칙을 적용할 수 없습니다. 차량번호: {}",
getRuleName(), vhclno);
return ComparisonResult.notApplied();
}
// 2. 필요한 데이터 추출
go.kr.project.api.model.response.BasicResponse.Record basicInfo =
context.getApiResponse().getBasicInfo().getRecord().get(0);
String ersrRegistSeCode = basicInfo.getErsrRegistSeCode(); // 말소등록구분코드
// 3. 비교 로직 (말소된 차량인지 확인)
if (ersrRegistSeCode == null || ersrRegistSeCode.isEmpty()) {
log.debug("[{}] 말소되지 않은 차량입니다. 차량번호: {}", getRuleName(), vhclno);
return ComparisonResult.notApplied();
}
log.info("[{}] 내사종결 감지! 차량번호: {}, 말소구분: {}",
getRuleName(), vhclno, ersrRegistSeCode);
// 4. DB 업데이트
CarFfnlgTrgtVO updateData = context.getExistingData();
updateData.setTaskPrcsSttsCd(STATUS_CODE);
updateData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
// 필요시 추가 필드 설정
int updateResult = mapper.update(updateData);
// 5. 결과 반환
if (updateResult > 0) {
log.info("[{}] 처리 완료! 차량번호: {}", getRuleName(), vhclno);
return ComparisonResult.applied(STATUS_CODE, "내사종결로 처리되었습니다.");
} else {
log.error("[{}] 업데이트 실패! 차량번호: {}", getRuleName(), vhclno);
throw new RuntimeException(String.format("내사종결 업데이트 실패: %s", vhclno));
}
}
@Override
public String getRuleName() {
return "내사종결";
}
@Override
public int getOrder() {
return 30; // 상품용(10), 이첩(20) 다음으로 실행
}
}
```
#### 2단계: 자동 등록
Spring이 자동으로 `@Component` 어노테이션이 붙은 클래스를 찾아서 등록합니다.
**별도의 설정 파일 수정이 필요 없습니다!**
#### 끝! 자동으로 등록됩니다.
---
### 📊 실행 순서 제어
`getOrder()` 메서드로 실행 순서를 제어할 수 있습니다.
```java
@Override
public int getOrder() {
return 30; // 숫자가 낮을수록 먼저 실행
}
```
**현재 순서:**
- 상품용: 10
- 이첩: 20
- (향후 추가): 30, 40, 50...
---
### 📦 API 응답 데이터 활용
`ComparisonContext`를 통해 다음 데이터에 접근할 수 있습니다:
```java
// 기존 과태료 대상 데이터
CarFfnlgTrgtVO existingData = context.getExistingData();
// API 응답 - 기본 정보
var basicInfo = context.getApiResponse().getBasicInfo().getRecord().get(0);
String mberNm = basicInfo.getMberNm(); // 대표소유자성명
String vhrno = basicInfo.getVhrno(); // 차량번호
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode(); // 사용본거지법정동코드
String ersrRegistSeCode = basicInfo.getErsrRegistSeCode(); // 말소등록구분코드
// ... 기타 필드들
// API 응답 - 등록원부
var ledgerInfo = context.getApiResponse().getLedgerInfo();
// 사용자 ID
String userId = context.getUserId();
```
---
### 💉 의존성 주입
규칙 클래스에서 필요한 빈을 자유롭게 주입받을 수 있습니다.
```java
@Component
@RequiredArgsConstructor
public class MyComparisonRule implements ComparisonRule {
private final CarFfnlgTrgtMapper mapper; // Mapper
private final UserMapper userMapper; // User 정보 필요 시
private final SomeOtherService someService; // 다른 서비스
// ...
}
```
---
### 📝 로깅
규칙 실행 중 상세한 로그가 자동으로 출력됩니다.
```
[상품용] 규칙 실행 중... 차량번호: 12가3456
[상품용] 상품용 감지! 차량번호: 12가3456, 소유자명: 상품용차량
[상품용] 처리 완료! 차량번호: 12가3456
```
---
### 🧪 테스트 방법
규칙을 독립적으로 테스트할 수 있습니다.
```java
@SpringBootTest
class InvestigationClosedComparisonRuleTest {
@Autowired
private InvestigationClosedComparisonRule rule;
@Test
void 말소된_차량은_내사종결로_처리된다() {
// Given
ComparisonContext context = ComparisonContext.builder()
.existingData(existingData)
.apiResponse(apiResponse)
.userId("USER001")
.build();
// When
ComparisonResult result = rule.execute(context);
// Then
assertTrue(result.isApplied());
assertEquals("04", result.getStatusCode());
}
}
```
---
### 📚 기존 규칙 예제
#### 상품용 규칙 (ProductUseComparisonRule)
```java
// 대표소유자성명에 "상품용" 문자열이 포함되어 있는지 확인
String mberNm = basicInfo.getMberNm();
if (mberNm != null && mberNm.contains("상품용")) {
// 상품용으로 처리
return ComparisonResult.applied("02", "상품용으로 처리되었습니다.");
}
```
#### 이첩 규칙 (TransferComparisonRule)
```java
// 법정동코드 앞 4자리와 사용자 조직코드 앞 4자리 비교
String legalDong4 = useStrnghldLegaldongCode.substring(0, 4);
String userOrg4 = userOrgCd.substring(0, 4);
if (!legalDong4.equals(userOrg4)) {
// 이첩으로 처리
return ComparisonResult.applied("03", "이첩으로 처리되었습니다.");
}
```
---
## 어떤 방법을 선택해야 할까?
### 방법1 선택 (Service/Impl) - 다음과 같은 경우 추천 ⭐
- ✅ **간단한 프로젝트** - 비교 조건이 10개 이하
- ✅ **빠른 개발 필요** - 당장 구현해야 할 때
- ✅ **팀원 경험 부족** - 디자인 패턴에 익숙하지 않은 경우
- ✅ **유지보수 단순** - 한 파일에서 모든 로직 확인 가능
- ✅ **작은 팀** - 1~3명 정도의 소규모 팀
### 방법2 선택 (Chain of Responsibility) - 다음과 같은 경우 추천
- ✅ **복잡한 프로젝트** - 비교 조건이 10개 이상
- ✅ **장기 유지보수** - 규칙이 자주 추가/변경될 것으로 예상
- ✅ **팀 규모 큽** - 여러 개발자가 동시에 작업
- ✅ **테스트 중요** - 각 규칙을 독립적으로 테스트해야 함
- ✅ **확장성 중시** - 규칙을 동적으로 추가/제거 필요
---
## 참고사항
### 💡 현재 설정 변경 방법
`CarFfnlgTrgtServiceImpl.java``executeComparisonLogic()` 메서드에서 주석을 변경하면 됩니다.
```java
private String executeComparisonLogic(...) {
// 방법1 사용하려면: (현재 설정)
return executeWithServicePattern(existingData, apiResponse, userId);
// 방법2 사용하려면:
// return executeWithChainPattern(existingData, apiResponse, userId);
}
```
---
### ⚠️ 주의사항
1. **두 방법을 동시에 사용하지 마세요** - 하나만 선택
2. **트랜잭션 관리** - 각 비교 메서드는 DB 업데이트를 직접 수행
3. **예외 처리** - 업데이트 실패 시 RuntimeException 발생 → 전체 롤백
4. **로깅** - 각 조건의 충족/미충족 여부를 명확히 로깅
5. **순서 관리** (방법2) - `getOrder()` 값이 중복되지 않도록 주의
6. **null 체크** - API 응답 데이터는 항상 null 체크 필수
---
### 🚀 빠른 시작
#### 방법1로 시작하기 (추천)
1. `ComparisonServiceImpl.java` 열기
2. 기존 메서드 참고하여 새 메서드 추가
3. `executeComparison()`에서 호출
4. 끝!
#### 방법2로 시작하기
1. `comparison/rules/` 폴더에 새 규칙 클래스 생성
2. `@Component` 붙이기
3. `ComparisonRule` 인터페이스 구현
4. 끝!
---
### 📞 문의
추가 질문이나 제안사항은 개발팀에 문의하세요.
---
### 📖 관련 파일
- **방법1 파일**:
- `service/ComparisonService.java`
- `service/impl/ComparisonServiceImpl.java`
- **방법2 파일**:
- `comparison/` 패키지 전체
- **통합 지점**:
- `CarFfnlgTrgtServiceImpl.java` - `executeComparisonLogic()` 메서드

@ -0,0 +1,38 @@
{
"vhrno": "11가1111",
"success": true,
"message": "조회 성공",
"basicInfo": {
"CNTC_RESULT_CODE": "MSG50000",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
"VHRNO": "11가1111",
"MBER_NM": "김철수",
"MBER_SE_CODE": "1",
"MBER_SE_NO": "8001011234567",
"USE_STRNGHLD_LEGALDONG_CODE": "1100000000",
"USE_STRNGHLD_ADRES_NM": "서울특별시 강남구 테헤란로 123",
"OWNER_ADRES_NM": "서울특별시 강남구 테헤란로 123",
"CNM": "그랜저",
"VHCTY_ASORT_NM": "승용차",
"VHCTY_TY_NM": "중형",
"VHCTY_SE_NM": "일반형",
"PRYE": "2022",
"REGIST_DE": "20220315",
"FRST_REGIST_DE": "20220315",
"INSPT_VALID_PD_BGNDE": "20240315",
"INSPT_VALID_PD_ENDDE": "20260315",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_DE": "",
"OWNER_LEGALDONG_CODE": "1100000000",
"USE_STRNGHLD_GRC_CODE": "1100",
"TELNO": "01012345678"
}
]
},
"ledgerInfo": {
"VHRNO": "11가1111",
"VIN": "KMHXX00XXXX111111"
}
}

@ -0,0 +1,38 @@
{
"vhrno": "22나2222",
"success": true,
"message": "조회 성공",
"basicInfo": {
"CNTC_RESULT_CODE": "MSG50000",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
"VHRNO": "22나2222",
"MBER_NM": "상품용",
"MBER_SE_CODE": "2",
"MBER_SE_NO": "1234567890123",
"USE_STRNGHLD_LEGALDONG_CODE": "1100000000",
"USE_STRNGHLD_ADRES_NM": "서울특별시 중구 세종대로 110",
"OWNER_ADRES_NM": "서울특별시 중구 세종대로 110",
"CNM": "쏘나타",
"VHCTY_ASORT_NM": "승용차",
"VHCTY_TY_NM": "중형",
"VHCTY_SE_NM": "일반형",
"PRYE": "2023",
"REGIST_DE": "20230101",
"FRST_REGIST_DE": "20230101",
"INSPT_VALID_PD_BGNDE": "20250101",
"INSPT_VALID_PD_ENDDE": "20270101",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_DE": "",
"OWNER_LEGALDONG_CODE": "1100000000",
"USE_STRNGHLD_GRC_CODE": "1100",
"TELNO": "0212345678"
}
]
},
"ledgerInfo": {
"VHRNO": "22나2222",
"VIN": "KMHYY00YYYY222222"
}
}

@ -0,0 +1,38 @@
{
"vhrno": "33다3333",
"success": true,
"message": "조회 성공",
"basicInfo": {
"CNTC_RESULT_CODE": "MSG50000",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
"VHRNO": "33다3333",
"MBER_NM": "이영희",
"MBER_SE_CODE": "1",
"MBER_SE_NO": "9002022345678",
"USE_STRNGHLD_LEGALDONG_CODE": "4100000000",
"USE_STRNGHLD_ADRES_NM": "경기도 수원시 팔달구 효원로 1",
"OWNER_ADRES_NM": "경기도 수원시 팔달구 효원로 1",
"CNM": "아반떼",
"VHCTY_ASORT_NM": "승용차",
"VHCTY_TY_NM": "소형",
"VHCTY_SE_NM": "일반형",
"PRYE": "2021",
"REGIST_DE": "20210520",
"FRST_REGIST_DE": "20210520",
"INSPT_VALID_PD_BGNDE": "20230520",
"INSPT_VALID_PD_ENDDE": "20250520",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_DE": "",
"OWNER_LEGALDONG_CODE": "4100000000",
"USE_STRNGHLD_GRC_CODE": "4100",
"TELNO": "01098765432"
}
]
},
"ledgerInfo": {
"VHRNO": "33다3333",
"VIN": "KMHZZ00ZZZZ333333"
}
}

@ -0,0 +1,38 @@
{
"vhrno": "44라4444",
"success": true,
"message": "조회 성공",
"basicInfo": {
"CNTC_RESULT_CODE": "MSG50000",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
"VHRNO": "44라4444",
"MBER_NM": "박민수",
"MBER_SE_CODE": "1",
"MBER_SE_NO": "8503033456789",
"USE_STRNGHLD_LEGALDONG_CODE": "2600000000",
"USE_STRNGHLD_ADRES_NM": "부산광역시 해운대구 우동 1234",
"OWNER_ADRES_NM": "부산광역시 해운대구 우동 1234",
"CNM": "카니발",
"VHCTY_ASORT_NM": "승합차",
"VHCTY_TY_NM": "대형",
"VHCTY_SE_NM": "일반형",
"PRYE": "2020",
"REGIST_DE": "20200815",
"FRST_REGIST_DE": "20200815",
"INSPT_VALID_PD_BGNDE": "20220815",
"INSPT_VALID_PD_ENDDE": "20240815",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_DE": "",
"OWNER_LEGALDONG_CODE": "2600000000",
"USE_STRNGHLD_GRC_CODE": "2600",
"TELNO": "01055554444"
}
]
},
"ledgerInfo": {
"VHRNO": "44라4444",
"VIN": "KMHAA00AAAA444444"
}
}

@ -0,0 +1,38 @@
{
"vhrno": "55마5555",
"success": true,
"message": "조회 성공",
"basicInfo": {
"CNTC_RESULT_CODE": "MSG50000",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
"VHRNO": "55마5555",
"MBER_NM": "최영수",
"MBER_SE_CODE": "1",
"MBER_SE_NO": "7504044567890",
"USE_STRNGHLD_LEGALDONG_CODE": "3000000000",
"USE_STRNGHLD_ADRES_NM": "대전광역시 유성구 대학로 99",
"OWNER_ADRES_NM": "대전광역시 유성구 대학로 99",
"CNM": "모닝",
"VHCTY_ASORT_NM": "승용차",
"VHCTY_TY_NM": "경형",
"VHCTY_SE_NM": "일반형",
"PRYE": "2019",
"REGIST_DE": "20190910",
"FRST_REGIST_DE": "20190910",
"INSPT_VALID_PD_BGNDE": "20210910",
"INSPT_VALID_PD_ENDDE": "20230910",
"ERSR_REGIST_SE_CODE": "1",
"ERSR_REGIST_DE": "20231201",
"OWNER_LEGALDONG_CODE": "3000000000",
"USE_STRNGHLD_GRC_CODE": "3000",
"TELNO": "01066667777"
}
]
},
"ledgerInfo": {
"VHRNO": "55마5555",
"VIN": "KMHBB00BBBB555555"
}
}

@ -0,0 +1,38 @@
{
"vhrno": "66바6666",
"success": true,
"message": "조회 성공",
"basicInfo": {
"CNTC_RESULT_CODE": "MSG50000",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
"VHRNO": "66바6666",
"MBER_NM": "정수진",
"MBER_SE_CODE": "1",
"MBER_SE_NO": "9205055678901",
"USE_STRNGHLD_LEGALDONG_CODE": "2800000000",
"USE_STRNGHLD_ADRES_NM": "인천광역시 남동구 구월동 1234-5",
"OWNER_ADRES_NM": "인천광역시 남동구 구월동 1234-5",
"CNM": "스포티지",
"VHCTY_ASORT_NM": "승용차",
"VHCTY_TY_NM": "중형",
"VHCTY_SE_NM": "SUV",
"PRYE": "2024",
"REGIST_DE": "20240101",
"FRST_REGIST_DE": "20240101",
"INSPT_VALID_PD_BGNDE": "20260101",
"INSPT_VALID_PD_ENDDE": "20280101",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_DE": "",
"OWNER_LEGALDONG_CODE": "2800000000",
"USE_STRNGHLD_GRC_CODE": "2800",
"TELNO": "01077778888"
}
]
},
"ledgerInfo": {
"VHRNO": "66바6666",
"VIN": "KMHCC00CCCC666666"
}
}

@ -0,0 +1,38 @@
{
"vhrno": "77사7777",
"success": true,
"message": "조회 성공",
"basicInfo": {
"CNTC_RESULT_CODE": "MSG50000",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
"VHRNO": "77사7777",
"MBER_NM": "전시용",
"MBER_SE_CODE": "2",
"MBER_SE_NO": "2208123456789",
"USE_STRNGHLD_LEGALDONG_CODE": "4100000000",
"USE_STRNGHLD_ADRES_NM": "경기도 성남시 분당구 판교역로 231",
"OWNER_ADRES_NM": "경기도 성남시 분당구 판교역로 231",
"CNM": "제네시스 G80",
"VHCTY_ASORT_NM": "승용차",
"VHCTY_TY_NM": "대형",
"VHCTY_SE_NM": "일반형",
"PRYE": "2025",
"REGIST_DE": "20250201",
"FRST_REGIST_DE": "20250201",
"INSPT_VALID_PD_BGNDE": "20270201",
"INSPT_VALID_PD_ENDDE": "20290201",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_DE": "",
"OWNER_LEGALDONG_CODE": "4100000000",
"USE_STRNGHLD_GRC_CODE": "4100",
"TELNO": "0315551234"
}
]
},
"ledgerInfo": {
"VHRNO": "77사7777",
"VIN": "KMHDD00DDDD777777"
}
}

@ -0,0 +1,38 @@
{
"vhrno": "88아8888",
"success": true,
"message": "조회 성공",
"basicInfo": {
"CNTC_RESULT_CODE": "MSG50000",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
"VHRNO": "88아8888",
"MBER_NM": "강원철",
"MBER_SE_CODE": "1",
"MBER_SE_NO": "8806066789012",
"USE_STRNGHLD_LEGALDONG_CODE": "5100000000",
"USE_STRNGHLD_ADRES_NM": "강원특별자치도 춘천시 중앙로 1",
"OWNER_ADRES_NM": "강원특별자치도 춘천시 중앙로 1",
"CNM": "투싼",
"VHCTY_ASORT_NM": "승용차",
"VHCTY_TY_NM": "소형",
"VHCTY_SE_NM": "SUV",
"PRYE": "2023",
"REGIST_DE": "20230715",
"FRST_REGIST_DE": "20230715",
"INSPT_VALID_PD_BGNDE": "20250715",
"INSPT_VALID_PD_ENDDE": "20270715",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_DE": "",
"OWNER_LEGALDONG_CODE": "5100000000",
"USE_STRNGHLD_GRC_CODE": "5100",
"TELNO": "01088889999"
}
]
},
"ledgerInfo": {
"VHRNO": "88아8888",
"VIN": "KMHEE00EEEE888888"
}
}

@ -0,0 +1,38 @@
{
"vhrno": "99자9999",
"success": true,
"message": "조회 성공",
"basicInfo": {
"CNTC_RESULT_CODE": "MSG50000",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
"VHRNO": "99자9999",
"MBER_NM": "상품용차량",
"MBER_SE_CODE": "2",
"MBER_SE_NO": "3209087890123",
"USE_STRNGHLD_LEGALDONG_CODE": "2700000000",
"USE_STRNGHLD_ADRES_NM": "대구광역시 중구 동성로 123",
"OWNER_ADRES_NM": "대구광역시 중구 동성로 123",
"CNM": "팰리세이드",
"VHCTY_ASORT_NM": "승용차",
"VHCTY_TY_NM": "대형",
"VHCTY_SE_NM": "SUV",
"PRYE": "2024",
"REGIST_DE": "20240901",
"FRST_REGIST_DE": "20240901",
"INSPT_VALID_PD_BGNDE": "20260901",
"INSPT_VALID_PD_ENDDE": "20280901",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_DE": "",
"OWNER_LEGALDONG_CODE": "2700000000",
"USE_STRNGHLD_GRC_CODE": "2700",
"TELNO": "0539990000"
}
]
},
"ledgerInfo": {
"VHRNO": "99자9999",
"VIN": "KMHFF00FFFF999999"
}
}

@ -0,0 +1,38 @@
{
"vhrno": "00차0000",
"success": true,
"message": "조회 성공",
"basicInfo": {
"CNTC_RESULT_CODE": "MSG50000",
"CNTC_RESULT_DTLS": "정상",
"record": [
{
"VHRNO": "00차0000",
"MBER_NM": "윤하나",
"MBER_SE_CODE": "1",
"MBER_SE_NO": "9507077890234",
"USE_STRNGHLD_LEGALDONG_CODE": "4300000000",
"USE_STRNGHLD_ADRES_NM": "충청남도 천안시 동남구 만남로 1",
"OWNER_ADRES_NM": "충청남도 천안시 동남구 만남로 1",
"CNM": "K5",
"VHCTY_ASORT_NM": "승용차",
"VHCTY_TY_NM": "중형",
"VHCTY_SE_NM": "일반형",
"PRYE": "2022",
"REGIST_DE": "20221010",
"FRST_REGIST_DE": "20221010",
"INSPT_VALID_PD_BGNDE": "20241010",
"INSPT_VALID_PD_ENDDE": "20261010",
"ERSR_REGIST_SE_CODE": "",
"ERSR_REGIST_DE": "",
"OWNER_LEGALDONG_CODE": "4300000000",
"USE_STRNGHLD_GRC_CODE": "4300",
"TELNO": "01000001111"
}
]
},
"ledgerInfo": {
"VHRNO": "00차0000",
"VIN": "KMHGG00GGGG000000"
}
}

@ -0,0 +1,89 @@
{
"data": [
{
"VHMNO": "KMHXX00XXXX111111-01",
"MTRS_FOM_NM": "G6DH",
"PRCS_IMPRTY_RSN_CD": "00",
"FRST_TRNSFR_YMD": "2022-03-15",
"MRTG_CNT": "0",
"COLOR_NM": "검정",
"ERSR_REG_SE_CD": null,
"CHCK_VLD_PD_END_YMD": null,
"NOPLT_CSDY_YN": "N",
"USG_SE_NM": "자가용",
"VEAG_END_YMD": null,
"record": [
{
"CHG_YMD": "2022-03-15",
"VHMNO": "KMHXX00XXXX111111-01",
"SPCABL_MTTR": "성명(상호) : 김철수 800101-1234567\n주소 : 서울특별시 강남구 테헤란로 123",
"CHG_TASK_SE_NM": "신규등록(신조차)",
"FLAG": null,
"SZR_RMV_DTL_SN": "1",
"MAIN_NO": "1-1",
"DTL_SN": "1",
"CHG_TASK_SE_CD": "01",
"SNO": null,
"APLY_RCPT_NO": "1100-202203-001234",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "김철수",
"HSHLDR_IDECNO": "800101-1234567"
}
],
"TAXXMPT_TRPR_SE_CD": "0",
"SPCABL_MTTR_CNT": "0",
"DRVNG_DSTNC": "25800",
"NOPLT_CSDY_AVTSMT_YMD": null,
"ERSR_REG_SE_NM": null,
"OWNR_ADDR": "서울특별시 강남구 테헤란로 123",
"DRIV_SRGBTRY_IDNTF_NO": null,
"YRIDNW": "2022",
"SPMNNO_1": "A01",
"BSS_USE_PD_YMD": null,
"LINK_RSLT_CD": "MSG50000",
"SPMNNO_2": "1-00012-0345-0678",
"SZR_CNT": "0",
"CARMDL_ASORT_CD": "1",
"USGSRHLD_ADDR_1": "서울특별시 강남구 테헤란로 123",
"USGSRHLD_ADDR_DTL_1": "101동 1001호",
"RPRS_OWNR_TELNO": "01012345678",
"NOPLT_SPCFCT_CD": "2",
"REG_DTL_CD": "100",
"ACQS_AMT": "35,000,000",
"FOM_NM": "DN8-G6DH-P3A",
"LINK_RSLT_DTL": "정상",
"REG_APLY_SE_NM": "신조차",
"OCTHT_ERSR_PRVNTC_AVTSMT_YMD": null,
"OWNR_ADDR_DTL": "101동 1001호",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"ISSU_NO": null,
"INSP_VLD_PD_END_YMD": "2026-03-14",
"CARMDL_ASORT_NM": "승용 중형",
"CHCK_VLD_PD_BGNG_YMD": null,
"USGSRHLD_DONG_NM": "삼성동",
"REG_DTL_NM": "일반소유용",
"NOPLT_SPCFCT_NM": "긴번호판",
"FBCTN_YMD": "2022-03-10",
"RPRSV_OWNR_IDECNO": "800101-1234567",
"FRST_REG_YMD": "2022-03-15",
"RPRS_OWNR_NM": "김철수",
"PRCS_IMPRTY_RSN_DTLS": "운행차량",
"FRST_REG_APLY_RCPT_NO": "1100-202203-001234",
"PRVNTC_CNT": "0",
"RPRS_OWNR_MBR_SE_CD": "11",
"TAXXMPT_APLCN_SE_CD": "미적용",
"ERSR_REG_YMD": null,
"COLOR_CD": "01",
"STRCT_CHG_CNT": "0",
"INDVDL_BZMN_YN": "N",
"VIN": "KMHXX00XXXX111111",
"USG_SE_CD": "2",
"XPORT_FLFL_YN_DCLR_YMD": null,
"VHRNO": "11가1111",
"INSP_VLD_PD_BGNG_YMD": "2022-03-15",
"ATMB_NM": "그랜저"
}
]
}

@ -0,0 +1,106 @@
{
"data": [
{
"VHMNO": "KMHYY00YYYY222222-01",
"MTRS_FOM_NM": "G4NA",
"PRCS_IMPRTY_RSN_CD": "00",
"FRST_TRNSFR_YMD": "2023-01-01",
"MRTG_CNT": "0",
"COLOR_NM": "은색",
"ERSR_REG_SE_CD": null,
"CHCK_VLD_PD_END_YMD": null,
"NOPLT_CSDY_YN": "N",
"USG_SE_NM": "영업용",
"VEAG_END_YMD": null,
"record": [
{
"CHG_YMD": "2023-01-01",
"VHMNO": "KMHYY00YYYY222222-01",
"SPCABL_MTTR": "성명(상호) : 상품용 123-45-67890\n주소 : 서울특별시 중구 세종대로 110",
"CHG_TASK_SE_NM": "신규등록(신조차)",
"FLAG": null,
"SZR_RMV_DTL_SN": "1",
"MAIN_NO": "1-1",
"DTL_SN": "1",
"CHG_TASK_SE_CD": "01",
"SNO": null,
"APLY_RCPT_NO": "1100-202301-005678",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "상품용",
"HSHLDR_IDECNO": "123-45-67890"
},
{
"CHG_YMD": "2023-06-15",
"VHMNO": "KMHYY00YYYY222222-01",
"SPCABL_MTTR": "촉탁기관 : 서울시청 구분:압류\r압류관리번호:1100-20230615-000123\n\r압류내역 : 서울시청 주정차위반과태료[2023-01-100001] 교통과-12345 \n\r촉탁일자 : 2023-06-15",
"CHG_TASK_SE_NM": "압류등록(압류)",
"FLAG": null,
"SZR_RMV_DTL_SN": "2",
"MAIN_NO": "(1-2)",
"DTL_SN": "2",
"CHG_TASK_SE_CD": "41",
"SNO": null,
"APLY_RCPT_NO": "1100-230615-000123",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "",
"HSHLDR_IDECNO": ""
}
],
"TAXXMPT_TRPR_SE_CD": "0",
"SPCABL_MTTR_CNT": "1",
"DRVNG_DSTNC": "18500",
"NOPLT_CSDY_AVTSMT_YMD": null,
"ERSR_REG_SE_NM": null,
"OWNR_ADDR": "서울특별시 중구 세종대로 110",
"DRIV_SRGBTRY_IDNTF_NO": null,
"YRIDNW": "2023",
"SPMNNO_1": "A01",
"BSS_USE_PD_YMD": null,
"LINK_RSLT_CD": "MSG50000",
"SPMNNO_2": "1-00023-0456-0789",
"SZR_CNT": "1",
"CARMDL_ASORT_CD": "1",
"USGSRHLD_ADDR_1": "서울특별시 중구 세종대로 110",
"USGSRHLD_ADDR_DTL_1": "상가동 1층",
"RPRS_OWNR_TELNO": "0212345678",
"NOPLT_SPCFCT_CD": "2",
"REG_DTL_CD": "100",
"ACQS_AMT": "28,500,000",
"FOM_NM": "LF-G4NA-P2A",
"LINK_RSLT_DTL": "정상",
"REG_APLY_SE_NM": "신조차",
"OCTHT_ERSR_PRVNTC_AVTSMT_YMD": null,
"OWNR_ADDR_DTL": "상가동 1층",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"ISSU_NO": null,
"INSP_VLD_PD_END_YMD": "2027-01-01",
"CARMDL_ASORT_NM": "승용 중형",
"CHCK_VLD_PD_BGNG_YMD": null,
"USGSRHLD_DONG_NM": "소공동",
"REG_DTL_NM": "일반소유용",
"NOPLT_SPCFCT_NM": "긴번호판",
"FBCTN_YMD": "2022-12-20",
"RPRSV_OWNR_IDECNO": "123-45-67890",
"FRST_REG_YMD": "2023-01-01",
"RPRS_OWNR_NM": "상품용",
"PRCS_IMPRTY_RSN_DTLS": "운행차량",
"FRST_REG_APLY_RCPT_NO": "1100-202301-005678",
"PRVNTC_CNT": "0",
"RPRS_OWNR_MBR_SE_CD": "13",
"TAXXMPT_APLCN_SE_CD": "미적용",
"ERSR_REG_YMD": null,
"COLOR_CD": "05",
"STRCT_CHG_CNT": "0",
"INDVDL_BZMN_YN": "N",
"VIN": "KMHYY00YYYY222222",
"USG_SE_CD": "1",
"XPORT_FLFL_YN_DCLR_YMD": null,
"VHRNO": "22나2222",
"INSP_VLD_PD_BGNG_YMD": "2023-01-01",
"ATMB_NM": "쏘나타"
}
]
}

@ -0,0 +1,123 @@
{
"data": [
{
"VHMNO": "KMHZZ00ZZZZ333333-01",
"MTRS_FOM_NM": "G4FD",
"PRCS_IMPRTY_RSN_CD": "00",
"FRST_TRNSFR_YMD": "2021-05-20",
"MRTG_CNT": "0",
"COLOR_NM": "흰색",
"ERSR_REG_SE_CD": null,
"CHCK_VLD_PD_END_YMD": null,
"NOPLT_CSDY_YN": "N",
"USG_SE_NM": "자가용",
"VEAG_END_YMD": null,
"record": [
{
"CHG_YMD": "2021-05-20",
"VHMNO": "KMHZZ00ZZZZ333333-01",
"SPCABL_MTTR": "성명(상호) : 이영희 900202-2345678\n주소 : 경기도 수원시 팔달구 효원로 1",
"CHG_TASK_SE_NM": "신규등록(신조차)",
"FLAG": null,
"SZR_RMV_DTL_SN": "1",
"MAIN_NO": "1-1",
"DTL_SN": "1",
"CHG_TASK_SE_CD": "01",
"SNO": null,
"APLY_RCPT_NO": "4100-202105-002345",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "이영희",
"HSHLDR_IDECNO": "900202-2345678"
},
{
"CHG_YMD": "2024-03-10",
"VHMNO": "KMHZZ00ZZZZ333333-01",
"SPCABL_MTTR": "촉탁기관 : 수원시청 구분:압류\r압류관리번호:4100-20240310-000456\n\r압류내역 : 수원시청 주정차위반과태료[2024-01-200001] 교통관리과-67890 \n\r촉탁일자 : 2024-03-10",
"CHG_TASK_SE_NM": "압류등록(압류)",
"FLAG": null,
"SZR_RMV_DTL_SN": "2",
"MAIN_NO": "(1-2)",
"DTL_SN": "2",
"CHG_TASK_SE_CD": "41",
"SNO": null,
"APLY_RCPT_NO": "4100-240310-000456",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "",
"HSHLDR_IDECNO": ""
},
{
"CHG_YMD": "2024-08-20",
"VHMNO": "KMHZZ00ZZZZ333333-01",
"SPCABL_MTTR": "촉탁기관 : 수원시청 구분:압류\r압류관리번호:4100-20240310-000456\n\r압류명세 : 수원시청 주정차위반과태료[2024-01-200001] 교통관리과-67890\n\r촉탁일자 : 2024-08-20",
"CHG_TASK_SE_NM": "압류해제(압류)",
"FLAG": null,
"SZR_RMV_DTL_SN": "3",
"MAIN_NO": null,
"DTL_SN": "3",
"CHG_TASK_SE_CD": "42",
"SNO": "(1-2)",
"APLY_RCPT_NO": "4100-240820-001789",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "",
"HSHLDR_IDECNO": ""
}
],
"TAXXMPT_TRPR_SE_CD": "0",
"SPCABL_MTTR_CNT": "0",
"DRVNG_DSTNC": "85600",
"NOPLT_CSDY_AVTSMT_YMD": null,
"ERSR_REG_SE_NM": null,
"OWNR_ADDR": "경기도 수원시 팔달구 효원로 1",
"DRIV_SRGBTRY_IDNTF_NO": null,
"YRIDNW": "2021",
"SPMNNO_1": "A01",
"BSS_USE_PD_YMD": null,
"LINK_RSLT_CD": "MSG50000",
"SPMNNO_2": "1-00034-0567-0890",
"SZR_CNT": "0",
"CARMDL_ASORT_CD": "1",
"USGSRHLD_ADDR_1": "경기도 수원시 팔달구 효원로 1",
"USGSRHLD_ADDR_DTL_1": "201호",
"RPRS_OWNR_TELNO": "01098765432",
"NOPLT_SPCFCT_CD": "2",
"REG_DTL_CD": "100",
"ACQS_AMT": "19,800,000",
"FOM_NM": "AD-G4FD-P1A",
"LINK_RSLT_DTL": "정상",
"REG_APLY_SE_NM": "신조차",
"OCTHT_ERSR_PRVNTC_AVTSMT_YMD": null,
"OWNR_ADDR_DTL": "201호",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"ISSU_NO": null,
"INSP_VLD_PD_END_YMD": "2025-05-19",
"CARMDL_ASORT_NM": "승용 소형",
"CHCK_VLD_PD_BGNG_YMD": null,
"USGSRHLD_DONG_NM": "인계동",
"REG_DTL_NM": "일반소유용",
"NOPLT_SPCFCT_NM": "긴번호판",
"FBCTN_YMD": "2021-05-10",
"RPRSV_OWNR_IDECNO": "900202-2345678",
"FRST_REG_YMD": "2021-05-20",
"RPRS_OWNR_NM": "이영희",
"PRCS_IMPRTY_RSN_DTLS": "운행차량",
"FRST_REG_APLY_RCPT_NO": "4100-202105-002345",
"PRVNTC_CNT": "0",
"RPRS_OWNR_MBR_SE_CD": "11",
"TAXXMPT_APLCN_SE_CD": "미적용",
"ERSR_REG_YMD": null,
"COLOR_CD": "02",
"STRCT_CHG_CNT": "0",
"INDVDL_BZMN_YN": "N",
"VIN": "KMHZZ00ZZZZ333333",
"USG_SE_CD": "2",
"XPORT_FLFL_YN_DCLR_YMD": null,
"VHRNO": "33다3333",
"INSP_VLD_PD_BGNG_YMD": "2021-05-20",
"ATMB_NM": "아반떼"
}
]
}

@ -0,0 +1,106 @@
{
"data": [
{
"VHMNO": "KMHAA00AAAA444444-01",
"MTRS_FOM_NM": "D4CB",
"PRCS_IMPRTY_RSN_CD": "00",
"FRST_TRNSFR_YMD": "2020-08-15",
"MRTG_CNT": "1",
"COLOR_NM": "파랑",
"ERSR_REG_SE_CD": null,
"CHCK_VLD_PD_END_YMD": null,
"NOPLT_CSDY_YN": "N",
"USG_SE_NM": "자가용",
"VEAG_END_YMD": null,
"record": [
{
"CHG_YMD": "2020-08-15",
"VHMNO": "KMHAA00AAAA444444-01",
"SPCABL_MTTR": "성명(상호) : 박민수 850303-3456789\n주소 : 부산광역시 해운대구 우동 1234",
"CHG_TASK_SE_NM": "신규등록(신조차)",
"FLAG": null,
"SZR_RMV_DTL_SN": "1",
"MAIN_NO": "1-1",
"DTL_SN": "1",
"CHG_TASK_SE_CD": "01",
"SNO": null,
"APLY_RCPT_NO": "2600-202008-003456",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "박민수",
"HSHLDR_IDECNO": "850303-3456789"
},
{
"CHG_YMD": "2021-03-01",
"VHMNO": "KMHAA00AAAA444444-01",
"SPCABL_MTTR": "채권최고액 : 30,000,000원\n채권자 : 국민은행 부산지점\n채무자 : 박민수",
"CHG_TASK_SE_NM": "저당권설정",
"FLAG": null,
"SZR_RMV_DTL_SN": "2",
"MAIN_NO": "(1-2)",
"DTL_SN": "2",
"CHG_TASK_SE_CD": "31",
"SNO": null,
"APLY_RCPT_NO": "2600-210301-004567",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "",
"HSHLDR_IDECNO": ""
}
],
"TAXXMPT_TRPR_SE_CD": "0",
"SPCABL_MTTR_CNT": "1",
"DRVNG_DSTNC": "42300",
"NOPLT_CSDY_AVTSMT_YMD": null,
"ERSR_REG_SE_NM": null,
"OWNR_ADDR": "부산광역시 해운대구 우동 1234",
"DRIV_SRGBTRY_IDNTF_NO": null,
"YRIDNW": "2020",
"SPMNNO_1": "B01",
"BSS_USE_PD_YMD": null,
"LINK_RSLT_CD": "MSG50000",
"SPMNNO_2": "2-00045-0678-0901",
"SZR_CNT": "0",
"CARMDL_ASORT_CD": "2",
"USGSRHLD_ADDR_1": "부산광역시 해운대구 우동 1234",
"USGSRHLD_ADDR_DTL_1": "303호",
"RPRS_OWNR_TELNO": "01055554444",
"NOPLT_SPCFCT_CD": "2",
"REG_DTL_CD": "100",
"ACQS_AMT": "38,500,000",
"FOM_NM": "KA4-D4CB-P4B",
"LINK_RSLT_DTL": "정상",
"REG_APLY_SE_NM": "신조차",
"OCTHT_ERSR_PRVNTC_AVTSMT_YMD": null,
"OWNR_ADDR_DTL": "303호",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"ISSU_NO": null,
"INSP_VLD_PD_END_YMD": "2024-08-14",
"CARMDL_ASORT_NM": "승합 대형",
"CHCK_VLD_PD_BGNG_YMD": null,
"USGSRHLD_DONG_NM": "우동",
"REG_DTL_NM": "일반소유용",
"NOPLT_SPCFCT_NM": "긴번호판",
"FBCTN_YMD": "2020-08-05",
"RPRSV_OWNR_IDECNO": "850303-3456789",
"FRST_REG_YMD": "2020-08-15",
"RPRS_OWNR_NM": "박민수",
"PRCS_IMPRTY_RSN_DTLS": "운행차량",
"FRST_REG_APLY_RCPT_NO": "2600-202008-003456",
"PRVNTC_CNT": "0",
"RPRS_OWNR_MBR_SE_CD": "11",
"TAXXMPT_APLCN_SE_CD": "미적용",
"ERSR_REG_YMD": null,
"COLOR_CD": "06",
"STRCT_CHG_CNT": "0",
"INDVDL_BZMN_YN": "N",
"VIN": "KMHAA00AAAA444444",
"USG_SE_CD": "2",
"XPORT_FLFL_YN_DCLR_YMD": null,
"VHRNO": "44라4444",
"INSP_VLD_PD_BGNG_YMD": "2020-08-15",
"ATMB_NM": "카니발"
}
]
}

@ -0,0 +1,106 @@
{
"data": [
{
"VHMNO": "KMHBB00BBBB555555-01",
"MTRS_FOM_NM": "G3LA",
"PRCS_IMPRTY_RSN_CD": "01",
"FRST_TRNSFR_YMD": "2019-09-10",
"MRTG_CNT": "0",
"COLOR_NM": "빨강",
"ERSR_REG_SE_CD": "1",
"CHCK_VLD_PD_END_YMD": null,
"NOPLT_CSDY_YN": "N",
"USG_SE_NM": "자가용",
"VEAG_END_YMD": null,
"record": [
{
"CHG_YMD": "2019-09-10",
"VHMNO": "KMHBB00BBBB555555-01",
"SPCABL_MTTR": "성명(상호) : 최영수 750404-4567890\n주소 : 대전광역시 유성구 대학로 99",
"CHG_TASK_SE_NM": "신규등록(신조차)",
"FLAG": null,
"SZR_RMV_DTL_SN": "1",
"MAIN_NO": "1-1",
"DTL_SN": "1",
"CHG_TASK_SE_CD": "01",
"SNO": null,
"APLY_RCPT_NO": "3000-201909-004567",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "최영수",
"HSHLDR_IDECNO": "750404-4567890"
},
{
"CHG_YMD": "2023-12-01",
"VHMNO": "KMHBB00BBBB555555-01",
"SPCABL_MTTR": "말소사유 : 폐차\n말소일자 : 2023-12-01",
"CHG_TASK_SE_NM": "말소등록",
"FLAG": null,
"SZR_RMV_DTL_SN": "2",
"MAIN_NO": null,
"DTL_SN": "2",
"CHG_TASK_SE_CD": "11",
"SNO": "(1-1)",
"APLY_RCPT_NO": "3000-231201-005678",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "",
"HSHLDR_IDECNO": ""
}
],
"TAXXMPT_TRPR_SE_CD": "0",
"SPCABL_MTTR_CNT": "0",
"DRVNG_DSTNC": "125400",
"NOPLT_CSDY_AVTSMT_YMD": null,
"ERSR_REG_SE_NM": "폐차",
"OWNR_ADDR": "대전광역시 유성구 대학로 99",
"DRIV_SRGBTRY_IDNTF_NO": null,
"YRIDNW": "2019",
"SPMNNO_1": "A01",
"BSS_USE_PD_YMD": null,
"LINK_RSLT_CD": "MSG50000",
"SPMNNO_2": "1-00056-0789-1012",
"SZR_CNT": "0",
"CARMDL_ASORT_CD": "1",
"USGSRHLD_ADDR_1": "대전광역시 유성구 대학로 99",
"USGSRHLD_ADDR_DTL_1": "A동 501호",
"RPRS_OWNR_TELNO": "01066667777",
"NOPLT_SPCFCT_CD": "2",
"REG_DTL_CD": "100",
"ACQS_AMT": "10,500,000",
"FOM_NM": "TA-G3LA-P1A",
"LINK_RSLT_DTL": "정상",
"REG_APLY_SE_NM": "신조차",
"OCTHT_ERSR_PRVNTC_AVTSMT_YMD": null,
"OWNR_ADDR_DTL": "A동 501호",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"ISSU_NO": null,
"INSP_VLD_PD_END_YMD": "2023-09-09",
"CARMDL_ASORT_NM": "승용 경형",
"CHCK_VLD_PD_BGNG_YMD": null,
"USGSRHLD_DONG_NM": "궁동",
"REG_DTL_NM": "일반소유용",
"NOPLT_SPCFCT_NM": "긴번호판",
"FBCTN_YMD": "2019-09-01",
"RPRSV_OWNR_IDECNO": "750404-4567890",
"FRST_REG_YMD": "2019-09-10",
"RPRS_OWNR_NM": "최영수",
"PRCS_IMPRTY_RSN_DTLS": "말소차량",
"FRST_REG_APLY_RCPT_NO": "3000-201909-004567",
"PRVNTC_CNT": "0",
"RPRS_OWNR_MBR_SE_CD": "11",
"TAXXMPT_APLCN_SE_CD": "미적용",
"ERSR_REG_YMD": "2023-12-01",
"COLOR_CD": "03",
"STRCT_CHG_CNT": "0",
"INDVDL_BZMN_YN": "N",
"VIN": "KMHBB00BBBB555555",
"USG_SE_CD": "2",
"XPORT_FLFL_YN_DCLR_YMD": null,
"VHRNO": "55마5555",
"INSP_VLD_PD_BGNG_YMD": "2019-09-10",
"ATMB_NM": "모닝"
}
]
}

@ -0,0 +1,89 @@
{
"data": [
{
"VHMNO": "KMHCC00CCCC666666-01",
"MTRS_FOM_NM": "D4HA",
"PRCS_IMPRTY_RSN_CD": "00",
"FRST_TRNSFR_YMD": "2024-01-01",
"MRTG_CNT": "0",
"COLOR_NM": "회색",
"ERSR_REG_SE_CD": null,
"CHCK_VLD_PD_END_YMD": null,
"NOPLT_CSDY_YN": "N",
"USG_SE_NM": "자가용",
"VEAG_END_YMD": null,
"record": [
{
"CHG_YMD": "2024-01-01",
"VHMNO": "KMHCC00CCCC666666-01",
"SPCABL_MTTR": "성명(상호) : 정수진 920505-5678901\n주소 : 인천광역시 남동구 구월동 1234-5",
"CHG_TASK_SE_NM": "신규등록(신조차)",
"FLAG": null,
"SZR_RMV_DTL_SN": "1",
"MAIN_NO": "1-1",
"DTL_SN": "1",
"CHG_TASK_SE_CD": "01",
"SNO": null,
"APLY_RCPT_NO": "2800-202401-005678",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "정수진",
"HSHLDR_IDECNO": "920505-5678901"
}
],
"TAXXMPT_TRPR_SE_CD": "0",
"SPCABL_MTTR_CNT": "0",
"DRVNG_DSTNC": "8500",
"NOPLT_CSDY_AVTSMT_YMD": null,
"ERSR_REG_SE_NM": null,
"OWNR_ADDR": "인천광역시 남동구 구월동 1234-5",
"DRIV_SRGBTRY_IDNTF_NO": null,
"YRIDNW": "2024",
"SPMNNO_1": "A01",
"BSS_USE_PD_YMD": null,
"LINK_RSLT_CD": "MSG50000",
"SPMNNO_2": "1-00067-0890-1123",
"SZR_CNT": "0",
"CARMDL_ASORT_CD": "1",
"USGSRHLD_ADDR_1": "인천광역시 남동구 구월동 1234-5",
"USGSRHLD_ADDR_DTL_1": "B동 1502호",
"RPRS_OWNR_TELNO": "01077778888",
"NOPLT_SPCFCT_CD": "2",
"REG_DTL_CD": "100",
"ACQS_AMT": "32,800,000",
"FOM_NM": "NQ5-D4HA-P3C",
"LINK_RSLT_DTL": "정상",
"REG_APLY_SE_NM": "신조차",
"OCTHT_ERSR_PRVNTC_AVTSMT_YMD": null,
"OWNR_ADDR_DTL": "B동 1502호",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"ISSU_NO": null,
"INSP_VLD_PD_END_YMD": "2028-01-01",
"CARMDL_ASORT_NM": "승용 중형",
"CHCK_VLD_PD_BGNG_YMD": null,
"USGSRHLD_DONG_NM": "구월동",
"REG_DTL_NM": "일반소유용",
"NOPLT_SPCFCT_NM": "긴번호판",
"FBCTN_YMD": "2023-12-20",
"RPRSV_OWNR_IDECNO": "920505-5678901",
"FRST_REG_YMD": "2024-01-01",
"RPRS_OWNR_NM": "정수진",
"PRCS_IMPRTY_RSN_DTLS": "운행차량",
"FRST_REG_APLY_RCPT_NO": "2800-202401-005678",
"PRVNTC_CNT": "0",
"RPRS_OWNR_MBR_SE_CD": "11",
"TAXXMPT_APLCN_SE_CD": "미적용",
"ERSR_REG_YMD": null,
"COLOR_CD": "07",
"STRCT_CHG_CNT": "0",
"INDVDL_BZMN_YN": "N",
"VIN": "KMHCC00CCCC666666",
"USG_SE_CD": "2",
"XPORT_FLFL_YN_DCLR_YMD": null,
"VHRNO": "66바6666",
"INSP_VLD_PD_BGNG_YMD": "2024-01-01",
"ATMB_NM": "스포티지"
}
]
}

@ -0,0 +1,89 @@
{
"data": [
{
"VHMNO": "KMHDD00DDDD777777-01",
"MTRS_FOM_NM": "T3LA",
"PRCS_IMPRTY_RSN_CD": "00",
"FRST_TRNSFR_YMD": "2025-02-01",
"MRTG_CNT": "0",
"COLOR_NM": "진주",
"ERSR_REG_SE_CD": null,
"CHCK_VLD_PD_END_YMD": null,
"NOPLT_CSDY_YN": "N",
"USG_SE_NM": "영업용",
"VEAG_END_YMD": null,
"record": [
{
"CHG_YMD": "2025-02-01",
"VHMNO": "KMHDD00DDDD777777-01",
"SPCABL_MTTR": "성명(상호) : 전시용 220-81-23456\n주소 : 경기도 성남시 분당구 판교역로 231",
"CHG_TASK_SE_NM": "신규등록(신조차)",
"FLAG": null,
"SZR_RMV_DTL_SN": "1",
"MAIN_NO": "1-1",
"DTL_SN": "1",
"CHG_TASK_SE_CD": "01",
"SNO": null,
"APLY_RCPT_NO": "4100-202502-006789",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "전시용",
"HSHLDR_IDECNO": "220-81-23456"
}
],
"TAXXMPT_TRPR_SE_CD": "0",
"SPCABL_MTTR_CNT": "0",
"DRVNG_DSTNC": "150",
"NOPLT_CSDY_AVTSMT_YMD": null,
"ERSR_REG_SE_NM": null,
"OWNR_ADDR": "경기도 성남시 분당구 판교역로 231",
"DRIV_SRGBTRY_IDNTF_NO": null,
"YRIDNW": "2025",
"SPMNNO_1": "A01",
"BSS_USE_PD_YMD": null,
"LINK_RSLT_CD": "MSG50000",
"SPMNNO_2": "1-00078-0901-1234",
"SZR_CNT": "0",
"CARMDL_ASORT_CD": "1",
"USGSRHLD_ADDR_1": "경기도 성남시 분당구 판교역로 231",
"USGSRHLD_ADDR_DTL_1": "판교테크원타워 10층",
"RPRS_OWNR_TELNO": "0315551234",
"NOPLT_SPCFCT_CD": "2",
"REG_DTL_CD": "100",
"ACQS_AMT": "68,900,000",
"FOM_NM": "RG3-T3LA-P5D",
"LINK_RSLT_DTL": "정상",
"REG_APLY_SE_NM": "신조차",
"OCTHT_ERSR_PRVNTC_AVTSMT_YMD": null,
"OWNR_ADDR_DTL": "판교테크원타워 10층",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"ISSU_NO": null,
"INSP_VLD_PD_END_YMD": "2029-02-01",
"CARMDL_ASORT_NM": "승용 대형",
"CHCK_VLD_PD_BGNG_YMD": null,
"USGSRHLD_DONG_NM": "삼평동",
"REG_DTL_NM": "일반소유용",
"NOPLT_SPCFCT_NM": "긴번호판",
"FBCTN_YMD": "2025-01-20",
"RPRSV_OWNR_IDECNO": "220-81-23456",
"FRST_REG_YMD": "2025-02-01",
"RPRS_OWNR_NM": "전시용",
"PRCS_IMPRTY_RSN_DTLS": "운행차량",
"FRST_REG_APLY_RCPT_NO": "4100-202502-006789",
"PRVNTC_CNT": "0",
"RPRS_OWNR_MBR_SE_CD": "13",
"TAXXMPT_APLCN_SE_CD": "미적용",
"ERSR_REG_YMD": null,
"COLOR_CD": "08",
"STRCT_CHG_CNT": "0",
"INDVDL_BZMN_YN": "N",
"VIN": "KMHDD00DDDD777777",
"USG_SE_CD": "1",
"XPORT_FLFL_YN_DCLR_YMD": null,
"VHRNO": "77사7777",
"INSP_VLD_PD_BGNG_YMD": "2025-02-01",
"ATMB_NM": "제네시스 G80"
}
]
}

@ -0,0 +1,106 @@
{
"data": [
{
"VHMNO": "KMHEE00EEEE888888-01",
"MTRS_FOM_NM": "G4FJ",
"PRCS_IMPRTY_RSN_CD": "00",
"FRST_TRNSFR_YMD": "2023-07-15",
"MRTG_CNT": "0",
"COLOR_NM": "청색",
"ERSR_REG_SE_CD": null,
"CHCK_VLD_PD_END_YMD": null,
"NOPLT_CSDY_YN": "N",
"USG_SE_NM": "자가용",
"VEAG_END_YMD": null,
"record": [
{
"CHG_YMD": "2023-07-15",
"VHMNO": "KMHEE00EEEE888888-01",
"SPCABL_MTTR": "성명(상호) : 강원철 880606-6789012\n주소 : 강원특별자치도 춘천시 중앙로 1",
"CHG_TASK_SE_NM": "신규등록(신조차)",
"FLAG": null,
"SZR_RMV_DTL_SN": "1",
"MAIN_NO": "1-1",
"DTL_SN": "1",
"CHG_TASK_SE_CD": "01",
"SNO": null,
"APLY_RCPT_NO": "5100-202307-007890",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "강원철",
"HSHLDR_IDECNO": "880606-6789012"
},
{
"CHG_YMD": "2024-11-01",
"VHMNO": "KMHEE00EEEE888888-01",
"SPCABL_MTTR": "예방조치기관 : 춘천시청\n예방조치사유 : 과태료 미납\n예방조치일자 : 2024-11-01",
"CHG_TASK_SE_NM": "예방조치",
"FLAG": null,
"SZR_RMV_DTL_SN": "2",
"MAIN_NO": "(1-2)",
"DTL_SN": "2",
"CHG_TASK_SE_CD": "51",
"SNO": null,
"APLY_RCPT_NO": "5100-241101-008901",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "",
"HSHLDR_IDECNO": ""
}
],
"TAXXMPT_TRPR_SE_CD": "0",
"SPCABL_MTTR_CNT": "1",
"DRVNG_DSTNC": "35200",
"NOPLT_CSDY_AVTSMT_YMD": null,
"ERSR_REG_SE_NM": null,
"OWNR_ADDR": "강원특별자치도 춘천시 중앙로 1",
"DRIV_SRGBTRY_IDNTF_NO": null,
"YRIDNW": "2023",
"SPMNNO_1": "A01",
"BSS_USE_PD_YMD": null,
"LINK_RSLT_CD": "MSG50000",
"SPMNNO_2": "1-00089-1012-1345",
"SZR_CNT": "0",
"CARMDL_ASORT_CD": "1",
"USGSRHLD_ADDR_1": "강원특별자치도 춘천시 중앙로 1",
"USGSRHLD_ADDR_DTL_1": "춘천타워 602호",
"RPRS_OWNR_TELNO": "01088889999",
"NOPLT_SPCFCT_CD": "2",
"REG_DTL_CD": "100",
"ACQS_AMT": "27,300,000",
"FOM_NM": "TL-G4FJ-P2B",
"LINK_RSLT_DTL": "정상",
"REG_APLY_SE_NM": "신조차",
"OCTHT_ERSR_PRVNTC_AVTSMT_YMD": null,
"OWNR_ADDR_DTL": "춘천타워 602호",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"ISSU_NO": null,
"INSP_VLD_PD_END_YMD": "2027-07-14",
"CARMDL_ASORT_NM": "승용 소형",
"CHCK_VLD_PD_BGNG_YMD": null,
"USGSRHLD_DONG_NM": "조양동",
"REG_DTL_NM": "일반소유용",
"NOPLT_SPCFCT_NM": "긴번호판",
"FBCTN_YMD": "2023-07-05",
"RPRSV_OWNR_IDECNO": "880606-6789012",
"FRST_REG_YMD": "2023-07-15",
"RPRS_OWNR_NM": "강원철",
"PRCS_IMPRTY_RSN_DTLS": "운행차량",
"FRST_REG_APLY_RCPT_NO": "5100-202307-007890",
"PRVNTC_CNT": "1",
"RPRS_OWNR_MBR_SE_CD": "11",
"TAXXMPT_APLCN_SE_CD": "미적용",
"ERSR_REG_YMD": null,
"COLOR_CD": "06",
"STRCT_CHG_CNT": "0",
"INDVDL_BZMN_YN": "N",
"VIN": "KMHEE00EEEE888888",
"USG_SE_CD": "2",
"XPORT_FLFL_YN_DCLR_YMD": null,
"VHRNO": "88아8888",
"INSP_VLD_PD_BGNG_YMD": "2023-07-15",
"ATMB_NM": "투싼"
}
]
}

@ -0,0 +1,123 @@
{
"data": [
{
"VHMNO": "KMHFF00FFFF999999-01",
"MTRS_FOM_NM": "T4LA",
"PRCS_IMPRTY_RSN_CD": "00",
"FRST_TRNSFR_YMD": "2024-09-01",
"MRTG_CNT": "1",
"COLOR_NM": "백색",
"ERSR_REG_SE_CD": null,
"CHCK_VLD_PD_END_YMD": null,
"NOPLT_CSDY_YN": "N",
"USG_SE_NM": "영업용",
"VEAG_END_YMD": null,
"record": [
{
"CHG_YMD": "2024-09-01",
"VHMNO": "KMHFF00FFFF999999-01",
"SPCABL_MTTR": "성명(상호) : 상품용차량 320-90-87890\n주소 : 대구광역시 중구 동성로 123",
"CHG_TASK_SE_NM": "신규등록(신조차)",
"FLAG": null,
"SZR_RMV_DTL_SN": "1",
"MAIN_NO": "1-1",
"DTL_SN": "1",
"CHG_TASK_SE_CD": "01",
"SNO": null,
"APLY_RCPT_NO": "2700-202409-009012",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "상품용차량",
"HSHLDR_IDECNO": "320-90-87890"
},
{
"CHG_YMD": "2024-10-15",
"VHMNO": "KMHFF00FFFF999999-01",
"SPCABL_MTTR": "채권최고액 : 50,000,000원\n채권자 : 우리은행 대구지점\n채무자 : 상품용차량",
"CHG_TASK_SE_NM": "저당권설정",
"FLAG": null,
"SZR_RMV_DTL_SN": "2",
"MAIN_NO": "(1-2)",
"DTL_SN": "2",
"CHG_TASK_SE_CD": "31",
"SNO": null,
"APLY_RCPT_NO": "2700-241015-010123",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "",
"HSHLDR_IDECNO": ""
},
{
"CHG_YMD": "2024-12-01",
"VHMNO": "KMHFF00FFFF999999-01",
"SPCABL_MTTR": "촉탁기관 : 대구시청 구분:압류\r압류관리번호:2700-20241201-000789\n\r압류내역 : 대구시청 주정차위반과태료[2024-01-300002] 교통관리과-11111 \n\r촉탁일자 : 2024-12-01",
"CHG_TASK_SE_NM": "압류등록(압류)",
"FLAG": null,
"SZR_RMV_DTL_SN": "3",
"MAIN_NO": "(1-3)",
"DTL_SN": "3",
"CHG_TASK_SE_CD": "41",
"SNO": null,
"APLY_RCPT_NO": "2700-241201-000789",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "",
"HSHLDR_IDECNO": ""
}
],
"TAXXMPT_TRPR_SE_CD": "0",
"SPCABL_MTTR_CNT": "2",
"DRVNG_DSTNC": "12300",
"NOPLT_CSDY_AVTSMT_YMD": null,
"ERSR_REG_SE_NM": null,
"OWNR_ADDR": "대구광역시 중구 동성로 123",
"DRIV_SRGBTRY_IDNTF_NO": null,
"YRIDNW": "2024",
"SPMNNO_1": "A01",
"BSS_USE_PD_YMD": null,
"LINK_RSLT_CD": "MSG50000",
"SPMNNO_2": "1-00090-1123-1456",
"SZR_CNT": "1",
"CARMDL_ASORT_CD": "1",
"USGSRHLD_ADDR_1": "대구광역시 중구 동성로 123",
"USGSRHLD_ADDR_DTL_1": "대구빌딩 8층",
"RPRS_OWNR_TELNO": "0539990000",
"NOPLT_SPCFCT_CD": "2",
"REG_DTL_CD": "100",
"ACQS_AMT": "52,800,000",
"FOM_NM": "LX2-T4LA-P4E",
"LINK_RSLT_DTL": "정상",
"REG_APLY_SE_NM": "신조차",
"OCTHT_ERSR_PRVNTC_AVTSMT_YMD": null,
"OWNR_ADDR_DTL": "대구빌딩 8층",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"ISSU_NO": null,
"INSP_VLD_PD_END_YMD": "2028-09-01",
"CARMDL_ASORT_NM": "승용 대형",
"CHCK_VLD_PD_BGNG_YMD": null,
"USGSRHLD_DONG_NM": "동인동",
"REG_DTL_NM": "일반소유용",
"NOPLT_SPCFCT_NM": "긴번호판",
"FBCTN_YMD": "2024-08-20",
"RPRSV_OWNR_IDECNO": "320-90-87890",
"FRST_REG_YMD": "2024-09-01",
"RPRS_OWNR_NM": "상품용차량",
"PRCS_IMPRTY_RSN_DTLS": "운행차량",
"FRST_REG_APLY_RCPT_NO": "2700-202409-009012",
"PRVNTC_CNT": "0",
"RPRS_OWNR_MBR_SE_CD": "13",
"TAXXMPT_APLCN_SE_CD": "미적용",
"ERSR_REG_YMD": null,
"COLOR_CD": "02",
"STRCT_CHG_CNT": "0",
"INDVDL_BZMN_YN": "N",
"VIN": "KMHFF00FFFF999999",
"USG_SE_CD": "1",
"XPORT_FLFL_YN_DCLR_YMD": null,
"VHRNO": "99자9999",
"INSP_VLD_PD_BGNG_YMD": "2024-09-01",
"ATMB_NM": "팰리세이드"
}
]
}

@ -0,0 +1,106 @@
{
"data": [
{
"VHMNO": "KMHGG00GGGG000000-01",
"MTRS_FOM_NM": "G4NC",
"PRCS_IMPRTY_RSN_CD": "00",
"FRST_TRNSFR_YMD": "2022-10-10",
"MRTG_CNT": "0",
"COLOR_NM": "검정",
"ERSR_REG_SE_CD": null,
"CHCK_VLD_PD_END_YMD": null,
"NOPLT_CSDY_YN": "N",
"USG_SE_NM": "자가용",
"VEAG_END_YMD": null,
"record": [
{
"CHG_YMD": "2022-10-10",
"VHMNO": "KMHGG00GGGG000000-01",
"SPCABL_MTTR": "성명(상호) : 윤하나 950707-7890234\n주소 : 충청남도 천안시 동남구 만남로 1",
"CHG_TASK_SE_NM": "신규등록(신조차)",
"FLAG": null,
"SZR_RMV_DTL_SN": "1",
"MAIN_NO": "1-1",
"DTL_SN": "1",
"CHG_TASK_SE_CD": "01",
"SNO": null,
"APLY_RCPT_NO": "4300-202210-011234",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "윤하나",
"HSHLDR_IDECNO": "950707-7890234"
},
{
"CHG_YMD": "2023-05-20",
"VHMNO": "KMHGG00GGGG000000-01",
"SPCABL_MTTR": "양수인 : 김영호 870808-8901345\n주소 : 충청남도 천안시 서북구 성정로 50",
"CHG_TASK_SE_NM": "이전등록",
"FLAG": null,
"SZR_RMV_DTL_SN": "2",
"MAIN_NO": "1-2",
"DTL_SN": "2",
"CHG_TASK_SE_CD": "21",
"SNO": null,
"APLY_RCPT_NO": "4300-230520-012345",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"HSHLDR_NM": "김영호",
"HSHLDR_IDECNO": "870808-8901345"
}
],
"TAXXMPT_TRPR_SE_CD": "0",
"SPCABL_MTTR_CNT": "0",
"DRVNG_DSTNC": "45800",
"NOPLT_CSDY_AVTSMT_YMD": null,
"ERSR_REG_SE_NM": null,
"OWNR_ADDR": "충청남도 천안시 서북구 성정로 50",
"DRIV_SRGBTRY_IDNTF_NO": null,
"YRIDNW": "2022",
"SPMNNO_1": "A01",
"BSS_USE_PD_YMD": null,
"LINK_RSLT_CD": "MSG50000",
"SPMNNO_2": "1-00001-1234-1567",
"SZR_CNT": "0",
"CARMDL_ASORT_CD": "1",
"USGSRHLD_ADDR_1": "충청남도 천안시 서북구 성정로 50",
"USGSRHLD_ADDR_DTL_1": "성정아파트 101동 704호",
"RPRS_OWNR_TELNO": "01000001111",
"NOPLT_SPCFCT_CD": "2",
"REG_DTL_CD": "100",
"ACQS_AMT": "29,500,000",
"FOM_NM": "DL3-G4NC-P2C",
"LINK_RSLT_DTL": "정상",
"REG_APLY_SE_NM": "신조차",
"OCTHT_ERSR_PRVNTC_AVTSMT_YMD": null,
"OWNR_ADDR_DTL": "성정아파트 101동 704호",
"LEDGER_INDIV_NO": "1",
"LEDGER_GROUP_NO": "1",
"ISSU_NO": null,
"INSP_VLD_PD_END_YMD": "2026-10-09",
"CARMDL_ASORT_NM": "승용 중형",
"CHCK_VLD_PD_BGNG_YMD": null,
"USGSRHLD_DONG_NM": "성정동",
"REG_DTL_NM": "일반소유용",
"NOPLT_SPCFCT_NM": "긴번호판",
"FBCTN_YMD": "2022-10-01",
"RPRSV_OWNR_IDECNO": "870808-8901345",
"FRST_REG_YMD": "2022-10-10",
"RPRS_OWNR_NM": "김영호",
"PRCS_IMPRTY_RSN_DTLS": "운행차량",
"FRST_REG_APLY_RCPT_NO": "4300-202210-011234",
"PRVNTC_CNT": "0",
"RPRS_OWNR_MBR_SE_CD": "11",
"TAXXMPT_APLCN_SE_CD": "미적용",
"ERSR_REG_YMD": null,
"COLOR_CD": "01",
"STRCT_CHG_CNT": "0",
"INDVDL_BZMN_YN": "N",
"VIN": "KMHGG00GGGG000000",
"USG_SE_CD": "2",
"XPORT_FLFL_YN_DCLR_YMD": null,
"VHRNO": "00차0000",
"INSP_VLD_PD_BGNG_YMD": "2022-10-10",
"ATMB_NM": "K5"
}
]
}
Loading…
Cancel
Save