diff --git a/build.gradle b/build.gradle
index c831be6..46ddf16 100644
--- a/build.gradle
+++ b/build.gradle
@@ -140,10 +140,6 @@ dependencies {
// 파라미터 바인딩된 SQL 쿼리 로깅을 위한 datasource-proxy
implementation 'net.ttddyy:datasource-proxy:1.10.1'
- // ===== VMIS 통합 관련 의존성 =====
- // GPKI 암호화 라이브러리 (정부 API 연동)
- implementation files('lib/libgpkiapi_jni_1.5.jar')
-
// ===== 개발 도구 의존성 =====
// Lombok - 반복 코드 생성 도구 (Getter, Setter, Builder 등 자동 생성)
compileOnly 'org.projectlombok:lombok'
diff --git a/docs/자동차과태료_비교로직_정리.md b/docs/자동차과태료_비교로직_정리.md
new file mode 100644
index 0000000..f16eb8c
--- /dev/null
+++ b/docs/자동차과태료_비교로직_정리.md
@@ -0,0 +1,195 @@
+# 자동차 과태료 비교 로직 명세서
+
+## 개요
+
+자동차 과태료 부과 대상을 검증하기 위한 비교 로직 정의서입니다.
+
+### 기본 설정
+
+- **API 선택**: YML flag 값에 따라 구/신 API 호출 결정
+- **통합 모델**: 구/신 API 응답을 통일된 model object로 처리
+ - 구 API: 자동차기본정보 API
+ - 신 API: 자동차기본정보 API, 자동차등록원부(갑)
+- **통합 오브젝트**: 자동차기본정보(구, 신)만 필요
+
+### 처리 규칙
+
+> **중요**: 순서가 중요함!
+> - 조건에 걸리는 순간 다음 차량번호 비교로 진행
+> - 각 비교 로직별로 개별 API 호출 수행
+
+---
+
+## 비교 로직 상세
+
+### 1. 상품용 검증
+
+**필요 API**: 자동차등록원부(갑)
+
+#### API 호출 순서
+
+| 순서 | API | 입력 파라미터 | 출력 데이터 |
+|------|-----|--------------|-------------|
+| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` |
+| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘일자` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` |
+| 3 | 자동차등록원본(갑) | `2.차량번호`, `2.성명`, `2.민원인주민번호`, `2.민원인법정동코드` | 갑부 상세 List |
+
+#### 비교 조건
+
+```java
+// 조건 1: 소유자명에 '상품용' 포함 여부
+api.MBER_NM.contains("상품용")
+
+// 조건 2: 갑부 상세 목록에서 명의이전 이력 확인
+for (LedgerRecord record : 갑부상세List) {
+ if (record.CHG_YMD >= TB_CAR_FFNLG_TRGT.유효기간만료일
+ && record.CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자
+ && record.CHANGE_JOB_SE_CODE == "11") { // 11 = 명의이전 코드
+ return true;
+ }
+}
+```
+
+#### 결과 처리
+
+- **비고 컬럼**: `"[상품용] 갑부정보"`
+
+---
+
+### 2. 이첩 검증 (이첩-1, 이첩-2 병합 로직)
+
+**필요 API**: 자동차기본정보
+
+#### 부과기준일 결정
+
+```java
+int dayCnt = TB_CAR_FFNLG_TRGT.DAYCNT; // textFile 일수
+
+if (dayCnt > 115) {
+ // 이첩-2
+ 부과기준일 = TB_CAR_FFNLG_TRGT.검사종료일자.plusDays(115);
+} else {
+ // 이첩-1
+ 부과기준일 = TB_CAR_FFNLG_TRGT.검사일자;
+}
+```
+
+#### API 호출
+
+```java
+// 부과기준일 기준으로 자동차기본정보 API 호출
+BasicResponse response = 자동차기본정보API.call(부과기준일, 차량번호);
+```
+
+#### 법정동코드 비교 로직 (공통)
+
+```java
+/**
+ * 이첩 조건: 법정동코드 불일치 검증
+ * 사용본거지법정동코드 앞 4자리 != 사용자 조직코드 앞 4자리
+ */
+private boolean checkTransferCondition_LegalDongMismatch(
+ BasicResponse.Record basicInfo,
+ String userId,
+ String vhclno
+) {
+ String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode();
+
+ // 1. 법정동코드 유효성 검사
+ if (useStrnghldLegaldongCode == null || useStrnghldLegaldongCode.length() < 4) {
+ log.debug("[이첩] 법정동코드 없음. 차량번호: {}", vhclno);
+ return false;
+ }
+
+ // 2. 사용자 정보 조회
+ SystemUserVO userInfo = userMapper.selectUser(userId);
+ if (userInfo == null || userInfo.getOrgCd() == null) {
+ log.debug("[이첩] 사용자 정보 없음. 사용자ID: {}", userId);
+ return false;
+ }
+
+ // 3. 법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리 비교
+ String legalDong4 = useStrnghldLegaldongCode.substring(0, 4);
+ String userOrgCd = userInfo.getOrgCd();
+ String userOrg4 = userOrgCd.length() >= 4
+ ? userOrgCd.substring(0, 4)
+ : userOrgCd;
+
+ // 4. 일치 여부 판단
+ if (legalDong4.equals(userOrg4)) {
+ log.debug("[이첩] 법정동코드 일치. 차량번호: {}, 법정동: {}, 조직: {}",
+ vhclno, legalDong4, userOrg4);
+ return false; // 일치하면 이첩 대상 아님
+ }
+
+ log.info("[이첩] 법정동코드 불일치! 차량번호: {}, 법정동: {}, 조직: {}",
+ vhclno, legalDong4, userOrg4);
+ return true; // 불일치하면 이첩 대상
+}
+```
+
+#### 결과 처리
+
+| 구분 | 조건 | 비고 컬럼 형식 |
+|------|------|---------------|
+| 이첩-1 | `DAYCNT <= 115` | `"서울시 용산구/ 이경호, 검사일사용본거지, [검사대상, 사용자 조직코드 앞 4자리 및 법정동명]"` |
+| 이첩-2 | `DAYCNT > 115` | `"전라남도 순천시 / 김정대, 115일 도래지, [2개의 api 법정동코드 및 법정동명]"` |
+
+---
+
+## 데이터 모델
+
+### TB_CAR_FFNLG_TRGT (과태료 대상 테이블)
+
+| 컬럼명 | 설명 | 용도 |
+|--------|------|------|
+| 검사일 | 검사 기준일 | API 호출 파라미터 |
+| 검사종료일자 | 검사 종료 일자 | 115일 계산 기준 |
+| 유효기간만료일 | 유효기간 만료일 | 상품용 갑부 비교 시작일 |
+| DAYCNT | textFile 일수 | 이첩-1/2 분기 조건 |
+| 비고 | 검증 결과 메시지 | 결과 저장 |
+
+### 코드 정의
+
+| 코드 | 코드값 | 설명 |
+|------|--------|------|
+| CHANGE_JOB_SE_CODE | 11 | 명의이전 |
+
+---
+
+## 처리 흐름도
+
+```
+시작
+ │
+ ▼
+[차량번호 조회]
+ │
+ ▼
+[1. 상품용 검증] ──(조건 충족)──> [비고 기록] ──> [다음 차량]
+ │
+ │ (조건 미충족)
+ ▼
+[2. DAYCNT 확인]
+ │
+ ├─ (> 115) ──> [이첩-2: 115일 도래지 기준]
+ │
+ └─ (<= 115) ──> [이첩-1: 검사일 기준]
+ │
+ ▼
+[법정동코드 비교]
+ │
+ ├─ (불일치) ──> [비고 기록] ──> [다음 차량]
+ │
+ └─ (일치) ──> [다음 차량]
+```
+
+---
+
+## 구현 시 주의사항
+
+1. **API 호출 순서 준수**: 각 검증 단계별로 필요한 API만 호출
+2. **조건 우선순위**: 상품용 > 이첩 순서로 검증
+3. **조기 종료**: 조건 충족 시 즉시 다음 차량으로 이동
+4. **비고 컬럼**: 각 조건별 정해진 형식으로 기록
+5. **법정동코드 길이 검증**: 최소 4자리 이상 필요
diff --git a/lib/libgpkiapi_jni_1.5.jar b/lib/libgpkiapi_jni_1.5.jar
deleted file mode 100644
index 5e3bdea..0000000
Binary files a/lib/libgpkiapi_jni_1.5.jar and /dev/null differ
diff --git a/src/main/java/go/kr/project/api/config/ApiMapperConfig.java b/src/main/java/go/kr/project/api/config/ApiMapperConfig.java
deleted file mode 100644
index 5059938..0000000
--- a/src/main/java/go/kr/project/api/config/ApiMapperConfig.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package go.kr.project.api.config;
-
-import org.mybatis.spring.annotation.MapperScan;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * API 전용 MyBatis Mapper 스캔 설정
- *
- *
VMIS API 통합 모듈의 Mapper 인터페이스만 스캔합니다.
- *
- *
- * DataSource: egovframework 기본 설정 사용 (DataSourceProxyConfig)
- * TransactionManager: egovframework 기본 설정 사용 (EgovConfigTransaction.txManager)
- * SqlSessionFactory: MyBatis Spring Boot Starter가 자동 생성
- * MapperScan: go.kr.project.api.internal.mapper만 스캔 (API 전용)
- *
- *
- * 일반 프로젝트의 Mapper는 egovframework 설정에서 별도로 스캔됩니다.
- */
-@Configuration
-@MapperScan(basePackages = "go.kr.project.api.internal.mapper")
-public class ApiMapperConfig {
-}
diff --git a/src/main/java/go/kr/project/api/config/RestTemplateConfig.java b/src/main/java/go/kr/project/api/config/RestTemplateConfig.java
index 771c537..ab73c83 100644
--- a/src/main/java/go/kr/project/api/config/RestTemplateConfig.java
+++ b/src/main/java/go/kr/project/api/config/RestTemplateConfig.java
@@ -22,9 +22,7 @@ import java.io.IOException;
/**
* RestTemplate 설정 클래스
- * VMIS Integration Mode에 따라 다른 설정 적용
- * - Internal Mode: 정부 API 호출용 (빠른 타임아웃)
- * - External Mode: 외부 VMIS-interface API 호출용 (여유 있는 타임아웃)
+ * 외부 VMIS-interface API 호출용 설정
* Apache HttpClient 4 기반 연결 풀 관리 및 타임아웃 설정
*/
@Slf4j
@@ -36,17 +34,9 @@ public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
- // VMIS Integration Mode에 따라 적절한 설정 선택
- String mode = vmisProperties.getIntegration().getMode();
- VmisProperties.RestTemplateProps.ModeConfig config;
+ VmisProperties.RestTemplateProps config = vmisProperties.getRestTemplate();
- if ("internal".equalsIgnoreCase(mode)) {
- config = vmisProperties.getRestTemplate().getInternal();
- log.info("RestTemplate 설정 - Internal Mode (정부 API 호출용)");
- } else {
- config = vmisProperties.getRestTemplate().getExternal();
- log.info("RestTemplate 설정 - External Mode (외부 VMIS-interface API 호출용)");
- }
+ log.info("RestTemplate 설정 - 외부 VMIS-interface API 호출용");
// 타임아웃 설정
int connectTimeout = config.getTimeout().getConnectTimeoutMillis();
diff --git a/src/main/java/go/kr/project/api/config/VmisIntegrationConfig.java b/src/main/java/go/kr/project/api/config/VmisIntegrationConfig.java
deleted file mode 100644
index bc880ab..0000000
--- a/src/main/java/go/kr/project/api/config/VmisIntegrationConfig.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package go.kr.project.api.config;
-
-import go.kr.project.api.config.properties.VmisProperties;
-import go.kr.project.api.service.VehicleInfoService;
-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.getRestTemplate().getInternal().getTimeout().getConnectTimeoutMillis());
- log.info(" - 읽기 타임아웃: {}ms",
- vmisProperties.getRestTemplate().getInternal().getTimeout().getReadTimeoutMillis());
- log.info(" - Rate Limit: 초당 {} 건",
- vmisProperties.getRestTemplate().getInternal().getRateLimit().getPermitsPerSecond());
-
- 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 Base URL: {}",
- vmisProperties.getExternal().getApi().getUrl().getBase());
- log.info(" - 연결 타임아웃: {}ms",
- vmisProperties.getRestTemplate().getExternal().getTimeout().getConnectTimeoutMillis());
- log.info(" - 읽기 타임아웃: {}ms",
- vmisProperties.getRestTemplate().getExternal().getTimeout().getReadTimeoutMillis());
- log.info(" - Rate Limit: 초당 {} 건",
- vmisProperties.getRestTemplate().getExternal().getRateLimit().getPermitsPerSecond());
- log.warn(" - 외부 VMIS-interface 서버가 실행 중이어야 합니다.");
- log.info(" - 기본사항 조회: POST {}",
- vmisProperties.getExternal().getApi().getUrl().buildBasicUrl());
- log.info(" - 등록원부 조회: POST {}",
- vmisProperties.getExternal().getApi().getUrl().buildLedgerUrl());
- }
-}
diff --git a/src/main/java/go/kr/project/api/config/properties/VmisProperties.java b/src/main/java/go/kr/project/api/config/properties/VmisProperties.java
index 499bad6..138e65a 100644
--- a/src/main/java/go/kr/project/api/config/properties/VmisProperties.java
+++ b/src/main/java/go/kr/project/api/config/properties/VmisProperties.java
@@ -2,35 +2,23 @@ package go.kr.project.api.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
+@Component
@ConfigurationProperties(prefix = "vmis")
@Validated
public class VmisProperties {
- @NotNull
- private IntegrationProps integration = new IntegrationProps();
@NotNull
private RestTemplateProps restTemplate = new RestTemplateProps();
@NotNull
- private SystemProps system = new SystemProps();
- @NotNull
- private GpkiProps gpki = new GpkiProps();
- @NotNull
- private GovProps gov = new GovProps();
- @NotNull
private ExternalProps external = new ExternalProps();
- @Data
- public static class IntegrationProps {
- @NotBlank
- private String mode = "external";
- }
-
@Data
public static class ExternalProps {
@NotNull
@@ -50,7 +38,7 @@ public class VmisProperties {
@NotBlank
private String ledger = "/ledger"; // 자동차등록원부 엔드포인트 경로
- // 중요: 외부 VMIS-interface 호출용 전체 URL 조합 헬퍼 (공통 RestTemplate 타임아웃 설정 사용)
+ // 외부 VMIS-interface 호출용 전체 URL 조합 헬퍼
public String buildBasicUrl() { return join(base, basic); }
public String buildLedgerUrl() { return join(base, ledger); }
@@ -69,125 +57,30 @@ public class VmisProperties {
}
}
- @Data
- public static class SystemProps {
- @NotBlank
- private String infoSysId;
- /** INFO_SYS_IP */
- private String infoSysIp;
- /** 시군구코드 (SIGUNGU_CODE) */
- private String sigunguCode;
- private String departmentCode;
- // 담당자 정보
- private String chargerId;
- private String chargerIp;
- private String chargerNm;
- }
-
- @Data
- public static class GpkiProps {
- /** "Y" 또는 "N" */
- @NotBlank
- private String enabled = "N";
- private boolean useSign = true;
- @NotBlank
- private String charset = "UTF-8";
- @NotBlank
- private String certServerId;
- @NotBlank
- private String targetServerId;
- // Optional advanced config for native GPKI util
- private Boolean ldap; // null -> util default
- private String gpkiLicPath; // e.g., C:/gpki2/gpkisecureweb/conf
- private String certFilePath; // directory for target cert files when LDAP=false
- private String envCertFilePathName; // ..._env.cer
- private String envPrivateKeyFilePathName; // ..._env.key
- private String envPrivateKeyPasswd;
- private String sigCertFilePathName; // ..._sig.cer
- private String sigPrivateKeyFilePathName; // ..._sig.key
- private String sigPrivateKeyPasswd;
-
- public boolean isEnabledFlag() { return "Y".equalsIgnoreCase(enabled); }
- }
-
@Data
public static class RestTemplateProps {
@NotNull
- private ModeConfig internal = new ModeConfig();
+ private TimeoutConfig timeout = new TimeoutConfig();
@NotNull
- private ModeConfig external = new ModeConfig();
-
- @Data
- public static class ModeConfig {
- @NotNull
- private TimeoutConfig timeout = new TimeoutConfig();
- @NotNull
- private ConnectionPoolConfig connectionPool = new ConnectionPoolConfig();
- @NotNull
- private RateLimitConfig rateLimit = new RateLimitConfig();
-
- @Data
- public static class TimeoutConfig {
- private int connectTimeoutMillis = 10000;
- private int readTimeoutMillis = 12000;
- }
-
- @Data
- public static class ConnectionPoolConfig {
- private int maxTotal = 100;
- private int maxPerRoute = 20;
- }
-
- @Data
- public static class RateLimitConfig {
- private double permitsPerSecond = 5.0;
- }
- }
- }
-
- @Data
- public static class GovProps {
- @NotBlank
- private String scheme = "http";
- @NotBlank
- private String host;
- @NotBlank
- private String basePath;
+ private ConnectionPoolConfig connectionPool = new ConnectionPoolConfig();
@NotNull
- private Services services = new Services();
+ private RateLimitConfig rateLimit = new RateLimitConfig();
- public String buildServiceUrl(String path) {
- StringBuilder sb = new StringBuilder();
- sb.append(scheme).append("://").append(host);
- if (basePath != null && !basePath.isEmpty()) {
- if (!basePath.startsWith("/")) sb.append('/');
- sb.append(basePath);
- }
- if (path != null && !path.isEmpty()) {
- if (!path.startsWith("/")) sb.append('/');
- sb.append(path);
- }
- return sb.toString();
+ @Data
+ public static class TimeoutConfig {
+ private int connectTimeoutMillis = 10000;
+ private int readTimeoutMillis = 12000;
}
@Data
- public static class Services {
- @NotNull
- private Service basic = new Service();
- @NotNull
- private Service ledger = new Service();
+ public static class ConnectionPoolConfig {
+ private int maxTotal = 100;
+ private int maxPerRoute = 20;
}
@Data
- public static class Service {
- @NotBlank
- private String path;
- @NotBlank
- private String cntcInfoCode;
- @NotBlank
- private String apiKey;
- @NotBlank
- private String cvmisApikey;
+ public static class RateLimitConfig {
+ private double permitsPerSecond = 5.0;
}
}
}
diff --git a/src/main/java/go/kr/project/api/controller/VehicleInterfaceController.java b/src/main/java/go/kr/project/api/controller/VehicleInterfaceController.java
index 011d5e2..00ec930 100644
--- a/src/main/java/go/kr/project/api/controller/VehicleInterfaceController.java
+++ b/src/main/java/go/kr/project/api/controller/VehicleInterfaceController.java
@@ -1,12 +1,11 @@
package go.kr.project.api.controller;
import go.kr.project.api.model.Envelope;
-import go.kr.project.api.model.VehicleApiResponseVO;
import go.kr.project.api.model.request.BasicRequest;
import go.kr.project.api.model.request.LedgerRequest;
import go.kr.project.api.model.response.BasicResponse;
import go.kr.project.api.model.response.LedgerResponse;
-import go.kr.project.api.service.VehicleInfoService;
+import go.kr.project.api.service.ExternalVehicleApiService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
@@ -47,45 +46,10 @@ import java.util.Collections;
@RequestMapping("/api/v1/vehicles")
@RequiredArgsConstructor
@Slf4j
-@Tag(name = "VMIS 차량정보 (Swagger)", description = "vmis.integration.mode에 따라 내부/외부 분기 호출")
+@Tag(name = "VMIS 차량정보 (Swagger)", description = "외부 호출")
public class VehicleInterfaceController {
- private final VehicleInfoService vehicleInfoService; // 모드별 구현체 자동 주입
-
- /**
- * 자동차 기본정보 + 등록원부(갑) 통합 조회
- * - Internal/External 공통 진입점 (VehicleInfoService 사용)
- * - 요청은 Envelope 형식으로 VHRNO(차량번호) 및 추가 파라미터 포함
- */
- @PostMapping(value = "/info.ajax", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
- @Operation(
- summary = "자동차 통합 조회 (기본+등록원부)",
- description = "vmis.integration.mode 값에 따라 내부 모듈 또는 외부 REST API를 통해 통합 조회 수행",
- requestBody = @RequestBody(
- content = @Content(
- mediaType = MediaType.APPLICATION_JSON_VALUE,
- examples = @ExampleObject(
- name = "통합 조회 예제",
- value = "{\"data\": [{\"VHRNO\": \"12가3456\",\"LEVY_STDDE\": \"20250101\"}]}"
- )
- )
- )
- )
- public ResponseEntity> info(
- @org.springframework.web.bind.annotation.RequestBody Envelope envelope
- ) {
- // 중요 로직: Swagger 요청 Envelope에서 BasicRequest 추출 (차량번호 및 필수 파라미터 포함)
- BasicRequest request = (envelope != null && !envelope.getData().isEmpty()) ? envelope.getData().get(0) : null;
- if (request == null || request.getVhrno() == null || request.getVhrno().trim().isEmpty()) {
- // 간단한 검증 실패 시 빈 데이터로 반환
- return ResponseEntity.ok(new Envelope<>(Collections.emptyList()));
- }
-
- // VehicleInfoService는 모드에 따라 구현체가 자동 주입됨
- VehicleApiResponseVO resp = vehicleInfoService.getVehicleInfo(request);
- Envelope out = new Envelope<>(resp);
- return ResponseEntity.ok(out);
- }
+ private final ExternalVehicleApiService service;
/**
* 자동차 기본사항만 조회
@@ -115,7 +79,7 @@ public class VehicleInterfaceController {
}
// VehicleInfoService는 모드에 따라 구현체가 자동 주입되어 분기 처리
- BasicResponse basic = vehicleInfoService.getBasicInfo(request);
+ BasicResponse basic = service.getBasicInfo(request);
Envelope out = (basic != null) ? new Envelope<>(basic) : new Envelope<>(Collections.emptyList());
return ResponseEntity.ok(out);
}
@@ -147,8 +111,7 @@ public class VehicleInterfaceController {
return ResponseEntity.ok(new Envelope<>(Collections.emptyList()));
}
- // VehicleInfoService는 모드에 따라 구현체가 자동 주입되어 분기 처리
- LedgerResponse ledger = vehicleInfoService.getLedgerInfo(request);
+ LedgerResponse ledger = service.getLedgerInfo(request);
Envelope out = (ledger != null) ? new Envelope<>(ledger) : new Envelope<>(Collections.emptyList());
return ResponseEntity.ok(out);
}
diff --git a/src/main/java/go/kr/project/api/external/service/impl/ExternalVehicleInfoServiceImpl.java b/src/main/java/go/kr/project/api/external/service/impl/ExternalVehicleInfoServiceImpl.java
deleted file mode 100644
index 0b87a98..0000000
--- a/src/main/java/go/kr/project/api/external/service/impl/ExternalVehicleInfoServiceImpl.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package go.kr.project.api.external.service.impl;
-
-import go.kr.project.api.external.service.ExternalVehicleApiService;
-import go.kr.project.api.model.VehicleApiResponseVO;
-import go.kr.project.api.model.request.BasicRequest;
-import go.kr.project.api.model.request.LedgerRequest;
-import go.kr.project.api.model.response.BasicResponse;
-import go.kr.project.api.model.response.LedgerResponse;
-import go.kr.project.api.service.VehicleInfoService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.stereotype.Service;
-
-/**
- * 외부 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 extends EgovAbstractServiceImpl implements VehicleInfoService {
-
- private final ExternalVehicleApiService externalVehicleApiService;
-
- @Override
- public VehicleApiResponseVO getVehicleInfo(BasicRequest basicRequest) {
- String vehicleNumber = basicRequest.getVhrno();
- log.info("[External Mode] 차량 정보 조회 시작 - 차량번호: {}, 부과기준일: {}, 조회구분: {}",
- vehicleNumber, basicRequest.getLevyStdde(), basicRequest.getInqireSeCode());
-
- VehicleApiResponseVO response = externalVehicleApiService.getVehicleInfo(basicRequest);
-
- if (response.isSuccess()) {
- log.info("[External Mode] 차량번호 {} 조회 성공", vehicleNumber);
- } else {
- log.warn("[External Mode] 차량번호 {} 조회 실패 - {}", vehicleNumber, response.getMessage());
- }
-
- return response;
- }
-
- /**
- * 외부 REST - 기본정보 단독 조회
- * 중요 로직: 외부 API 호출은 ExternalVehicleApiService에 위임 (BasicRequest 전체 전달)
- */
- @Override
- public BasicResponse getBasicInfo(BasicRequest request) {
- // 중요 로직: 외부 API 호출은 ExternalVehicleApiService에 위임
- return externalVehicleApiService.getBasicInfo(request);
- }
-
- /**
- * 외부 REST - 등록원부 단독 조회
- * 중요 로직: 등록원부 조회는 LedgerRequest 전체를 받아서 외부 API에 전달
- */
- @Override
- public LedgerResponse getLedgerInfo(LedgerRequest request) {
- // 중요 로직: 외부 API 호출은 ExternalVehicleApiService에 위임
- return externalVehicleApiService.getLedgerInfo(request);
- }
-}
diff --git a/src/main/java/go/kr/project/api/internal/client/GovernmentApi.java b/src/main/java/go/kr/project/api/internal/client/GovernmentApi.java
deleted file mode 100644
index 0a7199f..0000000
--- a/src/main/java/go/kr/project/api/internal/client/GovernmentApi.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package go.kr.project.api.internal.client;
-
-import go.kr.project.api.model.Envelope;
-import go.kr.project.api.model.request.BasicRequest;
-import go.kr.project.api.model.request.LedgerRequest;
-import go.kr.project.api.model.response.BasicResponse;
-import go.kr.project.api.model.response.LedgerResponse;
-import org.springframework.http.ResponseEntity;
-
-/**
- * 정부 시스템 연계 API 추상화 인터페이스.
- *
- * 외부 정부 시스템과의 통신 계약을 명확히 하여 테스트 용이성과
- * 추후 교체 가능성을 높입니다.
- */
-public interface GovernmentApi {
-
- ResponseEntity> callBasic(Envelope envelope);
-
- ResponseEntity> callLedger(Envelope envelope);
-}
diff --git a/src/main/java/go/kr/project/api/internal/client/GovernmentApiClient.java b/src/main/java/go/kr/project/api/internal/client/GovernmentApiClient.java
deleted file mode 100644
index c753bb5..0000000
--- a/src/main/java/go/kr/project/api/internal/client/GovernmentApiClient.java
+++ /dev/null
@@ -1,627 +0,0 @@
-package go.kr.project.api.internal.client;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import go.kr.project.api.config.properties.VmisProperties;
-import go.kr.project.api.internal.gpki.GpkiService;
-import go.kr.project.api.internal.util.TxIdUtil;
-import go.kr.project.api.model.Envelope;
-import go.kr.project.api.model.request.BasicRequest;
-import go.kr.project.api.model.request.LedgerRequest;
-import go.kr.project.api.model.response.BasicResponse;
-import go.kr.project.api.model.response.LedgerResponse;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.http.*;
-import org.springframework.stereotype.Component;
-import org.springframework.web.client.HttpStatusCodeException;
-import org.springframework.web.client.RestTemplate;
-
-import java.nio.charset.StandardCharsets;
-
-/**
- * 정부 시스템 API 클라이언트
- *
- * 이 클래스는 시군구연계 자동차 정보 조회를 위해 정부 시스템의 API를 호출하는
- * 클라이언트 역할을 수행합니다. HTTP 통신, 암호화, 에러 처리 등 정부 API와의
- * 모든 상호작용을 캡슐화합니다.
- *
- * 주요 책임:
- *
- * 정부 API 엔드포인트로 HTTP 요청 전송
- * GPKI(행정전자서명) 암호화/복호화 처리
- * 필수 HTTP 헤더 구성 및 관리
- * 요청/응답 데이터의 JSON 직렬화/역직렬화
- * 트랜잭션 ID(tx_id) 생성 및 추적
- * 네트워크 오류 및 HTTP 에러 처리
- * 상세한 로깅을 통한 디버깅 지원
- *
- *
- * 아키텍처 패턴:
- *
- * Adapter 패턴: 외부 정부 시스템 API를 내부 인터페이스로 변환
- * Template Method 패턴: callModel 메서드가 공통 흐름을 정의
- * Dependency Injection: 생성자를 통한 의존성 주입
- *
- *
- * 보안 특성:
- *
- * GPKI 암호화를 통한 데이터 보안 (선택적 활성화)
- * API 키 기반 인증
- * 기관 식별 정보(INFO_SYS_ID, REGION_CODE 등)를 헤더에 포함
- *
- *
- * @see RestTemplate
- * @see GpkiService
- * @see VmisProperties
- */
-@Slf4j
-@RequiredArgsConstructor
-@Component
-public class GovernmentApiClient implements GovernmentApi {
-
- /**
- * Spring RestTemplate (통합 RestTemplate 사용)
- *
- * HTTP 클라이언트로서 실제 네트워크 통신을 수행합니다.
- * 이 객체는 Spring Bean으로 주입되며, 설정에 따라 다음을 포함할 수 있습니다:
- *
- * Connection Timeout 설정
- * Read Timeout 설정
- * Connection Pool 관리
- * Rate Limiting (초당 요청 수 제한)
- * 메시지 컨버터 (Jackson for JSON)
- * 인터셉터 (로깅, 헤더 추가 등)
- *
- */
- private final RestTemplate restTemplate;
-
- /**
- * VMIS 설정 속성
- *
- * application.yml 또는 application.properties에서 로드된 설정값들입니다.
- * 포함되는 주요 설정:
- *
- * 정부 API URL (호스트, 포트, 경로)
- * API 키 및 인증 정보
- * 시스템 식별 정보 (INFO_SYS_ID, REGION_CODE 등)
- * GPKI 설정 (인증서 서버 ID 등)
- *
- */
- private final VmisProperties props;
-
- /**
- * GPKI(행정전자서명) 서비스
- *
- * 정부24 등 공공기관 간 통신에 사용되는 암호화 서비스입니다.
- * 주요 기능:
- *
- * 요청 데이터 암호화 (공개키 암호화)
- * 응답 데이터 복호화 (개인키 복호화)
- * 전자서명 생성 및 검증
- * 암호화 활성화 여부 확인
- *
- *
- * 암호화가 비활성화된 경우 평문(Plain Text)으로 통신합니다.
- */
- private final GpkiService gpkiService;
-
- /**
- * Jackson ObjectMapper
- *
- * Java 객체와 JSON 문자열 간의 변환을 담당합니다.
- * 주요 역할:
- *
- * 요청 객체를 JSON 문자열로 직렬화 (Serialization)
- * 응답 JSON을 Java 객체로 역직렬화 (Deserialization)
- * 제네릭 타입 처리 (TypeReference 사용)
- * 날짜/시간 포맷 변환
- * null 값 처리
- *
- *
- * Spring Boot가 자동 구성한 ObjectMapper를 주입받아 사용하므로
- * 전역 설정(날짜 포맷, 네이밍 전략 등)이 일관되게 적용됩니다.
- */
- private final ObjectMapper objectMapper;
-
-
- /**
- * 서비스 타입 열거형
- *
- * 정부 API 서비스의 종류를 구분하는 열거형입니다.
- * 각 서비스 타입은 서로 다른 엔드포인트와 API 키를 가집니다.
- *
- * 서비스 타입:
- *
- * BASIC: 자동차 기본사항 조회 서비스
- *
- * 차량번호로 기본 정보(소유자, 차종, 용도 등) 조회
- * 비교적 간단한 정보 제공
- * 응답 속도가 빠름
- *
- *
- * LEDGER: 자동차 등록원부(갑) 조회 서비스
- *
- * 상세한 등록 정보 및 법적 권리관계 조회
- * 저당권, 압류, 소유권 이전 이력 등 포함
- * 민감 정보를 포함하여 권한 검증이 엄격함
- *
- *
- *
- */
- public enum ServiceType {
- /**
- * Basic service type.
- */
- BASIC,
- /**
- * Ledger service type.
- */
- LEDGER }
-
- /**
- * HTTP 헤더 구성
- *
- * 정부 API 호출에 필요한 모든 HTTP 헤더를 구성하는 private 메서드입니다.
- * 정부 시스템은 엄격한 헤더 검증을 수행하므로 모든 필수 헤더가 정확히 포함되어야 합니다.
- *
- * 헤더 구성 항목:
- *
- *
- * 헤더명
- * 설명
- * 예시값
- * 필수여부
- *
- *
- * Content-Type
- * 요청 바디의 미디어 타입 및 문자 인코딩
- * application/json; charset=UTF-8
- * 필수
- *
- *
- * Accept
- * 클라이언트가 수용 가능한 응답 형식
- * application/json
- * 필수
- *
- *
- * gpki_yn
- * GPKI 암호화 사용 여부 (Y/N)
- * Y
- * 필수
- *
- *
- * tx_id
- * 트랜잭션 고유 ID (요청 추적용)
- * 20250104123045_abc123
- * 필수
- *
- *
- * cert_server_id
- * 인증서 서버 식별자
- * VMIS_SERVER_01
- * 필수
- *
- *
- * api_key
- * 서비스별 API 인증 키
- * abc123def456...
- * 필수
- *
- *
- * cvmis_apikey
- * CVMIS 시스템 API 키
- * xyz789uvw012...
- * 필수
- *
- *
- * INFO_SYS_ID
- * 정보시스템 식별자
- * VMIS_SEOUL
- * 필수
- *
- *
- *
- * 문자 인코딩:
- *
- * Content-Type에 UTF-8 인코딩을 명시적으로 지정
- * 한글 데이터 처리를 위해 필수
- * 정부 시스템이 다양한 클라이언트와 호환되도록 표준 인코딩 사용
- *
- *
- * 보안 고려사항:
- *
- * API 키는 설정 파일에서 안전하게 관리
- * 로그에 API 키가 노출되지 않도록 주의
- * 각 서비스(BASIC, LEDGER)마다 별도의 API 키 사용 가능
- *
- *
- * @param svc 서비스 설정 객체 (API 키, 경로 등 포함)
- * @param txId 이번 요청의 트랜잭션 ID
- * @return HttpHeaders 구성된 HTTP 헤더 객체
- */
- private HttpHeaders buildHeaders(VmisProperties.GovProps.Service svc, String txId) {
- // 1. 빈 HttpHeaders 객체 생성
- HttpHeaders headers = new HttpHeaders();
-
- // 2. Content-Type 설정
- // UTF-8 인코딩을 명시하여 한글 데이터가 올바르게 전송되도록 함
- headers.setContentType(new MediaType("application", "json", StandardCharsets.UTF_8));
-
- // 3. Accept 헤더 설정
- // 서버에게 JSON 형식의 응답을 요청함
- headers.setAccept(java.util.Collections.singletonList(MediaType.APPLICATION_JSON));
-
- // 4. GPKI 암호화 사용 여부
- // 정부 서버가 요청 바디 복호화 여부를 결정하는 데 사용
- headers.add("gpki_yn", gpkiService.isEnabled() ? "Y" : "N");
-
- // 5. 트랜잭션 ID
- // 요청 추적, 로그 연관, 문제 해결 시 사용
- headers.add("tx_id", txId);
-
- // 6. 인증서 서버 ID
- // GPKI 인증서를 발급받은 서버의 식별자
- headers.add("cert_server_id", props.getGpki().getCertServerId());
-
- // 7. API 인증 키
- // 서비스별로 다른 API 키 사용 가능 (BASIC과 LEDGER 각각)
- headers.add("api_key", svc.getApiKey());
-
- // 8. CVMIS API 키
- // CVMIS(Car Vehicle Management Information System) 전용 API 키
- headers.add("cvmis_apikey", svc.getCvmisApikey());
-
- // 구성 완료된 헤더 반환
- return headers;
- }
-
- /**
- * 자동차 기본사항 조회 API 호출
- *
- * 타입 안전성이 보장되는 자동차 기본사항 조회 메서드입니다.
- * 내부적으로 {@link #callModel}을 호출하여 실제 통신을 수행합니다.
- *
- * 특징:
- *
- * 제네릭 타입으로 컴파일 타입 타입 체크
- * 요청/응답 객체가 Envelope로 감싸져 있음
- * Jackson TypeReference를 사용한 제네릭 역직렬화
- * API 호출 전후로 DB에 로그성 데이터 저장
- *
- *
- * 처리 흐름:
- *
- * 요청 정보를 DB에 INSERT (로그 저장)
- * 정부 API 호출
- * 응답 정보를 DB에 UPDATE
- * 에러 발생 시 에러 정보도 DB에 UPDATE
- *
- *
- * 사용 예시:
- *
- * BasicRequest request = new BasicRequest();
- * request.setVehicleNo("12가3456");
- *
- * Envelope<BasicRequest> envelope = new Envelope<>();
- * envelope.setData(request);
- *
- * ResponseEntity<Envelope<BasicResponse>> response = govClient.callBasic(envelope);
- * BasicResponse data = response.getBody().getData();
- *
- *
- * @param envelope 자동차 기본사항 조회 요청을 담은 Envelope
- * @return ResponseEntity<Envelope<BasicResponse>> 조회 결과를 담은 응답
- */
- public ResponseEntity> callBasic(Envelope envelope) {
- // 순수한 전송 책임만 수행: DB 로깅은 서비스 레이어에서 처리
- return callModel(ServiceType.BASIC, envelope, new TypeReference>(){});
- }
-
- /**
- * 자동차 등록원부(갑) 조회 API 호출
- *
- * 타입 안전성이 보장되는 자동차 등록원부 조회 메서드입니다.
- * 내부적으로 {@link #callModel}을 호출하여 실제 통신을 수행합니다.
- *
- * 특징:
- *
- * 제네릭 타입으로 컴파일 타임 타입 체크
- * 요청/응답 객체가 Envelope로 감싸져 있음
- * Jackson TypeReference를 사용한 제네릭 역직렬화
- *
- *
- * 사용 예시:
- *
- * LedgerRequest request = new LedgerRequest();
- * request.setVehicleNo("12가3456");
- * request.setOwnerName("홍길동");
- *
- * Envelope<LedgerRequest> envelope = new Envelope<>();
- * envelope.setData(request);
- *
- * ResponseEntity<Envelope<LedgerResponse>> response = govClient.callLedger(envelope);
- * LedgerResponse data = response.getBody().getData();
- *
- *
- * @param envelope 자동차 등록원부 조회 요청을 담은 Envelope
- * @return ResponseEntity<Envelope<LedgerResponse>> 조회 결과를 담은 응답
- */
- public ResponseEntity> callLedger(Envelope envelope) {
- // TypeReference를 사용하여 제네릭 타입 정보 전달
- // 익명 클래스를 생성하여 타입 소거(Type Erasure) 문제 해결
- return callModel(ServiceType.LEDGER, envelope, new TypeReference>(){});
- }
-
- /**
- * 정부 API 호출 (타입 안전 모델 기반)
- *
- * 이 메서드는 정부 API 호출의 핵심 로직을 담고 있는 제네릭 메서드입니다.
- * Java 객체를 받아 JSON으로 변환하고, 암호화하여 전송한 후, 응답을 복호화하여
- * 다시 Java 객체로 변환하는 전체 파이프라인을 처리합니다.
- *
- * Template Method 패턴:
- *
- * 이 메서드는 Template Method 패턴의 템플릿 역할
- * 공통 처리 흐름을 정의하고 서비스별 차이는 파라미터로 처리
- * 코드 중복을 제거하고 일관성을 보장
- *
- *
- * 제네릭 타입 파라미터:
- *
- * <TReq>: 요청 데이터 타입 (BasicRequest 또는 LedgerRequest)
- * <TResp>: 응답 데이터 타입 (BasicResponse 또는 LedgerResponse)
- * 타입 안전성을 보장하여 런타임 에러 방지
- *
- *
- * 처리 흐름 (상세):
- *
- * 설정 로드:
- * 서비스 타입에 따라 BASIC 또는 LEDGER 설정 선택
- *
- * URL 및 트랜잭션 ID 구성:
- * 완전한 API URL 생성
- * 고유 트랜잭션 ID 생성
- *
- * 직렬화 (Serialization):
- * Java 객체(Envelope<TReq>)를 JSON 문자열로 변환
- * ObjectMapper.writeValueAsString() 사용
- *
- * 헤더 구성:
- * buildHeaders() 메서드 호출
- * 모든 필수 헤더 추가
- *
- * GPKI 암호화 (선택적):
- * GPKI가 활성화된 경우 JSON을 암호화
- * gpkiEncrypt() 메서드 호출
- *
- * HTTP 요청 전송:
- * RestTemplate.exchange()로 POST 요청
- * 요청 로그 기록
- *
- * GPKI 복호화 (선택적):
- * 성공 응답(2xx)이고 GPKI가 활성화된 경우
- * gpkiDecrypt() 메서드 호출
- *
- * 역직렬화 (Deserialization):
- * JSON 문자열을 Java 객체(Envelope<TResp>)로 변환
- * TypeReference를 사용하여 제네릭 타입 정보 보존
- *
- * 응답 반환:
- * ResponseEntity로 감싸서 HTTP 정보 포함
- *
- *
- *
- * 에러 처리 전략 (3단계):
- *
- * HttpStatusCodeException (HTTP 에러):
- *
- * 정부 API가 4xx 또는 5xx 상태 코드를 반환한 경우
- * 에러 응답 바디를 파싱하여 Envelope 객체로 변환 시도
- * 파싱 실패 시 빈 Envelope 객체 반환
- * 에러 로그 기록 (WARN 레벨)
- *
- *
- * JSON 파싱 에러:
- *
- * 응답 JSON이 예상한 형식과 다른 경우
- * RuntimeException으로 래핑하여 상위로 전파
- *
- *
- * 기타 예외:
- *
- * 네트워크 타임아웃, 연결 실패 등
- * RuntimeException으로 래핑하여 상위로 전파
- *
- *
- *
- *
- * TypeReference의 필요성:
- * Java의 제네릭은 런타임에 타입 소거(Type Erasure)가 발생하여
- * {@code objectMapper.readValue(json, Envelope.class)}와 같은 코드는
- * 컴파일되지 않습니다. TypeReference는 익명 클래스를 사용하여 컴파일 타임의
- * 제네릭 타입 정보를 런타임에 전달하는 Jackson의 메커니즘입니다.
- *
- * 로깅 정보:
- *
- * 요청 로그: [GOV-REQ] url, tx_id, gpki, length
- * 에러 로그: [GOV-ERR] status, body
- *
- *
- * @param 요청 데이터의 제네릭 타입
- * @param 응답 데이터의 제네릭 타입
- * @param type 서비스 타입 (BASIC 또는 LEDGER)
- * @param envelope 요청 데이터를 담은 Envelope 객체
- * @param respType 응답 타입에 대한 TypeReference (제네릭 타입 정보 보존용)
- * @return ResponseEntity<Envelope<TResp>> 응답 데이터를 담은 ResponseEntity
- * @throws RuntimeException JSON 직렬화/역직렬화 실패, 네트워크 오류, GPKI 암복호화 실패 등
- */
- private ResponseEntity> callModel(ServiceType type,
- Envelope envelope,
- TypeReference> respType) {
- // 1. 서비스 타입에 따른 설정 로드
- VmisProperties.GovProps gov = props.getGov();
- VmisProperties.GovProps.Service svc = (type == ServiceType.BASIC)
- ? gov.getServices().getBasic()
- : gov.getServices().getLedger();
-
- // 2. URL 및 트랜잭션 ID 생성
- String url = gov.buildServiceUrl(svc.getPath());
- String txId = TxIdUtil.generate();
-
- try {
- // 3. 직렬화: Java 객체 → JSON 문자열
- // ObjectMapper가 Envelope 객체를 JSON으로 변환
- // 날짜, null 값 등의 처리는 ObjectMapper 설정에 따름
- String jsonBody = objectMapper.writeValueAsString(envelope);
-
- // 4. HTTP 헤더 구성
- HttpHeaders headers = buildHeaders(svc, txId);
-
- // 5. GPKI 암호화 처리
- String bodyToSend = jsonBody;
- if (gpkiService.isEnabled()) {
- // JSON 평문을 암호화된 문자열로 변환
- bodyToSend = gpkiEncrypt(jsonBody);
- }
-
- // 6. HTTP 엔티티 생성 (헤더 + 바디)
- HttpEntity request = new HttpEntity<>(bodyToSend, headers);
-
- // 7. 요청 로그 기록
- log.info("[GOV-REQ] url={}, tx_id={}, gpki={}, length={}", url, txId, gpkiService.isEnabled(), bodyToSend != null ? bodyToSend.length() : 0);
-
- // 8. 실제 HTTP POST 요청 전송
- ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
- String respBody = response.getBody();
-
- // 9. GPKI 복호화 처리 (성공 응답인 경우만)
- if (gpkiService.isEnabled() && response.getStatusCode().is2xxSuccessful()) {
- // 암호화된 응답을 평문 JSON으로 복호화
- respBody = gpkiDecrypt(respBody);
- }
-
- // 10. 역직렬화: JSON 문자열 → Java 객체
- // TypeReference를 사용하여 제네릭 타입 정보 전달
- Envelope mapped = objectMapper.readValue(respBody, respType);
-
- // 11. 응답 반환 (상태 코드, 헤더, 바디 모두 포함)
- return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(mapped);
-
- } catch (HttpStatusCodeException ex) {
- // HTTP 에러 처리 (4xx, 5xx)
- log.warn("[GOV-ERR] status={}, body={}", ex.getStatusCode(), ex.getResponseBodyAsString());
-
- // 에러 응답 바디 파싱 시도
- // 정부 API는 에러 응답도 Envelope 형식으로 반환할 수 있음
- Envelope empty = new Envelope<>();
- try {
- // 에러 응답을 Envelope 객체로 파싱
- Envelope parsed = objectMapper.readValue(ex.getResponseBodyAsString(), respType);
- return ResponseEntity.status(ex.getStatusCode()).headers(ex.getResponseHeaders() != null ? ex.getResponseHeaders() : new HttpHeaders()).body(parsed);
- } catch (Exception parseEx) {
- // 파싱 실패 시 빈 Envelope 반환
- // 호출자는 HTTP 상태 코드로 에러 판단 가능
- return ResponseEntity.status(ex.getStatusCode()).headers(ex.getResponseHeaders() != null ? ex.getResponseHeaders() : new HttpHeaders()).body(empty);
- }
- } catch (Exception e) {
- // 기타 모든 예외 (네트워크 오류, JSON 파싱 오류, GPKI 오류 등)
- // RuntimeException으로 래핑하여 상위로 전파
- // Spring의 @ExceptionHandler에서 처리 가능
- throw new RuntimeException("정부 API 호출 중 오류", e);
- }
- }
-
- /**
- * GPKI 암호화 처리
- *
- * 평문 JSON 문자열을 GPKI(행정전자서명) 알고리즘으로 암호화하는 헬퍼 메서드입니다.
- * 암호화 실패 시 명확한 에러 메시지와 함께 RuntimeException을 발생시킵니다.
- *
- * 암호화 과정:
- *
- * 평문 JSON 문자열을 바이트 배열로 변환
- * 정부 시스템의 공개키를 사용하여 암호화
- * 암호화된 바이트 배열을 Base64로 인코딩
- * Base64 문자열 반환
- *
- *
- * 에러 처리:
- *
- * 인증서 오류: GPKI 인증서가 유효하지 않거나 만료된 경우
- * 암호화 오류: 암호화 알고리즘 실행 중 오류 발생
- * 인코딩 오류: Base64 인코딩 실패
- * 모든 예외는 RuntimeException으로 래핑하여 즉시 중단
- *
- *
- * 보안 고려사항:
- *
- * 공개키 암호화 방식 사용 (비대칭키)
- * 정부 시스템만 개인키로 복호화 가능
- * 민감한 개인정보 보호
- *
- *
- * @param jsonBody 암호화할 평문 JSON 문자열
- * @return String Base64로 인코딩된 암호화 문자열
- * @throws RuntimeException GPKI 암호화 실패 시
- */
- private String gpkiEncrypt(String jsonBody) {
- try {
- // GpkiService에 암호화 위임
- // 실제 암호화 로직은 GpkiService가 캡슐화
- return gpkiService.encrypt(jsonBody);
- } catch (Exception e) {
- // 암호화 실패는 치명적 오류
- // 평문 데이터를 전송할 수 없으므로 즉시 중단
- throw new RuntimeException("GPKI 암호화 실패", e);
- }
- }
-
- /**
- * GPKI 복호화 처리
- *
- * 암호화된 응답 문자열을 GPKI(행정전자서명) 알고리즘으로 복호화하는 헬퍼 메서드입니다.
- * 복호화 실패 시 명확한 에러 메시지와 함께 RuntimeException을 발생시킵니다.
- *
- * 복호화 과정:
- *
- * Base64로 인코딩된 암호문을 바이트 배열로 디코딩
- * 우리 시스템의 개인키를 사용하여 복호화
- * 복호화된 바이트 배열을 UTF-8 문자열로 변환
- * 평문 JSON 문자열 반환
- *
- *
- * 에러 처리:
- *
- * 인증서 오류: GPKI 인증서가 유효하지 않거나 만료된 경우
- * 복호화 오류: 암호문이 손상되었거나 올바르지 않은 경우
- * 디코딩 오류: Base64 디코딩 실패
- * 문자 인코딩 오류: UTF-8 변환 실패
- * 모든 예외는 RuntimeException으로 래핑하여 즉시 중단
- *
- *
- * 보안 고려사항:
- *
- * 개인키 암호화 방식 사용 (비대칭키)
- * 개인키는 안전하게 저장 및 관리 필요
- * 복호화 실패 시 상세 에러 정보 로깅 주의 (정보 유출 방지)
- *
- *
- * @param cipher Base64로 인코딩된 암호화 문자열
- * @return String 복호화된 평문 JSON 문자열
- * @throws RuntimeException GPKI 복호화 실패 시
- */
- private String gpkiDecrypt(String cipher) {
- try {
- // GpkiService에 복호화 위임
- // 실제 복호화 로직은 GpkiService가 캡슐화
- return gpkiService.decrypt(cipher);
- } catch (Exception e) {
- // 복호화 실패는 치명적 오류
- // 암호문을 해석할 수 없으므로 즉시 중단
- throw new RuntimeException("GPKI 복호화 실패", e);
- }
- }
-
-}
diff --git a/src/main/java/go/kr/project/api/internal/config/GpkiConfig.java b/src/main/java/go/kr/project/api/internal/config/GpkiConfig.java
deleted file mode 100644
index 12210c9..0000000
--- a/src/main/java/go/kr/project/api/internal/config/GpkiConfig.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package go.kr.project.api.internal.config;
-
-import go.kr.project.api.config.properties.VmisProperties;
-import go.kr.project.api.internal.gpki.GpkiService;
-import go.kr.project.api.internal.gpki.NoopGpkiService;
-import go.kr.project.api.internal.gpki.RealGpkiService;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class GpkiConfig {
-
- @Bean
- public GpkiService gpkiService(VmisProperties properties) {
- if (properties.getGpki().isEnabledFlag()) {
- return new RealGpkiService(properties);
- }
- return new NoopGpkiService();
- }
-}
diff --git a/src/main/java/go/kr/project/api/internal/config/OpenApiConfig.java b/src/main/java/go/kr/project/api/internal/config/OpenApiConfig.java
deleted file mode 100644
index c9d6277..0000000
--- a/src/main/java/go/kr/project/api/internal/config/OpenApiConfig.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package go.kr.project.api.internal.config;
-
-import io.swagger.v3.oas.models.ExternalDocumentation;
-import io.swagger.v3.oas.models.OpenAPI;
-import io.swagger.v3.oas.models.info.Contact;
-import io.swagger.v3.oas.models.info.Info;
-import io.swagger.v3.oas.models.info.License;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class OpenApiConfig {
-
- @Bean
- public OpenAPI vmisOpenAPI() {
- return new OpenAPI()
- .info(new Info()
- .title("VMIS Interface API")
- .description("시군구연계 자동차 정보 인터페이스 API (자망연계)")
- .version("v0.1.0")
- .contact(new Contact().name("VMIS").email("support@example.com"))
- .license(new License().name("Apache 2.0").url("https://www.apache.org/licenses/LICENSE-2.0.html")))
- .externalDocs(new ExternalDocumentation()
- .description("Reference")
- .url(""));
- }
-}
diff --git a/src/main/java/go/kr/project/api/internal/config/PropertiesConfig.java b/src/main/java/go/kr/project/api/internal/config/PropertiesConfig.java
deleted file mode 100644
index 76a948e..0000000
--- a/src/main/java/go/kr/project/api/internal/config/PropertiesConfig.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package go.kr.project.api.internal.config;
-
-import go.kr.project.api.config.properties.VmisProperties;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-@EnableConfigurationProperties(VmisProperties.class)
-public class PropertiesConfig {
-}
diff --git a/src/main/java/go/kr/project/api/internal/gpki/GpkiService.java b/src/main/java/go/kr/project/api/internal/gpki/GpkiService.java
deleted file mode 100644
index bfb1272..0000000
--- a/src/main/java/go/kr/project/api/internal/gpki/GpkiService.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package go.kr.project.api.internal.gpki;
-
-public interface GpkiService {
- String encrypt(String plain) throws Exception;
- String decrypt(String cipher) throws Exception;
- boolean isEnabled();
-}
diff --git a/src/main/java/go/kr/project/api/internal/gpki/NoopGpkiService.java b/src/main/java/go/kr/project/api/internal/gpki/NoopGpkiService.java
deleted file mode 100644
index 9d4084c..0000000
--- a/src/main/java/go/kr/project/api/internal/gpki/NoopGpkiService.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package go.kr.project.api.internal.gpki;
-
-public class NoopGpkiService implements GpkiService {
- @Override
- public String encrypt(String plain) {
- return plain;
- }
-
- @Override
- public String decrypt(String cipher) {
- return cipher;
- }
-
- @Override
- public boolean isEnabled() {
- return false;
- }
-}
diff --git a/src/main/java/go/kr/project/api/internal/gpki/RealGpkiService.java b/src/main/java/go/kr/project/api/internal/gpki/RealGpkiService.java
deleted file mode 100644
index 294d289..0000000
--- a/src/main/java/go/kr/project/api/internal/gpki/RealGpkiService.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package go.kr.project.api.internal.gpki;
-
-import go.kr.project.api.config.properties.VmisProperties;
-import go.kr.project.api.internal.util.GpkiCryptoUtil;
-
-/**
- * Real GPKI service backed by native GPKI JNI via legacy NewGpkiUtil wrapper.
- * Uses YAML-configured paths and options in {@link VmisProperties.GpkiProps}.
- */
-public class RealGpkiService implements GpkiService {
-
- private final VmisProperties props;
- private final GpkiCryptoUtil crypto;
-
- public RealGpkiService(VmisProperties props) {
- this.props = props;
- try {
- this.crypto = GpkiCryptoUtil.from(props.getGpki());
- } catch (Exception e) {
- throw new IllegalStateException("Failed to initialize GPKI (JNI) util. Check YAML paths/passwords and license.", e);
- }
- }
-
- @Override
- public String encrypt(String plain) throws Exception {
- String charset = props.getGpki().getCharset();
- String targetId = props.getGpki().getTargetServerId();
- return crypto.encryptToBase64(plain, targetId, charset);
- }
-
- @Override
- public String decrypt(String cipher) throws Exception {
- String charset = props.getGpki().getCharset();
- return crypto.decryptFromBase64(cipher, charset);
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-}
diff --git a/src/main/java/go/kr/project/api/internal/service/VmisCarBassMatterInqireService.java b/src/main/java/go/kr/project/api/internal/service/VmisCarBassMatterInqireService.java
deleted file mode 100644
index 45e7672..0000000
--- a/src/main/java/go/kr/project/api/internal/service/VmisCarBassMatterInqireService.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package go.kr.project.api.internal.service;
-
-import go.kr.project.api.model.Envelope;
-import go.kr.project.api.model.request.BasicRequest;
-import go.kr.project.api.model.response.BasicResponse;
-import org.springframework.http.ResponseEntity;
-
-/**
- * 자동차 기본사항 조회 서비스 인터페이스
- *
- * API 호출 정보를 관리하는 서비스입니다.
- *
- * 요청 데이터 보강
- * 최초 요청 로그 저장
- * 외부 API 호출
- * 응답 로그 업데이트
- *
- */
-public interface VmisCarBassMatterInqireService {
-
- /**
- * 자동차 기본사항 조회
- *
- * @param envelope 요청 Envelope
- * @return 응답 Envelope
- */
- ResponseEntity> basic(Envelope envelope);
-}
diff --git a/src/main/java/go/kr/project/api/internal/service/VmisCarLedgerFrmbkService.java b/src/main/java/go/kr/project/api/internal/service/VmisCarLedgerFrmbkService.java
deleted file mode 100644
index 3e30c10..0000000
--- a/src/main/java/go/kr/project/api/internal/service/VmisCarLedgerFrmbkService.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package go.kr.project.api.internal.service;
-
-import go.kr.project.api.model.Envelope;
-import go.kr.project.api.model.request.LedgerRequest;
-import go.kr.project.api.model.response.LedgerResponse;
-import org.springframework.http.ResponseEntity;
-
-/**
- * 자동차 등록 원부(갑) 서비스 인터페이스
- * - 요청 보강, 외부 API 호출, 로그 서비스 위임
- */
-public interface VmisCarLedgerFrmbkService {
-
- /**
- * 자동차 등록원부(갑) 조회
- *
- * @param envelope 요청 Envelope
- * @return 응답 Envelope
- */
- ResponseEntity> ledger(Envelope envelope);
-}
diff --git a/src/main/java/go/kr/project/api/internal/service/VmisRequestEnricher.java b/src/main/java/go/kr/project/api/internal/service/VmisRequestEnricher.java
deleted file mode 100644
index e7fc39d..0000000
--- a/src/main/java/go/kr/project/api/internal/service/VmisRequestEnricher.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package go.kr.project.api.internal.service;
-
-import go.kr.project.api.config.properties.VmisProperties;
-import go.kr.project.api.model.Envelope;
-import go.kr.project.api.model.request.BasicRequest;
-import go.kr.project.api.model.request.LedgerRequest;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-/**
- * Populates incoming request models with values from YAML configuration.
- * Unconditionally overwrites the listed fields per requirement:
- * - INFO_SYS_ID, INFO_SYS_IP, SIGUNGU_CODE
- * - CNTC_INFO_CODE (service specific)
- * - CHARGER_ID, CHARGER_IP, CHARGER_NM
- */
-@Slf4j
-@Component
-public class VmisRequestEnricher {
-
- private final VmisProperties props;
-
- public VmisRequestEnricher(VmisProperties props) {
- this.props = props;
- }
-
- public void enrichBasic(Envelope envelope) {
- if (envelope == null || envelope.getData() == null) return;
- VmisProperties.SystemProps sys = props.getSystem();
- String cntc = props.getGov().getServices().getBasic().getCntcInfoCode();
- for (BasicRequest req : envelope.getData()) {
- if (req == null) continue;
- req.setInfoSysId(sys.getInfoSysId());
- req.setInfoSysIp(sys.getInfoSysIp());
- req.setSigunguCode(sys.getSigunguCode());
- req.setCntcInfoCode(cntc);
- 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);
- }
-
- public void enrichLedger(Envelope envelope) {
- if (envelope == null || envelope.getData() == null) return;
- VmisProperties.SystemProps sys = props.getSystem();
- String cntc = props.getGov().getServices().getLedger().getCntcInfoCode();
- for (LedgerRequest req : envelope.getData()) {
- if (req == null) continue;
- req.setInfoSysId(sys.getInfoSysId());
- req.setInfoSysIp(sys.getInfoSysIp());
- req.setSigunguCode(sys.getSigunguCode());
- req.setCntcInfoCode(cntc);
- req.setChargerId(sys.getChargerId());
- req.setChargerIp(sys.getChargerIp());
- req.setChargerNm(sys.getChargerNm());
-
- // 고정값 설정 (값이 없는 경우에만 설정)
- if (req.getOnesInformationOpen() == null || req.getOnesInformationOpen().isEmpty()) {
- req.setOnesInformationOpen("1"); // 개인정보공개 (소유자공개)
- }
- if (req.getRouteSeCode() == null || req.getRouteSeCode().isEmpty()) {
- req.setRouteSeCode("3"); // 경로구분코드
- }
- if (req.getDetailExpression() == null || req.getDetailExpression().isEmpty()) {
- req.setDetailExpression("1"); // 내역표시 (전체내역)
- }
- if (req.getInqireSeCode() == null || req.getInqireSeCode().isEmpty()) {
- req.setInqireSeCode("1"); // 조회구분코드 (열람)
- }
- }
- log.debug("[ENRICH] ledger: applied INFO_SYS_ID={}, INFO_SYS_IP={}, SIGUNGU_CODE={}, CNTC_INFO_CODE={}",
- sys.getInfoSysId(), sys.getInfoSysIp(), sys.getSigunguCode(), cntc);
- }
-}
diff --git a/src/main/java/go/kr/project/api/internal/service/impl/InternalVehicleInfoServiceImpl.java b/src/main/java/go/kr/project/api/internal/service/impl/InternalVehicleInfoServiceImpl.java
deleted file mode 100644
index d5491f5..0000000
--- a/src/main/java/go/kr/project/api/internal/service/impl/InternalVehicleInfoServiceImpl.java
+++ /dev/null
@@ -1,213 +0,0 @@
-package go.kr.project.api.internal.service.impl;
-
-import go.kr.project.api.config.ApiConstant;
-import go.kr.project.api.internal.service.VmisCarBassMatterInqireService;
-import go.kr.project.api.internal.service.VmisCarLedgerFrmbkService;
-import go.kr.project.api.model.Envelope;
-import go.kr.project.api.model.VehicleApiResponseVO;
-import go.kr.project.api.model.request.BasicRequest;
-import go.kr.project.api.model.request.LedgerRequest;
-import go.kr.project.api.model.response.BasicResponse;
-import go.kr.project.api.model.response.LedgerResponse;
-import go.kr.project.api.service.VehicleInfoService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * 내부 VMIS 모듈을 직접 호출하는 차량 정보 조회 서비스 구현체
- *
- * 이 구현체는 외부 REST API 호출 없이 내부 VMIS 모듈을 직접 사용하여
- * 정부 시스템과 통신합니다. 네트워크 오버헤드가 없어 성능이 향상됩니다.
- *
- * 활성화 조건:
- *
- * # application.yml
- * vmis:
- * integration:
- * mode: internal
- *
- *
- * 처리 흐름:
- *
- * 차량번호를 받아 BasicRequest, LedgerRequest 생성
- * VmisCarBassMatterInqireService.basic() 호출 (기본정보)
- * VmisCarLedgerFrmbkService.ledger() 호출 (등록원부)
- * BasicResponse, LedgerResponse를 직접 VehicleApiResponseVO에 설정
- * VehicleApiResponseVO로 결과 반환
- *
- *
- * @see VehicleInfoService
- * @see VmisCarBassMatterInqireService
- * @see VmisCarLedgerFrmbkService
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-@ConditionalOnProperty(name = "vmis.integration.mode", havingValue = "internal")
-public class InternalVehicleInfoServiceImpl extends EgovAbstractServiceImpl implements VehicleInfoService {
-
- private final VmisCarBassMatterInqireService carBassMatterInqireService;
- private final VmisCarLedgerFrmbkService carLedgerFrmbkService;
- private final go.kr.project.carInspectionPenalty.history.service.VehicleApiHistoryService vehicleApiHistoryService;
-
-
- @Override
- public VehicleApiResponseVO getVehicleInfo(BasicRequest basicRequest) {
- String vehicleNumber = basicRequest.getVhrno();
- log.info("[Internal Mode] 차량 정보 조회 시작 - 차량번호: {}, 부과기준일: {}, 조회구분: {}",
- vehicleNumber, basicRequest.getLevyStdde(), basicRequest.getInqireSeCode());
-
- VehicleApiResponseVO response = new VehicleApiResponseVO();
- response.setVhrno(vehicleNumber);
-
- try {
- // 1. 차량 기본정보 조회
- // 중요 로직: BasicRequest 전체를 사용하여 조회 (RequestEnricher가 나머지 채움)
- BasicResponse basicInfo = getBasicInfo(basicRequest);
- response.setBasicInfo(basicInfo);
-
- // 2. 자동차 등록원부 조회
- // 중요 로직: 통합 조회 시에는 차량번호와 기본정보를 바탕으로 LedgerRequest 생성 (RequestEnricher가 나머지 채움)
- LedgerRequest ledgerRequest = new LedgerRequest();
- ledgerRequest.setVhrno(vehicleNumber);
- ledgerRequest.setOnesInformationOpen("1"); //개인정보공개 {1:소유자공개, 2:비공개, 3:비공개(주민등록번호), 4:비공개(사용본거지)}
-
- // basicInfo에서 민원인 정보 가져오기
- if (basicInfo != null && basicInfo.getRecord() != null && !basicInfo.getRecord().isEmpty()) {
- BasicResponse.Record record = basicInfo.getRecord().get(0);
- ledgerRequest.setCpttrNm(record.getMberNm()); // 민원인성명
- ledgerRequest.setCpttrIhidnum(record.getMberSeNo()); // 민원인주민번호
- }
-
- // 고정값 설정
- ledgerRequest.setCpttrLegaldongCode(null); // 민원인법정동코드
-
- LedgerResponse ledgerInfo = getLedgerInfo(ledgerRequest);
- response.setLedgerInfo(ledgerInfo);
-
- // 3. 결과 검증
- if (basicInfo != null && ApiConstant.CNTC_RESULT_CODE_SUCCESS.equals(basicInfo.getCntcResultCode())) {
- response.setSuccess(true);
- response.setMessage("조회 성공");
- log.info("[Internal Mode] 차량번호 {} 조회 성공", vehicleNumber);
-
- // 4. API 호출 성공 시 히스토리 ID 조회 및 설정
- try {
- String carBassMatterInqireId = vehicleApiHistoryService.selectLatestCarBassMatterInqireIdByVhclno(vehicleNumber);
- String carLedgerFrmbkId = vehicleApiHistoryService.selectLatestCarLedgerFrmbkIdByVhclno(vehicleNumber);
- response.setCarBassMatterInqireId(carBassMatterInqireId);
- response.setCarLedgerFrmbkId(carLedgerFrmbkId);
- log.debug("[Internal Mode] 히스토리 ID 설정 완료 - 차량번호: {}, 기본정보ID: {}, 원부ID: {}",
- vehicleNumber, carBassMatterInqireId, carLedgerFrmbkId);
- } catch (Exception e) {
- log.warn("[Internal Mode] 히스토리 ID 조회 실패 - 차량번호: {}", vehicleNumber, e);
- // ID 조회 실패는 치명적이지 않으므로 계속 진행
- }
- } 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;
- }
-
-
- /**
- * 차량 기본정보 조회 (내부 모듈 직접 호출)
- * 중요 로직: 기본정보 조회는 BasicRequest 전체를 받아서 내부 서비스에 전달
- *
- * @param request 기본정보 조회 요청 (차량번호, 부과기준일, 조회구분 등 포함)
- * @return 차량 기본정보 응답
- */
- @Override
- public BasicResponse getBasicInfo(BasicRequest request) {
- log.debug("[Internal Mode] 차량 기본정보 조회 - 차량번호: {}", request.getVhrno());
-
- // 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()) {
-
- return responseEntity.getBody().getData().get(0);
- }
-
- log.warn("[Internal Mode] 차량 기본정보 조회 응답이 비어있음 - 차량번호: {}", request.getVhrno());
- return null;
-
- } catch (Exception e) {
- log.error("[Internal Mode] 차량 기본정보 조회 실패 - 차량번호: {}", request.getVhrno(), e);
- throw new RuntimeException("차량 기본정보 조회 실패: " + e.getMessage(), e);
- }
- }
-
- /**
- * 자동차 등록원부(갑) 조회 (내부 모듈 직접 호출)
- * 중요 로직: 등록원부 조회는 LedgerRequest 전체를 받아서 내부 서비스에 전달
- *
- * @param request 등록원부 조회 요청 (차량번호, 소유자정보, 조회구분 등 포함)
- * @return 등록원부 정보 응답
- */
- @Override
- public LedgerResponse getLedgerInfo(LedgerRequest request) {
- log.debug("[Internal Mode] 자동차 등록원부 조회 - 차량번호: {}", request.getVhrno());
-
- // 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()) {
-
- return responseEntity.getBody().getData().get(0);
- }
-
- log.warn("[Internal Mode] 자동차 등록원부 조회 응답이 비어있음 - 차량번호: {}", request.getVhrno());
- return null;
-
- } catch (Exception e) {
- log.error("[Internal Mode] 자동차 등록원부 조회 실패 - 차량번호: {}", request.getVhrno(), 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/api/internal/service/impl/VmisCarBassMatterInqireServiceImpl.java b/src/main/java/go/kr/project/api/internal/service/impl/VmisCarBassMatterInqireServiceImpl.java
deleted file mode 100644
index 4a21bd6..0000000
--- a/src/main/java/go/kr/project/api/internal/service/impl/VmisCarBassMatterInqireServiceImpl.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package go.kr.project.api.internal.service.impl;
-
-import go.kr.project.api.config.ApiConstant;
-import go.kr.project.api.internal.client.GovernmentApi;
-import go.kr.project.api.internal.service.VmisCarBassMatterInqireService;
-import go.kr.project.api.internal.service.VmisRequestEnricher;
-import go.kr.project.api.internal.util.ExceptionDetailUtil;
-import go.kr.project.api.model.Envelope;
-import go.kr.project.api.model.request.BasicRequest;
-import go.kr.project.api.model.response.BasicResponse;
-import go.kr.project.api.model.response.VmisCarBassMatterInqireVO;
-import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-/**
- * 자동차 기본 사항 조회 서비스 구현체
- *
- * API 호출 정보를 관리하는 서비스 클래스입니다.
- *
- * 최초 요청: createInitialRequest() - 시퀀스로 ID 생성 후 INSERT
- * 결과 업데이트: updateResponse() - 응답 데이터 UPDATE
- *
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class VmisCarBassMatterInqireServiceImpl extends EgovAbstractServiceImpl implements VmisCarBassMatterInqireService {
-
- private final GovernmentApi governmentApi;
- private final VmisRequestEnricher enricher;
- private final VmisCarBassMatterInqireLogService logService;
-
- /**
- * 자동차 기본사항 조회: 보강 -> 최초요청로그 -> 외부호출 -> 응답로그.
- */
- @Override
- @Transactional
- public ResponseEntity> basic(Envelope envelope) {
- // 1) 요청 보강
- enricher.enrichBasic(envelope);
-
- String generatedId = null;
- try {
- // 2) 최초 요청 로그 저장 (첫 번째 데이터 기준)
- if (envelope.getData() != null && !envelope.getData().isEmpty()) {
- BasicRequest req = envelope.getData().get(0);
- VmisCarBassMatterInqireVO logEntity = VmisCarBassMatterInqireVO.fromRequest(req);
- generatedId = logService.createInitialRequestNewTx(logEntity);
- }
-
- // 3) 외부 API 호출
- ResponseEntity> response = governmentApi.callBasic(envelope);
-
- // 4) 응답 로그 업데이트
- // 원본 소스, 정상적인 호출, 리턴(에러 리턴포함) 일 경우에만 에러 로그 남김
- if (generatedId != null && response.getBody() != null) {
- VmisCarBassMatterInqireVO update = VmisCarBassMatterInqireVO.fromResponse(generatedId, response.getBody());
- if (update != null) {
- logService.updateResponseNewTx(update);
- }
- }
-
- return response;
- } catch (Exception e) {
- // 5) 오류 로그 업데이트
- if (generatedId != null) {
- try {
- String detail = ExceptionDetailUtil.buildForLog(e);
- VmisCarBassMatterInqireVO errorLog = VmisCarBassMatterInqireVO.builder()
- .carBassMatterInqireId(generatedId) // 자동차기본사항조회 ID (PK)
- .cntcResultCode(ApiConstant.CNTC_RESULT_CODE_ERROR) // 연계결과코드 (에러)
- .cntcResultDtls(detail) // 연계결과상세 (에러 메시지)
- .build();
- logService.updateResponseNewTx(errorLog);
- log.error("[BASIC-ERR-LOG] API 호출 에러 정보 저장 완료(별도TX) - ID: {}, detail: {}", generatedId, detail, e);
- } catch (Exception ignore) {
- log.error("[BASIC-ERR-LOG] 에러 로그 저장 실패 - ID: {}", generatedId, ignore);
- }
- }
- throw e;
- }
- }
-}
diff --git a/src/main/java/go/kr/project/api/internal/service/impl/VmisCarLedgerFrmbkServiceImpl.java b/src/main/java/go/kr/project/api/internal/service/impl/VmisCarLedgerFrmbkServiceImpl.java
deleted file mode 100644
index e8a05fb..0000000
--- a/src/main/java/go/kr/project/api/internal/service/impl/VmisCarLedgerFrmbkServiceImpl.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package go.kr.project.api.internal.service.impl;
-
-import go.kr.project.api.config.ApiConstant;
-import go.kr.project.api.internal.client.GovernmentApi;
-import go.kr.project.api.internal.service.VmisCarLedgerFrmbkService;
-import go.kr.project.api.internal.service.VmisRequestEnricher;
-import go.kr.project.api.internal.util.ExceptionDetailUtil;
-import go.kr.project.api.model.Envelope;
-import go.kr.project.api.model.request.LedgerRequest;
-import go.kr.project.api.model.response.LedgerResponse;
-import go.kr.project.api.model.response.VmisCarLedgerFrmbkDtlVO;
-import go.kr.project.api.model.response.VmisCarLedgerFrmbkVO;
-import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.List;
-
-/**
- * 자동차 등록 원부(갑) 서비스 구현체 (오케스트레이션)
- * - 요청 보강, 외부 API 호출, 로그 서비스 위임
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class VmisCarLedgerFrmbkServiceImpl extends EgovAbstractServiceImpl implements VmisCarLedgerFrmbkService {
-
- private final GovernmentApi governmentApi;
- private final VmisRequestEnricher enricher;
- private final VmisCarLedgerFrmbkLogService logService;
-
- /**
- * 자동차 등록원부(갑) 조회: 보강 -> 최초요청로그(별도TX) -> 외부호출 -> 응답로그(마스터/상세, 별도TX) -> 오류 시 에러로그(별도TX).
- */
- @Override
- @Transactional
- public ResponseEntity> ledger(Envelope envelope) {
- // 1) 요청 보강
- enricher.enrichLedger(envelope);
-
- String generatedId = null;
- try {
- // 2) 최초 요청 로그 저장 (첫 번째 데이터 기준)
- if (envelope.getData() != null && !envelope.getData().isEmpty()) {
- LedgerRequest req = envelope.getData().get(0);
- VmisCarLedgerFrmbkVO init = VmisCarLedgerFrmbkVO.fromRequest(req);
- generatedId = logService.createInitialRequestNewTx(init);
- }
-
- // 3) 외부 API 호출
- ResponseEntity> response = governmentApi.callLedger(envelope);
-
- // 4) 응답 로그 업데이트 (마스터 + 상세)
- if (generatedId != null && response.getBody() != null &&
- response.getBody().getData() != null && !response.getBody().getData().isEmpty()) {
- LedgerResponse body = response.getBody().getData().get(0);
- VmisCarLedgerFrmbkVO masterUpdate = VmisCarLedgerFrmbkVO.fromResponseMaster(generatedId, body);
- logService.updateResponseNewTx(masterUpdate);
-
- List details = VmisCarLedgerFrmbkDtlVO.listFromResponse(body, generatedId);
- if (details != null && !details.isEmpty()) {
- logService.saveDetailsNewTx(generatedId, details);
- }
- }
-
- return response;
- } catch (Exception e) {
- // 5) 오류 로그 업데이트
- if (generatedId != null) {
- try {
- String detail = ExceptionDetailUtil.buildForLog(e);
- VmisCarLedgerFrmbkVO errorLog = VmisCarLedgerFrmbkVO.builder()
- .carLedgerFrmbkId(generatedId)
- .cntcResultCode(ApiConstant.CNTC_RESULT_CODE_ERROR)
- .cntcResultDtls(detail)
- .build();
- logService.updateResponseNewTx(errorLog);
- log.error("[LEDGER-ERR-LOG] API 호출 에러 정보 저장 완료(별도TX) - ID: {}, detail: {}", generatedId, detail, e);
- } catch (Exception ignore) {
- log.error("[LEDGER-ERR-LOG] 에러 로그 저장 실패 - ID: {}", generatedId, ignore);
- }
- }
- throw e;
- }
- }
-}
diff --git a/src/main/java/go/kr/project/api/internal/util/GpkiCryptoUtil.java b/src/main/java/go/kr/project/api/internal/util/GpkiCryptoUtil.java
deleted file mode 100644
index 1987776..0000000
--- a/src/main/java/go/kr/project/api/internal/util/GpkiCryptoUtil.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package go.kr.project.api.internal.util;
-
-import go.kr.project.api.config.properties.VmisProperties;
-import lombok.Getter;
-import lombok.Setter;
-
-/**
- * Wrapper utility around legacy {@link NewGpkiUtil} using configuration from YAML.
- *
- * Notes:
- * - Place this class under src/main/java/util as requested.
- * - Uses Lombok for getters/setters.
- */
-@Getter
-@Setter
-public class GpkiCryptoUtil {
-
- private String gpkiLicPath;
- private Boolean ldap; // null -> legacy default
- private String certFilePath;
- private String envCertFilePathName;
- private String envPrivateKeyFilePathName;
- private String envPrivateKeyPasswd;
- private String sigCertFilePathName;
- private String sigPrivateKeyFilePathName;
- private String sigPrivateKeyPasswd;
- private String myServerId; // equals to certServerId (INFO system server cert id)
- private String targetServerIdList; // comma joined list (can be single id)
-
- private transient NewGpkiUtil delegate;
-
- public static GpkiCryptoUtil from(VmisProperties.GpkiProps props) throws Exception {
- GpkiCryptoUtil util = new GpkiCryptoUtil();
- util.setGpkiLicPath(props.getGpkiLicPath());
- util.setLdap(props.getLdap());
- util.setCertFilePath(props.getCertFilePath());
- util.setEnvCertFilePathName(props.getEnvCertFilePathName());
- util.setEnvPrivateKeyFilePathName(props.getEnvPrivateKeyFilePathName());
- util.setEnvPrivateKeyPasswd(props.getEnvPrivateKeyPasswd());
- util.setSigCertFilePathName(props.getSigCertFilePathName());
- util.setSigPrivateKeyFilePathName(props.getSigPrivateKeyFilePathName());
- util.setSigPrivateKeyPasswd(props.getSigPrivateKeyPasswd());
- util.setMyServerId(props.getCertServerId());
- // Accept single targetServerId but allow list if provided by YAML in future
- util.setTargetServerIdList(props.getTargetServerId());
- util.initialize();
- return util;
- }
-
- public void initialize() throws Exception {
- NewGpkiUtil g = new NewGpkiUtil();
- if (gpkiLicPath != null) g.setGpkiLicPath(gpkiLicPath);
- if (ldap != null) g.setIsLDAP(ldap);
- if (certFilePath != null) g.setCertFilePath(certFilePath);
- if (envCertFilePathName != null) g.setEnvCertFilePathName(envCertFilePathName);
- if (envPrivateKeyFilePathName != null) g.setEnvPrivateKeyFilePathName(envPrivateKeyFilePathName);
- if (envPrivateKeyPasswd != null) g.setEnvPrivateKeyPasswd(envPrivateKeyPasswd);
- if (sigCertFilePathName != null) g.setSigCertFilePathName(sigCertFilePathName);
- if (sigPrivateKeyFilePathName != null) g.setSigPrivateKeyFilePathName(sigPrivateKeyFilePathName);
- if (sigPrivateKeyPasswd != null) g.setSigPrivateKeyPasswd(sigPrivateKeyPasswd);
- if (myServerId != null) g.setMyServerId(myServerId);
- if (targetServerIdList != null) g.setTargetServerIdList(targetServerIdList);
- g.init();
- this.delegate = g;
- }
-
- public String encryptToBase64(String plain, String targetServerId, String charset) throws Exception {
- ensureInit();
- byte[] enc = delegate.encrypt(plain.getBytes(charset), targetServerId, true);
- return delegate.encode(enc);
- }
-
- public String decryptFromBase64(String base64, String charset) throws Exception {
- ensureInit();
- byte[] bin = delegate.decode(base64);
- byte[] dec = delegate.decrypt(bin);
- return new String(dec, charset);
- }
-
- public String signToBase64(String plain, String charset) throws Exception {
- ensureInit();
- byte[] sig = delegate.sign(plain.getBytes(charset));
- return delegate.encode(sig);
- }
-
- public String verifyAndExtractBase64(String signedBase64, String charset) throws Exception {
- ensureInit();
- byte[] signed = delegate.decode(signedBase64);
- byte[] data = delegate.validate(signed);
- return new String(data, charset);
- }
-
- private void ensureInit() {
- if (delegate == null) {
- throw new IllegalStateException("GpkiCryptoUtil is not initialized. Call initialize() or from(props).");
- }
- }
-}
diff --git a/src/main/java/go/kr/project/api/internal/util/NewGpkiUtil.java b/src/main/java/go/kr/project/api/internal/util/NewGpkiUtil.java
deleted file mode 100644
index 08dda0e..0000000
--- a/src/main/java/go/kr/project/api/internal/util/NewGpkiUtil.java
+++ /dev/null
@@ -1,382 +0,0 @@
-package go.kr.project.api.internal.util;
-
-import com.gpki.gpkiapi.GpkiApi;
-import com.gpki.gpkiapi.cert.X509Certificate;
-import com.gpki.gpkiapi.crypto.PrivateKey;
-import com.gpki.gpkiapi.storage.Disk;
-import com.gpki.gpkiapi_jni;
-import lombok.extern.slf4j.Slf4j;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-@Slf4j
-public class NewGpkiUtil {
- byte[] myEnvCert, myEnvKey, mySigCert, mySigKey;
- private Map targetServerCertMap = new HashMap();
-
- // properties
- private String myServerId;
- private String targetServerIdList;
- private String envCertFilePathName;
- private String envPrivateKeyFilePathName;
- private String envPrivateKeyPasswd;
- private String sigCertFilePathName;
- private String sigPrivateKeyFilePathName;
- private String sigPrivateKeyPasswd;
- private String certFilePath;
- private String gpkiLicPath = ".";
- private boolean isLDAP;
- private boolean testGPKI = false;
-
-
- public void init() throws Exception {
- GpkiApi.init(gpkiLicPath);
- gpkiapi_jni gpki = this.getGPKI();
- if(log.isDebugEnabled()){
- if(gpki.API_GetInfo()==0)
- log.debug(gpki.sReturnString);
- else
- log.error(gpki.sDetailErrorString);
- }
- if(targetServerIdList!=null){
- String certIdList[] = targetServerIdList.split(",");
- for(int i = 0 ; i < certIdList.length ; i++){
- String certId = certIdList[i].trim();
- if(!certId.equals("")){
- load(gpki, certId);
- }
- }
- }
-
- log.info("Loading gpki certificate : myServerId="
- + this.getMyServerId());
-
- X509Certificate _myEnvCert = Disk.readCert(this
- .getEnvCertFilePathName());
- myEnvCert = _myEnvCert.getCert();
-
- PrivateKey _myEnvKey = Disk.readPriKey(this
- .getEnvPrivateKeyFilePathName(), this.getEnvPrivateKeyPasswd());
- myEnvKey = _myEnvKey.getKey();
-
- X509Certificate _mySigCert = Disk.readCert(this
- .getSigCertFilePathName());
- mySigCert = _mySigCert.getCert();
-
- PrivateKey _mySigKey = Disk.readPriKey(this
- .getSigPrivateKeyFilePathName(), this.getSigPrivateKeyPasswd());
- mySigKey = _mySigKey.getKey();
-
- //test my cert GPKI
- if(testGPKI){
- load(gpki, this.getMyServerId());
- testGpki(gpki);
- }
- this.finish(gpki);
- log.info("GpkiUtil initialized");
- }
-
- private void load(gpkiapi_jni gpki, String certId) throws Exception {
-
- log.debug("Loading gpki certificate : targetServerId="+ certId);
-
- X509Certificate cert = targetServerCertMap.get(certId);
- if (cert != null) {
- return;
- }
-
- if (isLDAP) {
-// String ldapUrl = "ldap://10.1.7.140:389/cn=";
-// String ldapUrl = "ldap://ldap.gcc.go.kr:389/cn=";
- String ldapUrl = "ldap://10.1.7.118:389/cn="; // 행정망인 경우
-// String ldapUrl = "ldap://152.99.57.127:389/cn="; // 인터넷망인 경우
- String ldapUri;
- if (certId.charAt(3) > '9') {
- ldapUri = ",ou=Group of Server,o=Public of Korea,c=KR";
- } else {
- ldapUri = ",ou=Group of Server,o=Government of Korea,c=KR";
- }
-
- int ret = gpki.LDAP_GetAnyDataByURL("userCertificate;binary", ldapUrl + certId + ldapUri);
- this.checkResult(ret, gpki);
- cert = new X509Certificate(gpki.baReturnArray);
- } else {
- if(certFilePath != null){
- cert = Disk.readCert(certFilePath + File.separator + certId + ".cer");
- }else{
- log.debug("not certFilePath");
- }
- }
-
- targetServerCertMap.put(certId, cert);
- }
-
- private gpkiapi_jni getGPKI(){
- gpkiapi_jni gpki = new gpkiapi_jni();
- if(gpki.API_Init(gpkiLicPath) != 0){
- log.error(gpki.sDetailErrorString);
- }
- return gpki;
- }
- private void finish(gpkiapi_jni gpki){
- if(gpki.API_Finish() != 0){
- log.error(gpki.sDetailErrorString);
- }
- }
-
- public byte[] encrypt(byte[] plain, String certId , boolean load) throws Exception {
- X509Certificate targetEnvCert = targetServerCertMap.get(certId);
- if (targetEnvCert == null) {
- throw new Exception("Certificate not found : targetServerId=" + certId);
- }
-
- gpkiapi_jni gpki = this.getGPKI();
- try{
- int result = gpki.CMS_MakeEnvelopedData(targetEnvCert.getCert(), plain,
- gpkiapi_jni.SYM_ALG_NEAT_CBC);
- checkResult(result, "Fail to encrypt message", gpki);
-
- return gpki.baReturnArray;
- }catch(Exception ex){
- throw ex;
- }finally{
- finish(gpki);
- }
- }
-
- public byte[] encrypt(byte[] plain, String certId) throws Exception {
- return encrypt(plain,certId , false);
- }
-
- public byte[] decrypt(byte[] encrypted) throws Exception {
-
- gpkiapi_jni gpki = this.getGPKI();
- try{
- int result = gpki.CMS_ProcessEnvelopedData(myEnvCert, myEnvKey,
- encrypted);
- checkResult(result, "Fail to decrpyt message", gpki);
-
- return gpki.baReturnArray;
- }catch(Exception ex){
- throw ex;
- }finally{
- finish(gpki);
- }
- }
-
- public byte[] sign(byte[] plain) throws Exception {
-
- gpkiapi_jni gpki = this.getGPKI();
- try{
- int result = gpki.CMS_MakeSignedData(mySigCert, mySigKey, plain, null);
- checkResult(result, "Fail to sign message", gpki);
-
- return gpki.baReturnArray;
- }catch(Exception ex){
- throw ex;
- }finally{
- finish(gpki);
- }
- }
-
- public byte[] validate(byte[] signed) throws Exception {
-
- gpkiapi_jni gpki = this.getGPKI();
- try{
- int result = gpki.CMS_ProcessSignedData(signed);
- checkResult(result, "Fail to validate signed message", gpki);
- return gpki.baData;
- }catch(Exception ex){
- throw ex;
- }finally{
- finish(gpki);
- }
- }
-
- public String encode(byte[] plain) throws Exception {
-
- gpkiapi_jni gpki = this.getGPKI();
- try{
- int result = gpki.BASE64_Encode(plain);
- checkResult(result, "Fail to encode message", gpki);
-
- return gpki.sReturnString;
- }catch(Exception ex){
- throw ex;
- }finally{
- finish(gpki);
- }
-
- }
-
- public byte[] decode(String base64) throws Exception {
-
- gpkiapi_jni gpki = this.getGPKI();
- try{
- int result = gpki.BASE64_Decode(base64);
- checkResult(result, "Fail to decode base64 message", gpki);
-
- return gpki.baReturnArray;
- }catch(Exception ex){
- throw ex;
- }finally{
- finish(gpki);
- }
- }
-
- private void checkResult(int result, gpkiapi_jni gpki)throws Exception{
- this.checkResult(result, null, gpki);
- }
-
- private void checkResult(int result ,String message, gpkiapi_jni gpki)throws Exception{
- if( 0 != result){
- if(null != gpki){
- throw new Exception(message + " : gpkiErrorMessage=" + gpki.sDetailErrorString);
- }else{
- throw new Exception(message + " : gpkiErrorCode=" + result);
- }
- }
- }
-
- public void testGpki(gpkiapi_jni gpki) throws Exception{
- //gpki test eng
- log.info("=======================================================");
- log.info("================ TEST GPKI START ======================");
- log.info("=======================================================");
- String original_Eng = "abc";
- log.info("=== TEST ENG STRING: "+ original_Eng);
- try {
- byte[] encrypted = encrypt(original_Eng.getBytes(), myServerId);
- log.info("=== TEST ENG ENCRYPT STRING: "+ encode(encrypted));
- String decrypted = new String(decrypt(encrypted));
- log.info("=== TEST ENG DECRYPT STRING: "+decrypted);
-
- if (!original_Eng.equals(decrypted)) {
- throw new Exception("GpkiUtil not initialized properly(english)");
- }
- log.info("=== TEST ENG: OK");
- } catch (Exception e) {
- log.warn("Gpki Test error(english)", e);
- throw e;
- }
- //gpki test kor
- String original = "한글테스트";
- log.info("=== TEST KOR STRING: "+ original);
- try {
- byte[] encrypted = encrypt(original.getBytes(), myServerId);
- log.info("=== TEST KOR ENCRYPT STRING: "+ encode(encrypted));
- String decrypted = new String(decrypt(encrypted));
- log.info("=== TEST KOR DECRYPT STRING: "+decrypted);
- if (!original.equals(decrypted)) {
- throw new Exception("GpkiUtil not initialized properly(korean)");
- }
- log.info("=== TEST KOR: OK");
- } catch (Exception e) {
- log.warn("Gpki Test error(korean)", e);
- throw e;
- }finally{
- log.info("=======================================================");
- log.info("================ TEST GPKI END ========================");
- log.info("=======================================================");
- }
- }
-
- public String getMyServerId() {
- return myServerId;
- }
-
- public void setMyServerId(String myServerId) {
- this.myServerId = myServerId.trim();
- }
-
- public String getEnvCertFilePathName() {
- return envCertFilePathName;
- }
-
- public void setEnvCertFilePathName(String envCertFilePathName) {
- this.envCertFilePathName = envCertFilePathName.trim();
- }
-
- public String getEnvPrivateKeyFilePathName() {
- return envPrivateKeyFilePathName;
- }
-
- public void setEnvPrivateKeyFilePathName(String envPrivateKeyFilePathName) {
- this.envPrivateKeyFilePathName = envPrivateKeyFilePathName.trim();
- }
-
- public String getEnvPrivateKeyPasswd() {
- return envPrivateKeyPasswd;
- }
-
- public void setEnvPrivateKeyPasswd(String envPrivateKeyPasswd) {
- this.envPrivateKeyPasswd = envPrivateKeyPasswd.trim();
- }
-
- public String getSigPrivateKeyPasswd() {
- return sigPrivateKeyPasswd;
- }
-
- public void setSigPrivateKeyPasswd(String sigPrivateKeyPasswd) {
- this.sigPrivateKeyPasswd = sigPrivateKeyPasswd.trim();
- }
-
- public String getSigCertFilePathName() {
- return sigCertFilePathName;
- }
-
- public void setSigCertFilePathName(String sigCertFilePathName) {
- this.sigCertFilePathName = sigCertFilePathName.trim();
- }
-
- public String getSigPrivateKeyFilePathName() {
- return sigPrivateKeyFilePathName;
- }
-
- public void setSigPrivateKeyFilePathName(String sigPrivateKeyFilePathName) {
- this.sigPrivateKeyFilePathName = sigPrivateKeyFilePathName.trim();
- }
-
- public boolean getIsLDAP() {
- return isLDAP;
- }
-
- public void setIsLDAP(boolean isLDAP) {
- this.isLDAP = isLDAP;
- }
-
- public String getCertFilePath() {
- return certFilePath;
- }
-
- public void setCertFilePath(String certFilePath) {
- this.certFilePath = certFilePath.trim();
- }
-
- public String getTargetServerIdList() {
- return targetServerIdList;
- }
-
- public void setTargetServerIdList(String targetServerIdList) {
- this.targetServerIdList = targetServerIdList;
- }
-
- public String getGpkiLicPath() {
- return gpkiLicPath;
- }
-
- public void setGpkiLicPath(String gpkiLicPath) {
- this.gpkiLicPath = gpkiLicPath;
- }
-
- public boolean getTestGPKI() {
- return testGPKI;
- }
-
- public void setTestGPKI(boolean testGPKI) {
- this.testGPKI = testGPKI;
- }
-
-}
diff --git a/src/main/java/go/kr/project/api/internal/util/TxIdUtil.java b/src/main/java/go/kr/project/api/internal/util/TxIdUtil.java
deleted file mode 100644
index ceb20fd..0000000
--- a/src/main/java/go/kr/project/api/internal/util/TxIdUtil.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package go.kr.project.api.internal.util;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Random;
-
-public final class TxIdUtil {
- private static final Random RANDOM = new Random();
-
- private TxIdUtil() {}
-
- public static String generate() {
- String time = new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.KOREA).format(new Date());
- int random = 100000 + RANDOM.nextInt(900000);
- return time + "_" + random;
- }
-}
diff --git a/src/main/java/go/kr/project/api/internal/mapper/VmisCarBassMatterInqireMapper.java b/src/main/java/go/kr/project/api/mapper/VmisCarBassMatterInqireMapper.java
similarity index 97%
rename from src/main/java/go/kr/project/api/internal/mapper/VmisCarBassMatterInqireMapper.java
rename to src/main/java/go/kr/project/api/mapper/VmisCarBassMatterInqireMapper.java
index 7dc49cc..74b09e5 100644
--- a/src/main/java/go/kr/project/api/internal/mapper/VmisCarBassMatterInqireMapper.java
+++ b/src/main/java/go/kr/project/api/mapper/VmisCarBassMatterInqireMapper.java
@@ -1,4 +1,4 @@
-package go.kr.project.api.internal.mapper;
+package go.kr.project.api.mapper;
import go.kr.project.api.model.response.VmisCarBassMatterInqireVO;
import org.apache.ibatis.annotations.Mapper;
diff --git a/src/main/java/go/kr/project/api/internal/mapper/VmisCarLedgerFrmbkMapper.java b/src/main/java/go/kr/project/api/mapper/VmisCarLedgerFrmbkMapper.java
similarity index 95%
rename from src/main/java/go/kr/project/api/internal/mapper/VmisCarLedgerFrmbkMapper.java
rename to src/main/java/go/kr/project/api/mapper/VmisCarLedgerFrmbkMapper.java
index 34e5ef0..cb4a7b7 100644
--- a/src/main/java/go/kr/project/api/internal/mapper/VmisCarLedgerFrmbkMapper.java
+++ b/src/main/java/go/kr/project/api/mapper/VmisCarLedgerFrmbkMapper.java
@@ -1,4 +1,4 @@
-package go.kr.project.api.internal.mapper;
+package go.kr.project.api.mapper;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkDtlVO;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkVO;
diff --git a/src/main/java/go/kr/project/api/external/service/ExternalVehicleApiService.java b/src/main/java/go/kr/project/api/service/ExternalVehicleApiService.java
similarity index 69%
rename from src/main/java/go/kr/project/api/external/service/ExternalVehicleApiService.java
rename to src/main/java/go/kr/project/api/service/ExternalVehicleApiService.java
index 8ffa5b8..b35e802 100644
--- a/src/main/java/go/kr/project/api/external/service/ExternalVehicleApiService.java
+++ b/src/main/java/go/kr/project/api/service/ExternalVehicleApiService.java
@@ -1,6 +1,5 @@
-package go.kr.project.api.external.service;
+package go.kr.project.api.service;
-import go.kr.project.api.model.VehicleApiResponseVO;
import go.kr.project.api.model.request.BasicRequest;
import go.kr.project.api.model.request.LedgerRequest;
import go.kr.project.api.model.response.BasicResponse;
@@ -12,14 +11,6 @@ import go.kr.project.api.model.response.LedgerResponse;
*/
public interface ExternalVehicleApiService {
- /**
- * 단일 차량에 대한 정보 조회 (상세 파라미터 포함)
- *
- * @param basicRequest 기본정보 조회 요청 (차량번호, 부과기준일, 조회구분 등 포함)
- * @return 차량 정보 응답
- */
- VehicleApiResponseVO getVehicleInfo(BasicRequest basicRequest);
-
/**
* 차량 기본정보만 조회 (외부 REST 호출)
* 중요: 기본정보 조회는 차량번호 외에 부과기준일, 조회구분 등 필수 파라미터 필요
diff --git a/src/main/java/go/kr/project/api/service/VehicleInfoService.java b/src/main/java/go/kr/project/api/service/VehicleInfoService.java
deleted file mode 100644
index d20bfc8..0000000
--- a/src/main/java/go/kr/project/api/service/VehicleInfoService.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package go.kr.project.api.service;
-
-import go.kr.project.api.model.VehicleApiResponseVO;
-import go.kr.project.api.model.request.BasicRequest;
-import go.kr.project.api.model.request.LedgerRequest;
-import go.kr.project.api.model.response.BasicResponse;
-import go.kr.project.api.model.response.LedgerResponse;
-
-/**
- * 차량 정보 조회 서비스 인터페이스
- *
- * 이 인터페이스는 차량 정보를 조회하는 두 가지 구현체를 추상화합니다:
- *
- * 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")
- * );
- *
- * // 단독 조회 (기본/등록원부)
- * BasicResponse basic = vehicleInfoService.getBasicInfo("12가3456");
- * LedgerResponse ledger = vehicleInfoService.getLedgerInfo("12가3456");
- * }
- *
- */
-public interface VehicleInfoService {
-
- /**
- * 단일 차량에 대한 정보 조회 (상세 파라미터 포함)
- *
- * 차량 기본정보와 등록원부 정보를 함께 조회합니다.
- * 차량번호 외에 부과기준일, 조회구분, 차대번호 등 추가 파라미터를 포함하여 조회할 수 있습니다.
- *
- * @param basicRequest 기본정보 조회 요청 (차량번호, 부과기준일, 조회구분 등 포함)
- * @return 차량 정보 응답 (기본정보 + 등록원부 정보)
- */
- VehicleApiResponseVO getVehicleInfo(BasicRequest basicRequest);
-
- /**
- * 차량 기본정보만 조회 (단독)
- * 중요: 차량번호 외에 부과기준일, 조회구분, 차대번호 등 필수 파라미터를 모두 포함한 BasicRequest 필요
- *
- * @param request 기본정보 조회 요청 (차량번호, 부과기준일, 조회구분 등 포함)
- * @return 기본정보 응답
- */
- BasicResponse getBasicInfo(BasicRequest request);
-
- /**
- * 자동차 등록원부(갑)만 조회 (단독)
- * 중요: 차량번호 외에 소유자정보, 조회구분 등 필수 파라미터를 모두 포함한 LedgerRequest 필요
- *
- * @param request 등록원부 조회 요청 (차량번호, 소유자정보, 조회구분 등 포함)
- * @return 등록원부 응답
- */
- LedgerResponse getLedgerInfo(LedgerRequest request);
-}
diff --git a/src/main/java/go/kr/project/api/external/service/impl/ExternalVehicleApiServiceImpl.java b/src/main/java/go/kr/project/api/service/impl/ExternalVehicleApiServiceImpl.java
similarity index 75%
rename from src/main/java/go/kr/project/api/external/service/impl/ExternalVehicleApiServiceImpl.java
rename to src/main/java/go/kr/project/api/service/impl/ExternalVehicleApiServiceImpl.java
index c606fdc..262d3db 100644
--- a/src/main/java/go/kr/project/api/external/service/impl/ExternalVehicleApiServiceImpl.java
+++ b/src/main/java/go/kr/project/api/service/impl/ExternalVehicleApiServiceImpl.java
@@ -1,10 +1,10 @@
-package go.kr.project.api.external.service.impl;
+package go.kr.project.api.service.impl;
import egovframework.exception.MessageException;
import go.kr.project.api.config.ApiConstant;
import go.kr.project.api.config.properties.VmisProperties;
-import go.kr.project.api.external.service.ExternalVehicleApiService;
-import go.kr.project.api.internal.util.ExceptionDetailUtil;
+import go.kr.project.api.service.ExternalVehicleApiService;
+import go.kr.project.api.util.ExceptionDetailUtil;
import go.kr.project.api.model.Envelope;
import go.kr.project.api.model.VehicleApiResponseVO;
import go.kr.project.api.model.request.BasicRequest;
@@ -36,60 +36,6 @@ public class ExternalVehicleApiServiceImpl extends EgovAbstractServiceImpl imple
private final VmisCarBassMatterInqireLogService bassMatterLogService; // 기본사항 조회 로그 서비스
private final VmisCarLedgerFrmbkLogService ledgerLogService; // 등록원부 로그 서비스
- @Override
- public VehicleApiResponseVO getVehicleInfo(BasicRequest basicRequest) {
- String vehicleNumber = basicRequest.getVhrno();
- log.info("차량 정보 조회 시작 - 차량번호: {}, 부과기준일: {}, 조회구분: {}",
- vehicleNumber, basicRequest.getLevyStdde(), basicRequest.getInqireSeCode());
-
- VehicleApiResponseVO response = new VehicleApiResponseVO();
- response.setVhrno(vehicleNumber);
-
- try {
- // 1. 차량 기본정보 조회
- // 중요 로직: BasicRequest 전체를 사용하여 조회
- BasicResponse basicInfo = getBasicInfo(basicRequest);
- response.setBasicInfo(basicInfo);
-
- // 2. 자동차 등록원부 조회
- // 중요 로직: 통합 조회 시에는 차량번호와 기본정보를 바탕으로 LedgerRequest 생성
- LedgerRequest ledgerRequest = new LedgerRequest();
- ledgerRequest.setVhrno(vehicleNumber);
-
- // basicInfo에서 민원인 정보 가져오기
- if (basicInfo != null && basicInfo.getRecord() != null && !basicInfo.getRecord().isEmpty()) {
- BasicResponse.Record record = basicInfo.getRecord().get(0);
- ledgerRequest.setCpttrNm(record.getMberNm()); // 민원인성명
- ledgerRequest.setCpttrIhidnum(record.getMberSeNo()); // 민원인주민번호
- }
- // 고정값 설정
- ledgerRequest.setCpttrLegaldongCode(null); // 민원인법정동코드
- ledgerRequest.setRouteSeCode("3"); // 경로구분코드
- ledgerRequest.setDetailExpression("1"); // 내역표시 (전체내역)
-
- LedgerResponse ledgerInfo = getLedgerInfo(ledgerRequest);
- response.setLedgerInfo(ledgerInfo);
-
- // 3. 결과 검증
- if (basicInfo != null && ApiConstant.CNTC_RESULT_CODE_SUCCESS.equals(basicInfo.getCntcResultCode())) {
- response.setSuccess(true);
- response.setMessage("조회 성공");
- log.info("차량번호 {} 조회 성공", vehicleNumber);
- } else {
- response.setSuccess(false);
- response.setMessage(basicInfo != null ? basicInfo.getCntcResultDtls() : "조회 실패");
- log.warn("차량번호 {} 조회 실패 - {}", vehicleNumber, response.getMessage());
- }
-
- } catch (Exception e) {
- response.setSuccess(false);
- response.setMessage("API 호출 오류: " + e.getMessage());
- log.error("차량번호 {} API 호출 중 오류 발생", vehicleNumber, e);
- }
-
- return response;
- }
-
/**
* 차량 기본정보 조회 API 호출
* 중요 로직: 기본정보 조회는 BasicRequest 전체를 받아서 외부 API에 전달
diff --git a/src/main/java/go/kr/project/api/service/impl/VmisCarBassMatterInqireLogServiceImpl.java b/src/main/java/go/kr/project/api/service/impl/VmisCarBassMatterInqireLogServiceImpl.java
index 20bdd1e..b6c8250 100644
--- a/src/main/java/go/kr/project/api/service/impl/VmisCarBassMatterInqireLogServiceImpl.java
+++ b/src/main/java/go/kr/project/api/service/impl/VmisCarBassMatterInqireLogServiceImpl.java
@@ -1,7 +1,7 @@
package go.kr.project.api.service.impl;
import egovframework.util.SessionUtil;
-import go.kr.project.api.internal.mapper.VmisCarBassMatterInqireMapper;
+import go.kr.project.api.mapper.VmisCarBassMatterInqireMapper;
import go.kr.project.api.model.response.VmisCarBassMatterInqireVO;
import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
import lombok.RequiredArgsConstructor;
diff --git a/src/main/java/go/kr/project/api/service/impl/VmisCarLedgerFrmbkLogServiceImpl.java b/src/main/java/go/kr/project/api/service/impl/VmisCarLedgerFrmbkLogServiceImpl.java
index 4cd6db0..c331fa1 100644
--- a/src/main/java/go/kr/project/api/service/impl/VmisCarLedgerFrmbkLogServiceImpl.java
+++ b/src/main/java/go/kr/project/api/service/impl/VmisCarLedgerFrmbkLogServiceImpl.java
@@ -1,7 +1,7 @@
package go.kr.project.api.service.impl;
import egovframework.util.SessionUtil;
-import go.kr.project.api.internal.mapper.VmisCarLedgerFrmbkMapper;
+import go.kr.project.api.mapper.VmisCarLedgerFrmbkMapper;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkDtlVO;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkVO;
import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
diff --git a/src/main/java/go/kr/project/api/internal/util/ExceptionDetailUtil.java b/src/main/java/go/kr/project/api/util/ExceptionDetailUtil.java
similarity index 95%
rename from src/main/java/go/kr/project/api/internal/util/ExceptionDetailUtil.java
rename to src/main/java/go/kr/project/api/util/ExceptionDetailUtil.java
index ccc1e17..43c15d3 100644
--- a/src/main/java/go/kr/project/api/internal/util/ExceptionDetailUtil.java
+++ b/src/main/java/go/kr/project/api/util/ExceptionDetailUtil.java
@@ -1,4 +1,4 @@
-package go.kr.project.api.internal.util;
+package go.kr.project.api.util;
/**
* Common helper to extract root-cause message and truncate to DB column limit (default 4000 chars).
diff --git a/src/main/java/go/kr/project/carInspectionPenalty/callApi/controller/VehicleInquiryController.java b/src/main/java/go/kr/project/carInspectionPenalty/callApi/controller/VehicleInquiryController.java
index f2b3db3..4e218ac 100644
--- a/src/main/java/go/kr/project/carInspectionPenalty/callApi/controller/VehicleInquiryController.java
+++ b/src/main/java/go/kr/project/carInspectionPenalty/callApi/controller/VehicleInquiryController.java
@@ -2,12 +2,11 @@ package go.kr.project.carInspectionPenalty.callApi.controller;
import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil;
-import go.kr.project.api.model.VehicleApiResponseVO;
import go.kr.project.api.model.request.BasicRequest;
import go.kr.project.api.model.request.LedgerRequest;
import go.kr.project.api.model.response.BasicResponse;
import go.kr.project.api.model.response.LedgerResponse;
-import go.kr.project.api.service.VehicleInfoService;
+import go.kr.project.api.service.ExternalVehicleApiService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
@@ -29,7 +28,7 @@ import org.springframework.web.bind.annotation.*;
@Tag(name = "차량 정보 조회", description = "차량 정보 조회 API")
public class VehicleInquiryController {
- private final VehicleInfoService vehicleInfoService;
+ private final ExternalVehicleApiService service;
/**
* 차량 정보 조회 화면
@@ -40,41 +39,6 @@ public class VehicleInquiryController {
return "carInspectionPenalty/callApi/inquiry" + TilesConstants.BASE;
}
- /**
- * 자동차 통합 조회 (기본정보 + 등록원부)
- *
- * @param request 기본정보 조회 요청
- * @return 차량 통합 정보 조회 결과
- */
- @PostMapping("/getIntegratedInfo.do")
- @ResponseBody
- @Operation(summary = "자동차 통합 조회", description = "차량 기본정보와 등록원부 정보를 함께 조회합니다.")
- public ResponseEntity> getIntegratedInfo(@RequestBody BasicRequest request) {
- log.info("========== 자동차 통합 조회 시작 ==========");
- log.info("요청 차량번호: {}", request.getVhrno());
- log.info("부과기준일: {}", request.getLevyStdde());
- log.info("조회구분코드: {}", request.getInqireSeCode());
- log.info("차대번호: {}", request.getVin());
-
- // 입력값 검증
- if (!StringUtils.hasText(request.getVhrno())) {
- log.warn("차량번호가 입력되지 않았습니다.");
- return ApiResponseUtil.error("차량번호를 입력해주세요.");
- }
-
- // 차량 정보 조회
- VehicleApiResponseVO response = vehicleInfoService.getVehicleInfo(request);
- if(!response.isSuccess()) {
- log.warn("자동차 통합 조회 실패 - 차량번호: {}, 메시지: {}", request.getVhrno(), response.getMessage());
- log.warn("========== 자동차 통합 조회 실패 ==========");
- return ApiResponseUtil.error(response.getMessage());
- }
-
- log.info("자동차 통합 조회 성공 - 차량번호: {}", request.getVhrno());
- log.info("========== 자동차 통합 조회 완료 ==========");
- return ApiResponseUtil.success(response, "자동차 통합 조회가 완료되었습니다.");
- }
-
/**
* 자동차 기본사항 조회 (단독)
*
@@ -98,7 +62,7 @@ public class VehicleInquiryController {
}
// 차량 기본정보 조회
- BasicResponse response = vehicleInfoService.getBasicInfo(request);
+ BasicResponse response = service.getBasicInfo(request);
log.info("자동차 기본사항 조회 성공 - 차량번호: {}", request.getVhrno());
log.info("========== 자동차 기본사항 조회 완료 ==========");
@@ -128,7 +92,7 @@ public class VehicleInquiryController {
}
// 차량 등록원부 조회
- LedgerResponse response = vehicleInfoService.getLedgerInfo(request);
+ LedgerResponse response = service.getLedgerInfo(request);
log.info("자동차 등록원부(갑) 조회 성공 - 차량번호: {}", request.getVhrno());
log.info("========== 자동차 등록원부(갑) 조회 완료 ==========");
diff --git a/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/CarFfnlgTrgtServiceImpl.java b/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/CarFfnlgTrgtServiceImpl.java
index f4200ef..7652c42 100644
--- a/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/CarFfnlgTrgtServiceImpl.java
+++ b/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/CarFfnlgTrgtServiceImpl.java
@@ -2,9 +2,7 @@ package go.kr.project.carInspectionPenalty.registration.service.impl;
import egovframework.constant.TaskPrcsSttsConstants;
import egovframework.exception.MessageException;
-import go.kr.project.api.model.VehicleApiResponseVO;
-import go.kr.project.api.model.request.BasicRequest;
-import go.kr.project.api.service.VehicleInfoService;
+import go.kr.project.api.service.ExternalVehicleApiService;
import go.kr.project.carInspectionPenalty.registration.config.CarFfnlgTxtParseConfig;
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
@@ -37,7 +35,7 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
private final CarFfnlgTrgtMapper mapper;
private final CarFfnlgTxtParseConfig parseConfig;
- private final VehicleInfoService vehicleInfoService;
+ private final ExternalVehicleApiService service;
private final ComparisonService comparisonService;
@@ -947,33 +945,9 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
continue;
}
- // 2. API 호출 (통합 조회)
- BasicRequest apiRequest = new BasicRequest();
- apiRequest.setVhrno(vhclno);
- // TODO : 기본적으로 검사일 기준으로 api 호출
- apiRequest.setLevyStdde(inspYmd != null ? inspYmd.replace("-", "") : "");
- //apiRequest.setLevyStdde(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
-
- VehicleApiResponseVO apiResponse = vehicleInfoService.getVehicleInfo(apiRequest);
-
- if (!apiResponse.isSuccess()) {
- compareResult.put("success", false);
- compareResult.put("message", "API 호출 실패: " + apiResponse.getMessage());
- failCount++;
- compareResults.add(compareResult);
- continue;
- }
-
- // 2.5. API 호출 성공 시 히스토리 ID 업데이트
- if (apiResponse.getCarBassMatterInqireId() != null || apiResponse.getCarLedgerFrmbkId() != null) {
- existingData.setCarBassMatterInqireId(apiResponse.getCarBassMatterInqireId());
- existingData.setCarLedgerFrmbkId(apiResponse.getCarLedgerFrmbkId());
- log.info("API 히스토리 ID 업데이트 - 차량번호: {}, 기본정보ID: {}, 원부ID: {}",
- vhclno, apiResponse.getCarBassMatterInqireId(), apiResponse.getCarLedgerFrmbkId());
- }
// 3. 비교 로직 실행
- String statusCode = comparisonService.executeComparison(existingData, apiResponse, rgtr);
+ String statusCode = null;
// 결과 처리
if (statusCode != null) {
diff --git a/src/main/java/go/kr/project/config/ProjectMapperConfig.java b/src/main/java/go/kr/project/config/ProjectMapperConfig.java
index d74a9ea..6d5bb7b 100644
--- a/src/main/java/go/kr/project/config/ProjectMapperConfig.java
+++ b/src/main/java/go/kr/project/config/ProjectMapperConfig.java
@@ -19,10 +19,7 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration
@MapperScan(basePackages = {
- "go.kr.project.carInspectionPenalty.**.mapper",
- "go.kr.project.common.mapper",
- "go.kr.project.login.mapper",
- "go.kr.project.system.**.mapper",
+ "go.kr.project.**.mapper",
"egovframework.**.mapper"
})
public class ProjectMapperConfig {
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index dc7177a..f3d4bbe 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -138,7 +138,7 @@ file:
max-size: 10 # 단일 파일 최대 크기 (MB)
max-total-size: 100 # 총 파일 최대 크기 (MB)
max-files: 10 # 최대 파일 개수
- allowed-extensions: hwp,jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip
+ allowed-extensions: txt,hwp,jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip
real-file-delete: true # 실제 파일 삭제 여부
sub-dirs:
bbs-notice: bbs/notice # 공지사항 sample 파일 저장 경로
@@ -153,83 +153,21 @@ juso:
# ===== VMIS 통합 설정 (Dev 환경) =====
vmis:
- integration:
- mode: external # internal: 내부 VMIS 모듈 직접 호출, external: 외부 REST API 호출
-
- # RestTemplate 설정 (모드별 분기)
+ # RestTemplate 설정
rest-template:
- # Internal Mode용 설정 (정부 API 호출)
- internal:
- timeout:
- connect-timeout-millis: 5000 # 연결 타임아웃 (정부 API는 빠른 응답 기대)
- read-timeout-millis: 10000 # 읽기 타임아웃
- connection-pool:
- max-total: 100 # 최대 연결 수
- max-per-route: 20 # 경로당 최대 연결 수
- rate-limit:
- permits-per-second: 5.0 # 초당 5건 제한
-
- # External Mode용 설정 (외부 VMIS-interface API 호출)
- external:
- 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 # 초당 5건 제한
-
- # Internal Mode 설정 (내부 VMIS 모듈 사용 시)
- system:
- infoSysId: "41-345" # 정보시스템 ID
- infoSysIp: "105.19.10.135" # 시스템 IP
- sigunguCode: "41460" # 시군구 코드
- departmentCode: "" # 부서 코드
- chargerId: "" # 담당자 ID
- chargerIp: "" # 담당자 IP
- chargerNm: "" # 담당자명
-
- # GPKI 암호화 설정 (개발 환경: 비활성화)
- gpki:
- enabled: "N" # GPKI 사용 여부 (개발환경에서는 비활성화)
- useSign: true # 서명 사용 여부
- charset: "UTF-8" # 문자셋 인코딩
- certServerId: "SVR5640020001" # 인증서 서버 ID (요청 시스템)
- targetServerId: "SVR1611000006" # 대상 서버 ID (차세대교통안전공단)
- ldap: true # LDAP 사용 여부
- gpkiLicPath: "C:\\GPKI\\Lic" # GPKI 라이선스 파일 경로
- certFilePath: "c:\\GPKI\\Certificate\\class1" # 인증서 파일 디렉토리 경로
- envCertFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_env.cer" # 암호화용 인증서 파일 경로
- envPrivateKeyFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_env.key" # 암호화용 개인키 파일 경로
- envPrivateKeyPasswd: "*sbm204221" # 암호화용 개인키 비밀번호
- sigCertFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_sig.cer" # 서명용 인증서 파일 경로
- sigPrivateKeyFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_sig.key" # 서명용 개인키 파일 경로
- sigPrivateKeyPasswd: "*sbm204221" # 서명용 개인키 비밀번호
-
- # 정부 API 연동 설정 (개발 행정망)
- # 타임아웃 설정은 공통 rest-template 설정 사용
- gov:
- scheme: "http"
- host: "10.188.225.94:29001" # 개발(DEV) 행정망
- basePath: "/piss/api/molit"
- services:
- basic: # 시군구연계 자동차기본사항조회
- path: "/SignguCarBassMatterInqireService"
- cntcInfoCode: "AC1_FD11_01"
- apiKey: "05e8d748fb366a0831dce71a32424460746a72d591cf483ccc130534dd51e394"
- cvmisApikey: "014F9215-B6D9A3B6-4CED5225-68408C46"
- ledger: # 시군구연계 자동차등록원부(갑)
- path: "/SignguCarLedgerFrmbkService"
- cntcInfoCode: "AC1_FD11_02"
- apiKey: "1beeb01857c2e7e9b41c002b007ccb9754d9c272f66d4bb64fc45b302c69e529"
- cvmisApikey: "63DF159B-7B9C64C5-86CCB15C-5F93E750"
-
- # External Mode 설정 (외부 REST API 사용 시)
- # 타임아웃 설정은 공통 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 # 초당 5건 제한
+
+ # External API 설정
external:
api:
url:
- base: "http://localhost:8081/api/v1/vehicles" # VMIS-interface 서버 URL (로컬)
+ base: "http://localhost:8081/api/v1/vehicles" # VMIS-interface 서버 URL (개발)
basic: "/basic" # 자동차기본정보
ledger: "/ledger" # 자동차등록원부
\ No newline at end of file
diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml
index ba52880..f63609a 100644
--- a/src/main/resources/application-local.yml
+++ b/src/main/resources/application-local.yml
@@ -158,82 +158,20 @@ juso:
key: "devU01TX0FVVEgyMDI1MDkyMjEyMTM1NzExNjI0NzE="
url: "https://business.juso.go.kr/addrlink/addrLinkApiJsonp.do"
-# ===== VMIS 통합 설정 (Dev 환경) =====
+# ===== VMIS 통합 설정 (Local 환경) =====
vmis:
- integration:
- mode: external # internal: 내부 VMIS 모듈 직접 호출, external: 외부 REST API 호출
-
- # RestTemplate 설정 (모드별 분기)
+ # RestTemplate 설정
rest-template:
- # Internal Mode용 설정 (정부 API 호출)
- internal:
- timeout:
- connect-timeout-millis: 5000 # 연결 타임아웃 (정부 API는 빠른 응답 기대)
- read-timeout-millis: 10000 # 읽기 타임아웃
- connection-pool:
- max-total: 100 # 최대 연결 수
- max-per-route: 20 # 경로당 최대 연결 수
- rate-limit:
- permits-per-second: 5.0 # 초당 5건 제한
-
- # External Mode용 설정 (외부 VMIS-interface API 호출)
- external:
- 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 # 초당 5건 제한
-
- # Internal Mode 설정 (내부 VMIS 모듈 사용 시)
- system:
- infoSysId: "41-345" # 정보시스템 ID
- infoSysIp: "105.19.10.135" # 시스템 IP
- sigunguCode: "41460" # 시군구 코드
- departmentCode: "" # 부서 코드
- chargerId: "" # 담당자 ID
- chargerIp: "" # 담당자 IP
- chargerNm: "" # 담당자명
-
- # GPKI 암호화 설정 (개발 환경: 비활성화)
- gpki:
- enabled: "N" # GPKI 사용 여부 (개발환경에서는 비활성화)
- useSign: true # 서명 사용 여부
- charset: "UTF-8" # 문자셋 인코딩
- certServerId: "SVR5640020001" # 인증서 서버 ID (요청 시스템)
- targetServerId: "SVR1611000006" # 대상 서버 ID (차세대교통안전공단)
- ldap: true # LDAP 사용 여부
- gpkiLicPath: "C:\\GPKI\\Lic" # GPKI 라이선스 파일 경로
- certFilePath: "c:\\GPKI\\Certificate\\class1" # 인증서 파일 디렉토리 경로
- envCertFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_env.cer" # 암호화용 인증서 파일 경로
- envPrivateKeyFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_env.key" # 암호화용 개인키 파일 경로
- envPrivateKeyPasswd: "*sbm204221" # 암호화용 개인키 비밀번호
- sigCertFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_sig.cer" # 서명용 인증서 파일 경로
- sigPrivateKeyFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_sig.key" # 서명용 개인키 파일 경로
- sigPrivateKeyPasswd: "*sbm204221" # 서명용 개인키 비밀번호
-
- # 정부 API 연동 설정 (개발 행정망)
- # 타임아웃 설정은 공통 rest-template 설정 사용
- gov:
- scheme: "http"
- host: "10.188.225.94:29001" # 개발(DEV) 행정망
- basePath: "/piss/api/molit"
- services:
- basic: # 시군구연계 자동차기본사항조회
- path: "/SignguCarBassMatterInqireService"
- cntcInfoCode: "AC1_FD11_01"
- apiKey: "05e8d748fb366a0831dce71a32424460746a72d591cf483ccc130534dd51e394"
- cvmisApikey: "014F9215-B6D9A3B6-4CED5225-68408C46"
- ledger: # 시군구연계 자동차등록원부(갑)
- path: "/SignguCarLedgerFrmbkService"
- cntcInfoCode: "AC1_FD11_02"
- apiKey: "1beeb01857c2e7e9b41c002b007ccb9754d9c272f66d4bb64fc45b302c69e529"
- cvmisApikey: "63DF159B-7B9C64C5-86CCB15C-5F93E750"
-
- # External Mode 설정 (외부 REST API 사용 시)
- # 타임아웃 설정은 공통 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 # 초당 5건 제한
+
+ # External API 설정
external:
api:
url:
diff --git a/src/main/resources/application-prd.yml b/src/main/resources/application-prd.yml
index f972bf8..856d567 100644
--- a/src/main/resources/application-prd.yml
+++ b/src/main/resources/application-prd.yml
@@ -138,7 +138,7 @@ file:
max-size: 10 # 단일 파일 최대 크기 (MB)
max-total-size: 100 # 총 파일 최대 크기 (MB)
max-files: 10 # 최대 파일 개수
- allowed-extensions: hwp,jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip
+ allowed-extensions: txt,hwp,jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip
real-file-delete: true # 실제 파일 삭제 여부
sub-dirs:
bbs-notice: bbs/notice # 공지사항 sample 파일 저장 경로
@@ -155,83 +155,21 @@ juso:
# ===== VMIS 통합 설정 (운영 환경) =====
# 주의: 실제 운영 키/호스트는 배포 환경 변수나 외부 설정(Secret)로 주입 권장
vmis:
- integration:
- mode: external # internal: 내부 VMIS 모듈 직접 호출, external: 외부 REST API 호출
-
- # RestTemplate 설정 (모드별 분기)
+ # RestTemplate 설정
rest-template:
- # Internal Mode용 설정 (정부 API 호출)
- internal:
- timeout:
- connect-timeout-millis: 5000 # 연결 타임아웃 (정부 API는 빠른 응답 기대)
- read-timeout-millis: 10000 # 읽기 타임아웃
- connection-pool:
- max-total: 100 # 최대 연결 수
- max-per-route: 20 # 경로당 최대 연결 수
- rate-limit:
- permits-per-second: 5.0 # 초당 5건 제한
-
- # External Mode용 설정 (외부 VMIS-interface API 호출)
- external:
- 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 # 초당 5건 제한
-
- # Internal Mode 설정 (내부 VMIS 모듈 사용 시)
- system:
- infoSysId: "41-345" # 운영 실제값으로 교체 필요
- infoSysIp: "105.19.10.135"
- sigunguCode: "41460" # 시군구 코드 (운영 실제값으로 교체 필요)
- departmentCode: "" # 운영 실제값
- chargerId: ""
- chargerIp: ""
- chargerNm: ""
-
- # GPKI 암호화 설정 (운영 환경: 활성화)
- gpki:
- enabled: "Y" # GPKI 사용 여부 (운영환경에서는 활성화)
- useSign: true # 서명 사용 여부
- charset: "UTF-8" # 문자셋 인코딩
- certServerId: "SVR5640020001" # 인증서 서버 ID (요청 시스템)
- targetServerId: "SVR1611000006" # 대상 서버 ID (차세대교통안전공단)
- ldap: true # LDAP 사용 여부
- gpkiLicPath: "C:\\GPKI\\VMIS-Lic" # GPKI 라이선스 파일 경로
- certFilePath: "c:\\GPKI\\Certificate\\class1" # 인증서 파일 디렉토리 경로
- envCertFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_env.cer" # 암호화용 인증서 파일 경로
- envPrivateKeyFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_env.key" # 암호화용 개인키 파일 경로
- envPrivateKeyPasswd: "*sbm204221" # 암호화용 개인키 비밀번호
- sigCertFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_sig.cer" # 서명용 인증서 파일 경로
- sigPrivateKeyFilePathName: "c:\\GPKI\\Certificate\\class1\\SVR5640020001_sig.key" # 서명용 개인키 파일 경로
- sigPrivateKeyPasswd: "*sbm204221" # 서명용 개인키 비밀번호
-
- # 정부 API 연동 설정 (운영 행정망)
- # 타임아웃 설정은 공통 rest-template 설정 사용
- gov:
- scheme: "http"
- host: "10.188.225.25:29001" # 운영 행정망
- basePath: "/piss/api/molit"
- services:
- basic: # 시군구연계 자동차기본사항조회
- path: "/SignguCarBassMatterInqireService"
- cntcInfoCode: "AC1_FD11_01"
- apiKey: "05e8d748fb366a0831dce71a32424460746a72d591cf483ccc130534dd51e394"
- cvmisApikey: "014F9215-B6D9A3B6-4CED5225-68408C46"
- ledger: # 시군구연계 자동차등록원부(갑)
- path: "/SignguCarLedgerFrmbkService"
- cntcInfoCode: "AC1_FD11_02"
- apiKey: "1beeb01857c2e7e9b41c002b007ccb9754d9c272f66d4bb64fc45b302c69e529"
- cvmisApikey: "63DF159B-7B9C64C5-86CCB15C-5F93E750"
-
- # External Mode 설정 (외부 REST API 사용 시)
- # 타임아웃 설정은 공통 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 # 초당 5건 제한
+
+ # External API 설정
external:
api:
url:
- base: "http://localhost:18080/api/v1/vehicles" # VMIS-interface 서버 URL (로컬)
+ base: "http://localhost:18080/api/v1/vehicles" # VMIS-interface 서버 URL (운영)
basic: "/basic" # 자동차기본정보
ledger: "/ledger" # 자동차등록원부
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index fd736ff..c55512e 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -31,7 +31,6 @@ mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations:
- classpath:mybatis/mapper/**/*_${Globals.DbType}.xml
- - classpath:mybatis/mapper/api-internal/**/*_${Globals.DbType}.xml
type-aliases-package: go.kr.project.**.model,egovframework.**.model
# Springdoc OpenAPI 설정
diff --git a/src/main/resources/mybatis/mapper/api-internal/CarBassMatterInqireMapper_maria.xml b/src/main/resources/mybatis/mapper/api/CarBassMatterInqireMapper_maria.xml
similarity index 99%
rename from src/main/resources/mybatis/mapper/api-internal/CarBassMatterInqireMapper_maria.xml
rename to src/main/resources/mybatis/mapper/api/CarBassMatterInqireMapper_maria.xml
index 4c2faad..bc83a04 100644
--- a/src/main/resources/mybatis/mapper/api-internal/CarBassMatterInqireMapper_maria.xml
+++ b/src/main/resources/mybatis/mapper/api/CarBassMatterInqireMapper_maria.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/src/main/resources/mybatis/mapper/api-internal/CarLedgerFrmbkMapper_maria.xml b/src/main/resources/mybatis/mapper/api/CarLedgerFrmbkMapper_maria.xml
similarity index 99%
rename from src/main/resources/mybatis/mapper/api-internal/CarLedgerFrmbkMapper_maria.xml
rename to src/main/resources/mybatis/mapper/api/CarLedgerFrmbkMapper_maria.xml
index 842afcc..cd3a4e1 100644
--- a/src/main/resources/mybatis/mapper/api-internal/CarLedgerFrmbkMapper_maria.xml
+++ b/src/main/resources/mybatis/mapper/api/CarLedgerFrmbkMapper_maria.xml
@@ -2,7 +2,7 @@
-
+