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 인터페이스만 스캔합니다.

- * - * - * - *

일반 프로젝트의 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 통합 모드에 대한 정보를 제공하고, - * 애플리케이션 시작 시 현재 활성화된 모드를 로그로 출력합니다.

- * - *

주요 기능:

- * - * - *

지원하는 모드:

- * - */ -@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
- * 
- * - *

처리 흐름:

- *
    - *
  1. 기존 ExternalVehicleApiService에 요청 위임
  2. - *
  3. ExternalVehicleApiService가 외부 REST API 호출
  4. - *
  5. 결과를 그대로 반환
  6. - *
- * - *

외부 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_ynGPKI 암호화 사용 여부 (Y/N)Y필수
tx_id트랜잭션 고유 ID (요청 추적용)20250104123045_abc123필수
cert_server_id인증서 서버 식별자VMIS_SERVER_01필수
api_key서비스별 API 인증 키abc123def456...필수
cvmis_apikeyCVMIS 시스템 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에 로그성 데이터 저장
  • - *
- * - *

처리 흐름:

- *
    - *
  1. 요청 정보를 DB에 INSERT (로그 저장)
  2. - *
  3. 정부 API 호출
  4. - *
  5. 응답 정보를 DB에 UPDATE
  6. - *
  7. 에러 발생 시 에러 정보도 DB에 UPDATE
  8. - *
- * - *

사용 예시:

- *
-     * 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)
  • - *
  • 타입 안전성을 보장하여 런타임 에러 방지
  • - *
- * - *

처리 흐름 (상세):

- *
    - *
  1. 설정 로드: - *
    • 서비스 타입에 따라 BASIC 또는 LEDGER 설정 선택
    - *
  2. - *
  3. URL 및 트랜잭션 ID 구성: - *
    • 완전한 API URL 생성
    • - *
    • 고유 트랜잭션 ID 생성
    - *
  4. - *
  5. 직렬화 (Serialization): - *
    • Java 객체(Envelope<TReq>)를 JSON 문자열로 변환
    • - *
    • ObjectMapper.writeValueAsString() 사용
    - *
  6. - *
  7. 헤더 구성: - *
    • buildHeaders() 메서드 호출
    • - *
    • 모든 필수 헤더 추가
    - *
  8. - *
  9. GPKI 암호화 (선택적): - *
    • GPKI가 활성화된 경우 JSON을 암호화
    • - *
    • gpkiEncrypt() 메서드 호출
    - *
  10. - *
  11. HTTP 요청 전송: - *
    • RestTemplate.exchange()로 POST 요청
    • - *
    • 요청 로그 기록
    - *
  12. - *
  13. GPKI 복호화 (선택적): - *
    • 성공 응답(2xx)이고 GPKI가 활성화된 경우
    • - *
    • gpkiDecrypt() 메서드 호출
    - *
  14. - *
  15. 역직렬화 (Deserialization): - *
    • JSON 문자열을 Java 객체(Envelope<TResp>)로 변환
    • - *
    • TypeReference를 사용하여 제네릭 타입 정보 보존
    - *
  16. - *
  17. 응답 반환: - *
    • ResponseEntity로 감싸서 HTTP 정보 포함
    - *
  18. - *
- * - *

에러 처리 전략 (3단계):

- *
    - *
  1. HttpStatusCodeException (HTTP 에러): - *
      - *
    • 정부 API가 4xx 또는 5xx 상태 코드를 반환한 경우
    • - *
    • 에러 응답 바디를 파싱하여 Envelope 객체로 변환 시도
    • - *
    • 파싱 실패 시 빈 Envelope 객체 반환
    • - *
    • 에러 로그 기록 (WARN 레벨)
    • - *
    - *
  2. - *
  3. JSON 파싱 에러: - *
      - *
    • 응답 JSON이 예상한 형식과 다른 경우
    • - *
    • RuntimeException으로 래핑하여 상위로 전파
    • - *
    - *
  4. - *
  5. 기타 예외: - *
      - *
    • 네트워크 타임아웃, 연결 실패 등
    • - *
    • RuntimeException으로 래핑하여 상위로 전파
    • - *
    - *
  6. - *
- * - *

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을 발생시킵니다.

- * - *

암호화 과정:

- *
    - *
  1. 평문 JSON 문자열을 바이트 배열로 변환
  2. - *
  3. 정부 시스템의 공개키를 사용하여 암호화
  4. - *
  5. 암호화된 바이트 배열을 Base64로 인코딩
  6. - *
  7. Base64 문자열 반환
  8. - *
- * - *

에러 처리:

- *
    - *
  • 인증서 오류: 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을 발생시킵니다.

- * - *

복호화 과정:

- *
    - *
  1. Base64로 인코딩된 암호문을 바이트 배열로 디코딩
  2. - *
  3. 우리 시스템의 개인키를 사용하여 복호화
  4. - *
  5. 복호화된 바이트 배열을 UTF-8 문자열로 변환
  6. - *
  7. 평문 JSON 문자열 반환
  8. - *
- * - *

에러 처리:

- *
    - *
  • 인증서 오류: 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
- * 
- * - *

처리 흐름:

- *
    - *
  1. 차량번호를 받아 BasicRequest, LedgerRequest 생성
  2. - *
  3. VmisCarBassMatterInqireService.basic() 호출 (기본정보)
  4. - *
  5. VmisCarLedgerFrmbkService.ledger() 호출 (등록원부)
  6. - *
  7. BasicResponse, LedgerResponse를 직접 VehicleApiResponseVO에 설정
  8. - *
  9. VehicleApiResponseVO로 결과 반환
  10. - *
- * - * @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 @@ - +