외부호출만 있는 프로젝트로 변경 작업 진행중...

main
박성영 4 weeks ago
parent 298f1fe27f
commit e128e126be

@ -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'

@ -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자리 이상 필요

Binary file not shown.

@ -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
*
* <p>VMIS API Mapper .</p>
*
* <ul>
* <li>DataSource: egovframework (DataSourceProxyConfig)</li>
* <li>TransactionManager: egovframework (EgovConfigTransaction.txManager)</li>
* <li>SqlSessionFactory: MyBatis Spring Boot Starter </li>
* <li>MapperScan: go.kr.project.api.internal.mapper (API )</li>
* </ul>
*
* <p> Mapper egovframework .</p>
*/
@Configuration
@MapperScan(basePackages = "go.kr.project.api.internal.mapper")
public class ApiMapperConfig {
}

@ -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();

@ -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
*
* <p> VMIS ,
* .</p>
*
* <h3> :</h3>
* <ul>
* <li> VMIS </li>
* <li> VehicleInfoService </li>
* <li> </li>
* </ul>
*
* <h3> :</h3>
* <ul>
* <li>internal: VMIS (InternalVehicleInfoServiceImpl)</li>
* <li>external: REST API (ExternalVehicleInfoServiceImpl)</li>
* </ul>
*/
@Slf4j
@Configuration
@RequiredArgsConstructor
public class VmisIntegrationConfig {
private final VmisProperties vmisProperties;
/**
* VMIS
*
* <p> VMIS
* .</p>
*
* @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());
}
}

@ -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,56 +57,8 @@ 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();
@NotNull
private ModeConfig external = new ModeConfig();
@Data
public static class ModeConfig {
@NotNull
private TimeoutConfig timeout = new TimeoutConfig();
@NotNull
@ -143,51 +83,4 @@ public class VmisProperties {
private double permitsPerSecond = 5.0;
}
}
}
@Data
public static class GovProps {
@NotBlank
private String scheme = "http";
@NotBlank
private String host;
@NotBlank
private String basePath;
@NotNull
private Services services = new Services();
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 Services {
@NotNull
private Service basic = new Service();
@NotNull
private Service ledger = new Service();
}
@Data
public static class Service {
@NotBlank
private String path;
@NotBlank
private String cntcInfoCode;
@NotBlank
private String apiKey;
@NotBlank
private String cvmisApikey;
}
}
}

@ -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<BasicRequest> 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<Envelope<VehicleApiResponseVO>> info(
@org.springframework.web.bind.annotation.RequestBody Envelope<BasicRequest> 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<VehicleApiResponseVO> 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<BasicResponse> 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<LedgerResponse> out = (ledger != null) ? new Envelope<>(ledger) : new Envelope<>(Collections.emptyList());
return ResponseEntity.ok(out);
}

@ -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
*
* <p> VMIS-interface REST API
* . ExternalVehicleApiService .</p>
*
* <h3> :</h3>
* <pre>
* # application.yml
* vmis:
* integration:
* mode: external
* </pre>
*
* <h3> :</h3>
* <ol>
* <li> ExternalVehicleApiService </li>
* <li>ExternalVehicleApiService REST API </li>
* <li> </li>
* </ol>
*
* <h3> API :</h3>
* <ul>
* <li> URL: vmis.external.api.url </li>
* <li>: http://localhost:8081/api/v1/vehicles</li>
* <li> VMIS-interface </li>
* </ul>
*
* @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);
}
}

@ -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 .
*
* <p>
* .</p>
*/
public interface GovernmentApi {
ResponseEntity<Envelope<BasicResponse>> callBasic(Envelope<BasicRequest> envelope);
ResponseEntity<Envelope<LedgerResponse>> callLedger(Envelope<LedgerRequest> envelope);
}

@ -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
*
* <p> API
* . HTTP , , API
* .</p>
*
* <h3> :</h3>
* <ul>
* <li> API HTTP </li>
* <li>GPKI() / </li>
* <li> HTTP </li>
* <li>/ JSON /</li>
* <li> ID(tx_id) </li>
* <li> HTTP </li>
* <li> </li>
* </ul>
*
* <h3> :</h3>
* <ul>
* <li>Adapter : API </li>
* <li>Template Method : callModel </li>
* <li>Dependency Injection: </li>
* </ul>
*
* <h3> :</h3>
* <ul>
* <li>GPKI ( )</li>
* <li>API </li>
* <li> (INFO_SYS_ID, REGION_CODE ) </li>
* </ul>
*
* @see RestTemplate
* @see GpkiService
* @see VmisProperties
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class GovernmentApiClient implements GovernmentApi {
/**
* Spring RestTemplate ( RestTemplate )
*
* <p>HTTP .
* Spring Bean , :</p>
* <ul>
* <li>Connection Timeout </li>
* <li>Read Timeout </li>
* <li>Connection Pool </li>
* <li>Rate Limiting ( )</li>
* <li> (Jackson for JSON)</li>
* <li> (, )</li>
* </ul>
*/
private final RestTemplate restTemplate;
/**
* VMIS
*
* <p>application.yml application.properties .
* :</p>
* <ul>
* <li> API URL (, , )</li>
* <li>API </li>
* <li> (INFO_SYS_ID, REGION_CODE )</li>
* <li>GPKI ( ID )</li>
* </ul>
*/
private final VmisProperties props;
/**
* GPKI()
*
* <p>24 .
* :</p>
* <ul>
* <li> ( )</li>
* <li> ( )</li>
* <li> </li>
* <li> </li>
* </ul>
*
* <p> (Plain Text) .</p>
*/
private final GpkiService gpkiService;
/**
* Jackson ObjectMapper
*
* <p>Java JSON .
* :</p>
* <ul>
* <li> JSON (Serialization)</li>
* <li> JSON Java (Deserialization)</li>
* <li> (TypeReference )</li>
* <li>/ </li>
* <li>null </li>
* </ul>
*
* <p>Spring Boot ObjectMapper
* ( , ) .</p>
*/
private final ObjectMapper objectMapper;
/**
*
*
* <p> API .
* API .</p>
*
* <h4> :</h4>
* <ul>
* <li>BASIC:
* <ul>
* <li> (, , ) </li>
* <li> </li>
* <li> </li>
* </ul>
* </li>
* <li>LEDGER: ()
* <ul>
* <li> </li>
* <li>, , </li>
* <li> </li>
* </ul>
* </li>
* </ul>
*/
public enum ServiceType {
/**
* Basic service type.
*/
BASIC,
/**
* Ledger service type.
*/
LEDGER }
/**
* HTTP
*
* <p> API HTTP private .
* .</p>
*
* <h3> :</h3>
* <table border="1">
* <tr>
* <th></th>
* <th></th>
* <th></th>
* <th></th>
* </tr>
* <tr>
* <td>Content-Type</td>
* <td> </td>
* <td>application/json; charset=UTF-8</td>
* <td></td>
* </tr>
* <tr>
* <td>Accept</td>
* <td> </td>
* <td>application/json</td>
* <td></td>
* </tr>
* <tr>
* <td>gpki_yn</td>
* <td>GPKI (Y/N)</td>
* <td>Y</td>
* <td></td>
* </tr>
* <tr>
* <td>tx_id</td>
* <td> ID ( )</td>
* <td>20250104123045_abc123</td>
* <td></td>
* </tr>
* <tr>
* <td>cert_server_id</td>
* <td> </td>
* <td>VMIS_SERVER_01</td>
* <td></td>
* </tr>
* <tr>
* <td>api_key</td>
* <td> API </td>
* <td>abc123def456...</td>
* <td></td>
* </tr>
* <tr>
* <td>cvmis_apikey</td>
* <td>CVMIS API </td>
* <td>xyz789uvw012...</td>
* <td></td>
* </tr>
* <tr>
* <td>INFO_SYS_ID</td>
* <td> </td>
* <td>VMIS_SEOUL</td>
* <td></td>
* </tr>
* </table>
*
* <h3> :</h3>
* <ul>
* <li>Content-Type UTF-8 </li>
* <li> </li>
* <li> </li>
* </ul>
*
* <h3> :</h3>
* <ul>
* <li>API </li>
* <li> API </li>
* <li> (BASIC, LEDGER) API </li>
* </ul>
*
* @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
*
* <p> .
* {@link #callModel} .</p>
*
* <h3>:</h3>
* <ul>
* <li> </li>
* <li>/ Envelope </li>
* <li>Jackson TypeReference </li>
* <li>API DB </li>
* </ul>
*
* <h3> :</h3>
* <ol>
* <li> DB INSERT ( )</li>
* <li> API </li>
* <li> DB UPDATE</li>
* <li> DB UPDATE</li>
* </ol>
*
* <h3> :</h3>
* <pre>
* BasicRequest request = new BasicRequest();
* request.setVehicleNo("12가3456");
*
* Envelope&lt;BasicRequest&gt; envelope = new Envelope&lt;&gt;();
* envelope.setData(request);
*
* ResponseEntity&lt;Envelope&lt;BasicResponse&gt;&gt; response = govClient.callBasic(envelope);
* BasicResponse data = response.getBody().getData();
* </pre>
*
* @param envelope Envelope
* @return ResponseEntity&lt;Envelope&lt;BasicResponse&gt;&gt;
*/
public ResponseEntity<Envelope<BasicResponse>> callBasic(Envelope<BasicRequest> envelope) {
// 순수한 전송 책임만 수행: DB 로깅은 서비스 레이어에서 처리
return callModel(ServiceType.BASIC, envelope, new TypeReference<Envelope<BasicResponse>>(){});
}
/**
* () API
*
* <p> .
* {@link #callModel} .</p>
*
* <h3>:</h3>
* <ul>
* <li> </li>
* <li>/ Envelope </li>
* <li>Jackson TypeReference </li>
* </ul>
*
* <h3> :</h3>
* <pre>
* LedgerRequest request = new LedgerRequest();
* request.setVehicleNo("12가3456");
* request.setOwnerName("홍길동");
*
* Envelope&lt;LedgerRequest&gt; envelope = new Envelope&lt;&gt;();
* envelope.setData(request);
*
* ResponseEntity&lt;Envelope&lt;LedgerResponse&gt;&gt; response = govClient.callLedger(envelope);
* LedgerResponse data = response.getBody().getData();
* </pre>
*
* @param envelope Envelope
* @return ResponseEntity&lt;Envelope&lt;LedgerResponse&gt;&gt;
*/
public ResponseEntity<Envelope<LedgerResponse>> callLedger(Envelope<LedgerRequest> envelope) {
// TypeReference를 사용하여 제네릭 타입 정보 전달
// 익명 클래스를 생성하여 타입 소거(Type Erasure) 문제 해결
return callModel(ServiceType.LEDGER, envelope, new TypeReference<Envelope<LedgerResponse>>(){});
}
/**
* API ( )
*
* <p> API .
* Java JSON , ,
* Java .</p>
*
* <h3>Template Method :</h3>
* <ul>
* <li> Template Method 릿 </li>
* <li> </li>
* <li> </li>
* </ul>
*
* <h3> :</h3>
* <ul>
* <li>&lt;TReq&gt;: (BasicRequest LedgerRequest)</li>
* <li>&lt;TResp&gt;: (BasicResponse LedgerResponse)</li>
* <li> </li>
* </ul>
*
* <h3> ():</h3>
* <ol>
* <li><b> :</b>
* <ul><li> BASIC LEDGER </li></ul>
* </li>
* <li><b>URL ID :</b>
* <ul><li> API URL </li>
* <li> ID </li></ul>
* </li>
* <li><b> (Serialization):</b>
* <ul><li>Java (Envelope&lt;TReq&gt;) JSON </li>
* <li>ObjectMapper.writeValueAsString() </li></ul>
* </li>
* <li><b> :</b>
* <ul><li>buildHeaders() </li>
* <li> </li></ul>
* </li>
* <li><b>GPKI ():</b>
* <ul><li>GPKI JSON </li>
* <li>gpkiEncrypt() </li></ul>
* </li>
* <li><b>HTTP :</b>
* <ul><li>RestTemplate.exchange() POST </li>
* <li> </li></ul>
* </li>
* <li><b>GPKI ():</b>
* <ul><li> (2xx) GPKI </li>
* <li>gpkiDecrypt() </li></ul>
* </li>
* <li><b> (Deserialization):</b>
* <ul><li>JSON Java (Envelope&lt;TResp&gt;) </li>
* <li>TypeReference </li></ul>
* </li>
* <li><b> :</b>
* <ul><li>ResponseEntity HTTP </li></ul>
* </li>
* </ol>
*
* <h3> (3):</h3>
* <ol>
* <li><b>HttpStatusCodeException (HTTP ):</b>
* <ul>
* <li> API 4xx 5xx </li>
* <li> Envelope </li>
* <li> Envelope </li>
* <li> (WARN )</li>
* </ul>
* </li>
* <li><b>JSON :</b>
* <ul>
* <li> JSON </li>
* <li>RuntimeException </li>
* </ul>
* </li>
* <li><b> :</b>
* <ul>
* <li> , </li>
* <li>RuntimeException </li>
* </ul>
* </li>
* </ol>
*
* <h3>TypeReference :</h3>
* <p>Java (Type Erasure)
* {@code objectMapper.readValue(json, Envelope<TResp>.class)}
* . TypeReference
* Jackson .</p>
*
* <h3> :</h3>
* <ul>
* <li> : [GOV-REQ] url, tx_id, gpki, length</li>
* <li> : [GOV-ERR] status, body</li>
* </ul>
*
* @param <TReq>
* @param <TResp>
* @param type (BASIC LEDGER)
* @param envelope Envelope
* @param respType TypeReference ( )
* @return ResponseEntity&lt;Envelope&lt;TResp&gt;&gt; ResponseEntity
* @throws RuntimeException JSON / , , GPKI
*/
private <TReq, TResp> ResponseEntity<Envelope<TResp>> callModel(ServiceType type,
Envelope<TReq> envelope,
TypeReference<Envelope<TResp>> 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<String> 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<String> 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<TResp> 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<TResp> empty = new Envelope<>();
try {
// 에러 응답을 Envelope 객체로 파싱
Envelope<TResp> 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
*
* <p> JSON GPKI() .
* RuntimeException .</p>
*
* <h3> :</h3>
* <ol>
* <li> JSON </li>
* <li> </li>
* <li> Base64 </li>
* <li>Base64 </li>
* </ol>
*
* <h3> :</h3>
* <ul>
* <li> : GPKI </li>
* <li> : </li>
* <li> : Base64 </li>
* <li> RuntimeException </li>
* </ul>
*
* <h3> :</h3>
* <ul>
* <li> ()</li>
* <li> </li>
* <li> </li>
* </ul>
*
* @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
*
* <p> GPKI() .
* RuntimeException .</p>
*
* <h3> :</h3>
* <ol>
* <li>Base64 </li>
* <li> </li>
* <li> UTF-8 </li>
* <li> JSON </li>
* </ol>
*
* <h3> :</h3>
* <ul>
* <li> : GPKI </li>
* <li> : </li>
* <li> : Base64 </li>
* <li> : UTF-8 </li>
* <li> RuntimeException </li>
* </ul>
*
* <h3> :</h3>
* <ul>
* <li> ()</li>
* <li> </li>
* <li> ( )</li>
* </ul>
*
* @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);
}
}
}

