From dd42483eae1da5dae55a15b84ee4a99c4fcc20be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E5=AE=87=E5=A5=87?= Date: Wed, 16 Apr 2025 16:05:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/xkt/ExpressController.java | 4 +- .../src/main/resources/application.yml | 4 +- .../xkt/dto/express/ExpressShipReqDTO.java | 74 +++++++++++ .../com/ruoyi/xkt/enums/EExpressChannel.java | 2 +- .../com/ruoyi/xkt/manager/ExpressManager.java | 9 ++ .../manager/impl/ZtExpressManagerImpl.java | 34 ----- .../manager/impl/ZtoExpressManagerImpl.java | 120 +++++++++++++++++ .../ruoyi/xkt/service/IStoreOrderService.java | 4 +- .../service/impl/StoreOrderServiceImpl.java | 44 +++++-- .../xkt/thirdpart/zto/EncryptionType.java | 12 ++ .../ruoyi/xkt/thirdpart/zto/ZopClient.java | 61 +++++++++ .../xkt/thirdpart/zto/ZopDigestUtil.java | 60 +++++++++ .../xkt/thirdpart/zto/ZopProperties.java | 30 +++++ .../xkt/thirdpart/zto/ZopPublicRequest.java | 122 ++++++++++++++++++ .../zto/ZtoCreateOrderReqDTO.java} | 4 +- .../ruoyi/xkt/thirdpart/zto/ZtoHttpUtil.java | 103 +++++++++++++++ 16 files changed, 637 insertions(+), 50 deletions(-) create mode 100644 xkt/src/main/java/com/ruoyi/xkt/dto/express/ExpressShipReqDTO.java delete mode 100644 xkt/src/main/java/com/ruoyi/xkt/manager/impl/ZtExpressManagerImpl.java create mode 100644 xkt/src/main/java/com/ruoyi/xkt/manager/impl/ZtoExpressManagerImpl.java create mode 100644 xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/EncryptionType.java create mode 100644 xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopClient.java create mode 100644 xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopDigestUtil.java create mode 100644 xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopProperties.java create mode 100644 xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopPublicRequest.java rename xkt/src/main/java/com/ruoyi/xkt/{dto/express/zt/CreateOrderReqDTO.java => thirdpart/zto/ZtoCreateOrderReqDTO.java} (98%) create mode 100644 xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoHttpUtil.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/ExpressController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/ExpressController.java index 081bd2424..8b683c11c 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/ExpressController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/ExpressController.java @@ -6,7 +6,7 @@ import com.ruoyi.common.core.domain.R; import com.ruoyi.web.controller.xkt.vo.express.ExpressRegionTreeNodeVO; import com.ruoyi.xkt.dto.express.ExpressRegionTreeNodeDTO; import com.ruoyi.xkt.manager.ExpressManager; -import com.ruoyi.xkt.manager.impl.ZtExpressManagerImpl; +import com.ruoyi.xkt.manager.impl.ZtoExpressManagerImpl; import com.ruoyi.xkt.service.IExpressService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -32,7 +32,7 @@ public class ExpressController extends XktBaseController { @Autowired private List expressManagers; @Autowired - private ZtExpressManagerImpl ztExpressManager; + private ZtoExpressManagerImpl ztoExpressManager; @PreAuthorize("@ss.hasPermi('system:express:query')") @ApiOperation("获取行政规划树") diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 13674db96..b8f61328e 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -171,10 +171,12 @@ express: county: 510114 # 中通配置 -zt: +zto: appKey: 95dc4ecf72fce1a2fbe79 appSecret: 694ea7e3ea89e3b6aa21aea1a0285d25 gatewayUrl: https://japi-test.zto.com/ + accountId: test + accountPassword: ZTO123 # 支付宝配置 alipay: diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/express/ExpressShipReqDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/express/ExpressShipReqDTO.java new file mode 100644 index 000000000..5c3c0c0b4 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/express/ExpressShipReqDTO.java @@ -0,0 +1,74 @@ +package com.ruoyi.xkt.dto.express; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author liangyq + * @date 2025-04-16 15:24 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ExpressShipReqDTO { + /** + * 请求号 + */ + private String expressReqNo; + /** + * 发货人-名称 + */ + private String originContactName; + /** + * 发货人-电话 + */ + private String originContactPhoneNumber; + /** + * 发货人-省编码 + */ + private String originProvinceCode; + private String originProvinceName; + /** + * 发货人-市编码 + */ + private String originCityCode; + private String originCityName; + /** + * 发货人-区县编码 + */ + private String originCountyCode; + private String originCountyName; + /** + * 发货人-详细地址 + */ + private String originDetailAddress; + /** + * 收货人-名称 + */ + private String destinationContactName; + /** + * 收货人-电话 + */ + private String destinationContactPhoneNumber; + /** + * 收货人-省编码 + */ + private String destinationProvinceCode; + private String destinationProvinceName; + /** + * 收货人-市编码 + */ + private String destinationCityCode; + private String destinationCityName; + /** + * 收货人-区县编码 + */ + private String destinationCountyCode; + private String destinationCountyName; + /** + * 收货人-详细地址 + */ + private String destinationDetailAddress; + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/enums/EExpressChannel.java b/xkt/src/main/java/com/ruoyi/xkt/enums/EExpressChannel.java index 7da074317..f5c64a89f 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/enums/EExpressChannel.java +++ b/xkt/src/main/java/com/ruoyi/xkt/enums/EExpressChannel.java @@ -11,7 +11,7 @@ import lombok.Getter; @AllArgsConstructor public enum EExpressChannel { - ZT(1, "中通", 1L, "ZT"); + ZTO(1, "中通", 1L, "ZTO"); private final Integer value; private final String label; diff --git a/xkt/src/main/java/com/ruoyi/xkt/manager/ExpressManager.java b/xkt/src/main/java/com/ruoyi/xkt/manager/ExpressManager.java index 192e7d82a..ad8a4fd8a 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/manager/ExpressManager.java +++ b/xkt/src/main/java/com/ruoyi/xkt/manager/ExpressManager.java @@ -1,5 +1,6 @@ package com.ruoyi.xkt.manager; +import com.ruoyi.xkt.dto.express.ExpressShipReqDTO; import com.ruoyi.xkt.enums.EExpressChannel; /** @@ -14,4 +15,12 @@ public interface ExpressManager { */ EExpressChannel channel(); + /** + * 订单发货 + * + * @param shipReqDTO + * @return 运单号 + */ + String shipStoreOrder(ExpressShipReqDTO shipReqDTO); + } diff --git a/xkt/src/main/java/com/ruoyi/xkt/manager/impl/ZtExpressManagerImpl.java b/xkt/src/main/java/com/ruoyi/xkt/manager/impl/ZtExpressManagerImpl.java deleted file mode 100644 index 9247ccae4..000000000 --- a/xkt/src/main/java/com/ruoyi/xkt/manager/impl/ZtExpressManagerImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.ruoyi.xkt.manager.impl; - -import com.ruoyi.xkt.enums.EExpressChannel; -import com.ruoyi.xkt.manager.ExpressManager; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -/** - * @author liangyq - * @date 2025-04-15 15:45 - */ -@Slf4j -@Component -public class ZtExpressManagerImpl implements ExpressManager { - - private static final String CREATE_ORDER_URI = "zto.open.createOrder"; - - private static final String STRUCTURE_ADDRESS_URI = "zto.innovate.structureNamePhoneAddress"; - - @Value("${zt.appKey:}") - private String appKey; - - @Value("${zt.appSecret:}") - private String appSecret; - - @Value("${zt.gatewayUrl:}") - private String gatewayUrl; - - @Override - public EExpressChannel channel() { - return EExpressChannel.ZT; - } -} diff --git a/xkt/src/main/java/com/ruoyi/xkt/manager/impl/ZtoExpressManagerImpl.java b/xkt/src/main/java/com/ruoyi/xkt/manager/impl/ZtoExpressManagerImpl.java new file mode 100644 index 000000000..1228ed2f8 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/manager/impl/ZtoExpressManagerImpl.java @@ -0,0 +1,120 @@ +package com.ruoyi.xkt.manager.impl; + +import cn.hutool.core.lang.Assert; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.xkt.dto.express.ExpressShipReqDTO; +import com.ruoyi.xkt.enums.EExpressChannel; +import com.ruoyi.xkt.manager.ExpressManager; +import com.ruoyi.xkt.thirdpart.zto.EncryptionType; +import com.ruoyi.xkt.thirdpart.zto.ZopClient; +import com.ruoyi.xkt.thirdpart.zto.ZopPublicRequest; +import com.ruoyi.xkt.thirdpart.zto.ZtoCreateOrderReqDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * @author liangyq + * @date 2025-04-15 15:45 + */ +@Slf4j +@Component +public class ZtoExpressManagerImpl implements ExpressManager { + + private static final String CREATE_ORDER_URI = "zto.open.createOrder"; + + private static final String STRUCTURE_ADDRESS_URI = "zto.innovate.structureNamePhoneAddress"; + + @Value("${zto.appKey:}") + private String appKey; + + @Value("${zto.appSecret:}") + private String appSecret; + + @Value("${zto.gatewayUrl:}") + private String gatewayUrl; + + @Value("${zto.accountId:}") + private String accountId; + + @Value("${zto.accountPassword:}") + private String accountPassword; + + @Override + public EExpressChannel channel() { + return EExpressChannel.ZTO; + } + + @Override + public String shipStoreOrder(ExpressShipReqDTO shipReqDTO) { + Assert.notNull(shipReqDTO); + Assert.notEmpty(shipReqDTO.getExpressReqNo()); + ZtoCreateOrderReqDTO createOrderReq = trans2CreateOrderReq(shipReqDTO); + ZopClient client = new ZopClient(appKey, appSecret); + ZopPublicRequest request = new ZopPublicRequest(); + request.setBody(JSONUtil.toJsonStr(createOrderReq)); + request.setUrl(gatewayUrl + CREATE_ORDER_URI); + request.setEncryptionType(EncryptionType.MD5); + try { + String bodyStr = client.execute(request); + log.info("中通订单创建返回信息: {}", bodyStr); + JSONObject bodyJson = JSONUtil.parseObj(bodyStr); + boolean success = bodyJson.getBool("status"); + if (success) { + return bodyJson.getJSONObject("result").getStr("billCode"); + } + + } catch (Exception e) { + log.error("中通订单创建异常", e); + } + throw new ServiceException("中通订单创建失败"); + } + + private ZtoCreateOrderReqDTO trans2CreateOrderReq(ExpressShipReqDTO expressShipReqDTO) { + ZtoCreateOrderReqDTO reqDTO = new ZtoCreateOrderReqDTO(); + //合作模式 ,1:集团客户;2:非集团客户 + reqDTO.setPartnerType("2"); + //partnerType为1时,orderType:1:全网件 2:预约件。 partnerType为2时,orderType:1:全网件 2:预约件(返回运单号) 3:预约件(不返回运单号) 4:星联全网件 + reqDTO.setOrderType("1"); + //合作商订单号 + reqDTO.setPartnerOrderCode(expressShipReqDTO.getExpressReqNo()); + + //账号信息 + ZtoCreateOrderReqDTO.AccountInfo accountInfo = new ZtoCreateOrderReqDTO.AccountInfo(); + reqDTO.setAccountInfo(accountInfo); + //电子面单账号(partnerType为2,orderType传1,2,4时必传) + accountInfo.setAccountId(accountId); + //电子面单密码(测试环境传ZTO123) + accountInfo.setAccountPassword(accountPassword); + //单号类型:1.普通电子面单;74.星联电子面单;默认是1 + accountInfo.setType(1); + //集团客户编码(partnerType传1时必传) + accountInfo.setCustomerId(null); + + //发件人信息 + ZtoCreateOrderReqDTO.SenderInfo senderInfo = new ZtoCreateOrderReqDTO.SenderInfo(); + reqDTO.setSenderInfo(senderInfo); + senderInfo.setSenderName(expressShipReqDTO.getOriginCountyName()); + senderInfo.setSenderPhone(expressShipReqDTO.getOriginContactPhoneNumber()); + senderInfo.setSenderProvince(expressShipReqDTO.getOriginProvinceName()); + senderInfo.setSenderCity(expressShipReqDTO.getOriginCityName()); + senderInfo.setSenderDistrict(expressShipReqDTO.getOriginCountyName()); + senderInfo.setSenderAddress(expressShipReqDTO.getOriginDetailAddress()); + + //收件人信息 + ZtoCreateOrderReqDTO.ReceiveInfo receiveInfo = new ZtoCreateOrderReqDTO.ReceiveInfo(); + reqDTO.setReceiveInfo(receiveInfo); + receiveInfo.setReceiverName(expressShipReqDTO.getDestinationContactName()); + receiveInfo.setReceiverPhone(expressShipReqDTO.getDestinationContactPhoneNumber()); + receiveInfo.setReceiverProvince(expressShipReqDTO.getDestinationProvinceName()); + receiveInfo.setReceiverCity(expressShipReqDTO.getDestinationCityName()); + receiveInfo.setReceiverDistrict(expressShipReqDTO.getDestinationCountyName()); + receiveInfo.setReceiverAddress(expressShipReqDTO.getDestinationDetailAddress()); + + return reqDTO; + } + + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java index ed9b101c1..c4185e7bd 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java @@ -93,8 +93,8 @@ public interface IStoreOrderService { * @param operatorId * @return */ - StoreOrderExt prepareShipOrderByPlatform(Long storeOrderId, List storeOrderDetailIds, Long expressId, - Long operatorId); + StoreOrderExt shipOrderByPlatform(Long storeOrderId, List storeOrderDetailIds, Long expressId, + Long operatorId); /** * 发货(档口物流) diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java index 5549b9d1d..8a31882a2 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreOrderServiceImpl.java @@ -17,6 +17,7 @@ import com.ruoyi.common.utils.bean.BeanValidators; import com.ruoyi.xkt.domain.*; import com.ruoyi.xkt.dto.express.ExpressContactDTO; import com.ruoyi.xkt.dto.express.ExpressRegionDTO; +import com.ruoyi.xkt.dto.express.ExpressShipReqDTO; import com.ruoyi.xkt.dto.order.*; import com.ruoyi.xkt.dto.storeProductFile.StoreProdMainPicDTO; import com.ruoyi.xkt.enums.*; @@ -564,10 +565,10 @@ public class StoreOrderServiceImpl implements IStoreOrderService { @Transactional(rollbackFor = Exception.class) @Override - public StoreOrderExt prepareShipOrderByPlatform(Long storeOrderId, List storeOrderDetailIds, Long expressId, - Long operatorId) { + public StoreOrderExt shipOrderByPlatform(Long storeOrderId, List storeOrderDetailIds, Long expressId, + Long operatorId) { Assert.notEmpty(storeOrderDetailIds); -// ExpressManager expressManager = getExpressManager(expressId); + ExpressManager expressManager = getExpressManager(expressId); Express express = expressService.getById(expressId); if (!BeanValidators.exists(express) || !express.getSystemDeliverAccess()) { throw new ServiceException("快递[" + expressId + "]不可用"); @@ -600,10 +601,8 @@ public class StoreOrderServiceImpl implements IStoreOrderService { if (orderSuccess == 0) { throw new ServiceException(Constants.VERSION_LOCK_ERROR_COMMON_MSG); } - //生成请求号 - String expressReqNo = IdUtil.simpleUUID(); - List orderDetailIdList = new ArrayList<>(orderDetails.size()); for (StoreOrderDetail orderDetail : orderDetails) { + //校验明细状态 if (!BeanValidators.exists(orderDetail)) { throw new ServiceException("订单明细[" + orderDetail.getId() + "]不存在"); } @@ -613,12 +612,20 @@ public class StoreOrderServiceImpl implements IStoreOrderService { if (!EOrderStatus.PENDING_SHIPMENT.getValue().equals(orderDetail.getDetailStatus())) { throw new ServiceException("订单明细[" + order.getId() + "]当前状态无法发货"); } + } + //发货 + ExpressShipReqDTO shipReq = trans2ShipReq(order, orderDetails); + String expressWaybillNo = expressManager.shipStoreOrder(shipReq); + + List orderDetailIdList = new ArrayList<>(orderDetails.size()); + for (StoreOrderDetail orderDetail : orderDetails) { //明细->已发货 orderDetail.setDetailStatus(EOrderStatus.SHIPPED.getValue()); orderDetail.setExpressId(expressId); orderDetail.setExpressType(EExpressType.PLATFORM.getValue()); - orderDetail.setExpressStatus(EExpressStatus.PLACING.getValue()); - orderDetail.setExpressReqNo(expressReqNo); + orderDetail.setExpressStatus(EExpressStatus.PLACED.getValue()); + orderDetail.setExpressReqNo(shipReq.getExpressReqNo()); + orderDetail.setExpressWaybillNo(expressWaybillNo); int orderDetailSuccess = storeOrderDetailMapper.updateById(orderDetail); if (orderDetailSuccess == 0) { throw new ServiceException(Constants.VERSION_LOCK_ERROR_COMMON_MSG); @@ -897,6 +904,27 @@ public class StoreOrderServiceImpl implements IStoreOrderService { throw new ServiceException("未知物流渠道"); } + private ExpressShipReqDTO trans2ShipReq(StoreOrder order, List orderDetails) { + ExpressShipReqDTO reqDTO = BeanUtil.toBean(order, ExpressShipReqDTO.class); + //生成请求号 + reqDTO.setExpressReqNo(IdUtil.simpleUUID()); + //行政区划信息 + Map regionMap = expressService.getRegionMapCache(); + reqDTO.setDestinationProvinceName(Optional.ofNullable(regionMap.get(order.getDestinationProvinceCode())) + .map(ExpressRegionDTO::getParentRegionName).orElse(null)); + reqDTO.setDestinationCityName(Optional.ofNullable(regionMap.get(order.getDestinationCityCode())) + .map(ExpressRegionDTO::getParentRegionName).orElse(null)); + reqDTO.setDestinationCountyName(Optional.ofNullable(regionMap.get(order.getDestinationCountyCode())) + .map(ExpressRegionDTO::getParentRegionName).orElse(null)); + reqDTO.setOriginProvinceName(Optional.ofNullable(regionMap.get(order.getOriginProvinceCode())) + .map(ExpressRegionDTO::getParentRegionName).orElse(null)); + reqDTO.setOriginCityName(Optional.ofNullable(regionMap.get(order.getOriginCityCode())) + .map(ExpressRegionDTO::getParentRegionName).orElse(null)); + reqDTO.setOriginCountyName(Optional.ofNullable(regionMap.get(order.getOriginCountyCode())) + .map(ExpressRegionDTO::getParentRegionName).orElse(null)); + return reqDTO; + } + @Data @AllArgsConstructor @NoArgsConstructor diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/EncryptionType.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/EncryptionType.java new file mode 100644 index 000000000..765d7fcef --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/EncryptionType.java @@ -0,0 +1,12 @@ +package com.ruoyi.xkt.thirdpart.zto; + +/** + * Created By WangWei on 2022/1/18 9:27 + */ + +public enum EncryptionType { + MD5, + SHA256, + HmacSHA256 + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopClient.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopClient.java new file mode 100644 index 000000000..799b20366 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopClient.java @@ -0,0 +1,61 @@ +package com.ruoyi.xkt.thirdpart.zto; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +public class ZopClient { + private final ZopProperties properties; + + public ZopClient(ZopProperties properties) { + this.properties = properties; + } + + public ZopClient(String appKey, String appSecret) { + this.properties = new ZopProperties(appKey, appSecret); + } + + public String execute(ZopPublicRequest request) throws IOException { + String jsonBody = request.getBody(); + if (jsonBody == null) { + Map params = request.getParams(); + StringBuilder queryBuilder = new StringBuilder(); + StringBuilder strToDigestBuilder = new StringBuilder(); + for (Map.Entry e : params.entrySet()) { + strToDigestBuilder.append(e.getKey()).append("=").append(e.getValue()).append("&"); + queryBuilder.append(urlEncode(e.getKey())).append("=").append(urlEncode(e.getValue())).append("&"); + } + String queryString = queryBuilder.substring(0, queryBuilder.length() - 1); + String strToDigest = strToDigestBuilder.substring(0, strToDigestBuilder.length() - 1); + strToDigest = strToDigest + properties.getKey(); + Map headers = new HashMap(); + headers.put("x-companyid", properties.getCompanyId()); + headers.put("x-datadigest", ZopDigestUtil.digest(strToDigest, request.getBase64(), request.getEncryptionType(), request.getTimestamp(), request.getSecretKey())); + if (request.getTimestamp() != null) { + headers.put("x-timestamp", String.valueOf(request.getTimestamp())); + } + return ZtoHttpUtil.post(request.getUrl(), headers, queryString); + } else { + Map headers = new HashMap<>(); + String strToDigest = jsonBody + properties.getKey(); + headers.put("x-companyid", properties.getCompanyId()); + headers.put("x-datadigest", ZopDigestUtil.digest(strToDigest, request.getBase64(), request.getEncryptionType(), request.getTimestamp(), request.getSecretKey())); + if (request.getTimestamp() != null) { + headers.put("x-timestamp", String.valueOf(request.getTimestamp())); + } + return ZtoHttpUtil.postJson(request.getUrl(), headers, jsonBody); + } + } + + + private String urlEncode(String str) { + try { + return URLEncoder.encode(str, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return str; + } + } + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopDigestUtil.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopDigestUtil.java new file mode 100644 index 000000000..a973a555c --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopDigestUtil.java @@ -0,0 +1,60 @@ +package com.ruoyi.xkt.thirdpart.zto; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public class ZopDigestUtil { + + private static final String HMAC_SHA_256 = "HmacSHA256"; + + private static final Map MAC_MAP = new ConcurrentHashMap<>(); + + public static String digest(String str, Boolean isBase64, EncryptionType encryptionType, Long timestamp, String secretKey) { + if (timestamp != null) { + str = timestamp + str; + } + boolean base64 = isBase64 == null || isBase64; + switch (encryptionType) { + case SHA256: + return base64 ? Base64.encodeBase64String(DigestUtils.sha256(str)) : DigestUtils.sha256Hex(str); + case HmacSHA256: + return base64 ? Base64.encodeBase64String(hmacSha256(secretKey, str)) : hmacSha256Str(secretKey, str); + default: + return base64 ? Base64.encodeBase64String(DigestUtils.md5(str)) : DigestUtils.md5Hex(str); + } + } + + public static String hmacSha256Str(String key, String body) { + return Hex.encodeHexString(hmacSha256(key, body)); + } + + public static byte[] hmacSha256(String key, String body) { + if (Objects.isNull(key)) { + key = ""; + } + Mac mac = MAC_MAP.get(key); + if (mac == null) { + try { + mac = Mac.getInstance(HMAC_SHA_256); + SecretKey secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), HMAC_SHA_256); + mac.init(secretKey); + MAC_MAP.put(key, mac); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new RuntimeException(e); + } + } + return mac.doFinal(body.getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopProperties.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopProperties.java new file mode 100644 index 000000000..759c85f43 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopProperties.java @@ -0,0 +1,30 @@ +package com.ruoyi.xkt.thirdpart.zto; + +public class ZopProperties { + private String companyId; + private String key; + + public ZopProperties() { + } + + public ZopProperties(String appKey, String appSecret) { + this.companyId = appKey; + this.key = appSecret; + } + + public String getCompanyId() { + return companyId; + } + + public void setCompanyId(String companyId) { + this.companyId = companyId; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopPublicRequest.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopPublicRequest.java new file mode 100644 index 000000000..7499e916a --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopPublicRequest.java @@ -0,0 +1,122 @@ +package com.ruoyi.xkt.thirdpart.zto; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +public class ZopPublicRequest { + + private String url; + + private Map params = new HashMap(); + + // 如果body有值,表示使用application/json的方式传值 + private String body; + + /** + * 签名是否需要base64 + */ + private Boolean isBase64; + + /** + * 加密方式,MD5或者SHA256 + */ + private EncryptionType encryptionType; + + /** + * HmacSHA256加密key + */ + private String secretKey; + + /** + * 时间戳,如果接口文档未标识使用时间戳请不要传值,否则会导致签名错误 + */ + private Long timestamp; + + public ZopPublicRequest() { + } + + public void setBody(String body){ + this.body = body; + } + + + public String getBody() { + return body; + } + + public void addParam(String k, String v) { + params.put(k, v); + } + + public void setData(String data) { + try { + JSONObject jsonObject = JSON.parseObject(data); + for (Map.Entry entry : jsonObject.entrySet()) { + params.put(entry.getKey(), entry.getValue().toString()); + } + } catch (Exception e) { + throw new RuntimeException("JSON格式不对,请检查数据", e); + } + } + + public void setDataObj(Map data) { + params.putAll(data); + } + + public void addParam(Map p) { + for (Map.Entry entry : p.entrySet()) { + params.put(entry.getKey(), entry.getValue()); + } + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Map getParams() { + return params; + } + + public void setParams(Map params) { + this.params = params; + } + + public Boolean getBase64() { + return isBase64; + } + + public void setBase64(Boolean base64) { + isBase64 = base64; + } + + public EncryptionType getEncryptionType() { + return encryptionType; + } + + public void setEncryptionType(EncryptionType encryptionType) { + this.encryptionType = encryptionType; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public Long getTimestamp() { + return timestamp; + } + + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/express/zt/CreateOrderReqDTO.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoCreateOrderReqDTO.java similarity index 98% rename from xkt/src/main/java/com/ruoyi/xkt/dto/express/zt/CreateOrderReqDTO.java rename to xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoCreateOrderReqDTO.java index 3c6e12fc4..1052bedfc 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/dto/express/zt/CreateOrderReqDTO.java +++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoCreateOrderReqDTO.java @@ -1,4 +1,4 @@ -package com.ruoyi.xkt.dto.express.zt; +package com.ruoyi.xkt.thirdpart.zto; import lombok.Data; @@ -11,7 +11,7 @@ import java.util.List; * @date 2025-04-15 19:20 */ @Data -public class CreateOrderReqDTO { +public class ZtoCreateOrderReqDTO { /** * 合作模式 ,1:集团客户;2:非集团客户 */ diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoHttpUtil.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoHttpUtil.java new file mode 100644 index 000000000..7dfd0fdcf --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoHttpUtil.java @@ -0,0 +1,103 @@ +package com.ruoyi.xkt.thirdpart.zto; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Map; + +public class ZtoHttpUtil { + private static final int DEFAULT_TIMEOUT = 3000; + + + public static String post(String interfaceUrl, Map headers, String queryString) throws IOException { + URL url = new URL(interfaceUrl); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); + con.setDoOutput(true); + con.setConnectTimeout(DEFAULT_TIMEOUT); + con.setReadTimeout(DEFAULT_TIMEOUT); + for (Map.Entry e : headers.entrySet()) { + con.setRequestProperty(e.getKey(), e.getValue()); + } + DataOutputStream out = null; + + BufferedReader in = null; + try { + out = new DataOutputStream(con.getOutputStream()); + out.write(queryString.getBytes(Charset.forName("UTF-8"))); + out.flush(); + in = new BufferedReader( + new InputStreamReader(con.getInputStream(), "UTF-8")); + String inputLine; + StringBuilder content = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + return content.toString(); + } finally { + if (out != null) { + try { + out.close(); + } catch (Exception ignored) { + } + } + if (in != null) { + try { + in.close(); + } catch (Exception ignored) { + } + } + } + + } + + + public static String postJson(String interfaceUrl, Map headers, String json) throws IOException { + URL url = new URL(interfaceUrl); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json; charset=utf-8"); + con.setDoOutput(true); + con.setConnectTimeout(DEFAULT_TIMEOUT); + con.setReadTimeout(DEFAULT_TIMEOUT); + for (Map.Entry e : headers.entrySet()) { + con.setRequestProperty(e.getKey(), e.getValue()); + } + DataOutputStream out = null; + + BufferedReader in = null; + try { + out = new DataOutputStream(con.getOutputStream()); + out.write(json.getBytes(Charset.forName("UTF-8"))); + out.flush(); + in = new BufferedReader( + new InputStreamReader(con.getInputStream(), "UTF-8")); + String inputLine; + StringBuilder content = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + return content.toString(); + } finally { + if (out != null) { + try { + out.close(); + } catch (Exception ignored) { + } + } + if (in != null) { + try { + in.close(); + } catch (Exception ignored) { + } + } + } + + } + +}