diff --git a/src/main/java/go/kr/project/common/service/VehicleInfoService.java b/src/main/java/go/kr/project/common/service/VehicleInfoService.java
new file mode 100644
index 0000000..56f2581
--- /dev/null
+++ b/src/main/java/go/kr/project/common/service/VehicleInfoService.java
@@ -0,0 +1,61 @@
+package go.kr.project.common.service;
+
+import go.kr.project.externalApi.vo.VehicleApiResponseVO;
+
+import java.util.List;
+
+/**
+ * 차량 정보 조회 서비스 인터페이스
+ *
+ *
이 인터페이스는 차량 정보를 조회하는 두 가지 구현체를 추상화합니다:
+ *
+ * - InternalVehicleInfoServiceImpl: 내부 VMIS 모듈을 직접 호출 (vmis.integration.mode=internal)
+ * - ExternalVehicleInfoServiceImpl: 외부 REST API를 호출 (vmis.integration.mode=external)
+ *
+ *
+ * 설정 방법:
+ *
+ * # application.yml
+ * vmis:
+ * integration:
+ * mode: internal # 또는 external
+ *
+ *
+ * 사용 예시:
+ *
+ * {@code
+ * @Autowired
+ * private VehicleInfoService vehicleInfoService;
+ *
+ * // 단일 차량 조회
+ * VehicleApiResponseVO response = vehicleInfoService.getVehicleInfo("12가3456");
+ *
+ * // 여러 차량 일괄 조회
+ * List responses = vehicleInfoService.getVehiclesInfo(
+ * Arrays.asList("12가3456", "34나5678")
+ * );
+ * }
+ *
+ */
+public interface VehicleInfoService {
+
+ /**
+ * 단일 차량번호에 대한 정보 조회
+ *
+ * 차량 기본정보와 등록원부 정보를 함께 조회합니다.
+ *
+ * @param vehicleNumber 차량번호
+ * @return 차량 정보 응답 (기본정보 + 등록원부 정보)
+ */
+ VehicleApiResponseVO getVehicleInfo(String vehicleNumber);
+
+ /**
+ * 여러 차량번호에 대한 정보를 일괄 조회
+ *
+ * 각 차량에 대해 기본정보와 등록원부 정보를 함께 조회합니다.
+ *
+ * @param vehicleNumbers 차량번호 리스트
+ * @return 차량 정보 응답 리스트
+ */
+ List getVehiclesInfo(List vehicleNumbers);
+}
diff --git a/src/main/java/go/kr/project/externalApi/service/ExternalVehicleInfoServiceImpl.java b/src/main/java/go/kr/project/externalApi/service/ExternalVehicleInfoServiceImpl.java
new file mode 100644
index 0000000..9b921a6
--- /dev/null
+++ b/src/main/java/go/kr/project/externalApi/service/ExternalVehicleInfoServiceImpl.java
@@ -0,0 +1,84 @@
+package go.kr.project.externalApi.service;
+
+import go.kr.project.common.service.VehicleInfoService;
+import go.kr.project.externalApi.vo.VehicleApiResponseVO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 외부 REST API를 호출하는 차량 정보 조회 서비스 구현체
+ *
+ * 이 구현체는 외부 VMIS-interface 서버의 REST API를 호출하여
+ * 차량 정보를 조회합니다. 기존 ExternalVehicleApiService를 그대로 활용합니다.
+ *
+ * 활성화 조건:
+ *
+ * # application.yml
+ * vmis:
+ * integration:
+ * mode: external
+ *
+ *
+ * 처리 흐름:
+ *
+ * - 기존 ExternalVehicleApiService에 요청 위임
+ * - ExternalVehicleApiService가 외부 REST API 호출
+ * - 결과를 그대로 반환
+ *
+ *
+ * 외부 API 서버:
+ *
+ * - 서버 URL: vmis.external.api.url 설정값 사용
+ * - 기본값: http://localhost:8081/api/v1/vehicles
+ * - 별도의 VMIS-interface 프로젝트가 실행 중이어야 함
+ *
+ *
+ * @see VehicleInfoService
+ * @see ExternalVehicleApiService
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "external", matchIfMissing = true)
+public class ExternalVehicleInfoServiceImpl implements VehicleInfoService {
+
+ private final ExternalVehicleApiService externalVehicleApiService;
+
+ @Override
+ public VehicleApiResponseVO getVehicleInfo(String vehicleNumber) {
+ log.info("[External Mode] 차량 정보 조회 시작 - 차량번호: {}", vehicleNumber);
+
+ VehicleApiResponseVO response = externalVehicleApiService.getVehicleInfo(vehicleNumber);
+
+ if (response.isSuccess()) {
+ log.info("[External Mode] 차량번호 {} 조회 성공", vehicleNumber);
+ } else {
+ log.warn("[External Mode] 차량번호 {} 조회 실패 - {}", vehicleNumber, response.getMessage());
+ }
+
+ return response;
+ }
+
+ @Override
+ public List getVehiclesInfo(List vehicleNumbers) {
+ log.info("[External Mode] 차량 정보 일괄 조회 시작 - 대상 차량 수: {}", vehicleNumbers.size());
+
+ List responses = externalVehicleApiService.getVehiclesInfo(vehicleNumbers);
+
+ int successCount = 0;
+ for (VehicleApiResponseVO response : responses) {
+ if (response.isSuccess()) {
+ successCount++;
+ }
+ }
+
+ log.info("[External Mode] 차량 정보 일괄 조회 완료 - 성공: {}, 실패: {}",
+ successCount, vehicleNumbers.size() - successCount);
+
+ return responses;
+ }
+}
diff --git a/src/main/java/go/kr/project/vmis/client/GovernmentApiClient.java b/src/main/java/go/kr/project/vmis/client/GovernmentApiClient.java
index 60d815e..8843fb4 100644
--- a/src/main/java/go/kr/project/vmis/client/GovernmentApiClient.java
+++ b/src/main/java/go/kr/project/vmis/client/GovernmentApiClient.java
@@ -11,6 +11,7 @@ import go.kr.project.vmis.model.ledger.LedgerRequest;
import go.kr.project.vmis.model.ledger.LedgerResponse;
import go.kr.project.vmis.util.TxIdUtil;
import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpStatusCodeException;
@@ -62,7 +63,7 @@ import java.nio.charset.StandardCharsets;
public class GovernmentApiClient implements GovernmentApi {
/**
- * Spring RestTemplate
+ * Spring RestTemplate (VMIS 정부 API 전용)
*
* HTTP 클라이언트로서 실제 네트워크 통신을 수행합니다.
* 이 객체는 Spring Bean으로 주입되며, 설정에 따라 다음을 포함할 수 있습니다:
@@ -74,6 +75,7 @@ public class GovernmentApiClient implements GovernmentApi {
* 인터셉터 (로깅, 헤더 추가 등)
*
*/
+ @Qualifier("vmisRestTemplate")
private final RestTemplate restTemplate;
/**
diff --git a/src/main/java/go/kr/project/vmis/config/HttpClientConfig.java b/src/main/java/go/kr/project/vmis/config/HttpClientConfig.java
index 4ff063d..2be084a 100644
--- a/src/main/java/go/kr/project/vmis/config/HttpClientConfig.java
+++ b/src/main/java/go/kr/project/vmis/config/HttpClientConfig.java
@@ -14,12 +14,13 @@ import org.springframework.web.client.RestTemplate;
/**
* HttpClient 설정
* Apache HttpClient 4를 사용한 RestTemplate 구성
+ * VMIS 정부 API 전용 RestTemplate (vmisRestTemplate)
*/
@Configuration
public class HttpClientConfig {
- @Bean
- public RestTemplate restTemplate(VmisProperties props, RestTemplateBuilder builder) {
+ @Bean(name = "vmisRestTemplate")
+ public RestTemplate vmisRestTemplate(VmisProperties props, RestTemplateBuilder builder) {
VmisProperties.GovProps gov = props.getGov();
int connectTimeout = gov.getConnectTimeoutMillis();
int readTimeout = gov.getReadTimeoutMillis();
diff --git a/src/main/java/go/kr/project/vmis/config/VmisIntegrationConfig.java b/src/main/java/go/kr/project/vmis/config/VmisIntegrationConfig.java
new file mode 100644
index 0000000..d89d003
--- /dev/null
+++ b/src/main/java/go/kr/project/vmis/config/VmisIntegrationConfig.java
@@ -0,0 +1,113 @@
+package go.kr.project.vmis.config;
+
+import go.kr.project.common.service.VehicleInfoService;
+import go.kr.project.vmis.config.properties.VmisProperties;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * VMIS 통합 설정 및 모니터링
+ *
+ * 이 설정 클래스는 VMIS 통합 모드에 대한 정보를 제공하고,
+ * 애플리케이션 시작 시 현재 활성화된 모드를 로그로 출력합니다.
+ *
+ * 주요 기능:
+ *
+ * - 애플리케이션 시작 시 VMIS 통합 모드 출력
+ * - 활성화된 VehicleInfoService 구현체 표시
+ * - 설정 검증 및 경고 메시지 출력
+ *
+ *
+ * 지원하는 모드:
+ *
+ * - internal: 내부 VMIS 모듈 직접 호출 (InternalVehicleInfoServiceImpl)
+ * - external: 외부 REST API 호출 (ExternalVehicleInfoServiceImpl)
+ *
+ */
+@Slf4j
+@Configuration
+@RequiredArgsConstructor
+public class VmisIntegrationConfig {
+
+ private final VmisProperties vmisProperties;
+
+ /**
+ * VMIS 통합 모드 정보 출력
+ *
+ * 애플리케이션 시작 시 현재 설정된 VMIS 통합 모드와
+ * 관련 설정 정보를 로그로 출력합니다.
+ *
+ * @param vehicleInfoService 활성화된 VehicleInfoService 구현체
+ * @return CommandLineRunner
+ */
+ @Bean
+ public CommandLineRunner vmisIntegrationModeLogger(VehicleInfoService vehicleInfoService) {
+ return args -> {
+ String mode = vmisProperties.getIntegration().getMode();
+ String implClass = vehicleInfoService.getClass().getSimpleName();
+
+ log.info("========================================");
+ log.info("VMIS Integration Mode: {}", mode);
+ log.info("Active Implementation: {}", implClass);
+ log.info("========================================");
+
+ if ("internal".equalsIgnoreCase(mode)) {
+ logInternalModeInfo();
+ } else if ("external".equalsIgnoreCase(mode)) {
+ logExternalModeInfo();
+ } else {
+ log.warn("알 수 없는 VMIS 통합 모드: {}. 'internal' 또는 'external'을 사용하세요.", mode);
+ }
+ };
+ }
+
+ /**
+ * Internal 모드 설정 정보 출력
+ */
+ private void logInternalModeInfo() {
+ log.info("[Internal Mode] 내부 VMIS 모듈을 직접 사용합니다");
+ log.info(" - 정부 API 호스트: {}://{}",
+ vmisProperties.getGov().getScheme(),
+ vmisProperties.getGov().getHost());
+ log.info(" - 기본사항 조회 경로: {}",
+ vmisProperties.getGov().getServices().getBasic().getPath());
+ log.info(" - 등록원부 조회 경로: {}",
+ vmisProperties.getGov().getServices().getLedger().getPath());
+ log.info(" - GPKI 암호화: {}",
+ vmisProperties.getGpki().getEnabled());
+ log.info(" - 연결 타임아웃: {}ms",
+ vmisProperties.getGov().getConnectTimeoutMillis());
+ log.info(" - 읽기 타임아웃: {}ms",
+ vmisProperties.getGov().getReadTimeoutMillis());
+
+ if ("Y".equalsIgnoreCase(vmisProperties.getGpki().getEnabled())) {
+ log.info(" - GPKI 인증서 서버 ID: {}",
+ vmisProperties.getGpki().getCertServerId());
+ log.info(" - GPKI 대상 서버 ID: {}",
+ vmisProperties.getGpki().getTargetServerId());
+ } else {
+ log.warn(" - GPKI 암호화가 비활성화되어 있습니다. 개발 환경에서만 사용하세요.");
+ }
+ }
+
+ /**
+ * External 모드 설정 정보 출력
+ */
+ private void logExternalModeInfo() {
+ log.info("[External Mode] 외부 REST API를 사용합니다");
+ log.info(" - 외부 API URL: {}",
+ vmisProperties.getExternal().getApi().getUrl());
+ log.info(" - 연결 타임아웃: {}ms",
+ vmisProperties.getExternal().getApi().getConnectTimeoutMillis());
+ log.info(" - 읽기 타임아웃: {}ms",
+ vmisProperties.getExternal().getApi().getReadTimeoutMillis());
+ log.warn(" - 외부 VMIS-interface 서버가 실행 중이어야 합니다.");
+ log.info(" - 기본사항 조회: POST {}/basic",
+ vmisProperties.getExternal().getApi().getUrl());
+ log.info(" - 등록원부 조회: POST {}/ledger",
+ vmisProperties.getExternal().getApi().getUrl());
+ }
+}
diff --git a/src/main/java/go/kr/project/vmis/config/properties/VmisProperties.java b/src/main/java/go/kr/project/vmis/config/properties/VmisProperties.java
index 74b5961..aed67d5 100644
--- a/src/main/java/go/kr/project/vmis/config/properties/VmisProperties.java
+++ b/src/main/java/go/kr/project/vmis/config/properties/VmisProperties.java
@@ -9,19 +9,57 @@ import org.springframework.validation.annotation.Validated;
@Validated
public class VmisProperties {
+ @NotNull
+ private IntegrationProps integration = new IntegrationProps();
@NotNull
private SystemProps system = new SystemProps();
@NotNull
private GpkiProps gpki = new GpkiProps();
@NotNull
private GovProps gov = new GovProps();
+ @NotNull
+ private ExternalProps external = new ExternalProps();
+ public IntegrationProps getIntegration() { return integration; }
+ public void setIntegration(IntegrationProps integration) { this.integration = integration; }
public SystemProps getSystem() { return system; }
public void setSystem(SystemProps system) { this.system = system; }
public GpkiProps getGpki() { return gpki; }
public void setGpki(GpkiProps gpki) { this.gpki = gpki; }
public GovProps getGov() { return gov; }
public void setGov(GovProps gov) { this.gov = gov; }
+ public ExternalProps getExternal() { return external; }
+ public void setExternal(ExternalProps external) { this.external = external; }
+
+ public static class IntegrationProps {
+ @NotBlank
+ private String mode = "external";
+
+ public String getMode() { return mode; }
+ public void setMode(String mode) { this.mode = mode; }
+ }
+
+ public static class ExternalProps {
+ @NotNull
+ private ApiProps api = new ApiProps();
+
+ public ApiProps getApi() { return api; }
+ public void setApi(ApiProps api) { this.api = api; }
+
+ public static class ApiProps {
+ @NotBlank
+ private String url = "http://localhost:8081/api/v1/vehicles";
+ private int connectTimeoutMillis = 5000;
+ private int readTimeoutMillis = 10000;
+
+ public String getUrl() { return url; }
+ public void setUrl(String url) { this.url = url; }
+ public int getConnectTimeoutMillis() { return connectTimeoutMillis; }
+ public void setConnectTimeoutMillis(int connectTimeoutMillis) { this.connectTimeoutMillis = connectTimeoutMillis; }
+ public int getReadTimeoutMillis() { return readTimeoutMillis; }
+ public void setReadTimeoutMillis(int readTimeoutMillis) { this.readTimeoutMillis = readTimeoutMillis; }
+ }
+ }
public static class SystemProps {
@NotBlank
diff --git a/src/main/java/go/kr/project/vmis/service/InternalVehicleInfoServiceImpl.java b/src/main/java/go/kr/project/vmis/service/InternalVehicleInfoServiceImpl.java
new file mode 100644
index 0000000..43ec36e
--- /dev/null
+++ b/src/main/java/go/kr/project/vmis/service/InternalVehicleInfoServiceImpl.java
@@ -0,0 +1,219 @@
+package go.kr.project.vmis.service;
+
+import go.kr.project.common.service.VehicleInfoService;
+import go.kr.project.externalApi.vo.VehicleApiResponseVO;
+import go.kr.project.externalApi.vo.VehicleBasicInfoVO;
+import go.kr.project.externalApi.vo.VehicleLedgerVO;
+import go.kr.project.vmis.model.basic.BasicRequest;
+import go.kr.project.vmis.model.basic.BasicResponse;
+import go.kr.project.vmis.model.common.Envelope;
+import go.kr.project.vmis.model.ledger.LedgerRequest;
+import go.kr.project.vmis.model.ledger.LedgerResponse;
+import go.kr.project.vmis.util.VehicleResponseMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 내부 VMIS 모듈을 직접 호출하는 차량 정보 조회 서비스 구현체
+ *
+ * 이 구현체는 외부 REST API 호출 없이 내부 VMIS 모듈을 직접 사용하여
+ * 정부 시스템과 통신합니다. 네트워크 오버헤드가 없어 성능이 향상됩니다.
+ *
+ * 활성화 조건:
+ *
+ * # application.yml
+ * vmis:
+ * integration:
+ * mode: internal
+ *
+ *
+ * 처리 흐름:
+ *
+ * - 차량번호를 받아 BasicRequest, LedgerRequest 생성
+ * - CarBassMatterInqireService.basic() 호출 (기본정보)
+ * - CarLedgerFrmbkService.ledger() 호출 (등록원부)
+ * - 내부 모델을 외부 VO로 변환 (VehicleResponseMapper 사용)
+ * - VehicleApiResponseVO로 결과 반환
+ *
+ *
+ * @see VehicleInfoService
+ * @see CarBassMatterInqireService
+ * @see CarLedgerFrmbkService
+ */
+@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 VehicleApiResponseVO getVehicleInfo(String vehicleNumber) {
+ log.info("[Internal Mode] 차량 정보 조회 시작 - 차량번호: {}", vehicleNumber);
+
+ VehicleApiResponseVO response = new VehicleApiResponseVO();
+ response.setVhrno(vehicleNumber);
+
+ try {
+ // 1. 차량 기본정보 조회
+ VehicleBasicInfoVO basicInfo = getBasicInfo(vehicleNumber);
+ response.setBasicInfo(basicInfo);
+
+ // 2. 자동차 등록원부 조회
+ VehicleLedgerVO ledgerInfo = getLedgerInfo(vehicleNumber);
+ response.setLedgerInfo(ledgerInfo);
+
+ // 3. 결과 검증
+ if (basicInfo != null && "00".equals(basicInfo.getCntcResultCode())) {
+ response.setSuccess(true);
+ response.setMessage("조회 성공");
+ log.info("[Internal Mode] 차량번호 {} 조회 성공", vehicleNumber);
+ } else {
+ response.setSuccess(false);
+ response.setMessage(basicInfo != null ? basicInfo.getCntcResultDtls() : "조회 실패");
+ log.warn("[Internal Mode] 차량번호 {} 조회 실패 - {}", vehicleNumber, response.getMessage());
+ }
+
+ } catch (Exception e) {
+ response.setSuccess(false);
+ response.setMessage("내부 API 호출 오류: " + e.getMessage());
+ log.error("[Internal Mode] 차량번호 {} 내부 API 호출 중 오류 발생", vehicleNumber, e);
+ }
+
+ return response;
+ }
+
+ @Override
+ public List getVehiclesInfo(List vehicleNumbers) {
+ log.info("[Internal Mode] 차량 정보 일괄 조회 시작 - 대상 차량 수: {}", vehicleNumbers.size());
+
+ List responses = new ArrayList();
+
+ for (String vehicleNumber : vehicleNumbers) {
+ try {
+ VehicleApiResponseVO response = getVehicleInfo(vehicleNumber);
+ responses.add(response);
+ } catch (Exception e) {
+ log.error("[Internal Mode] 차량번호 {} 조회 중 오류 발생: {}", vehicleNumber, e.getMessage(), e);
+
+ // 오류 발생 시에도 응답 객체 생성하여 추가
+ VehicleApiResponseVO errorResponse = VehicleApiResponseVO.builder()
+ .vhrno(vehicleNumber)
+ .success(false)
+ .message("조회 실패: " + e.getMessage())
+ .build();
+ responses.add(errorResponse);
+ }
+ }
+
+ log.info("[Internal Mode] 차량 정보 일괄 조회 완료 - 성공: {}, 실패: {}",
+ countSuccessful(responses),
+ vehicleNumbers.size() - countSuccessful(responses));
+
+ return responses;
+ }
+
+ /**
+ * 차량 기본정보 조회 (내부 모듈 직접 호출)
+ *
+ * @param vehicleNumber 차량번호
+ * @return 차량 기본정보 VO
+ */
+ private VehicleBasicInfoVO getBasicInfo(String vehicleNumber) {
+ log.debug("[Internal Mode] 차량 기본정보 조회 - 차량번호: {}", vehicleNumber);
+
+ // 요청 객체 생성 - 차량번호만 설정 (나머지는 RequestEnricher가 자동 설정)
+ BasicRequest request = new BasicRequest();
+ request.setVhrno(vehicleNumber);
+
+ // Envelope로 감싸기
+ Envelope requestEnvelope = new Envelope();
+ requestEnvelope.setData(Collections.singletonList(request));
+
+ try {
+ // 내부 서비스 호출
+ ResponseEntity> responseEntity =
+ carBassMatterInqireService.basic(requestEnvelope);
+
+ if (responseEntity.getBody() != null &&
+ responseEntity.getBody().getData() != null &&
+ !responseEntity.getBody().getData().isEmpty()) {
+
+ BasicResponse basicResponse = responseEntity.getBody().getData().get(0);
+
+ // 내부 모델을 외부 VO로 변환
+ return VehicleResponseMapper.toVehicleBasicInfoVO(basicResponse);
+ }
+
+ log.warn("[Internal Mode] 차량 기본정보 조회 응답이 비어있음 - 차량번호: {}", vehicleNumber);
+ return null;
+
+ } catch (Exception e) {
+ log.error("[Internal Mode] 차량 기본정보 조회 실패 - 차량번호: {}", vehicleNumber, e);
+ throw new RuntimeException("차량 기본정보 조회 실패: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 자동차 등록원부(갑) 조회 (내부 모듈 직접 호출)
+ *
+ * @param vehicleNumber 차량번호
+ * @return 등록원부 정보 VO
+ */
+ private VehicleLedgerVO getLedgerInfo(String vehicleNumber) {
+ log.debug("[Internal Mode] 자동차 등록원부 조회 - 차량번호: {}", vehicleNumber);
+
+ // 요청 객체 생성 - 차량번호만 설정 (나머지는 RequestEnricher가 자동 설정)
+ LedgerRequest request = new LedgerRequest();
+ request.setVhrno(vehicleNumber);
+
+ // Envelope로 감싸기
+ Envelope requestEnvelope = new Envelope();
+ requestEnvelope.setData(Collections.singletonList(request));
+
+ try {
+ // 내부 서비스 호출
+ ResponseEntity> responseEntity =
+ carLedgerFrmbkService.ledger(requestEnvelope);
+
+ if (responseEntity.getBody() != null &&
+ responseEntity.getBody().getData() != null &&
+ !responseEntity.getBody().getData().isEmpty()) {
+
+ LedgerResponse ledgerResponse = responseEntity.getBody().getData().get(0);
+
+ // 내부 모델을 외부 VO로 변환
+ return VehicleResponseMapper.toLedgerVO(ledgerResponse);
+ }
+
+ log.warn("[Internal Mode] 자동차 등록원부 조회 응답이 비어있음 - 차량번호: {}", vehicleNumber);
+ return null;
+
+ } catch (Exception e) {
+ log.error("[Internal Mode] 자동차 등록원부 조회 실패 - 차량번호: {}", vehicleNumber, e);
+ throw new RuntimeException("자동차 등록원부 조회 실패: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 성공한 응답 개수 계산
+ */
+ private int countSuccessful(List responses) {
+ int count = 0;
+ for (VehicleApiResponseVO response : responses) {
+ if (response.isSuccess()) {
+ count++;
+ }
+ }
+ return count;
+ }
+}
diff --git a/src/main/java/go/kr/project/vmis/util/VehicleResponseMapper.java b/src/main/java/go/kr/project/vmis/util/VehicleResponseMapper.java
new file mode 100644
index 0000000..635c3c4
--- /dev/null
+++ b/src/main/java/go/kr/project/vmis/util/VehicleResponseMapper.java
@@ -0,0 +1,165 @@
+package go.kr.project.vmis.util;
+
+import go.kr.project.externalApi.vo.VehicleBasicInfoVO;
+import go.kr.project.externalApi.vo.VehicleLedgerVO;
+import go.kr.project.vmis.model.basic.BasicResponse;
+import go.kr.project.vmis.model.ledger.LedgerResponse;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 차량 응답 모델 변환 유틸리티
+ *
+ * 내부 VMIS 모델(BasicResponse, LedgerResponse)을
+ * 외부 API VO(VehicleBasicInfoVO, VehicleLedgerVO)로 변환합니다.
+ */
+public class VehicleResponseMapper {
+
+ /**
+ * BasicResponse를 VehicleBasicInfoVO로 변환
+ *
+ * @param basicResponse 내부 기본정보 응답
+ * @return 외부 API용 기본정보 VO
+ */
+ public static VehicleBasicInfoVO toVehicleBasicInfoVO(BasicResponse basicResponse) {
+ if (basicResponse == null) {
+ return null;
+ }
+
+ VehicleBasicInfoVO vo = new VehicleBasicInfoVO();
+ vo.setCntcResultCode(basicResponse.getCntcResultCode());
+ vo.setCntcResultDtls(basicResponse.getCntcResultDtls());
+
+ // record 변환
+ if (basicResponse.getRecord() != null && !basicResponse.getRecord().isEmpty()) {
+ List records = new ArrayList();
+ for (BasicResponse.Record src : basicResponse.getRecord()) {
+ VehicleBasicInfoVO.Record dest = new VehicleBasicInfoVO.Record();
+
+ // 주요 필드 매핑
+ dest.setVhrno(src.getVhrno());
+ dest.setVin(src.getVin());
+ dest.setCnm(src.getCnm());
+ dest.setMberNm(src.getMberNm());
+ dest.setMberSeNo(src.getMberSeNo());
+ dest.setMberSeCode(src.getMberSeCode());
+ dest.setTelno(src.getTelno());
+ dest.setPrye(src.getPrye());
+ dest.setRegistDe(src.getRegistDe());
+ dest.setFrstRegistDe(src.getFrstRegistDe());
+ dest.setColorNm(src.getColorNm());
+ dest.setDsplvl(src.getDsplvl());
+ dest.setVhctyAsortNm(src.getVhctyAsortNm());
+ dest.setVhctyTyNm(src.getVhctyTyNm());
+ dest.setVhctySeNm(src.getVhctySeNm());
+ dest.setInsptValidPdBgnde(src.getInsptValidPdBgnde());
+ dest.setInsptValidPdEndde(src.getInsptValidPdEndde());
+ dest.setUsgsrhldAdresFull(src.getUsgsrhldAdresFull());
+ dest.setOwnerAdresFull(src.getOwnerAdresFull());
+
+ // 추가 필드
+ dest.setErsrRegistSeCode(src.getErsrRegistSeCode());
+ dest.setErsrRegistSeNm(src.getErsrRegistSeNm());
+ dest.setErsrRegistDe(src.getErsrRegistDe());
+ dest.setRegistDetailCode(src.getRegistDetailCode());
+ dest.setUseStrnghldLegaldongCode(src.getUseStrnghldLegaldongCode());
+ dest.setUseStrnghldAdstrdCode(src.getUseStrnghldAdstrdCode());
+ dest.setUseStrnghldMntn(src.getUseStrnghldMntn());
+ dest.setUseStrnghldLnbr(src.getUseStrnghldLnbr());
+ dest.setUseStrnghldHo(src.getUseStrnghldHo());
+ dest.setUseStrnghldAdresNm(src.getUseStrnghldAdresNm());
+ dest.setVhcleTotWt(src.getVhcleTotWt());
+ dest.setCaagEndde(src.getCaagEndde());
+ dest.setVhctyAsortCode(src.getVhctyAsortCode());
+ dest.setVhctyTyCode(src.getVhctyTyCode());
+ dest.setVhctySeCode(src.getVhctySeCode());
+ dest.setMxmmLdg(src.getMxmmLdg());
+ dest.setFomNm(src.getFomNm());
+ dest.setUseFuelCode(src.getUseFuelCode());
+ dest.setPrposSeCode(src.getPrposSeCode());
+ dest.setMtrsFomNm(src.getMtrsFomNm());
+ dest.setAcqsAmount(src.getAcqsAmount());
+ dest.setTkcarPscapCo(src.getTkcarPscapCo());
+ dest.setSpmnno(src.getSpmnno());
+ dest.setTrvlDstnc(src.getTrvlDstnc());
+
+ records.add(dest);
+ }
+ vo.setRecord(records);
+ }
+
+ return vo;
+ }
+
+ /**
+ * LedgerResponse를 VehicleLedgerVO로 변환
+ *
+ * @param ledgerResponse 내부 등록원부 응답
+ * @return 외부 API용 등록원부 VO
+ */
+ public static VehicleLedgerVO toLedgerVO(LedgerResponse ledgerResponse) {
+ if (ledgerResponse == null) {
+ return null;
+ }
+
+ VehicleLedgerVO vo = new VehicleLedgerVO();
+
+ // 연계 결과
+ vo.setCntcResultCode(ledgerResponse.getCntcResultCode());
+ vo.setCntcResultDtls(ledgerResponse.getCntcResultDtls());
+
+ // 마스터 정보 (VehicleLedgerVO에 실제 존재하는 필드만 설정)
+ vo.setLedgerGroupNo(ledgerResponse.getLedgerGroupNo());
+ vo.setLedgerIndvdlzNo(ledgerResponse.getLedgerIndvdlzNo());
+ vo.setVhmno(ledgerResponse.getVhmno());
+ vo.setVhrno(ledgerResponse.getVhrno());
+ vo.setVin(ledgerResponse.getVin());
+ vo.setVhctyAsortCode(ledgerResponse.getVhctyAsortCode());
+ vo.setVhctyAsortNm(ledgerResponse.getVhctyAsortNm());
+ vo.setCnm(ledgerResponse.getCnm());
+ vo.setColorNm(ledgerResponse.getColorNm());
+ vo.setPrposSeCode(ledgerResponse.getPrposSeCode());
+ vo.setPrposSeNm(ledgerResponse.getPrposSeNm());
+ vo.setMtrsFomNm(ledgerResponse.getMtrsFomNm());
+ vo.setFomNm(ledgerResponse.getFomNm());
+ vo.setAcqsAmount(ledgerResponse.getAcqsAmount());
+ vo.setFrstRegistDe(ledgerResponse.getFrstRegistDe());
+ vo.setCaagEndde(ledgerResponse.getCaagEndde());
+ vo.setPrye(ledgerResponse.getPrye());
+ vo.setYblMd(ledgerResponse.getYblMd());
+ vo.setTrvlDstnc(ledgerResponse.getTrvlDstnc());
+ vo.setInsptValidPdBgnde(ledgerResponse.getInsptValidPdBgnde());
+ vo.setInsptValidPdEndde(ledgerResponse.getInsptValidPdEndde());
+ vo.setNmplCsdyAt(ledgerResponse.getNmplCsdyAt());
+ vo.setErsrRegistDe(ledgerResponse.getErsrRegistDe());
+ vo.setErsrRegistSeCode(ledgerResponse.getErsrRegistSeCode());
+ vo.setErsrRegistSeNm(ledgerResponse.getErsrRegistSeNm());
+ vo.setAdres(ledgerResponse.getAdres());
+ vo.setAdresNm(ledgerResponse.getAdresNm());
+ vo.setTelno(ledgerResponse.getTelno());
+ vo.setMberNm(ledgerResponse.getMberNm());
+ vo.setMberSeCode(ledgerResponse.getMberSeCode());
+ vo.setMberSeNo(ledgerResponse.getMberSeNo());
+
+ // record 변경내역 변환
+ if (ledgerResponse.getRecord() != null && !ledgerResponse.getRecord().isEmpty()) {
+ List records = new ArrayList();
+ for (LedgerResponse.Record src : ledgerResponse.getRecord()) {
+ VehicleLedgerVO.Record dest = new VehicleLedgerVO.Record();
+ dest.setMainchk(src.getMainchk());
+ dest.setChangeJobSeCode(src.getChangeJobSeCode());
+ dest.setMainno(src.getMainno());
+ dest.setSubno(src.getSubno());
+ dest.setDtls(src.getDtls());
+ dest.setChangeDe(src.getChangeDe());
+ dest.setGubunNm(src.getGubunNm());
+ records.add(dest);
+ }
+ vo.setRecord(records);
+ }
+
+ return vo;
+ }
+}