@ -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();
}
}

@ -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(""));
}
}

@ -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 {
}

@ -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();
}

@ -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;
}
}

@ -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;
}
}

@ -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;
/**
*
*
* <p>API .</p>
* <ul>
* <li> </li>
* <li> </li>
* <li> API </li>
* <li> </li>
* </ul>
*/
public interface VmisCarBassMatterInqireService {
/**
*
*
* @param envelope Envelope
* @return Envelope
*/
ResponseEntity<Envelope<BasicResponse>> basic(Envelope<BasicRequest> envelope);
}

@ -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<Envelope<LedgerResponse>> ledger(Envelope<LedgerRequest> envelope);
}

@ -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<BasicRequest> 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<LedgerRequest> 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);
}
}

@ -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
*
* <p> REST API VMIS
* . .</p>
*
* <h3> :</h3>
* <pre>
* # application.yml
* vmis:
* integration:
* mode: internal
* </pre>
*
* <h3> :</h3>
* <ol>
* <li> BasicRequest, LedgerRequest </li>
* <li>VmisCarBassMatterInqireService.basic() ()</li>
* <li>VmisCarLedgerFrmbkService.ledger() ()</li>
* <li>BasicResponse, LedgerResponse VehicleApiResponseVO </li>
* <li>VehicleApiResponseVO </li>
* </ol>
*
* @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<BasicRequest> requestEnvelope = new Envelope<BasicRequest>();
requestEnvelope.setData(Collections.singletonList(request));
try {
// 내부 서비스 호출
ResponseEntity<Envelope<BasicResponse>> 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<LedgerRequest> requestEnvelope = new Envelope<LedgerRequest>();
requestEnvelope.setData(Collections.singletonList(request));
try {
// 내부 서비스 호출
ResponseEntity<Envelope<LedgerResponse>> 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<VehicleApiResponseVO> responses) {
int count = 0;
for (VehicleApiResponseVO response : responses) {
if (response.isSuccess()) {
count++;
}
}
return count;
}
}

