- 14개 LastExecutionUpdateTasklet에 executionMode 체크 추가 - ShipDetailUpdateJobConfig: retryModeDecider → emptyResponseDecider 변경 - RecollectionJobExecutionListener: last_success_date 저장/복원 로직 제거 - RecollectionHistoryService: 미사용 메서드 제거 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
219 lines
9.2 KiB
Java
219 lines
9.2 KiB
Java
package com.snp.batch.service;
|
|
|
|
import com.snp.batch.global.model.BatchCollectionPeriod;
|
|
import com.snp.batch.global.repository.BatchCollectionPeriodRepository;
|
|
import com.snp.batch.global.repository.BatchLastExecutionRepository;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.batch.core.scope.context.StepContext;
|
|
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneOffset;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
|
|
@Slf4j
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class BatchDateService {
|
|
|
|
private final BatchLastExecutionRepository repository;
|
|
private final BatchCollectionPeriodRepository collectionPeriodRepository;
|
|
|
|
/**
|
|
* 현재 Step의 Job 파라미터에서 executionMode를 확인
|
|
*/
|
|
private String getExecutionMode() {
|
|
try {
|
|
StepContext context = StepSynchronizationManager.getContext();
|
|
if (context != null && context.getStepExecution() != null) {
|
|
return context.getStepExecution().getJobExecution()
|
|
.getJobParameters().getString("executionMode", "NORMAL");
|
|
}
|
|
} catch (Exception e) {
|
|
log.debug("StepSynchronizationManager 컨텍스트 접근 실패, NORMAL 모드로 처리", e);
|
|
}
|
|
return "NORMAL";
|
|
}
|
|
|
|
/**
|
|
* 현재 Step의 Job 파라미터에서 apiKey 파라미터를 확인 (재수집용)
|
|
*/
|
|
private String getRecollectApiKey() {
|
|
try {
|
|
StepContext context = StepSynchronizationManager.getContext();
|
|
if (context != null && context.getStepExecution() != null) {
|
|
return context.getStepExecution().getJobExecution()
|
|
.getJobParameters().getString("apiKey");
|
|
}
|
|
} catch (Exception e) {
|
|
// ignore
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 현재 Step의 Job 파라미터에서 executor를 확인.
|
|
* AUTO_RETRY/MANUAL_RETRY이면 실패건 재수집이므로 기간 테이블을 사용하지 않는다.
|
|
*/
|
|
private boolean isFailedRecordRetry() {
|
|
try {
|
|
StepContext context = StepSynchronizationManager.getContext();
|
|
if (context != null && context.getStepExecution() != null) {
|
|
String executor = context.getStepExecution().getJobExecution()
|
|
.getJobParameters().getString("executor");
|
|
return "AUTO_RETRY".equals(executor) || "MANUAL_RETRY".equals(executor);
|
|
}
|
|
} catch (Exception e) {
|
|
log.debug("executor 파라미터 확인 실패", e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public Map<String, String> getDateRangeWithoutTimeParams(String apiKey) {
|
|
// 기간 재수집 모드: batch_collection_period에서 날짜 조회
|
|
// 실패건 재수집(AUTO_RETRY/MANUAL_RETRY)은 정상 모드와 동일하게 last_success_date 기반 사용
|
|
if ("RECOLLECT".equals(getExecutionMode()) && !isFailedRecordRetry()) {
|
|
return getCollectionPeriodDateParams(apiKey);
|
|
}
|
|
|
|
// 정상 모드: last_success_date ~ now()
|
|
return repository.findDateRangeByApiKey(apiKey)
|
|
.map(projection -> {
|
|
LocalDateTime toDate = LocalDateTime.now();
|
|
saveToDateToJobContext(toDate);
|
|
|
|
Map<String, String> params = new HashMap<>();
|
|
putDateParams(params, "from", projection.getLastSuccessDate());
|
|
putDateParams(params, "to", toDate);
|
|
params.put("shipsCategory", "0");
|
|
return params;
|
|
})
|
|
.orElseGet(() -> {
|
|
log.warn("해당 apiKey에 대한 데이터를 찾을 수 없습니다: {}", apiKey);
|
|
return new HashMap<>();
|
|
});
|
|
}
|
|
|
|
public Map<String, String> getDateRangeWithTimezoneParams(String apiKey) {
|
|
return getDateRangeWithTimezoneParams(apiKey, "fromDate", "toDate");
|
|
}
|
|
|
|
public Map<String, String> getDateRangeWithTimezoneParams(String apiKey, String dateParam1, String dateParam2) {
|
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSX");
|
|
|
|
// 기간 재수집 모드: batch_collection_period에서 날짜 조회
|
|
// 실패건 재수집(AUTO_RETRY/MANUAL_RETRY)은 정상 모드와 동일하게 last_success_date 기반 사용
|
|
if ("RECOLLECT".equals(getExecutionMode()) && !isFailedRecordRetry()) {
|
|
return getCollectionPeriodTimezoneParams(apiKey, dateParam1, dateParam2, formatter);
|
|
}
|
|
|
|
// 정상 모드: last_success_date ~ now()
|
|
return repository.findDateRangeByApiKey(apiKey)
|
|
.map(projection -> {
|
|
LocalDateTime toDate = LocalDateTime.now();
|
|
saveToDateToJobContext(toDate);
|
|
|
|
Map<String, String> params = new HashMap<>();
|
|
params.put(dateParam1, formatToUtc(projection.getLastSuccessDate(), formatter));
|
|
params.put(dateParam2, formatToUtc(toDate, formatter));
|
|
return params;
|
|
})
|
|
.orElseGet(() -> {
|
|
log.warn("해당 apiKey에 대한 데이터를 찾을 수 없습니다: {}", apiKey);
|
|
return new HashMap<>();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 재수집 모드: batch_collection_period에서 년/월/일 파라미터 생성
|
|
*/
|
|
private Map<String, String> getCollectionPeriodDateParams(String apiKey) {
|
|
String recollectApiKey = getRecollectApiKey();
|
|
String lookupKey = recollectApiKey != null ? recollectApiKey : apiKey;
|
|
|
|
Optional<BatchCollectionPeriod> opt = collectionPeriodRepository.findById(lookupKey);
|
|
if (opt.isEmpty()) {
|
|
log.warn("[RECOLLECT] 수집기간 설정을 찾을 수 없습니다: {}", lookupKey);
|
|
return new HashMap<>();
|
|
}
|
|
|
|
BatchCollectionPeriod cp = opt.get();
|
|
Map<String, String> params = new HashMap<>();
|
|
putDateParams(params, "from", cp.getRangeFromDate());
|
|
putDateParams(params, "to", cp.getRangeToDate());
|
|
params.put("shipsCategory", "0");
|
|
|
|
log.info("[RECOLLECT] batch_collection_period 날짜 사용: apiKey={}, range={}~{}",
|
|
lookupKey, cp.getRangeFromDate(), cp.getRangeToDate());
|
|
return params;
|
|
}
|
|
|
|
/**
|
|
* 재수집 모드: batch_collection_period에서 UTC 타임존 파라미터 생성
|
|
*/
|
|
private Map<String, String> getCollectionPeriodTimezoneParams(
|
|
String apiKey, String dateParam1, String dateParam2, DateTimeFormatter formatter) {
|
|
String recollectApiKey = getRecollectApiKey();
|
|
String lookupKey = recollectApiKey != null ? recollectApiKey : apiKey;
|
|
|
|
Optional<BatchCollectionPeriod> opt = collectionPeriodRepository.findById(lookupKey);
|
|
if (opt.isEmpty()) {
|
|
log.warn("[RECOLLECT] 수집기간 설정을 찾을 수 없습니다: {}", lookupKey);
|
|
return new HashMap<>();
|
|
}
|
|
|
|
BatchCollectionPeriod cp = opt.get();
|
|
Map<String, String> params = new HashMap<>();
|
|
params.put(dateParam1, formatToUtc(cp.getRangeFromDate(), formatter));
|
|
params.put(dateParam2, formatToUtc(cp.getRangeToDate(), formatter));
|
|
|
|
log.info("[RECOLLECT] batch_collection_period 날짜 사용 (UTC): apiKey={}, range={}~{}",
|
|
lookupKey, cp.getRangeFromDate(), cp.getRangeToDate());
|
|
return params;
|
|
}
|
|
|
|
/**
|
|
* 배치 시작 시 캡처한 toDate를 JobExecutionContext에 저장
|
|
* LastExecutionUpdateTasklet에서 이 값을 꺼내 LAST_SUCCESS_DATE로 사용
|
|
*/
|
|
private void saveToDateToJobContext(LocalDateTime toDate) {
|
|
try {
|
|
StepContext context = StepSynchronizationManager.getContext();
|
|
if (context != null && context.getStepExecution() != null) {
|
|
context.getStepExecution().getJobExecution()
|
|
.getExecutionContext().put("batchToDate", toDate.toString());
|
|
log.debug("batchToDate JobContext 저장 완료: {}", toDate);
|
|
}
|
|
} catch (Exception e) {
|
|
log.warn("batchToDate JobContext 저장 실패", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* LocalDateTime에서 연, 월, 일을 추출하여 Map에 담는 헬퍼 메소드
|
|
*/
|
|
private void putDateParams(Map<String, String> params, String prefix, LocalDateTime dateTime) {
|
|
if (dateTime != null) {
|
|
params.put(prefix + "Year", String.valueOf(dateTime.getYear()));
|
|
params.put(prefix + "Month", String.valueOf(dateTime.getMonthValue()));
|
|
params.put(prefix + "Day", String.valueOf(dateTime.getDayOfMonth()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 한국 시간(LocalDateTime)을 UTC 문자열로 변환
|
|
*/
|
|
private String formatToUtc(LocalDateTime localDateTime, DateTimeFormatter formatter) {
|
|
if (localDateTime == null) return null;
|
|
return localDateTime.atZone(ZoneId.of("Asia/Seoul"))
|
|
.withZoneSameInstant(ZoneOffset.UTC)
|
|
.format(formatter);
|
|
}
|
|
}
|