refactor: 차량 정보 및 등록원부 서비스 구현 추가

- ExternalVehicleApiServiceImpl: 외부 VMIS API 호출 구현
  - 차량 기본정보 및 등록원부 조회 로직 추가
  - 오류 처리 및 실패 응답 객체 생성 로직 포함
- VmisCarBassMatterInqireServiceImpl/VmisCarLedgerFrmbkServiceImpl:
  - 내부 서비스 관계와 API 호출 보강, 응답 로그 저장 로직 추가
  - 오류 시 트랜잭션 독립적 로그 작성 보장
- 관련 로그 서비스 구현체 (VmisCarBassMatterInqireLogServiceImpl, VmisCarLedgerFrmbkLogServiceImpl): 요청/응답 로그 저장 및 오류 로그 추가
- 기존 인터페이스 및 클래스 위치 이동 (VehicleInfoService 등)
internalApi
박성영 1 month ago
parent 6b47e3f84c
commit 91f7d04f4a

@ -1,6 +1,6 @@
package go.kr.project.api.config; package go.kr.project.api.config;
import go.kr.project.api.VehicleInfoService; import go.kr.project.api.external.service.VehicleInfoService;
import go.kr.project.api.config.properties.VmisProperties; import go.kr.project.api.config.properties.VmisProperties;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;