@ -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;
/**
*
*
* <p>API .</p>
* <ul>
* <li> : createInitialRequest() - 퀀 ID INSERT</li>
* <li> : updateResponse() - UPDATE</li>
* </ul>
*/
@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<Envelope<BasicResponse>> basic(Envelope<BasicRequest> 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<Envelope<BasicResponse>> 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;
}
}
}

@ -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<Envelope<LedgerResponse>> ledger(Envelope<LedgerRequest> 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<Envelope<LedgerResponse>> 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<VmisCarLedgerFrmbkDtlVO> 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;
}
}
}

@ -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).");
}
}
}

@ -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<String, X509Certificate> targetServerCertMap = new HashMap<String, X509Certificate>();
// 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;
}
}

@ -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;
}
}

@ -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;

@ -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;

@ -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 )
* : ,

@ -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;
/**
*
*
* <p> :</p>
* <ul>
* <li>InternalVehicleInfoServiceImpl: VMIS (vmis.integration.mode=internal)</li>
* <li>ExternalVehicleInfoServiceImpl: REST API (vmis.integration.mode=external)</li>
* </ul>
*
* <h3> :</h3>
* <pre>
* # application.yml
* vmis:
* integration:
* mode: internal # external
* </pre>
*
* <h3> :</h3>
* <pre>
* {@code
* @Autowired
* private VehicleInfoService vehicleInfoService;
*
* // 단일 차량 조회
* VehicleApiResponseVO response = vehicleInfoService.getVehicleInfo("12가3456");
*
* // 여러 차량 일괄 조회
* List<VehicleApiResponseVO> responses = vehicleInfoService.getVehiclesInfo(
* Arrays.asList("12가3456", "34나5678")
* );
*
* // 단독 조회 (기본/등록원부)
* BasicResponse basic = vehicleInfoService.getBasicInfo("12가3456");
* LedgerResponse ledger = vehicleInfoService.getLedgerInfo("12가3456");
* }
* </pre>
*/
public interface VehicleInfoService {
/**
* ( )
*
* <p> .</p>
* <p> , , .</p>
*
* @param basicRequest (, , )
* @return ( + )
*/
VehicleApiResponseVO getVehicleInfo(BasicRequest basicRequest);
/**
* ()
* : , , BasicRequest
*
* @param request (, , )
* @return
*/
BasicResponse getBasicInfo(BasicRequest request);
/**
* () ()
* : , LedgerRequest
*
* @param request (, , )
* @return
*/
LedgerResponse getLedgerInfo(LedgerRequest request);
}

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

