TASK_BATCH_ACIDLIVY: AciDlivyTasklet 토큰 발급 로직 추가

- 배치 시작 시 issueAciKeyToken() 호출하여 ACIKey 토큰 발급
- yaml 고정 apikey는 토큰 발급 시 APIKEY Header로만 사용
- 발급받은 토큰은 메모리에만 유지, ACIKey Header에 세팅하여 API 호출
- acikey.url 주입 추가
This commit is contained in:
이주일
2026-05-25 02:29:50 +09:00
parent f7eaac1ca8
commit 4c500550d9

View File

@@ -21,8 +21,8 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
// ACI ???? ?? ? ???? ?? Tasklet
// ????: gc_order_shipping(ORDR_STTUS_CD=35) ?? ? ACI API ?? ? BLNO ?? ? ????
// ACI 출고자료 조회 및 출고처리 배치 Tasklet
// 흐름: gc_order_shipping(ORDR_STTUS_CD=35) 조회 → 토큰 발급 → ACI API 호출 → BLNO 추출 → 출고처리
@Slf4j
@Component
@RequiredArgsConstructor
@@ -31,6 +31,9 @@ public class AciDlivyTasklet implements Tasklet {
private final AciDlivyMapper aciDlivyMapper;
private final PlatformTransactionManager transactionManager;
@Value("${acikey.url}")
private String aciKeyUrl;
@Value("${aci.dlivy.api-url}")
private String aciDlivyApiUrl;
@@ -40,19 +43,22 @@ public class AciDlivyTasklet implements Tasklet {
@Value("${acikey.apikey}")
private String apiKey;
private static final String HDRYCPN_CD = "code22"; // ACI ????? ??
private static final String DLIVY_STTUS_CD = "55"; // ???? ??
private static final String HDRYCPN_CD = "code22"; // ACI 택배사 코드
private static final String DLIVY_STTUS_CD = "55"; // 출고완료 코드
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
log.info("[AciDlivyBatch] ?? ??");
log.info("[AciDlivyBatch] 배치 시작");
// ? ?? ?? ?? (ORDR_STTUS_CD = '35')
// 토큰 발급 (yaml 고정 apikey → ACIKey 토큰 발급, 메모리에만 유지)
String aciKeyToken = issueAciKeyToken();
// 대상 주문 목록 조회 (ORDR_STTUS_CD = '35')
List<AciDlivyOrderVO> targetList = aciDlivyMapper.selectAciDlivyTargetList();
log.info("[AciDlivyBatch] ?? ?? ??: {}", targetList.size());
log.info("[AciDlivyBatch] 대상 주문 건수: {}", targetList.size());
if (targetList.isEmpty()) {
log.info("[AciDlivyBatch] ??? ?? ??? ????.");
log.info("[AciDlivyBatch] 처리할 대상 주문이 없습니다.");
return RepeatStatus.FINISHED;
}
@@ -60,17 +66,17 @@ public class AciDlivyTasklet implements Tasklet {
int failCnt = 0;
int skipCnt = 0;
// ???? ?? ???? (REQUIRES_NEW) ? ? ? ??? ?? ?? ?? ???
// 건별로 독립 트랜잭션 (REQUIRES_NEW) 으로 처리 - 한 건 실패가 다른 건에 영향 없도록
TransactionTemplate txTemplate = new TransactionTemplate(transactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
for (AciDlivyOrderVO orderVO : targetList) {
try {
// ? ACI ???? API ?? ? BLNO ??
String blno = callAciDlivyApi(orderVO);
// 건별 ACI 출고자료 API 호출 → BLNO 추출
String blno = callAciDlivyApi(orderVO, aciKeyToken);
if (blno == null) {
log.info("[AciDlivyBatch] ?? - ORDR_DLVY_INNB={}, ORDR_INNB={} (BLNO ?? ?? ?? ??)",
log.info("[AciDlivyBatch] 스킵 - ORDR_DLVY_INNB={}, ORDR_INNB={} (BLNO 없음 또는 조건 미충족)",
orderVO.getOrdrDlvyInnb(), orderVO.getOrdrInnb());
skipCnt++;
} else {
@@ -78,24 +84,24 @@ public class AciDlivyTasklet implements Tasklet {
orderVO.setHdrycpnCd(HDRYCPN_CD);
orderVO.setDlivySttusCd(DLIVY_STTUS_CD);
// ? ???? (?? ????)
// 건별 처리 (독립 트랜잭션)
txTemplate.execute(status -> {
processDelivery(orderVO);
return null;
});
successCnt++;
log.info("[AciDlivyBatch] ???? ?? - ORDR_DLVY_INNB={}, BLNO={}",
log.info("[AciDlivyBatch] 출고처리 완료 - ORDR_DLVY_INNB={}, BLNO={}",
orderVO.getOrdrDlvyInnb(), blno);
}
} catch (Exception e) {
log.error("[AciDlivyBatch] ???? ?? - ORDR_DLVY_INNB={}, ??={}",
log.error("[AciDlivyBatch] 출고처리 실패 - ORDR_DLVY_INNB={}, 오류={}",
orderVO.getOrdrDlvyInnb(), e.getMessage(), e);
failCnt++;
}
// ? API ?? ?? ?? (?? 10? ? 1? ??)
// 건별 API 호출 간격 유지 (과부하 방지 - 1초 대기)
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
@@ -104,20 +110,63 @@ public class AciDlivyTasklet implements Tasklet {
}
}
log.info("[AciDlivyBatch] ?? ?? - ??: {}, ??: {}, ??: {}", successCnt, failCnt, skipCnt);
log.info("[AciDlivyBatch] 배치 완료 - 성공: {}, 실패: {}, 스킵: {}", successCnt, failCnt, skipCnt);
return RepeatStatus.FINISHED;
}
/**
* ACI ???? API ??
* Content-Type? text/html? ?? ??? JSON ? String ?? ? ObjectMapper ??
* @return BLNO(?????), ??? null
* ACIKey 토큰 발급
* yaml 고정 apikey를 APIKEY Header로 사용하여 POST 호출 → 발급받은 토큰은 메모리에만 유지
*/
private String callAciDlivyApi(AciDlivyOrderVO orderVO) {
private String issueAciKeyToken() {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("USERID", userId);
headers.set("APIKEY", apiKey);
HttpEntity<String> requestEntity = new HttpEntity<>("{}", headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
aciKeyUrl,
HttpMethod.POST,
requestEntity,
String.class
);
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> body = objectMapper.readValue(response.getBody(), Map.class);
String status = (String) body.get("Status");
String aciKey = (String) body.get("ACIKey");
String errMsg = (String) body.get("ErrMsg");
if ("success".equals(status) && aciKey != null) {
log.info("[AciDlivyBatch] ACIKey 토큰 발급 성공");
return aciKey;
} else {
throw new RuntimeException("ACIKey 토큰 발급 실패. Status: " + status + ", ErrMsg: " + errMsg);
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("ACIKey 토큰 발급 오류", e);
}
}
/**
* ACI 출고자료 API 호출
* Content-Type이 text/html로 오는 경우를 대비해 JSON을 String으로 받아 ObjectMapper 파싱
* @param aciKeyToken 발급받은 ACIKey 토큰
* @return BLNO(운송장번호), 없으면 null
*/
private String callAciDlivyApi(AciDlivyOrderVO orderVO, String aciKeyToken) {
try {
HttpHeaders headers = new HttpHeaders();
headers.set("UserID", userId);
headers.set("ACIKey", apiKey);
headers.set("ACIKey", aciKeyToken);
String url = aciDlivyApiUrl + "?searchType=1&orderNumber=" + orderVO.getOrdrInnb();
HttpEntity<Void> entity = new HttpEntity<>(headers);
@@ -125,7 +174,7 @@ public class AciDlivyTasklet implements Tasklet {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
log.debug("[AciDlivyBatch] API ?? ?? (ORDR_INNB={}): {}", orderVO.getOrdrInnb(), response.getBody());
log.debug("[AciDlivyBatch] API 응답 원문 (ORDR_INNB={}): {}", orderVO.getOrdrInnb(), response.getBody());
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> body = objectMapper.readValue(response.getBody(), Map.class);
@@ -133,13 +182,13 @@ public class AciDlivyTasklet implements Tasklet {
String status = (String) body.get("Status");
log.debug("[AciDlivyBatch] API Status={} (ORDR_INNB={})", status, orderVO.getOrdrInnb());
// ??/????? ?? ?? (?? Status?? ??? ?? ? ???? ?? ?? ??)
// 출고/발송완료 상태 검증 (정확한 Status값은 실제 응답 확인 후 조정 필요)
if (!"A10".equals(status)) {
log.info("[AciDlivyBatch] API ??? ?? - ORDR_INNB={}, Status={}", orderVO.getOrdrInnb(), status);
log.info("[AciDlivyBatch] API 출고 미완료 - ORDR_INNB={}, Status={}", orderVO.getOrdrInnb(), status);
return null;
}
// Data ?? (?? Map ?? List<Map> ?? ??)
// Data 파싱 (단일 Map 또는 List<Map> 모두 대응)
Object dataObj = body.get("Data");
if (dataObj == null) {
return null;
@@ -154,7 +203,7 @@ public class AciDlivyTasklet implements Tasklet {
return null;
}
// OverseaSellerNo == ORDR_DLVY_INNB ?? ? BLNO ??
// OverseaSellerNo == ORDR_DLVY_INNB 매칭 → BLNO 추출
for (Map<String, Object> item : dataList) {
Object sellerNo = item.get("OverseaSellerNo");
if (sellerNo != null && orderVO.getOrdrDlvyInnb().equals(String.valueOf(sellerNo).trim())) {
@@ -165,37 +214,37 @@ public class AciDlivyTasklet implements Tasklet {
}
}
log.info("[AciDlivyBatch] OverseaSellerNo ?? ?? - ORDR_DLVY_INNB={}", orderVO.getOrdrDlvyInnb());
log.info("[AciDlivyBatch] OverseaSellerNo 매칭 없음 - ORDR_DLVY_INNB={}", orderVO.getOrdrDlvyInnb());
return null;
} catch (Exception e) {
log.error("[AciDlivyBatch] API ?? ?? - ORDR_INNB={}, ??={}", orderVO.getOrdrInnb(), e.getMessage(), e);
log.error("[AciDlivyBatch] API 호출 오류 - ORDR_INNB={}, 오류={}", orderVO.getOrdrInnb(), e.getMessage(), e);
return null;
}
}
/**
* ???? (??: gc_goods_export ? gc_goods_export_item ? gc_order_item_option
* ? gc_order_shipping ? gc_order ? ???? ? ????)
* gc_order_shipping/gc_order ????? ?? ??? ?? ???? ?? ??
* 출고처리 (순서: gc_goods_export → gc_goods_export_item → gc_order_item_option
* → gc_order_shipping → gc_order → 출고로그 → 주문로그)
* gc_order_shipping/gc_order 상태변경은 모든 아이템 처리 완료 후 일괄 수행
*/
private void processDelivery(AciDlivyOrderVO orderVO) {
// ? gc_goods_export ??
// 출고 gc_goods_export 처리
int exportCnt = aciDlivyMapper.selectGoodsExportCount(orderVO);
if (exportCnt > 0) {
aciDlivyMapper.updateGoodsExport(orderVO); // selectKey? dlivyInnb ???
aciDlivyMapper.updateGoodsExport(orderVO);
} else {
aciDlivyMapper.insertGoodsExport(orderVO); // selectKey? dlivyInnb ???
aciDlivyMapper.insertGoodsExport(orderVO);
}
// dlivyCdNb ?? (GC_GOODS_EXPORT ? DLIVY_CD_NB ??? ?? ??)
// 출고코드 생성 (GC_GOODS_EXPORT의 DLIVY_CD_NB 규칙 따름)
String dlivyCdNb = "D" + new SimpleDateFormat("yyMMddHH").format(new Date()) + orderVO.getDlivyInnb();
orderVO.setDlivyCdNb(dlivyCdNb);
// ? ?? ?? ?? ?? ?? (gc_order_item_option)
// 주문 상품 옵션 목록 조회 및 건별 처리 (gc_order_item_option)
List<DataMap> itemList = aciDlivyMapper.selectAciDlivyTargetItemList(orderVO);
log.debug("[AciDlivyBatch] ?? ?? ?? ?={} (ORDR_DLVY_INNB={})", itemList.size(), orderVO.getOrdrDlvyInnb());
log.debug("[AciDlivyBatch] 주문 상품 옵션 수={} (ORDR_DLVY_INNB={})", itemList.size(), orderVO.getOrdrDlvyInnb());
for (DataMap itemMap : itemList) {
Object qyObj = itemMap.get("qy");
@@ -216,7 +265,7 @@ public class AciDlivyTasklet implements Tasklet {
itemMap.put("invcnb", orderVO.getBlno());
itemMap.put("dlivySttusCd", DLIVY_STTUS_CD);
// ? gc_goods_export_item ??
// gc_goods_export_item 처리
int exportItemCnt = aciDlivyMapper.selectGoodsExportItemCount(itemMap);
if (exportItemCnt > 0) {
aciDlivyMapper.updateGoodsExportItem(itemMap);
@@ -224,10 +273,10 @@ public class AciDlivyTasklet implements Tasklet {
aciDlivyMapper.insertGoodsExportItem(itemMap);
}
// ? gc_order_item_option ?? UPDATE
// gc_order_item_option 상태 UPDATE
aciDlivyMapper.updateOrderItemOptionStatus(itemMap);
// ? ?? ?? INSERT (???)
// 출고 로그 INSERT (건별)
DataMap exportLogMap = new DataMap();
exportLogMap.put("ordrInnb", orderVO.getOrdrInnb());
exportLogMap.put("ordrDlvyInnb", orderVO.getOrdrDlvyInnb());
@@ -235,27 +284,27 @@ public class AciDlivyTasklet implements Tasklet {
exportLogMap.put("dlivyInnb", orderVO.getDlivyInnb());
exportLogMap.put("dlivyGoodsCdNb", itemMap.get("ordrGoodsOptnInnb"));
exportLogMap.put("reqstProcsSttusCd", DLIVY_STTUS_CD);
exportLogMap.put("mssageCn", "[ACI ??] ORDR_INNB:" + orderVO.getOrdrInnb() + " ????");
exportLogMap.put("mssageCn", "[ACI 배치] ORDR_INNB:" + orderVO.getOrdrInnb() + " 출고처리");
exportLogMap.put("regusr", 0);
aciDlivyMapper.insertGoodsExportLog(exportLogMap);
}
// ? gc_order_shipping ?? UPDATE (?? item_option ?? ???? ?? ??)
// gc_order_shipping 상태 UPDATE (모든 item_option 처리 완료 후 일괄)
aciDlivyMapper.updateOrderShippingStatus(orderVO);
// ? gc_order ?? UPDATE (?? shipping ?? ???? ?? ??)
// gc_order 상태 UPDATE (모든 shipping 처리 완료 후 일괄)
aciDlivyMapper.updateOrderStatus(orderVO);
// ? ?? ?? INSERT
// 주문 로그 INSERT
DataMap orderLogMap = new DataMap();
orderLogMap.put("ordrInnb", orderVO.getOrdrInnb());
orderLogMap.put("sopmalCd", orderVO.getSopmalCd());
orderLogMap.put("logSj", "????(" + dlivyCdNb + ")");
orderLogMap.put("logDtlCn", "[ACI ??] ?? ???? ??");
orderLogMap.put("logSj", "출고처리(" + dlivyCdNb + ")");
orderLogMap.put("logDtlCn", "[ACI 배치] 배치 출고처리 완료");
orderLogMap.put("dlivyCdNb", dlivyCdNb);
orderLogMap.put("logDvCd", "01");
orderLogMap.put("dvCd", "BATCH");
orderLogMap.put("opetrNm", "ACI ??");
orderLogMap.put("opetrNm", "ACI 배치");
orderLogMap.put("qy", 0);
orderLogMap.put("amt", 0);
aciDlivyMapper.insertOrderLog(orderLogMap);