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