diff --git a/src/main/java/com/wb/domainserver/acidlivy/jobs/AciDlivyJobConfig.java b/src/main/java/com/wb/domainserver/acidlivy/jobs/AciDlivyJobConfig.java index 9e87b2d..8cc749b 100644 --- a/src/main/java/com/wb/domainserver/acidlivy/jobs/AciDlivyJobConfig.java +++ b/src/main/java/com/wb/domainserver/acidlivy/jobs/AciDlivyJobConfig.java @@ -1,6 +1,7 @@ package com.wb.domainserver.acidlivy.jobs; import com.wb.domainserver.acidlivy.tasklet.AciDlivyTasklet; +import com.wb.domainserver.acidlivy.tasklet.AciPodTasklet; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; @@ -19,6 +20,7 @@ public class AciDlivyJobConfig { private final JobRepository jobRepository; private final PlatformTransactionManager transactionManager; private final AciDlivyTasklet aciDlivyTasklet; + private final AciPodTasklet aciPodTasklet; @Bean public Job aciDlivyJob() { @@ -33,4 +35,18 @@ public class AciDlivyJobConfig { .tasklet(aciDlivyTasklet, transactionManager) .build(); } + + @Bean + public Job aciPodJob() { + return new JobBuilder("aciPodJob", jobRepository) + .start(aciDlivyStep()) + .build(); + } + + @Bean + public Step aciPodStep() { + return new StepBuilder("aciPodStep", jobRepository) + .tasklet(aciPodTasklet, transactionManager) + .build(); + } } diff --git a/src/main/java/com/wb/domainserver/acidlivy/tasklet/AciPodTasklet.java b/src/main/java/com/wb/domainserver/acidlivy/tasklet/AciPodTasklet.java new file mode 100644 index 0000000..e01840d --- /dev/null +++ b/src/main/java/com/wb/domainserver/acidlivy/tasklet/AciPodTasklet.java @@ -0,0 +1,301 @@ +package com.wb.domainserver.acidlivy.tasklet; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.wb.domainserver.acidlivy.vo.AciDlivyOrderVO; +import com.wb.domainserver.mapper.gcone.AciDlivyMapper; +import com.wb.domainserver.response.WnbtWebClientResponseHandler; +import com.wb.domainserver.util.communicate.RESRC; +import com.wb.domainserver.util.communicate.SendToResrc; +import com.wb.domainserver.util.dataaccess.DataMap; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.CollectionUtils; +import org.springframework.web.client.RestTemplate; + +import java.text.SimpleDateFormat; +import java.util.*; + +// ACI 배송자료 조회 및 배송처리 배치 Tasklet +// 흐름: gc_order_shipping(DLIVY_STTUS_CD=55) 조회 → 토큰 발급 → ACI API 호출 → BLNO 추출 → 배송처리 +@Slf4j +@Component +@RequiredArgsConstructor +public class AciPodTasklet implements Tasklet { + + private final AciDlivyMapper AciDlivyMapper; + private final PlatformTransactionManager transactionManager; + + @Autowired + WnbtWebClientResponseHandler responseHandler; + + @Autowired + private SendToResrc sendToResrc; + + @Value("${acikey.url}") + private String aciKeyUrl; + + @Value("${aci.pod.api-url}") + private String aciPodApiUrl; + + @Value("${acikey.userid}") + private String userId; + + @Value("${acikey.apikey}") + private String apiKey; + + private static final String DLIVY_STTUS_CD = "55"; // 출고완료 코드 + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + log.info("[AciPodBatch] 배치 시작"); + + // 토큰 발급 (yaml 고정 apikey → ACIKey 토큰 발급, 메모리에만 유지) + String aciKeyToken = issueAciKeyToken(); + + // 대상 주문 목록 조회 (DLIVY_STTUS_CD = '55') + List goodsExportList = AciDlivyMapper.selectGoodsExportList(DLIVY_STTUS_CD); + log.info("[AciPodBatch] 대상 출고 건수: {}", goodsExportList.size()); + + if (goodsExportList.isEmpty()) { + log.info("[AciPodBatch] 처리할 대상 출고건이 없습니다."); + return RepeatStatus.FINISHED; + } + + int successCnt = 0; + int failCnt = 0; + int skipCnt = 0; + + // 건별로 독립 트랜잭션 (REQUIRES_NEW) 으로 처리 - 한 건 실패가 다른 건에 영향 없도록 + TransactionTemplate txTemplate = new TransactionTemplate(transactionManager); + txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + + for (AciDlivyOrderVO dlivyOrderVO : goodsExportList) { + try { + // 건별 ACI 출고자료 API 호출 + List podInfoList = callAciPodApi(dlivyOrderVO, aciKeyToken); + if (podInfoList.isEmpty()) { + skipCnt++; + } else { + boolean dlvyInPrg = podInfoList.stream() + .anyMatch(podInfo -> !isBlank(podInfo, "dateLocalwhin")); + boolean dlvyCompt = podInfoList.stream() + .allMatch(podInfo -> !isBlank(podInfo, "dateDelivery")); + + // 건별 배송처리 + DataMap dataMap = new DataMap(); + dataMap.put("dlivyInnb", dlivyOrderVO.getDlivyInnb()); + dataMap.put("dlivySttusCd", dlivyOrderVO.getDlivySttusCd()); + dataMap.put("sopmalCd", dlivyOrderVO.getSopmalCd()); + if(dlvyInPrg) { + dataMap.put("dlivySttusCd", "65"); + processDeliveryPod(dataMap); + } + if(dlvyCompt) { + dataMap.put("dlivySttusCd", "75"); + processDeliveryPod(dataMap); + } + successCnt++; + } + } catch (Exception e) { + log.error("[AciPodBatch] 배송처리 실패 - DLIVY_INNB={}, 오류={}", + dlivyOrderVO.getDlivyInnb(), e.getMessage(), e); + failCnt++; + } + + // 건별 API 호출 간격 유지 (과부하 방지 - 1초 대기) + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + + log.info("[AciPodBatch] 배치 완료 - 성공: {}, 실패: {}, 스킵: {}", successCnt, failCnt, skipCnt); + return RepeatStatus.FINISHED; + } + + /** + * ACIKey 토큰 발급 + * yaml 고정 apikey를 APIKEY Header로 사용하여 POST 호출 → 발급받은 토큰은 메모리에만 유지 + */ + 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("[AciPodBatch] 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 호출 + * @param aciKeyToken 발급받은 ACIKey 토큰 + * @return + */ + private List callAciPodApi(AciDlivyOrderVO orderVO, String aciKeyToken) { + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("UserID", userId); + headers.set("ACIKey", aciKeyToken); + + Map body = new HashMap<>(); + body.put("BLNO", orderVO.getInvcnb()); + + HttpEntity> apiRequest = + new HttpEntity<>(body, headers); + + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.exchange( + aciPodApiUrl, + HttpMethod.POST, + apiRequest, + String.class + ); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(response.getBody()); + + log.debug("[AciPodBatch] API 응답 Status: {}", jsonNode.get("Status").asText()); + JsonNode podInfoNode = jsonNode.get("PODInfo"); + if(!podInfoNode.isNull()) { + List podInfoList = new ArrayList<>(); + for(JsonNode podInfoItem : podInfoNode) { + DataMap PODInfo = new DataMap(); + PODInfo.put("blno", podInfoItem.get("BLNO").asText()); + PODInfo.put("dateLocalwhin", podInfoItem.get("DATE_LOCALWHIN").asText()); + PODInfo.put("dateLocaldeparture", podInfoItem.get("DATE_LOCALDEPARTURE").asText()); + PODInfo.put("dateIcnarrival", podInfoItem.get("DATE_ICNARRIVAL").asText()); + PODInfo.put("dateCustomes", podInfoItem.get("DATE_CUSTOMES").asText()); + PODInfo.put("dateDelivery", podInfoItem.get("DATE_DELIVERY").asText()); + PODInfo.put("recipient", podInfoItem.get("RECIPIENT").asText()); + PODInfo.put("wta", podInfoItem.get("WTA").asText()); + PODInfo.put("wtdan", podInfoItem.get("WTDAN").asText()); + + List volumeList = new ArrayList<>(); + for(JsonNode volumeItem : podInfoItem.get("VOLUME")) { + DataMap volume = new DataMap(); + volume.put("width", volumeItem.get("WIDTH").asText()); + volume.put("length", volumeItem.get("LENGTH").asText()); + volume.put("height", volumeItem.get("HEIGHT").asText()); + + volumeList.add(volume); + } + PODInfo.put("volume", volumeList); + + List customsList = new ArrayList<>(); + for(JsonNode customsItem : podInfoItem.get("CUSTOMS")) { + List customDetail = new ArrayList<>(); + for(JsonNode customItem : customsItem) { + DataMap Custom = new DataMap(); + Custom.put("dateEvent", customItem.get("DATE_EVENT").asText()); + Custom.put("traceStatus", customItem.get("TRACE_STATUS").asText()); + + customDetail.add(Custom); + } + customsList.add(customDetail); + } + PODInfo.put("customs", customsList); + + List localPodList = new ArrayList<>(); + for(JsonNode localPodItem : podInfoItem.get("LOCAL_POD")) { + DataMap localPod = new DataMap(); + localPod.put("traceStatus", localPodItem.get("TRACE_STATUS").asText()); + localPod.put("dateEvent", localPodItem.get("DATE_EVENT").asText()); + localPod.put("position", localPodItem.get("POSITION").asText()); + + localPodList.add(localPod); + } + PODInfo.put("localPod", localPodList); + + podInfoList.add(PODInfo); + } + log.debug("[AciPodBatch] API 응답 PODInfo: {}", podInfoList); + return podInfoList; + } else { + log.debug("[AciPodBatch] API 응답 ErrMsg: {}", jsonNode.get("ErrMsg").asText()); + return null; + } + } catch (Exception e) { + log.error("[AciPodBatch] API 호출 오류 - DLIVY_INNB={}, 오류={}", orderVO.getDlivyInnb(), e.getMessage(), e); + return null; + } + } + + /** + * 배송처리 + */ + private void processDeliveryPod(DataMap dataMap) { + dataMap.put("opetrNm", "시스템"); + dataMap.put("regusr", 0); + dataMap.put("updusr", 0); + + Map result = responseHandler.handleResponseAsMap( + sendToResrc.ofPost(RESRC.GCONEBO, "/mngr/ordr/dlivy/updateDlivySttusGetbackBatchUpdt", dataMap) + ); + log.debug("result = {}", result); + + // 메시지전송 ================================================================== + /*List smsList = mapper.selectDlivySmsListInqire(tempVO); + log.debug("메시지 발송 파라미터 확인 : {}", smsList); + if (!CollectionUtils.isEmpty(smsList)) { + System.out.println("smsList = " + smsList); + + try { + DataMap result = mssageService.insertBOAtmcsndng(smsList); + } catch (Exception e) { + log.debug("메시지 발송 실패"); + } + }*/ + } + + /** + * 오브젝트 값 공백 확인 + */ + private Boolean isBlank(DataMap podInfo, String key) { + return podInfo.get(key) == null + || podInfo.get(key).toString().trim().isEmpty(); + } +} diff --git a/src/main/java/com/wb/domainserver/acidlivy/vo/AciDlivyOrderVO.java b/src/main/java/com/wb/domainserver/acidlivy/vo/AciDlivyOrderVO.java index 9f94024..192c7db 100644 --- a/src/main/java/com/wb/domainserver/acidlivy/vo/AciDlivyOrderVO.java +++ b/src/main/java/com/wb/domainserver/acidlivy/vo/AciDlivyOrderVO.java @@ -14,6 +14,7 @@ public class AciDlivyOrderVO { private String ordrDlvyInnb; // 주문배송고유번호 (ACI OverseaSellerNo 매칭기준) private String ordrInnb; // 주문번호 (API orderNumber 파라미터) private String sopmalCd; // 쇼핑몰코드 + private String invcnb; // 운송장번호 // ACI API 조회 및 처리 private String blno; // ACI 운송장번호 (BLNO) diff --git a/src/main/java/com/wb/domainserver/mapper/gcone/AciDlivyMapper.java b/src/main/java/com/wb/domainserver/mapper/gcone/AciDlivyMapper.java index 84cca2e..15829d2 100644 --- a/src/main/java/com/wb/domainserver/mapper/gcone/AciDlivyMapper.java +++ b/src/main/java/com/wb/domainserver/mapper/gcone/AciDlivyMapper.java @@ -52,4 +52,7 @@ public interface AciDlivyMapper { /** GC_ORDER_LOG INSERT */ int insertOrderLog(DataMap logMap); + + /** 배송처리 대상 주문 목록 조회 (gc_goods_export.DLIVY_STTUS_CD = '55') */ + List selectGoodsExportList(String dlivySttusCd); } diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index dc29a54..2f11c1e 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -46,3 +46,5 @@ acikey: aci: dlivy: api-url: https://acieshop.com/api/kr/delivery/v1.5/detail/index.php + pod: + api-url: https://acieshop.com/api/kr/pod/v1.5/ diff --git a/src/main/resources/mybatis/xml/mapper/gcone/AciDlivyMapper.xml b/src/main/resources/mybatis/xml/mapper/gcone/AciDlivyMapper.xml index 67461a8..0a93787 100644 --- a/src/main/resources/mybatis/xml/mapper/gcone/AciDlivyMapper.xml +++ b/src/main/resources/mybatis/xml/mapper/gcone/AciDlivyMapper.xml @@ -353,4 +353,41 @@ ]]> + + + + + + + + + + + +