@ -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;

@ -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;

@ -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).

@ -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("========== 자동차 등록원부(갑) 조회 완료 ==========");

@ -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) {

@ -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 {

@ -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,26 +153,10 @@ 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 # 연결 타임아웃 (외부 서버 여유 있게)
connect-timeout-millis: 10000 # 연결 타임아웃
read-timeout-millis: 12000 # 읽기 타임아웃
connection-pool:
max-total: 100 # 최대 연결 수
@ -180,56 +164,10 @@ vmis:
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 설정 사용
# 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" # 자동차등록원부

@ -158,28 +158,12 @@ 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 # 연결 타임아웃 (외부 서버 여유 있게)
connect-timeout-millis: 10000 # 연결 타임아웃
read-timeout-millis: 12000 # 읽기 타임아웃
connection-pool:
max-total: 100 # 최대 연결 수
@ -187,53 +171,7 @@ vmis:
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 설정 사용
# External API 설정
external:
api:
url:

@ -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,26 +155,10 @@ 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 # 연결 타임아웃 (외부 서버 여유 있게)
connect-timeout-millis: 10000 # 연결 타임아웃
read-timeout-millis: 12000 # 읽기 타임아웃
connection-pool:
max-total: 100 # 최대 연결 수
@ -182,56 +166,10 @@ vmis:
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 설정 사용
# 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" # 자동차등록원부

@ -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 설정

@ -2,7 +2,7 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="go.kr.project.api.internal.mapper.VmisCarBassMatterInqireMapper">
<mapper namespace="go.kr.project.api.mapper.VmisCarBassMatterInqireMapper">
<!-- 시퀀스로 새로운 ID 생성 -->
<select id="selectNextCarBassMatterInqireId" resultType="String">

@ -2,7 +2,7 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="go.kr.project.api.internal.mapper.VmisCarLedgerFrmbkMapper">
<mapper namespace="go.kr.project.api.mapper.VmisCarLedgerFrmbkMapper">
<!-- 시퀀스로 새로운 마스터/상세 ID 생성 -->
<select id="selectNextCarLedgerFrmbkId" resultType="String">
Loading…
Cancel
Save