@ -1,33 +1,14 @@
package go.kr.project.api.external.service; package go.kr.project.api.external.service;
import go.kr.project.api.model.request.BasicRequest; import go.kr.project.api.model.VehicleApiResponseVO;
import go.kr.project.api.model.request.LedgerRequest;
import go.kr.project.api.model.*; import go.kr.project.api.model.response.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* API * VMIS-interface API
* VMIS-interface API * VMIS-interface API
*/ */
@Service public interface ExternalVehicleApiService {
@RequiredArgsConstructor
@Slf4j
public class ExternalVehicleApiService {
private final RestTemplate restTemplate;
// VMIS-interface API URL
private static final String VMIS_API_BASE_URL = "http://localhost:8081/api/v1/vehicles";
private static final String BASIC_INFO_URL = VMIS_API_BASE_URL + "/basic";
private static final String LEDGER_INFO_URL = VMIS_API_BASE_URL + "/ledger";
/** /**
* *
@ -35,34 +16,7 @@ public class ExternalVehicleApiService {
* @param vehicleNumbers * @param vehicleNumbers
* @return * @return
*/ */
public List<VehicleApiResponseVO> getVehiclesInfo(List<String> vehicleNumbers) { List<VehicleApiResponseVO> getVehiclesInfo(List<String> vehicleNumbers);
log.info("차량 정보 일괄 조회 시작 - 대상 차량 수: {}", vehicleNumbers.size());
List<VehicleApiResponseVO> responses = new ArrayList<>();
for (String vehicleNumber : vehicleNumbers) {
try {
VehicleApiResponseVO response = getVehicleInfo(vehicleNumber);
responses.add(response);
} catch (Exception e) {
log.error("차량번호 {} 조회 중 오류 발생: {}", vehicleNumber, e.getMessage(), e);
// 오류 발생 시에도 응답 객체 생성하여 추가
VehicleApiResponseVO errorResponse = VehicleApiResponseVO.builder()
.vhrno(vehicleNumber)
.success(false)
.message("조회 실패: " + e.getMessage())
.build();
responses.add(errorResponse);
}
}
log.info("차량 정보 일괄 조회 완료 - 성공: {}, 실패: {}",
responses.stream().filter(VehicleApiResponseVO::isSuccess).count(),
responses.stream().filter(r -> !r.isSuccess()).count());
return responses;
}
/** /**
* *
@ -70,134 +24,5 @@ public class ExternalVehicleApiService {
* @param vehicleNumber * @param vehicleNumber
* @return * @return
*/ */
public VehicleApiResponseVO getVehicleInfo(String vehicleNumber) { VehicleApiResponseVO getVehicleInfo(String vehicleNumber);
log.info("차량 정보 조회 시작 - 차량번호: {}", vehicleNumber);
VehicleApiResponseVO response = new VehicleApiResponseVO();
response.setVhrno(vehicleNumber);
try {
// 1. 차량 기본정보 조회
BasicResponse basicInfo = getBasicInfo(vehicleNumber);
response.setBasicInfo(basicInfo);
// 2. 자동차 등록원부 조회
LedgerResponse ledgerInfo = getLedgerInfo(vehicleNumber);
response.setLedgerInfo(ledgerInfo);
// 3. 결과 검증
if (basicInfo != null && "00".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
* VMIS-interface RequestEnricher
*
* @param vehicleNumber
* @return
*/
private BasicResponse getBasicInfo(String vehicleNumber) {
log.debug("차량 기본정보 조회 API 호출 - 차량번호: {}", vehicleNumber);
// 요청 객체 생성 - 차량번호만 설정 (나머지는 RequestEnricher가 자동 설정)
BasicRequest request = new BasicRequest();
request.setVhrno(vehicleNumber);
// Envelope로 감싸기
Envelope<BasicRequest> requestEnvelope = new Envelope<>(request);
// HTTP 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Envelope<BasicRequest>> requestEntity = new HttpEntity<>(requestEnvelope, headers);
try {
// API 호출
ResponseEntity<Envelope<BasicResponse>> responseEntity = restTemplate.exchange(
BASIC_INFO_URL,
HttpMethod.POST,
requestEntity,
new ParameterizedTypeReference<Envelope<BasicResponse>>() {}
);
if (responseEntity.getStatusCode() == HttpStatus.OK && responseEntity.getBody() != null) {
List<BasicResponse> data = responseEntity.getBody().getData();
if (data != null && !data.isEmpty()) {
return data.get(0);
}
}
log.warn("차량 기본정보 조회 응답이 비어있음 - 차량번호: {}", vehicleNumber);
return null;
} catch (Exception e) {
log.error("차량 기본정보 조회 API 호출 실패 - 차량번호: {}", vehicleNumber, e);
throw new RuntimeException("차량 기본정보 조회 실패: " + e.getMessage(), e);
}
}
/**
* () API
* VMIS-interface RequestEnricher
*
* @param vehicleNumber
* @return
*/
private LedgerResponse getLedgerInfo(String vehicleNumber) {
log.debug("자동차 등록원부 조회 API 호출 - 차량번호: {}", vehicleNumber);
// 요청 객체 생성 - 차량번호만 설정 (나머지는 RequestEnricher가 자동 설정)
LedgerRequest request = new LedgerRequest();
request.setVhrno(vehicleNumber);
// Envelope로 감싸기
Envelope<LedgerRequest> requestEnvelope = new Envelope<>(request);
// HTTP 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Envelope<LedgerRequest>> requestEntity = new HttpEntity<>(requestEnvelope, headers);
try {
// API 호출
ResponseEntity<Envelope<LedgerResponse>> responseEntity = restTemplate.exchange(
LEDGER_INFO_URL,
HttpMethod.POST,
requestEntity,
new ParameterizedTypeReference<Envelope<LedgerResponse>>() {}
);
if (responseEntity.getStatusCode() == HttpStatus.OK && responseEntity.getBody() != null) {
List<LedgerResponse> data = responseEntity.getBody().getData();
if (data != null && !data.isEmpty()) {
return data.get(0);
}
}
log.warn("자동차 등록원부 조회 응답이 비어있음 - 차량번호: {}", vehicleNumber);
return null;
} catch (Exception e) {
log.error("자동차 등록원부 조회 API 호출 실패 - 차량번호: {}", vehicleNumber, e);
throw new RuntimeException("자동차 등록원부 조회 실패: " + e.getMessage(), e);
}
}
} }

@ -1,4 +1,4 @@
package go.kr.project.api; package go.kr.project.api.external.service;
import go.kr.project.api.model.VehicleApiResponseVO; import go.kr.project.api.model.VehicleApiResponseVO;

@ -0,0 +1,195 @@
package go.kr.project.api.external.service.impl;
import go.kr.project.api.external.service.ExternalVehicleApiService;
import go.kr.project.api.model.request.BasicRequest;
import go.kr.project.api.model.request.LedgerRequest;
import go.kr.project.api.model.*;
import go.kr.project.api.model.response.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
/**
* VMIS-interface API
* VMIS-interface API
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class ExternalVehicleApiServiceImpl implements ExternalVehicleApiService {
private final RestTemplate restTemplate;
// VMIS-interface API URL
private static final String VMIS_API_BASE_URL = "http://localhost:8081/api/v1/vehicles";
private static final String BASIC_INFO_URL = VMIS_API_BASE_URL + "/basic";
private static final String LEDGER_INFO_URL = VMIS_API_BASE_URL + "/ledger";
@Override
public List<VehicleApiResponseVO> getVehiclesInfo(List<String> vehicleNumbers) {
log.info("차량 정보 일괄 조회 시작 - 대상 차량 수: {}", vehicleNumbers.size());
List<VehicleApiResponseVO> responses = new ArrayList<>();
for (String vehicleNumber : vehicleNumbers) {
try {
VehicleApiResponseVO response = getVehicleInfo(vehicleNumber);
responses.add(response);
} catch (Exception e) {
log.error("차량번호 {} 조회 중 오류 발생: {}", vehicleNumber, e.getMessage(), e);
// 오류 발생 시에도 응답 객체 생성하여 추가
VehicleApiResponseVO errorResponse = VehicleApiResponseVO.builder()
.vhrno(vehicleNumber)
.success(false)
.message("조회 실패: " + e.getMessage())
.build();
responses.add(errorResponse);
}
}
log.info("차량 정보 일괄 조회 완료 - 성공: {}, 실패: {}",
responses.stream().filter(VehicleApiResponseVO::isSuccess).count(),
responses.stream().filter(r -> !r.isSuccess()).count());
return responses;
}
@Override
public VehicleApiResponseVO getVehicleInfo(String vehicleNumber) {
log.info("차량 정보 조회 시작 - 차량번호: {}", vehicleNumber);
VehicleApiResponseVO response = new VehicleApiResponseVO();
response.setVhrno(vehicleNumber);
try {
// 1. 차량 기본정보 조회
BasicResponse basicInfo = getBasicInfo(vehicleNumber);
response.setBasicInfo(basicInfo);
// 2. 자동차 등록원부 조회
LedgerResponse ledgerInfo = getLedgerInfo(vehicleNumber);
response.setLedgerInfo(ledgerInfo);
// 3. 결과 검증
if (basicInfo != null && "00".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
* VMIS-interface RequestEnricher
*
* @param vehicleNumber
* @return
*/
private BasicResponse getBasicInfo(String vehicleNumber) {
log.debug("차량 기본정보 조회 API 호출 - 차량번호: {}", vehicleNumber);
// 요청 객체 생성 - 차량번호만 설정 (나머지는 RequestEnricher가 자동 설정)
BasicRequest request = new BasicRequest();
request.setVhrno(vehicleNumber);
// Envelope로 감싸기
Envelope<BasicRequest> requestEnvelope = new Envelope<>(request);
// HTTP 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Envelope<BasicRequest>> requestEntity = new HttpEntity<>(requestEnvelope, headers);
try {
// API 호출
ResponseEntity<Envelope<BasicResponse>> responseEntity = restTemplate.exchange(
BASIC_INFO_URL,
HttpMethod.POST,
requestEntity,
new ParameterizedTypeReference<Envelope<BasicResponse>>() {}
);
if (responseEntity.getStatusCode() == HttpStatus.OK && responseEntity.getBody() != null) {
List<BasicResponse> data = responseEntity.getBody().getData();
if (data != null && !data.isEmpty()) {
return data.get(0);
}
}
log.warn("차량 기본정보 조회 응답이 비어있음 - 차량번호: {}", vehicleNumber);
return null;
} catch (Exception e) {
log.error("차량 기본정보 조회 API 호출 실패 - 차량번호: {}", vehicleNumber, e);
throw new RuntimeException("차량 기본정보 조회 실패: " + e.getMessage(), e);
}
}
/**
* () API
* VMIS-interface RequestEnricher
*
* @param vehicleNumber
* @return
*/
private LedgerResponse getLedgerInfo(String vehicleNumber) {
log.debug("자동차 등록원부 조회 API 호출 - 차량번호: {}", vehicleNumber);
// 요청 객체 생성 - 차량번호만 설정 (나머지는 RequestEnricher가 자동 설정)
LedgerRequest request = new LedgerRequest();
request.setVhrno(vehicleNumber);
// Envelope로 감싸기
Envelope<LedgerRequest> requestEnvelope = new Envelope<>(request);
// HTTP 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Envelope<LedgerRequest>> requestEntity = new HttpEntity<>(requestEnvelope, headers);
try {
// API 호출
ResponseEntity<Envelope<LedgerResponse>> responseEntity = restTemplate.exchange(
LEDGER_INFO_URL,
HttpMethod.POST,
requestEntity,
new ParameterizedTypeReference<Envelope<LedgerResponse>>() {}
);
if (responseEntity.getStatusCode() == HttpStatus.OK && responseEntity.getBody() != null) {
List<LedgerResponse> data = responseEntity.getBody().getData();
if (data != null && !data.isEmpty()) {
return data.get(0);
}
}
log.warn("자동차 등록원부 조회 응답이 비어있음 - 차량번호: {}", vehicleNumber);
return null;
} catch (Exception e) {
log.error("자동차 등록원부 조회 API 호출 실패 - 차량번호: {}", vehicleNumber, e);
throw new RuntimeException("자동차 등록원부 조회 실패: " + e.getMessage(), e);
}
}
}

@ -1,6 +1,7 @@
package go.kr.project.api.external.service; package go.kr.project.api.external.service.impl;
import go.kr.project.api.VehicleInfoService; import go.kr.project.api.external.service.ExternalVehicleApiService;
import go.kr.project.api.external.service.VehicleInfoService;
import go.kr.project.api.model.VehicleApiResponseVO; import go.kr.project.api.model.VehicleApiResponseVO;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;

@ -1,56 +1,25 @@
package go.kr.project.api.internal.service; package go.kr.project.api.internal.service;
import go.kr.project.api.internal.mapper.VmisCarBassMatterInqireMapper;
import go.kr.project.api.model.response.VmisCarBassMatterInqireVO; import go.kr.project.api.model.response.VmisCarBassMatterInqireVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/** /**
* . *
* *
* <p> (REQUIRES_NEW) , * <p> (REQUIRES_NEW) ,
* .</p> * .</p>
*/ */
@Slf4j public interface VmisCarBassMatterInqireLogService {
@Service
@RequiredArgsConstructor
public class VmisCarBassMatterInqireLogService {
private final VmisCarBassMatterInqireMapper carBassMatterInqireMapper;
/** /**
* API . (REQUIRES_NEW) * API . (REQUIRES_NEW)
* @param request * @param request
* @return ID * @return ID
*/ */
@Transactional(propagation = Propagation.REQUIRES_NEW) String createInitialRequestNewTx(VmisCarBassMatterInqireVO request);
public String createInitialRequestNewTx(VmisCarBassMatterInqireVO request) {
String generatedId = carBassMatterInqireMapper.selectNextCarBassMatterInqireId();
request.setCarBassMatterInqire(generatedId);
int result = carBassMatterInqireMapper.insertCarBassMatterInqire(request);
if (result != 1) {
throw new RuntimeException("자동차 기본 사항 조회 정보 등록 실패");
}
log.info("[BASIC-REQ-LOG] 요청 정보 저장 완료(별도TX) - ID: {}, 차량번호: {}", generatedId, request.getDmndVhrno());
return generatedId;
}
/** /**
* / . (REQUIRES_NEW) * / . (REQUIRES_NEW)
* @param response * @param response
*/ */
@Transactional(propagation = Propagation.REQUIRES_NEW) void updateResponseNewTx(VmisCarBassMatterInqireVO response);
public void updateResponseNewTx(VmisCarBassMatterInqireVO response) {
if (response.getCarBassMatterInqire() == null) {
throw new IllegalArgumentException("자동차 기본 사항 조회 ID는 필수입니다.");
}
int result = carBassMatterInqireMapper.updateCarBassMatterInqire(response);
if (result != 1) {
throw new RuntimeException("자동차 기본 사항 조회 정보 업데이트 실패 - ID: " + response.getCarBassMatterInqire());
}
log.info("[BASIC-RES-LOG] 응답/에러 정보 저장 완료(별도TX) - ID: {}, 결과코드: {}", response.getCarBassMatterInqire(), response.getCntcResultCode());
}
} }

@ -1,87 +1,28 @@
package go.kr.project.api.internal.service; package go.kr.project.api.internal.service;
import go.kr.project.api.internal.client.GovernmentApi;
import go.kr.project.api.config.ApiConstant;
import go.kr.project.api.internal.util.ExceptionDetailUtil;
import go.kr.project.api.model.request.BasicRequest; import go.kr.project.api.model.request.BasicRequest;
import go.kr.project.api.model.response.BasicResponse; import go.kr.project.api.model.response.BasicResponse;
import go.kr.project.api.model.response.VmisCarBassMatterInqireVO;
import go.kr.project.api.model.Envelope; import go.kr.project.api.model.Envelope;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/** /**
* *
* *
* <p>API .</p> * <p>API .</p>
* <ul> * <ul>
* <li> : createInitialRequest() - 퀀 ID INSERT</li> * <li> </li>
* <li> : updateResponse() - UPDATE</li> * <li> </li>
* <li> API </li>
* <li> </li>
* </ul> * </ul>
*/ */
@Slf4j public interface VmisCarBassMatterInqireService {
@Service
@RequiredArgsConstructor
public class VmisCarBassMatterInqireService {
private final GovernmentApi governmentApi;
private final VmisRequestEnricher enricher;
private final VmisCarBassMatterInqireLogService logService;
/** /**
* : -> -> -> . *
*
* @param envelope Envelope
* @return Envelope
*/ */
@Transactional ResponseEntity<Envelope<BasicResponse>> basic(Envelope<BasicRequest> envelope);
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()
.carBassMatterInqire(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,61 +1,33 @@
package go.kr.project.api.internal.service; package go.kr.project.api.internal.service;
import go.kr.project.api.internal.mapper.VmisCarLedgerFrmbkMapper;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkDtlVO; import go.kr.project.api.model.response.VmisCarLedgerFrmbkDtlVO;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkVO; import go.kr.project.api.model.response.VmisCarLedgerFrmbkVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
/** /**
* () . * ()
* - (write) (REQUIRES_NEW) . * - (write) (REQUIRES_NEW) .
*/ */
@Slf4j public interface VmisCarLedgerFrmbkLogService {
@Service
@RequiredArgsConstructor
public class VmisCarLedgerFrmbkLogService {
private final VmisCarLedgerFrmbkMapper mapper; /**
* . (REQUIRES_NEW)
* @param request
* @return ID
*/
String createInitialRequestNewTx(VmisCarLedgerFrmbkVO request);
@Transactional(propagation = Propagation.REQUIRES_NEW) /**
public String createInitialRequestNewTx(VmisCarLedgerFrmbkVO request) { * . (REQUIRES_NEW)
String id = mapper.selectNextCarLedgerFrmbkId(); * @param response
request.setCarLedgerFrmbkId(id); */
int result = mapper.insertCarLedgerFrmbk(request); void updateResponseNewTx(VmisCarLedgerFrmbkVO response);
if (result != 1) {
throw new RuntimeException("자동차 등록 원부(갑) 최초요청 등록 실패");
}
log.info("[LEDGER-REQ-LOG] 최초 요청 저장(별도TX) - ID: {}, 차량번호: {}", id, request.getDmndVhrno());
return id;
}
@Transactional(propagation = Propagation.REQUIRES_NEW) /**
public void updateResponseNewTx(VmisCarLedgerFrmbkVO response) { * . (REQUIRES_NEW)
if (response.getCarLedgerFrmbkId() == null) { * @param masterId ID
throw new IllegalArgumentException("자동차 등록 원부(갑) ID는 필수입니다."); * @param details
} */
int updated = mapper.updateCarLedgerFrmbk(response); void saveDetailsNewTx(String masterId, List<VmisCarLedgerFrmbkDtlVO> details);
if (updated != 1) {
throw new RuntimeException("자동차 등록 원부(갑) 정보 업데이트 실패 - ID: " + response.getCarLedgerFrmbkId());
}
log.info("[LEDGER-RES-LOG] 마스터 응답 업데이트(별도TX) - ID: {}, 결과코드: {}",
response.getCarLedgerFrmbkId(), response.getCntcResultCode());
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveDetailsNewTx(String masterId, List<VmisCarLedgerFrmbkDtlVO> details) {
if (details == null || details.isEmpty()) return;
for (VmisCarLedgerFrmbkDtlVO dtl : details) {
String dtlId = mapper.selectNextCarLedgerFrmbkDtlId();
dtl.setCarLedgerFrmbkDtlId(dtlId);
dtl.setCarLedgerFrmbkId(masterId);
mapper.insertCarLedgerFrmbkDtl(dtl);
}
log.info("[LEDGER-RES-LOG] 상세 {}건 저장(별도TX) - ID: {}", details.size(), masterId);
}
} }

@ -1,85 +1,21 @@
package go.kr.project.api.internal.service; package go.kr.project.api.internal.service;
import go.kr.project.api.internal.client.GovernmentApi;
import go.kr.project.api.config.ApiConstant;
import go.kr.project.api.model.Envelope; import go.kr.project.api.model.Envelope;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkDtlVO;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkVO;
import go.kr.project.api.model.request.LedgerRequest; import go.kr.project.api.model.request.LedgerRequest;
import go.kr.project.api.model.response.LedgerResponse; import go.kr.project.api.model.response.LedgerResponse;
import go.kr.project.api.internal.util.ExceptionDetailUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/** /**
* () () * ()
* - , API , * - , API ,
*/ */
@Slf4j public interface VmisCarLedgerFrmbkService {
@Service
@RequiredArgsConstructor
public class VmisCarLedgerFrmbkService {
private final GovernmentApi governmentApi;
private final VmisRequestEnricher enricher;
private final VmisCarLedgerFrmbkLogService logService;
/** /**
* () : -> (TX) -> -> (/, TX) -> (TX). * ()
*
* @param envelope Envelope
* @return Envelope
*/ */
@Transactional ResponseEntity<Envelope<LedgerResponse>> ledger(Envelope<LedgerRequest> envelope);
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,6 +1,8 @@
package go.kr.project.api.internal.service; package go.kr.project.api.internal.service.impl;
import go.kr.project.api.VehicleInfoService; import go.kr.project.api.external.service.VehicleInfoService;
import go.kr.project.api.internal.service.VmisCarBassMatterInqireService;
import go.kr.project.api.internal.service.VmisCarLedgerFrmbkService;
import go.kr.project.api.model.VehicleApiResponseVO; import go.kr.project.api.model.VehicleApiResponseVO;
import go.kr.project.api.model.request.BasicRequest; import go.kr.project.api.model.request.BasicRequest;
import go.kr.project.api.model.response.BasicResponse; import go.kr.project.api.model.response.BasicResponse;

@ -0,0 +1,59 @@
package go.kr.project.api.internal.service.impl;
import go.kr.project.api.internal.mapper.VmisCarBassMatterInqireMapper;
import go.kr.project.api.internal.service.VmisCarBassMatterInqireLogService;
import go.kr.project.api.model.response.VmisCarBassMatterInqireVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
*
*
* <p> (REQUIRES_NEW) ,
* .</p>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class VmisCarBassMatterInqireLogServiceImpl implements VmisCarBassMatterInqireLogService {
private final VmisCarBassMatterInqireMapper carBassMatterInqireMapper;
/**
* API . (REQUIRES_NEW)
* @param request
* @return ID
*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public String createInitialRequestNewTx(VmisCarBassMatterInqireVO request) {
String generatedId = carBassMatterInqireMapper.selectNextCarBassMatterInqireId();
request.setCarBassMatterInqire(generatedId);
int result = carBassMatterInqireMapper.insertCarBassMatterInqire(request);
if (result != 1) {
throw new RuntimeException("자동차 기본 사항 조회 정보 등록 실패");
}
log.info("[BASIC-REQ-LOG] 요청 정보 저장 완료(별도TX) - ID: {}, 차량번호: {}", generatedId, request.getDmndVhrno());
return generatedId;
}
/**
* / . (REQUIRES_NEW)
* @param response
*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateResponseNewTx(VmisCarBassMatterInqireVO response) {
if (response.getCarBassMatterInqire() == null) {
throw new IllegalArgumentException("자동차 기본 사항 조회 ID는 필수입니다.");
}
int result = carBassMatterInqireMapper.updateCarBassMatterInqire(response);
if (result != 1) {
throw new RuntimeException("자동차 기본 사항 조회 정보 업데이트 실패 - ID: " + response.getCarBassMatterInqire());
}
log.info("[BASIC-RES-LOG] 응답/에러 정보 저장 완료(별도TX) - ID: {}, 결과코드: {}", response.getCarBassMatterInqire(), response.getCntcResultCode());
}
}

@ -0,0 +1,87 @@
package go.kr.project.api.internal.service.impl;
import go.kr.project.api.internal.client.GovernmentApi;
import go.kr.project.api.config.ApiConstant;
import go.kr.project.api.internal.service.VmisCarBassMatterInqireLogService;
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.request.BasicRequest;
import go.kr.project.api.model.response.BasicResponse;
import go.kr.project.api.model.response.VmisCarBassMatterInqireVO;
import go.kr.project.api.model.Envelope;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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 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()
.carBassMatterInqire(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;
}
}
}

@ -0,0 +1,65 @@
package go.kr.project.api.internal.service.impl;
import go.kr.project.api.internal.mapper.VmisCarLedgerFrmbkMapper;
import go.kr.project.api.internal.service.VmisCarLedgerFrmbkLogService;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkDtlVO;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* ()
* - (write) (REQUIRES_NEW) .
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class VmisCarLedgerFrmbkLogServiceImpl implements VmisCarLedgerFrmbkLogService {
private final VmisCarLedgerFrmbkMapper mapper;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public String createInitialRequestNewTx(VmisCarLedgerFrmbkVO request) {
String id = mapper.selectNextCarLedgerFrmbkId();
request.setCarLedgerFrmbkId(id);
int result = mapper.insertCarLedgerFrmbk(request);
if (result != 1) {
throw new RuntimeException("자동차 등록 원부(갑) 최초요청 등록 실패");
}
log.info("[LEDGER-REQ-LOG] 최초 요청 저장(별도TX) - ID: {}, 차량번호: {}", id, request.getDmndVhrno());
return id;
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateResponseNewTx(VmisCarLedgerFrmbkVO response) {
if (response.getCarLedgerFrmbkId() == null) {
throw new IllegalArgumentException("자동차 등록 원부(갑) ID는 필수입니다.");
}
int updated = mapper.updateCarLedgerFrmbk(response);
if (updated != 1) {
throw new RuntimeException("자동차 등록 원부(갑) 정보 업데이트 실패 - ID: " + response.getCarLedgerFrmbkId());
}
log.info("[LEDGER-RES-LOG] 마스터 응답 업데이트(별도TX) - ID: {}, 결과코드: {}",
response.getCarLedgerFrmbkId(), response.getCntcResultCode());
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveDetailsNewTx(String masterId, List<VmisCarLedgerFrmbkDtlVO> details) {
if (details == null || details.isEmpty()) return;
for (VmisCarLedgerFrmbkDtlVO dtl : details) {
String dtlId = mapper.selectNextCarLedgerFrmbkDtlId();
dtl.setCarLedgerFrmbkDtlId(dtlId);
dtl.setCarLedgerFrmbkId(masterId);
mapper.insertCarLedgerFrmbkDtl(dtl);
}
log.info("[LEDGER-RES-LOG] 상세 {}건 저장(별도TX) - ID: {}", details.size(), masterId);
}
}

@ -0,0 +1,89 @@
package go.kr.project.api.internal.service.impl;
import go.kr.project.api.internal.client.GovernmentApi;
import go.kr.project.api.config.ApiConstant;
import go.kr.project.api.internal.service.VmisCarLedgerFrmbkLogService;
import go.kr.project.api.internal.service.VmisCarLedgerFrmbkService;
import go.kr.project.api.internal.service.VmisRequestEnricher;
import go.kr.project.api.model.Envelope;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkDtlVO;
import go.kr.project.api.model.response.VmisCarLedgerFrmbkVO;
import go.kr.project.api.model.request.LedgerRequest;
import go.kr.project.api.model.response.LedgerResponse;
import go.kr.project.api.internal.util.ExceptionDetailUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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 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;
}
}
}

@ -163,7 +163,7 @@ juso:
# ===== VMIS 통합 설정 (Local 환경) ===== # ===== VMIS 통합 설정 (Local 환경) =====
vmis: vmis:
integration: integration:
mode: external # internal: 내부 VMIS 모듈 직접 호출, external: 외부 REST API 호출 mode: internal # internal: 내부 VMIS 모듈 직접 호출, external: 외부 REST API 호출
# RestTemplate 설정 (모드별 분기) # RestTemplate 설정 (모드별 분기)
rest-template: rest-template:

Loading…
Cancel
Save