TASK_PRCS_STTS_CD 01~04 까지 src/main/java/egovframework/constant 아래에 class 추가하여 공통관리
현재 검색조건 list 전체를 paging 처리 없이 전체 api 호출하는 로직 추가 apiResponse.isSuccess() == true 일 경우, [tb_car_ffnlg_trgt.CAR_BASS_MATTER_INQIRE_ID, tb_car_ffnlg_trgt.CAR_LEDGER_FRMBK_ID] 값 updateinternalApi
parent
8eeee86a46
commit
b012e51aaf
@ -1,113 +0,0 @@
|
||||
package egovframework.constant;
|
||||
|
||||
/**
|
||||
* packageName : go.kr.project.common.constant
|
||||
* fileName : BatchConstants
|
||||
* author : 개발자
|
||||
* date : 2025-06-10
|
||||
* description : 배치 작업 관련 상수를 정의하는 클래스
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2025-06-10 개발자 최초 생성
|
||||
*/
|
||||
public class BatchConstants {
|
||||
|
||||
/**
|
||||
* 배치 작업 정보 상태 코드 (TB_BATCH_JOB_INFO.STATUS)
|
||||
*/
|
||||
public static final String JOB_INFO_STATUS_ACTIVE = "ACTIVE";
|
||||
public static final String JOB_INFO_STATUS_PAUSED = "PAUSED";
|
||||
public static final String JOB_INFO_STATUS_DELETED = "DELETED";
|
||||
|
||||
/**
|
||||
* 배치 작업 실행 상태 코드 (TB_BATCH_JOB_EXECUTION.STATUS)
|
||||
* 실행상태에서 부분완료는 제거됨 - 시작,완료,실패,거부만 사용
|
||||
*/
|
||||
public static final String JOB_EXECUTION_STATUS_STARTED = "STARTED";
|
||||
public static final String JOB_EXECUTION_STATUS_COMPLETED = "COMPLETED";
|
||||
public static final String JOB_EXECUTION_STATUS_FAILED = "FAILED";
|
||||
public static final String JOB_EXECUTION_STATUS_VETOED = "VETOED";
|
||||
|
||||
/**
|
||||
* 배치 작업 실행 종료 코드 (TB_BATCH_JOB_EXECUTION.EXIT_CODE)
|
||||
*/
|
||||
public static final String JOB_EXECUTION_EXIT_COMPLETED = "COMPLETED";
|
||||
public static final String JOB_EXECUTION_EXIT_FAILED = "FAILED";
|
||||
public static final String JOB_EXECUTION_EXIT_UNKNOWN = "UNKNOWN";
|
||||
public static final String JOB_EXECUTION_EXIT_PARTIALLY_COMPLETED = "PARTIALLY_COMPLETED";
|
||||
|
||||
|
||||
/**
|
||||
* 로그 레벨
|
||||
*/
|
||||
public static final String LOG_LEVEL_INFO = "INFO";
|
||||
public static final String LOG_LEVEL_WARN = "WARN";
|
||||
public static final String LOG_LEVEL_ERROR = "ERROR";
|
||||
|
||||
/**
|
||||
* 날짜 및 시간 형식
|
||||
*/
|
||||
public static final String DATE_FORMAT_DEFAULT = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/**
|
||||
* 시간 값 (밀리초)
|
||||
*/
|
||||
public static final long TIME_ITEM_PROCESS_DELAY = 100; // 0.1초
|
||||
public static final long TIME_JOB_DELAY_TEST = 3000; // 3초 (테스트용)
|
||||
public static final long TIME_JOB_DELAY_1_MIN = 60000; // 1분
|
||||
public static final long TIME_JOB_DELAY_3_MIN = 180000; // 3분
|
||||
public static final long TIME_JOB_DELAY_5_MIN = 300000; // 5분
|
||||
|
||||
/**
|
||||
* JobDataMap 키
|
||||
*/
|
||||
public static final String JOB_DATA_EXECUTION_ID = "executionId";
|
||||
|
||||
/**
|
||||
* 작업 이름 및 그룹
|
||||
*/
|
||||
public static final String JOB_NAME_SAMPLE = "sampleJob";
|
||||
public static final String JOB_GROUP_SAMPLE = "sampleGroup";
|
||||
public static final String JOB_TRIGGER_SUFFIX = "Trigger";
|
||||
|
||||
/**
|
||||
* Cron 표현식
|
||||
*/
|
||||
public static final String CRON_EVERY_MINUTE = "0 * * * * ?";
|
||||
public static final String CRON_EVERY_ONE_SECOND = "1 0 * * * ?";
|
||||
|
||||
/**
|
||||
* 작업 설명
|
||||
*/
|
||||
public static final String JOB_DESC_SAMPLE = "샘플 배치 작업 (매 분마다 실행)";
|
||||
|
||||
/**
|
||||
* 배치 로그 메시지 템플릿
|
||||
*/
|
||||
// 기본 배치 작업 로그 메시지
|
||||
public static final String LOG_MSG_BATCH_JOB_START = "===== 배치 작업 시작: %s.%s - %s =====";
|
||||
public static final String LOG_MSG_BATCH_JOB_COMPLETE = "===== 배치 작업 완료: %s.%s - %s =====";
|
||||
public static final String LOG_MSG_BATCH_JOB_PARTIAL_COMPLETE = "===== 배치 작업 부분 완료: %s.%s - 총 %d개 파일 중 처리: %d개, 성공: %d개, 실패: %d개 (%s) =====";
|
||||
public static final String LOG_MSG_BATCH_JOB_FAILED = "===== 배치 작업 실패: %s.%s - 총 %d개 파일 중 처리: %d개, 성공: %d개, 실패: %d개 (%s) =====";
|
||||
public static final String LOG_MSG_BATCH_JOB_ERROR = "===== 배치 작업 실행 중 오류 발생: %s.%s - %s =====";
|
||||
|
||||
/**
|
||||
* 배치 작업 초기화 관련 메시지
|
||||
*/
|
||||
public static final String LOG_MSG_BATCH_INIT_SERVER_BOOT_DETECTED = "서버 부팅 완료 감지 - 배치 작업 초기화를 5초 후에 시작합니다.";
|
||||
public static final String LOG_MSG_BATCH_INIT_START = "배치 작업 초기화 시작";
|
||||
public static final String LOG_MSG_BATCH_INIT_COMPLETE = "배치 작업 초기화 완료";
|
||||
public static final String LOG_MSG_BATCH_INIT_INTERRUPT_ERROR = "배치 작업 초기화 대기 중 인터럽트 발생: {}";
|
||||
public static final String LOG_MSG_BATCH_INIT_ERROR = "배치 작업 초기화 중 오류 발생: %s";
|
||||
public static final String LOG_MSG_BATCH_INIT_NO_JOBS = "등록된 배치 작업이 없습니다.";
|
||||
public static final String LOG_MSG_BATCH_INIT_JOBS_COUNT = "총 {}개의 배치 작업 정보를 조회했습니다.";
|
||||
public static final String LOG_MSG_BATCH_INIT_SKIP_DELETED = "삭제된 작업은 스케줄러에 등록하지 않습니다: {}.{}";
|
||||
public static final String LOG_MSG_BATCH_INIT_SCHEDULE_SUCCESS = "배치 작업을 스케줄러에 등록했습니다: {}.{}";
|
||||
public static final String LOG_MSG_BATCH_INIT_SCHEDULE_FAILED = "배치 작업 스케줄링에 실패했습니다: {}.{}";
|
||||
public static final String LOG_MSG_BATCH_INIT_CLASS_NOT_FOUND = "배치 작업 클래스를 찾을 수 없습니다: {}";
|
||||
public static final String LOG_MSG_BATCH_INIT_SCHEDULE_ERROR = "배치 작업 스케줄링 중 오류 발생: {}.{} - {}";
|
||||
public static final String LOG_MSG_BATCH_INIT_DB_ERROR = "배치 작업 초기화 중 오류 발생: {}";
|
||||
public static final String LOG_MSG_BATCH_INIT_REGISTER_COMPLETE = "배치 작업 정보 등록 완료: %s";
|
||||
public static final String LOG_MSG_BATCH_INIT_REGISTER_ERROR = "샘플 배치 작업 등록 중 오류 발생: %s";
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
package egovframework.constant;
|
||||
|
||||
/**
|
||||
* packageName : egovframework.constant
|
||||
* fileName : CrdnPrcsSttsConstants
|
||||
* author : 시스템 관리자
|
||||
* date : 2025-08-26
|
||||
* description : 단속 처리 상태 코드 상수를 정의하는 클래스
|
||||
* 중요로직: 처리 단계별 상태 코드, 순서는 처리 순서대로 정의
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2025-08-26 시스템 관리자 최초 생성
|
||||
*/
|
||||
public class CrdnPrcsSttsConstants {
|
||||
|
||||
/**
|
||||
* 단속 처리 상태 코드 - 10: 단속
|
||||
* 최초 단속 등록시 기본값으로 설정되는 상태
|
||||
*/
|
||||
public static final String CRDN_PRCS_STTS_CD_10_CRDN = "10";
|
||||
|
||||
/**
|
||||
* 단속 처리 상태 코드 - 20: 처분사전
|
||||
* 처분 사전 통지 단계
|
||||
*/
|
||||
public static final String CRDN_PRCS_STTS_CD_20_DSPS_BFHD = "20";
|
||||
|
||||
/**
|
||||
* 단속 처리 상태 코드 - 30: 시정명령
|
||||
* 시정 명령 단계
|
||||
*/
|
||||
public static final String CRDN_PRCS_STTS_CD_30_CRC_CMD = "30";
|
||||
|
||||
/**
|
||||
* 단속 처리 상태 코드 - 40: 시정촉구
|
||||
* 시정 촉구 단계
|
||||
*/
|
||||
public static final String CRDN_PRCS_STTS_CD_40_CRC_URG = "40";
|
||||
|
||||
/**
|
||||
* 단속 처리 상태 코드 - 50: 부과예고
|
||||
* 부과 예고 단계
|
||||
*/
|
||||
public static final String CRDN_PRCS_STTS_CD_50_LEVY_PRVNTC = "50";
|
||||
|
||||
/**
|
||||
* 단속 처리 상태 코드 - 60: 부과
|
||||
* 부과 단계
|
||||
*/
|
||||
public static final String CRDN_PRCS_STTS_CD_60_LEVY = "60";
|
||||
|
||||
/**
|
||||
* 단속 처리 상태 코드 - 70: 납부촉구
|
||||
* 납부 촉구 단계
|
||||
*/
|
||||
public static final String CRDN_PRCS_STTS_CD_70_PAY_URG = "70";
|
||||
|
||||
}
|
||||
@ -1,106 +0,0 @@
|
||||
package egovframework.constant;
|
||||
|
||||
/**
|
||||
* packageName : egovframework.constant
|
||||
* fileName : ImpltTaskSeConstants
|
||||
* author : 시스템 관리자
|
||||
* date : 2025-09-08
|
||||
* description : 이행업무구분코드 상수를 정의하는 클래스
|
||||
* 중요로직: 이행업무 처리 단계별 구분 코드, 순서는 처리 순서대로 정의
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2025-09-08 시스템 관리자 최초 생성
|
||||
*/
|
||||
public class ImpltTaskSeConstants {
|
||||
|
||||
/**
|
||||
* 이행업무구분코드 - 1: 처분사전
|
||||
* 처분 사전 통지 단계
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_1_DSPS_BFHD = "1";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드 - 2: 시정명령
|
||||
* 시정 명령 단계
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_2_CRC_CMD = "2";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드 - 3: 시정촉구
|
||||
* 시정 촉구 단계
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_3_CRC_URG = "3";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드 - 4: 부과예고
|
||||
* 부과 예고 단계
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_4_LEVY_PRVNTC = "4";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드 - 5: 부과
|
||||
* 부과 단계
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_5_LEVY = "5";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드 - 6: 납부촉구
|
||||
* 납부 촉구 단계
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_6_PAY_URG = "6";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드명 - 처분사전
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_NM_DSPS_BFHD = "처분사전";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드명 - 시정명령
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_NM_CRC_CMD = "시정명령";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드명 - 시정촉구
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_NM_CRC_URG = "시정촉구";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드명 - 부과예고
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_NM_LEVY_PRVNTC = "부과예고";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드명 - 부과
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_NM_LEVY = "부과";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드명 - 납부촉구
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_NM_PAY_URG = "납부촉구";
|
||||
|
||||
/**
|
||||
* 알수없음
|
||||
*/
|
||||
public static final String IMPLT_TASK_SE_CD_NM_UNKNOWN = "알수없음";
|
||||
|
||||
/**
|
||||
* 이행업무구분코드에 해당하는 코드명을 반환합니다.
|
||||
*
|
||||
* @param impltTaskSeCd 이행업무구분코드
|
||||
* @return 코드명
|
||||
*/
|
||||
public static String getImpltTaskSeCdNm(String impltTaskSeCd) {
|
||||
switch (impltTaskSeCd) {
|
||||
case IMPLT_TASK_SE_CD_1_DSPS_BFHD: return IMPLT_TASK_SE_CD_NM_DSPS_BFHD;
|
||||
case IMPLT_TASK_SE_CD_2_CRC_CMD: return IMPLT_TASK_SE_CD_NM_CRC_CMD;
|
||||
case IMPLT_TASK_SE_CD_3_CRC_URG: return IMPLT_TASK_SE_CD_NM_CRC_URG;
|
||||
case IMPLT_TASK_SE_CD_4_LEVY_PRVNTC: return IMPLT_TASK_SE_CD_NM_LEVY_PRVNTC;
|
||||
case IMPLT_TASK_SE_CD_5_LEVY: return IMPLT_TASK_SE_CD_NM_LEVY;
|
||||
case IMPLT_TASK_SE_CD_6_PAY_URG: return IMPLT_TASK_SE_CD_NM_PAY_URG;
|
||||
default: return IMPLT_TASK_SE_CD_NM_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
package egovframework.constant;
|
||||
|
||||
/**
|
||||
* packageName : egovframework.constant
|
||||
* fileName : SEQConstants
|
||||
* author : 박성영
|
||||
* date : 25. 8. 26.
|
||||
* description :
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 25. 8. 26. 박성영 최초 생성
|
||||
*/
|
||||
public class SEQConstants {
|
||||
|
||||
public static final String SEQ_CRDN = "seq_crdn_no_";
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package egovframework.constant;
|
||||
|
||||
/**
|
||||
* packageName : egovframework.constant
|
||||
* fileName : TaskPrcsSttsConstants
|
||||
* author : 시스템 관리자
|
||||
* date : 2025-11-17
|
||||
* description : 과태료 업무 처리 상태 코드 상수를 정의하는 클래스
|
||||
* 중요로직: 과태료 대상 처리 단계별 상태 코드
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 2025-11-17 시스템 관리자 최초 생성
|
||||
*/
|
||||
public class TaskPrcsSttsConstants {
|
||||
|
||||
/**
|
||||
* 업무 처리 상태 코드 - 01: 접수
|
||||
* 최초 과태료 대상 등록 시 기본값으로 설정되는 상태
|
||||
*/
|
||||
public static final String TASK_PRCS_STTS_CD_01_RCPT = "01";
|
||||
|
||||
/**
|
||||
* 업무 처리 상태 코드 - 02: 상품용
|
||||
* API 조회 결과 소유자명에 "상품용" 문자열이 포함된 경우
|
||||
*/
|
||||
public static final String TASK_PRCS_STTS_CD_02_PRODUCT_USE = "02";
|
||||
|
||||
/**
|
||||
* 업무 처리 상태 코드 - 03: 이첩
|
||||
* API 조회 결과 법정동코드가 사용자 조직코드와 불일치하는 경우
|
||||
*/
|
||||
public static final String TASK_PRCS_STTS_CD_03_TRANSFER = "03";
|
||||
|
||||
/**
|
||||
* 업무 처리 상태 코드 - 04: 내사종결
|
||||
* API 조회 결과 특정 조건에 해당하는 경우
|
||||
*/
|
||||
public static final String TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED = "04";
|
||||
|
||||
}
|
||||
@ -1,788 +0,0 @@
|
||||
# 과태료 대상 비교 로직 구현 가이드
|
||||
|
||||
## 📌 목차
|
||||
|
||||
1. [개요](#개요)
|
||||
2. [두 가지 구현 방법 비교](#두-가지-구현-방법-비교)
|
||||
3. [방법1: Service/Impl 패턴 (추천)](#방법1-serviceimpl-패턴-추천)
|
||||
4. [방법2: Chain of Responsibility 패턴](#방법2-chain-of-responsibility-패턴)
|
||||
5. [어떤 방법을 선택해야 할까?](#어떤-방법을-선택해야-할까)
|
||||
6. [참고사항](#참고사항)
|
||||
|
||||
---
|
||||
|
||||
## 개요
|
||||
|
||||
이 모듈은 과태료 대상 차량을 API 응답 데이터와 비교하여 자동으로 분류하는 기능을 제공합니다.
|
||||
현재 프로젝트는 **두 가지 비교 로직 구현 방법**을 제공하며, 프로젝트 상황에 맞게 선택하여 사용할 수 있습니다.
|
||||
|
||||
### 비교 로직 처리 흐름
|
||||
|
||||
```
|
||||
API 응답 데이터 수신
|
||||
↓
|
||||
비교 로직 실행
|
||||
↓
|
||||
조건1 (상품용) 체크 → 충족 → TASK_PRCS_STTS_CD = 02 → 종료
|
||||
↓ 미충족
|
||||
조건2 (이첩) 체크 → 충족 → TASK_PRCS_STTS_CD = 03 → 종료
|
||||
↓ 미충족
|
||||
조건3 (내사종결) 체크 → 충족 → TASK_PRCS_STTS_CD = 04 → 종료
|
||||
↓ 미충족
|
||||
모든 조건 미충족 → 정상 처리 (null 반환)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 두 가지 구현 방법 비교
|
||||
|
||||
### 비교표
|
||||
|
||||
| 항목 | 방법1 (Service/Impl) ⭐ | 방법2 (Chain of Responsibility) |
|
||||
|------|---------------------|--------------------------------|
|
||||
| **구현 난이도** | ⭐ 쉬움 | ⭐⭐⭐ 어려움 |
|
||||
| **학습 시간** | 10분 | 1시간+ |
|
||||
| **조건 추가 시간** | 5분 | 15분 |
|
||||
| **코드 가독성** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||||
| **확장성** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| **테스트 용이성** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| **파일 개수** | 2개 | N+4개 |
|
||||
| **디버깅** | ⭐⭐⭐⭐⭐ 쉬움 | ⭐⭐⭐ 보통 |
|
||||
| **추천 조건 개수** | ~10개 | 10개 이상 |
|
||||
|
||||
---
|
||||
|
||||
## 방법1: Service/Impl 패턴 (추천)
|
||||
|
||||
### 🌟 특징
|
||||
|
||||
- ✅ **간단하고 직관적** - 학습 곡선 낮음
|
||||
- ✅ **명확한 코드 흐름** - 순차적 실행으로 이해하기 쉬움
|
||||
- ✅ **빠른 개발** - 메서드 하나만 추가하면 됨
|
||||
- ✅ **디버깅 쉬움** - 코드 추적이 직관적
|
||||
- ✅ **한 파일에서 관리** - 모든 비교 로직을 한눈에 확인
|
||||
|
||||
### 📁 디렉토리 구조
|
||||
|
||||
```
|
||||
service/
|
||||
├── ComparisonService.java # 인터페이스
|
||||
└── impl/
|
||||
└── ComparisonServiceImpl.java # 구현체 (모든 비교 로직 포함)
|
||||
```
|
||||
|
||||
### 🚀 새로운 비교 조건 추가 방법
|
||||
|
||||
#### 예제 1: 일반 비교 조건 추가
|
||||
|
||||
**1단계: ComparisonServiceImpl에 private 메서드 추가**
|
||||
|
||||
```java
|
||||
/**
|
||||
* 4. 장기미검차량 체크
|
||||
*
|
||||
* <p>조건: 검사 유효기간 종료일로부터 1년 이상 경과</p>
|
||||
* <p>처리: TASK_PRCS_STTS_CD = 05</p>
|
||||
*
|
||||
* @return 05 (적용됨) 또는 null (미적용)
|
||||
*/
|
||||
private String checkLongTermUninspected(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String insptValidPdEndde = basicInfo.getInsptValidPdEndde(); // 검사유효기간종료일자
|
||||
|
||||
// 조건 체크 로직
|
||||
if (insptValidPdEndde == null || insptValidPdEndde.isEmpty()) {
|
||||
log.debug("[장기미검] 조건 미충족. 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1년 경과 여부 확인 로직
|
||||
LocalDate endDate = LocalDate.parse(insptValidPdEndde, DATE_FORMATTER);
|
||||
LocalDate oneYearAgo = LocalDate.now().minusYears(1);
|
||||
|
||||
if (endDate.isAfter(oneYearAgo)) {
|
||||
log.debug("[장기미검] 조건 미충족. 차량번호: {}, 종료일: {}", vhclno, insptValidPdEndde);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[장기미검] 조건 충족! 차량번호: {}, 종료일: {}", vhclno, insptValidPdEndde);
|
||||
|
||||
// DB 업데이트
|
||||
existingData.setTaskPrcsSttsCd("05"); // 장기미검
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
|
||||
int updateCount = carFfnlgTrgtMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new RuntimeException(String.format("[장기미검] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[장기미검] 처리 완료! 차량번호: {}", vhclno);
|
||||
return "05";
|
||||
}
|
||||
```
|
||||
|
||||
**2단계: executeComparison() 메서드에 호출 추가**
|
||||
|
||||
```java
|
||||
@Override
|
||||
public String executeComparison(CarFfnlgTrgtVO existingData, VehicleApiResponseVO apiResponse, String userId) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
log.info("========== 비교 로직 시작: {} ==========", vhclno);
|
||||
|
||||
// API 응답 데이터 유효성 검사
|
||||
if (!isValidApiResponse(apiResponse)) {
|
||||
log.warn("API 응답 데이터가 유효하지 않습니다. 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
BasicResponse.Record basicInfo = apiResponse.getBasicInfo().getRecord().get(0);
|
||||
|
||||
// ========== 1. 상품용 체크 ==========
|
||||
String productUseResult = checkProductUse(existingData, basicInfo);
|
||||
if (productUseResult != null) {
|
||||
log.info("========== 비교 로직 종료 (상품용): {} ==========", vhclno);
|
||||
return productUseResult;
|
||||
}
|
||||
|
||||
// ========== 2. 이첩 체크 ==========
|
||||
String transferResult = checkTransfer(existingData, basicInfo, userId);
|
||||
if (transferResult != null) {
|
||||
log.info("========== 비교 로직 종료 (이첩): {} ==========", vhclno);
|
||||
return transferResult;
|
||||
}
|
||||
|
||||
// ========== 3. 장기미검 체크 (새로 추가!) ==========
|
||||
String longTermResult = checkLongTermUninspected(existingData, basicInfo);
|
||||
if (longTermResult != null) {
|
||||
log.info("========== 비교 로직 종료 (장기미검): {} ==========", vhclno);
|
||||
return longTermResult;
|
||||
}
|
||||
|
||||
// 모든 비교 로직에 해당하지 않음
|
||||
log.info("========== 비교 로직 종료 (정상): {} ==========", vhclno);
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
**끝! 매우 간단합니다.**
|
||||
|
||||
---
|
||||
|
||||
#### 예제 2: 이첩 조건 추가 (OR 구조)
|
||||
|
||||
이첩 조건은 **OR 로직**으로 구성되어 있습니다. **여러 조건 중 하나라도 만족하면** 이첩으로 처리됩니다.
|
||||
|
||||
**현재 이첩 구조:**
|
||||
|
||||
```java
|
||||
private String checkTransfer(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo, String userId) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
|
||||
// ========== 이첩 조건들 (OR 로직: 하나라도 만족하면 이첩) ==========
|
||||
|
||||
// 조건1: 법정동코드 불일치
|
||||
if (checkTransferCondition1_LegalDongMismatch(basicInfo, userId, vhclno)) {
|
||||
return processTransfer(existingData, basicInfo, vhclno, "법정동코드 불일치");
|
||||
}
|
||||
|
||||
// 조건2: 향후 추가될 이첩 조건들...
|
||||
// if (checkTransferCondition2_XXX(basicInfo, userId, vhclno)) {
|
||||
// return processTransfer(existingData, basicInfo, vhclno, "XXX");
|
||||
// }
|
||||
|
||||
// 모든 이첩 조건에 해당하지 않음
|
||||
log.debug("[이첩] 모든 조건 미충족. 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
**새로운 이첩 조건 추가:**
|
||||
|
||||
**1단계: 조건 체크 메서드 추가**
|
||||
|
||||
```java
|
||||
/**
|
||||
* 이첩 조건2: 차량 소유자 주소 불일치 (예시)
|
||||
* 조건: 차량 등록지와 소유자 실거주지가 다른 경우
|
||||
*/
|
||||
private boolean checkTransferCondition2_AddressMismatch(BasicResponse.Record basicInfo, String userId, String vhclno) {
|
||||
String useStrnghldAdresNm = basicInfo.getUseStrnghldAdresNm(); // 사용본거지주소명
|
||||
String ownerAdresNm = basicInfo.getOwnerAdresNm(); // 소유자주소명
|
||||
|
||||
// 주소 유효성 검사
|
||||
if (useStrnghldAdresNm == null || ownerAdresNm == null) {
|
||||
log.debug("[이첩][조건2] 주소 정보 없음. 차량번호: {}", vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 주소 일치 여부 확인 (시/도 단위 비교 등)
|
||||
if (useStrnghldAdresNm.equals(ownerAdresNm)) {
|
||||
log.debug("[이첩][조건2] 주소 일치. 차량번호: {}", vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("[이첩][조건2] 주소 불일치! 차량번호: {}, 사용본거지: {}, 소유자주소: {}",
|
||||
vhclno, useStrnghldAdresNm, ownerAdresNm);
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
**2단계: checkTransfer() 메서드에 조건 추가**
|
||||
|
||||
```java
|
||||
private String checkTransfer(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo, String userId) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
|
||||
// ========== 이첩 조건들 (OR 로직: 하나라도 만족하면 이첩) ==========
|
||||
|
||||
// 조건1: 법정동코드 불일치
|
||||
if (checkTransferCondition1_LegalDongMismatch(basicInfo, userId, vhclno)) {
|
||||
return processTransfer(existingData, basicInfo, vhclno, "법정동코드 불일치");
|
||||
}
|
||||
|
||||
// 조건2: 주소 불일치 (새로 추가!)
|
||||
if (checkTransferCondition2_AddressMismatch(basicInfo, userId, vhclno)) {
|
||||
return processTransfer(existingData, basicInfo, vhclno, "주소 불일치");
|
||||
}
|
||||
|
||||
// 조건3: 향후 추가될 조건들...
|
||||
// if (checkTransferCondition3_XXX(basicInfo, userId, vhclno)) {
|
||||
// return processTransfer(existingData, basicInfo, vhclno, "XXX");
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
**끝! 매우 간단합니다.**
|
||||
|
||||
---
|
||||
|
||||
### 📊 이첩 조건 OR 로직 동작 방식
|
||||
|
||||
```java
|
||||
// 이첩 체크 시작
|
||||
checkTransfer(...)
|
||||
↓
|
||||
조건1 체크 → TRUE → 즉시 이첩 처리 후 return "03"
|
||||
↓ FALSE
|
||||
조건2 체크 → TRUE → 즉시 이첩 처리 후 return "03"
|
||||
↓ FALSE
|
||||
조건3 체크 → TRUE → 즉시 이첩 처리 후 return "03"
|
||||
↓ FALSE
|
||||
모든 조건 FALSE → return null (이첩 아님)
|
||||
```
|
||||
|
||||
### 🔍 로그 예시
|
||||
|
||||
#### 조건1에 걸린 경우
|
||||
```
|
||||
[이첩][조건1] 법정동코드 불일치! 차량번호: 12가3456, 법정동: 1100, 조직: 4100
|
||||
[이첩] 조건 충족! 차량번호: 12가3456, 사유: 법정동코드 불일치
|
||||
[이첩] 처리 완료! 차량번호: 12가3456, 시군구: 서울특별시(11000), 사유: 법정동코드 불일치
|
||||
```
|
||||
|
||||
#### 조건2에 걸린 경우
|
||||
```
|
||||
[이첩][조건1] 법정동코드 일치. 차량번호: 12가3456, 법정동: 1100, 조직: 1100
|
||||
[이첩][조건2] 주소 불일치! 차량번호: 12가3456
|
||||
[이첩] 조건 충족! 차량번호: 12가3456, 사유: 주소 불일치
|
||||
[이첩] 처리 완료! 차량번호: 12가3456, 시군구: 서울특별시(11000), 사유: 주소 불일치
|
||||
```
|
||||
|
||||
#### 모든 조건에 안 걸린 경우
|
||||
```
|
||||
[이첩][조건1] 법정동코드 일치. 차량번호: 12가3456
|
||||
[이첩][조건2] 주소 일치. 차량번호: 12가3456
|
||||
[이첩] 모든 조건 미충족. 차량번호: 12가3456
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🚀 빠른 템플릿
|
||||
|
||||
#### 일반 비교 조건 템플릿
|
||||
|
||||
```java
|
||||
/**
|
||||
* N. 비교 조건명
|
||||
*
|
||||
* <p>조건: 조건 설명</p>
|
||||
* <p>처리: TASK_PRCS_STTS_CD = XX</p>
|
||||
*
|
||||
* @return XX (적용됨) 또는 null (미적용)
|
||||
*/
|
||||
private String checkXXX(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
|
||||
// 1. 데이터 추출
|
||||
String data1 = basicInfo.getXXX();
|
||||
String data2 = basicInfo.getYYY();
|
||||
|
||||
// 2. 유효성 검사
|
||||
if (data1 == null || data2 == null) {
|
||||
log.debug("[비교조건명] 조건 미충족. 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 조건 체크
|
||||
if (조건_만족) {
|
||||
log.info("[비교조건명] 조건 충족! 차량번호: {}, 상세정보...", vhclno);
|
||||
|
||||
// 4. DB 업데이트
|
||||
existingData.setTaskPrcsSttsCd("XX");
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
// 필요한 필드 추가 설정
|
||||
|
||||
int updateCount = carFfnlgTrgtMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new RuntimeException(String.format("[비교조건명] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[비교조건명] 처리 완료! 차량번호: {}", vhclno);
|
||||
return "XX";
|
||||
}
|
||||
|
||||
log.debug("[비교조건명] 조건 미충족. 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
#### 이첩 조건 템플릿
|
||||
|
||||
```java
|
||||
/**
|
||||
* 이첩 조건N: XXX
|
||||
* 조건 설명
|
||||
*/
|
||||
private boolean checkTransferConditionN_XXX(BasicResponse.Record basicInfo, String userId, String vhclno) {
|
||||
// 1. 데이터 추출
|
||||
String data1 = basicInfo.getXXX();
|
||||
String data2 = basicInfo.getYYY();
|
||||
|
||||
// 2. 유효성 검사
|
||||
if (data1 == null || data2 == null) {
|
||||
log.debug("[이첩][조건N] 데이터 없음. 차량번호: {}", vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 조건 체크
|
||||
if (조건_만족) {
|
||||
log.info("[이첩][조건N] 조건 충족! 차량번호: {}, 상세정보...", vhclno);
|
||||
return true; // 이첩!
|
||||
}
|
||||
|
||||
log.debug("[이첩][조건N] 조건 미충족. 차량번호: {}", vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// checkTransfer()에 추가
|
||||
if (checkTransferConditionN_XXX(basicInfo, userId, vhclno)) {
|
||||
return processTransfer(existingData, basicInfo, vhclno, "XXX");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ 주의사항
|
||||
|
||||
1. **조건 순서**: 위에서부터 순차적으로 체크됩니다
|
||||
- 자주 걸리는 조건을 위쪽에 배치하면 성능 향상
|
||||
|
||||
2. **조건 메서드 네이밍**:
|
||||
- 일반: `checkXXX`
|
||||
- 이첩: `checkTransferConditionN_XXX` 형식 권장
|
||||
|
||||
3. **return 값**:
|
||||
- 일반 조건: `"상태코드"` (적용됨) 또는 `null` (미적용)
|
||||
- 이첩 조건: `true` (해당함) 또는 `false` (해당 안함)
|
||||
|
||||
4. **processTransfer() 공통 사용**:
|
||||
- DB 업데이트는 `processTransfer()` 메서드에서 공통으로 처리
|
||||
- 각 조건에서는 `true/false`만 반환
|
||||
|
||||
---
|
||||
|
||||
### 📝 체크리스트
|
||||
|
||||
새로운 비교 조건 추가 시 확인사항:
|
||||
|
||||
- [ ] 조건 메서드 작성 완료
|
||||
- [ ] executeComparison()에 조건 추가 완료 (일반 조건)
|
||||
- [ ] checkTransfer()에 조건 추가 완료 (이첩 조건)
|
||||
- [ ] 로그 메시지에 조건 정보 포함
|
||||
- [ ] null 체크 처리 완료
|
||||
- [ ] 컴파일 테스트 완료
|
||||
- [ ] 실제 데이터로 테스트 완료
|
||||
|
||||
---
|
||||
|
||||
## 방법2: Chain of Responsibility 패턴
|
||||
|
||||
### 🌟 특징
|
||||
|
||||
- ✅ **확장성 우수** - 규칙 추가/삭제가 독립적
|
||||
- ✅ **테스트 용이** - 각 규칙을 독립적으로 테스트 가능
|
||||
- ✅ **순서 제어** - getOrder()로 실행 순서 명확히 제어
|
||||
- ✅ **관심사 분리** - 각 규칙이 독립된 파일로 관리
|
||||
- ❌ **복잡한 구조** - 학습 곡선 높음
|
||||
- ❌ **파일 분산** - 여러 파일을 확인해야 함
|
||||
|
||||
### 📁 디렉토리 구조
|
||||
|
||||
```
|
||||
comparison/
|
||||
├── ComparisonRule.java # 비교 규칙 인터페이스
|
||||
├── ComparisonContext.java # 비교에 필요한 데이터 컨테이너
|
||||
├── ComparisonResult.java # 비교 결과 객체
|
||||
├── ComparisonRuleProcessor.java # 규칙 실행 체인 관리자
|
||||
└── rules/ # 개별 규칙 구현체
|
||||
├── ProductUseComparisonRule.java # 상품용 규칙
|
||||
└── TransferComparisonRule.java # 이첩 규칙
|
||||
```
|
||||
|
||||
### 🔄 동작 방식
|
||||
|
||||
1. **ComparisonRuleProcessor**가 모든 `@Component` 규칙을 자동으로 찾아서 등록
|
||||
2. 규칙들을 `getOrder()` 순서대로 정렬 (낮을수록 먼저 실행)
|
||||
3. 각 규칙을 순차적으로 실행
|
||||
4. 규칙이 `applied=true`를 반환하면 즉시 중단
|
||||
5. 모든 규칙이 `applied=false`를 반환하면 "정상" 처리
|
||||
|
||||
```
|
||||
ComparisonRuleProcessor
|
||||
↓
|
||||
규칙 자동 스캔 (@Component)
|
||||
↓
|
||||
순서대로 정렬 (getOrder())
|
||||
↓
|
||||
ProductUseComparisonRule (order=10) → 적용됨? → 종료
|
||||
↓ 미적용
|
||||
TransferComparisonRule (order=20) → 적용됨? → 종료
|
||||
↓ 미적용
|
||||
YourNewRule (order=30) → 적용됨? → 종료
|
||||
↓ 미적용
|
||||
정상 처리
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🚀 새로운 비교 규칙 추가 방법
|
||||
|
||||
#### 1단계: rules/ 폴더에 새 클래스 생성
|
||||
|
||||
```java
|
||||
package go.kr.project.carInspectionPenalty.registration.comparison.rules;
|
||||
|
||||
import go.kr.project.carInspectionPenalty.registration.comparison.*;
|
||||
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
|
||||
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 내사종결 비교 규칙
|
||||
*
|
||||
* 적용 조건:
|
||||
* - 예시: 차량이 말소된 경우
|
||||
*
|
||||
* 처리 내용:
|
||||
* - TASK_PRCS_STTS_CD = 04 (내사종결)
|
||||
* - TASK_PRCS_YMD = 현재 날짜
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InvestigationClosedComparisonRule implements ComparisonRule {
|
||||
|
||||
private static final String STATUS_CODE = "04"; // 내사종결
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
private final CarFfnlgTrgtMapper mapper;
|
||||
|
||||
@Override
|
||||
public ComparisonResult execute(ComparisonContext context) {
|
||||
String vhclno = context.getVhclno();
|
||||
|
||||
// 1. API 응답 데이터 유효성 검사
|
||||
if (context.getApiResponse().getBasicInfo() == null ||
|
||||
context.getApiResponse().getBasicInfo().getRecord() == null ||
|
||||
context.getApiResponse().getBasicInfo().getRecord().isEmpty()) {
|
||||
log.debug("[{}] API 응답 데이터가 없어 규칙을 적용할 수 없습니다. 차량번호: {}",
|
||||
getRuleName(), vhclno);
|
||||
return ComparisonResult.notApplied();
|
||||
}
|
||||
|
||||
// 2. 필요한 데이터 추출
|
||||
go.kr.project.api.model.response.BasicResponse.Record basicInfo =
|
||||
context.getApiResponse().getBasicInfo().getRecord().get(0);
|
||||
String ersrRegistSeCode = basicInfo.getErsrRegistSeCode(); // 말소등록구분코드
|
||||
|
||||
// 3. 비교 로직 (말소된 차량인지 확인)
|
||||
if (ersrRegistSeCode == null || ersrRegistSeCode.isEmpty()) {
|
||||
log.debug("[{}] 말소되지 않은 차량입니다. 차량번호: {}", getRuleName(), vhclno);
|
||||
return ComparisonResult.notApplied();
|
||||
}
|
||||
|
||||
log.info("[{}] 내사종결 감지! 차량번호: {}, 말소구분: {}",
|
||||
getRuleName(), vhclno, ersrRegistSeCode);
|
||||
|
||||
// 4. DB 업데이트
|
||||
CarFfnlgTrgtVO updateData = context.getExistingData();
|
||||
updateData.setTaskPrcsSttsCd(STATUS_CODE);
|
||||
updateData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
// 필요시 추가 필드 설정
|
||||
|
||||
int updateResult = mapper.update(updateData);
|
||||
|
||||
// 5. 결과 반환
|
||||
if (updateResult > 0) {
|
||||
log.info("[{}] 처리 완료! 차량번호: {}", getRuleName(), vhclno);
|
||||
return ComparisonResult.applied(STATUS_CODE, "내사종결로 처리되었습니다.");
|
||||
} else {
|
||||
log.error("[{}] 업데이트 실패! 차량번호: {}", getRuleName(), vhclno);
|
||||
throw new RuntimeException(String.format("내사종결 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRuleName() {
|
||||
return "내사종결";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 30; // 상품용(10), 이첩(20) 다음으로 실행
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2단계: 자동 등록
|
||||
|
||||
Spring이 자동으로 `@Component` 어노테이션이 붙은 클래스를 찾아서 등록합니다.
|
||||
**별도의 설정 파일 수정이 필요 없습니다!**
|
||||
|
||||
#### 끝! 자동으로 등록됩니다.
|
||||
|
||||
---
|
||||
|
||||
### 📊 실행 순서 제어
|
||||
|
||||
`getOrder()` 메서드로 실행 순서를 제어할 수 있습니다.
|
||||
|
||||
```java
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 30; // 숫자가 낮을수록 먼저 실행
|
||||
}
|
||||
```
|
||||
|
||||
**현재 순서:**
|
||||
- 상품용: 10
|
||||
- 이첩: 20
|
||||
- (향후 추가): 30, 40, 50...
|
||||
|
||||
---
|
||||
|
||||
### 📦 API 응답 데이터 활용
|
||||
|
||||
`ComparisonContext`를 통해 다음 데이터에 접근할 수 있습니다:
|
||||
|
||||
```java
|
||||
// 기존 과태료 대상 데이터
|
||||
CarFfnlgTrgtVO existingData = context.getExistingData();
|
||||
|
||||
// API 응답 - 기본 정보
|
||||
var basicInfo = context.getApiResponse().getBasicInfo().getRecord().get(0);
|
||||
String mberNm = basicInfo.getMberNm(); // 대표소유자성명
|
||||
String vhrno = basicInfo.getVhrno(); // 차량번호
|
||||
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode(); // 사용본거지법정동코드
|
||||
String ersrRegistSeCode = basicInfo.getErsrRegistSeCode(); // 말소등록구분코드
|
||||
// ... 기타 필드들
|
||||
|
||||
// API 응답 - 등록원부
|
||||
var ledgerInfo = context.getApiResponse().getLedgerInfo();
|
||||
|
||||
// 사용자 ID
|
||||
String userId = context.getUserId();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 💉 의존성 주입
|
||||
|
||||
규칙 클래스에서 필요한 빈을 자유롭게 주입받을 수 있습니다.
|
||||
|
||||
```java
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MyComparisonRule implements ComparisonRule {
|
||||
|
||||
private final CarFfnlgTrgtMapper mapper; // Mapper
|
||||
private final UserMapper userMapper; // User 정보 필요 시
|
||||
private final SomeOtherService someService; // 다른 서비스
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📝 로깅
|
||||
|
||||
규칙 실행 중 상세한 로그가 자동으로 출력됩니다.
|
||||
|
||||
```
|
||||
[상품용] 규칙 실행 중... 차량번호: 12가3456
|
||||
[상품용] 상품용 감지! 차량번호: 12가3456, 소유자명: 상품용차량
|
||||
[상품용] 처리 완료! 차량번호: 12가3456
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🧪 테스트 방법
|
||||
|
||||
규칙을 독립적으로 테스트할 수 있습니다.
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
class InvestigationClosedComparisonRuleTest {
|
||||
|
||||
@Autowired
|
||||
private InvestigationClosedComparisonRule rule;
|
||||
|
||||
@Test
|
||||
void 말소된_차량은_내사종결로_처리된다() {
|
||||
// Given
|
||||
ComparisonContext context = ComparisonContext.builder()
|
||||
.existingData(existingData)
|
||||
.apiResponse(apiResponse)
|
||||
.userId("USER001")
|
||||
.build();
|
||||
|
||||
// When
|
||||
ComparisonResult result = rule.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isApplied());
|
||||
assertEquals("04", result.getStatusCode());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📚 기존 규칙 예제
|
||||
|
||||
#### 상품용 규칙 (ProductUseComparisonRule)
|
||||
|
||||
```java
|
||||
// 대표소유자성명에 "상품용" 문자열이 포함되어 있는지 확인
|
||||
String mberNm = basicInfo.getMberNm();
|
||||
if (mberNm != null && mberNm.contains("상품용")) {
|
||||
// 상품용으로 처리
|
||||
return ComparisonResult.applied("02", "상품용으로 처리되었습니다.");
|
||||
}
|
||||
```
|
||||
|
||||
#### 이첩 규칙 (TransferComparisonRule)
|
||||
|
||||
```java
|
||||
// 법정동코드 앞 4자리와 사용자 조직코드 앞 4자리 비교
|
||||
String legalDong4 = useStrnghldLegaldongCode.substring(0, 4);
|
||||
String userOrg4 = userOrgCd.substring(0, 4);
|
||||
|
||||
if (!legalDong4.equals(userOrg4)) {
|
||||
// 이첩으로 처리
|
||||
return ComparisonResult.applied("03", "이첩으로 처리되었습니다.");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 어떤 방법을 선택해야 할까?
|
||||
|
||||
### 방법1 선택 (Service/Impl) - 다음과 같은 경우 추천 ⭐
|
||||
|
||||
- ✅ **간단한 프로젝트** - 비교 조건이 10개 이하
|
||||
- ✅ **빠른 개발 필요** - 당장 구현해야 할 때
|
||||
- ✅ **팀원 경험 부족** - 디자인 패턴에 익숙하지 않은 경우
|
||||
- ✅ **유지보수 단순** - 한 파일에서 모든 로직 확인 가능
|
||||
- ✅ **작은 팀** - 1~3명 정도의 소규모 팀
|
||||
|
||||
### 방법2 선택 (Chain of Responsibility) - 다음과 같은 경우 추천
|
||||
|
||||
- ✅ **복잡한 프로젝트** - 비교 조건이 10개 이상
|
||||
- ✅ **장기 유지보수** - 규칙이 자주 추가/변경될 것으로 예상
|
||||
- ✅ **팀 규모 큽** - 여러 개발자가 동시에 작업
|
||||
- ✅ **테스트 중요** - 각 규칙을 독립적으로 테스트해야 함
|
||||
- ✅ **확장성 중시** - 규칙을 동적으로 추가/제거 필요
|
||||
|
||||
---
|
||||
|
||||
## 참고사항
|
||||
|
||||
### 💡 현재 설정 변경 방법
|
||||
|
||||
`CarFfnlgTrgtServiceImpl.java`의 `executeComparisonLogic()` 메서드에서 주석을 변경하면 됩니다.
|
||||
|
||||
```java
|
||||
private String executeComparisonLogic(...) {
|
||||
// 방법1 사용하려면: (현재 설정)
|
||||
return executeWithServicePattern(existingData, apiResponse, userId);
|
||||
|
||||
// 방법2 사용하려면:
|
||||
// return executeWithChainPattern(existingData, apiResponse, userId);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ 주의사항
|
||||
|
||||
1. **두 방법을 동시에 사용하지 마세요** - 하나만 선택
|
||||
2. **트랜잭션 관리** - 각 비교 메서드는 DB 업데이트를 직접 수행
|
||||
3. **예외 처리** - 업데이트 실패 시 RuntimeException 발생 → 전체 롤백
|
||||
4. **로깅** - 각 조건의 충족/미충족 여부를 명확히 로깅
|
||||
5. **순서 관리** (방법2) - `getOrder()` 값이 중복되지 않도록 주의
|
||||
6. **null 체크** - API 응답 데이터는 항상 null 체크 필수
|
||||
|
||||
---
|
||||
|
||||
### 🚀 빠른 시작
|
||||
|
||||
#### 방법1로 시작하기 (추천)
|
||||
1. `ComparisonServiceImpl.java` 열기
|
||||
2. 기존 메서드 참고하여 새 메서드 추가
|
||||
3. `executeComparison()`에서 호출
|
||||
4. 끝!
|
||||
|
||||
#### 방법2로 시작하기
|
||||
1. `comparison/rules/` 폴더에 새 규칙 클래스 생성
|
||||
2. `@Component` 붙이기
|
||||
3. `ComparisonRule` 인터페이스 구현
|
||||
4. 끝!
|
||||
|
||||
---
|
||||
|
||||
### 📞 문의
|
||||
|
||||
추가 질문이나 제안사항은 개발팀에 문의하세요.
|
||||
|
||||
---
|
||||
|
||||
### 📖 관련 파일
|
||||
|
||||
- **방법1 파일**:
|
||||
- `service/ComparisonService.java`
|
||||
- `service/impl/ComparisonServiceImpl.java`
|
||||
|
||||
- **방법2 파일**:
|
||||
- `comparison/` 패키지 전체
|
||||
|
||||
- **통합 지점**:
|
||||
- `CarFfnlgTrgtServiceImpl.java` - `executeComparisonLogic()` 메서드
|
||||
Loading…
Reference in New Issue