diff --git a/src/main/java/com/wb/domainserver/acidlivy/AciDlivyTasklet.java b/src/main/java/com/wb/domainserver/acidlivy/AciDlivyTasklet.java index 3c62669..4eebc26 100644 --- a/src/main/java/com/wb/domainserver/acidlivy/AciDlivyTasklet.java +++ b/src/main/java/com/wb/domainserver/acidlivy/AciDlivyTasklet.java @@ -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 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 requestEntity = new HttpEntity<>("{}", headers); + + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.exchange( + aciKeyUrl, + HttpMethod.POST, + requestEntity, + String.class + ); + + ObjectMapper objectMapper = new ObjectMapper(); + Map 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 entity = new HttpEntity<>(headers); @@ -125,7 +174,7 @@ public class AciDlivyTasklet implements Tasklet { RestTemplate restTemplate = new RestTemplate(); ResponseEntity 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 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 ?? ??) + // Data 파싱 (단일 Map 또는 List 모두 대응) 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 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 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,29 +284,29 @@ 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); } -} +}