@ -16,6 +16,7 @@ import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl ;
import org.springframework.stereotype.Service ;
import org.springframework.transaction.annotation.Transactional ;
import org.springframework.transaction.support.TransactionTemplate ;
import org.springframework.web.multipart.MultipartFile ;
import java.io.BufferedReader ;
@ -27,6 +28,11 @@ import java.util.ArrayList;
import java.util.HashMap ;
import java.util.List ;
import java.util.Map ;
import java.util.concurrent.CompletableFuture ;
import java.util.concurrent.ExecutorService ;
import java.util.concurrent.Executors ;
import java.util.concurrent.TimeUnit ;
import java.util.stream.Collectors ;
/ * *
* 자 동 차 과 태 료 대 상 Service 구 현 체
@ -40,6 +46,7 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
private final CarFfnlgTxtParseConfig parseConfig ;
private final ExternalVehicleApiService service ;
private final ComparisonService comparisonService ;
private final TransactionTemplate transactionTemplate ;
// 날짜 형식 (YYYYMMDD)
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter . ofPattern ( "yyyyMMdd" ) ;
@ -899,117 +906,179 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
}
/ * *
* 선 택 된 목 록 에 대 해 API 호 출 및 기 본 정 보 / 등 록 원 부 비 교
* 중 요 : 1 건 이 라 도 실 패 시 전 체 롤 백 처 리 됨
* 선 택 된 목 록 에 대 해 API 호 출 및 기 본 정 보 / 등 록 원 부 비 교 ( 병 렬 처 리 )
* 변 경 사 항 : 각 건 을 병 렬 로 처 리 하 며 , 개 별 트 랜 잭 션 으 로 관 리 됩 니 다 .
* 실 패 한 건 만 실 패 처 리 되 고 , 성 공 한 건 은 정 상 처 리 됩 니 다 .
*
* @param targetList 선 택 된 과 태 료 대 상 목 록
* @return 비 교 결 과
* /
@Override
@Transactional
public Map < String , Object > compareWithApi ( List < Map < String , String > > targetList ) {
log . info ( "========== API 호출 및 비교 시작 ==========") ;
log . info ( "========== API 호출 및 비교 시작 (병렬처리) ==========") ;
log . info ( "선택된 데이터 건수: {}" , targetList ! = null ? targetList . size ( ) : 0 ) ;
if ( targetList = = null | | targetList . isEmpty ( ) ) {
throw new IllegalArgumentException ( "선택된 데이터가 없습니다." ) ;
}
List < Map < String , Object > > compareResults = new ArrayList < > ( ) ;
int successCount = 0 ;
int productUseCount = 0 ; // 상품용 건수
int transferCount = 0 ; // 이첩 건수
int normalCount = 0 ; // 정상 처리 건수
for ( Map < String , String > target : targetList ) {
String carFfnlgTrgtId = target . get ( "carFfnlgTrgtId" ) ;
String vhclno = target . get ( "vhclno" ) ;
String inspYmd = target . get ( "inspYmd" ) ;
String rgtr = target . get ( "rgtr" ) ; // 등록자 (사용자 ID)
// I/O 작업이므로 CPU 코어 수의 2배로 스레드 풀 생성
int threadPoolSize = Runtime . getRuntime ( ) . availableProcessors ( ) * 2 ;
ExecutorService executor = Executors . newFixedThreadPool ( threadPoolSize ) ;
log . info ( "처리 중 - 차량번호: {}, 검사일자: {}" , vhclno , inspYmd ) ;
log . info ( "병렬처리 스레드 풀 크기: {}" , threadPoolSize ) ;
Map < String , Object > compareResult = new HashMap < > ( ) ;
compareResult . put ( "carFfnlgTrgtId" , carFfnlgTrgtId ) ;
compareResult . put ( "vhclno" , vhclno ) ;
try {
// 1. 기존 데이터 조회
CarFfnlgTrgtVO existingData = new CarFfnlgTrgtVO ( ) ;
existingData . setCarFfnlgTrgtId ( carFfnlgTrgtId ) ;
existingData = mapper . selectOne ( existingData ) ;
try {
// 병렬로 각 건 처리
List < CompletableFuture < Map < String , Object > > > futures = targetList . stream ( )
. map ( target - > CompletableFuture . supplyAsync ( ( ) - > processOneTarget ( target ) , executor ) )
. collect ( Collectors . toList ( ) ) ;
if ( existingData = = null ) {
String errorMsg = String . format ( "기존 데이터를 찾을 수 없습니다. 차량번호: %s" , vhclno ) ;
log . error ( errorMsg ) ;
throw new MessageException ( errorMsg ) ;
}
// 모든 작업 완료 대기
CompletableFuture . allOf ( futures . toArray ( new CompletableFuture [ 0 ] ) ) . join ( ) ;
// 2. 처리상태 검증 - 접수상태(01)가 아닌 경우 API 호출 불가 (전체 롤백)
if ( ! TaskPrcsSttsConstants . TASK_PRCS_STTS_CD_01_RCPT . equals ( existingData . getTaskPrcsSttsCd ( ) ) ) {
String errorMsg = String . format ( "접수 상태(01)인 데이터만 API 호출이 가능합니다. 차량번호: %s, 현재 상태: %s" ,
vhclno , existingData . getTaskPrcsSttsCd ( ) ) ;
log . error ( errorMsg ) ;
throw new MessageException ( errorMsg ) ;
}
// 결과 수집
List < Map < String , Object > > compareResults = futures . stream ( )
. map ( CompletableFuture : : join )
. collect ( Collectors . toList ( ) ) ;
// 3. 비교 로직 실행
String statusCode = comparisonService . executeComparison ( existingData ) ;
// 통계 집계
int successCount = 0 ;
int failCount = 0 ;
int productUseCount = 0 ;
int transferCount = 0 ;
int normalCount = 0 ;
// 결과 처리
if ( statusCode ! = null ) {
// 비교 규칙이 적용됨
if ( TaskPrcsSttsConstants . TASK_PRCS_STTS_CD_02_PRODUCT_USE . equals ( statusCode ) ) {
for ( Map < String , Object > result : compareResults ) {
Boolean success = ( Boolean ) result . get ( "success" ) ;
if ( Boolean . TRUE . equals ( success ) ) {
successCount + + ;
String processStatus = ( String ) result . get ( "processStatus" ) ;
if ( "상품용" . equals ( processStatus ) ) {
productUseCount + + ;
compareResult . put ( "processStatus" , "상품용" ) ;
compareResult . put ( "message" , "상품용으로 처리되었습니다." ) ;
} else if ( TaskPrcsSttsConstants . TASK_PRCS_STTS_CD_03_TRANSFER . equals ( statusCode ) ) {
} else if ( "이첩" . equals ( processStatus ) ) {
transferCount + + ;
compareResult . put ( "processStatus" , "이첩" ) ;
compareResult . put ( "message" , "이첩으로 처리되었습니다." ) ;
} else if ( TaskPrcsSttsConstants . TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED . equals ( statusCode ) ) {
} else if ( "내사종결" . equals ( processStatus ) | | "정상" . equals ( processStatus ) ) {
normalCount + + ;
compareResult . put ( "processStatus" , "내사종결" ) ;
compareResult . put ( "message" , "내사종결로 처리되었습니다." ) ;
} else {
normalCount + + ;
compareResult . put ( "processStatus" , "기타" ) ;
compareResult . put ( "message" , "기타 상태로 처리되었습니다." ) ;
}
compareResult . put ( "success" , true ) ;
successCount + + ;
} else {
// 정상 처리 (비교 로직에 해당 안됨)
normalCount + + ;
compareResult . put ( "success" , true ) ;
compareResult . put ( "message" , "정상 처리되었습니다." ) ;
compareResult . put ( "processStatus" , "정상" ) ;
successCount + + ;
failCount + + ;
}
} catch ( Exception e ) {
log . error ( "데이터 비교 중 오류 발생 - 차량번호: {}, 전체 롤백 처리" , vhclno , e ) ;
// 예외 재발생하여 전체 롤백 처리
throw new MessageException ( e . getMessage ( ) , e ) ;
}
compareResults . add ( compareResult ) ;
Map < String , Object > resultData = new HashMap < > ( ) ;
resultData . put ( "compareResults" , compareResults ) ;
resultData . put ( "totalCount" , targetList . size ( ) ) ;
resultData . put ( "successCount" , successCount ) ;
resultData . put ( "failCount" , failCount ) ;
resultData . put ( "productUseCount" , productUseCount ) ;
resultData . put ( "transferCount" , transferCount ) ;
resultData . put ( "normalCount" , normalCount ) ;
log . info ( "========== API 호출 및 비교 완료 (병렬처리) ==========" ) ;
log . info ( "전체: {}건, 성공: {}건, 실패: {}건, 상품용: {}건, 이첩: {}건, 정상: {}건" ,
targetList . size ( ) , successCount , failCount , productUseCount , transferCount , normalCount ) ;
return resultData ;
} finally {
// ExecutorService 종료
executor . shutdown ( ) ;
try {
if ( ! executor . awaitTermination ( 120 , TimeUnit . SECONDS ) ) {
executor . shutdownNow ( ) ;
}
} catch ( InterruptedException e ) {
executor . shutdownNow ( ) ;
Thread . currentThread ( ) . interrupt ( ) ;
}
}
}
/ * *
* 개 별 건 을 처 리 ( 개 별 트 랜 잭 션 )
*
* @param target 처 리 할 대 상 데 이 터
* @return 처 리 결 과
* /
private Map < String , Object > processOneTarget ( Map < String , String > target ) {
String carFfnlgTrgtId = target . get ( "carFfnlgTrgtId" ) ;
String vhclno = target . get ( "vhclno" ) ;
String inspYmd = target . get ( "inspYmd" ) ;
log . info ( "처리 중 - 차량번호: {}, 검사일자: {}" , vhclno , inspYmd ) ;
Map < String , Object > compareResult = new HashMap < > ( ) ;
compareResult . put ( "carFfnlgTrgtId" , carFfnlgTrgtId ) ;
compareResult . put ( "vhclno" , vhclno ) ;
try {
// 개별 트랜잭션으로 실행
String statusCode = transactionTemplate . execute ( status - > {
try {
// 1. 기존 데이터 조회
CarFfnlgTrgtVO existingData = new CarFfnlgTrgtVO ( ) ;
existingData . setCarFfnlgTrgtId ( carFfnlgTrgtId ) ;
existingData = mapper . selectOne ( existingData ) ;
if ( existingData = = null ) {
String errorMsg = String . format ( "기존 데이터를 찾을 수 없습니다. 차량번호: %s" , vhclno ) ;
log . error ( errorMsg ) ;
throw new MessageException ( errorMsg ) ;
}
// 2. 처리상태 검증 - 접수상태(01)가 아닌 경우 API 호출 불가
if ( ! TaskPrcsSttsConstants . TASK_PRCS_STTS_CD_01_RCPT . equals ( existingData . getTaskPrcsSttsCd ( ) ) ) {
String errorMsg = String . format ( "접수 상태(01)인 데이터만 API 호출이 가능합니다. 차량번호: %s, 현재 상태: %s" ,
vhclno , existingData . getTaskPrcsSttsCd ( ) ) ;
log . error ( errorMsg ) ;
throw new MessageException ( errorMsg ) ;
}
// 3. 비교 로직 실행
return comparisonService . executeComparison ( existingData ) ;
Map < String , Object > resultData = new HashMap < > ( ) ;
resultData . put ( "compareResults" , compareResults ) ;
resultData . put ( "totalCount" , targetList . size ( ) ) ;
resultData . put ( "successCount" , successCount ) ;
resultData . put ( "failCount" , 0 ) ; // 1건이라도 실패하면 전체 롤백되므로 실패건수는 항상 0
resultData . put ( "productUseCount" , productUseCount ) ;
resultData . put ( "transferCount" , transferCount ) ;
resultData . put ( "normalCount" , normalCount ) ;
} catch ( Exception e ) {
// 트랜잭션 롤백
status . setRollbackOnly ( ) ;
throw e ;
}
} ) ;
// 결과 처리
if ( statusCode ! = null ) {
// 비교 규칙이 적용됨
if ( TaskPrcsSttsConstants . TASK_PRCS_STTS_CD_02_PRODUCT_USE . equals ( statusCode ) ) {
compareResult . put ( "processStatus" , "상품용" ) ;
compareResult . put ( "message" , "상품용으로 처리되었습니다." ) ;
} else if ( TaskPrcsSttsConstants . TASK_PRCS_STTS_CD_03_TRANSFER . equals ( statusCode ) ) {
compareResult . put ( "processStatus" , "이첩" ) ;
compareResult . put ( "message" , "이첩으로 처리되었습니다." ) ;
} else if ( TaskPrcsSttsConstants . TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED . equals ( statusCode ) ) {
compareResult . put ( "processStatus" , "내사종결" ) ;
compareResult . put ( "message" , "내사종결로 처리되었습니다." ) ;
} else {
compareResult . put ( "processStatus" , "기타" ) ;
compareResult . put ( "message" , "기타 상태로 처리되었습니다." ) ;
}
compareResult . put ( "success" , true ) ;
} else {
// 정상 처리 (비교 로직에 해당 안됨)
compareResult . put ( "success" , true ) ;
compareResult . put ( "message" , "정상 처리되었습니다." ) ;
compareResult . put ( "processStatus" , "정상" ) ;
}
log . info ( "========== API 호출 및 비교 완료 ==========" ) ;
log . info ( "성공: {}건, 상품용: {}건, 이첩: {}건, 정상: {}건" ,
successCount , productUseCount , transferCount , normalCount ) ;
} catch ( Exception e ) {
log . error ( "데이터 비교 중 오류 발생 - 차량번호: {}" , vhclno , e ) ;
compareResult . put ( "success" , false ) ;
compareResult . put ( "message" , "처리 실패: " + e . getMessage ( ) ) ;
compareResult . put ( "processStatus" , "실패" ) ;
}
return resultData ;
return compa reRe sult;
}
/ * *