diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/ExpressCallbackController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/ExpressCallbackController.java
index 6fba0dae25..c9d010ecc1 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/ExpressCallbackController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/ExpressCallbackController.java
@@ -1,17 +1,30 @@
package com.ruoyi.web.controller.xkt;
+import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.XmlUtil;
+import cn.hutool.json.JSONUtil;
import com.ruoyi.common.core.controller.XktBaseController;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.web.controller.xkt.vo.express.ZtoPrintOrderReqVO;
import com.ruoyi.web.controller.xkt.vo.express.ZtoPrintOrderRespVO;
+import com.ruoyi.xkt.dto.order.StoreOrderExpressTrackAddDTO;
+import com.ruoyi.xkt.enums.EExpressChannel;
+import com.ruoyi.xkt.enums.EExpressStatus;
+import com.ruoyi.xkt.service.IStoreOrderService;
+import com.ruoyi.xkt.thirdpart.yto.YtoSignUtil;
+import com.ruoyi.xkt.thirdpart.yto.YtoTrackObj;
+import com.ruoyi.xkt.thirdpart.zto.ZopDigestUtil;
+import com.ruoyi.xkt.thirdpart.zto.ZtoTrackObj;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import org.w3c.dom.Document;
import java.util.concurrent.TimeUnit;
@@ -24,8 +37,15 @@ import java.util.concurrent.TimeUnit;
@RequestMapping("/rest/v1/express-callback")
public class ExpressCallbackController extends XktBaseController {
+ @Value("${yto.appSecret2:}")
+ private String ytoAppSecret2;
+ @Value("${zto.appSecret:}")
+ private String ztoAppSecret;
+
@Autowired
private RedisCache redisCache;
+ @Autowired
+ private IStoreOrderService storeOrderService;
/**
* 中通-生成面单图片/PDF回推
@@ -43,11 +63,107 @@ public class ExpressCallbackController extends XktBaseController {
if (StrUtil.isNotEmpty(vo.getBillCode())
&& StrUtil.isNotEmpty(vo.getResult())) {
//缓存30分钟
- redisCache.setCacheObject("ZTO-"+vo.getBillCode(),vo.getResult(),30, TimeUnit.MINUTES);
+ redisCache.setCacheObject("ZTO-" + vo.getBillCode(), vo.getResult(), 30, TimeUnit.MINUTES);
return ZtoPrintOrderRespVO.success();
}
return ZtoPrintOrderRespVO.failure();
}
+ @ApiOperation("圆通-轨迹推送")
+ @RequestMapping(value = "yto/track")
+ public String ytoTrack(YtoTrackObj.Request request) {
+ if (StrUtil.isNotBlank(request.getLogistics_interface()) &&
+ //验签
+ YtoSignUtil.verify(request.getData_digest(), request.getLogistics_interface(), ytoAppSecret2)) {
+ logger.info("圆通-轨迹推送数据处理: {}", request);
+ Document dom = XmlUtil.parseXml(request.getLogistics_interface());
+ YtoTrackObj.Info obj = XmlUtil.xmlToBean(dom, YtoTrackObj.class).getUpdateInfo();
+ StoreOrderExpressTrackAddDTO trackAddDTO = trans2OrderTrack(obj);
+ storeOrderService.addTrack(trackAddDTO);
+ return YtoTrackObj.Response.builder()
+ .success(true)
+ .logisticProviderID(obj.getLogisticProviderID())
+ .txLogisticID(obj.getTxLogisticID())
+ .build()
+ .toXmlStr();
+ }
+ logger.warn("圆通-轨迹推送数据异常: {}", request);
+ return YtoTrackObj.Response.builder()
+ .success(false)
+ .build()
+ .toXmlStr();
+ }
+
+ @ApiOperation("中通-轨迹推送")
+ @PostMapping(value = "zto/track")
+ public String ztoTrack(@RequestBody ZtoTrackObj.Request request) {
+ if (StrUtil.isNotBlank(request.getData()) &&
+ //验签
+ ZopDigestUtil.verify(request.getData_digest(), request.getData(), ztoAppSecret)) {
+ logger.info("中通-轨迹推送数据处理: {}", request);
+ ZtoTrackObj.Info obj = JSONUtil.toBean(request.getData(), ZtoTrackObj.Info.class);
+ StoreOrderExpressTrackAddDTO trackAddDTO = trans2OrderTrack(obj);
+ storeOrderService.addTrack(trackAddDTO);
+ return ZtoTrackObj.Response.builder()
+ .status(true)
+ .build()
+ .toJsonStr();
+ }
+ logger.warn("中通-轨迹推送数据异常: {}", request);
+ return ZtoTrackObj.Response.builder()
+ .status(false)
+ .build()
+ .toJsonStr();
+ }
+
+ private StoreOrderExpressTrackAddDTO trans2OrderTrack(ZtoTrackObj.Info ztTrack) {
+ StoreOrderExpressTrackAddDTO dto = new StoreOrderExpressTrackAddDTO();
+ dto.setExpressWaybillNo(ztTrack.getBillCode());
+ dto.setAction(ztTrack.getAction());
+ dto.setDescription(ztTrack.getActionTime() + " " + ztTrack.getRemark());
+ dto.setExpressId(EExpressChannel.ZTO.getExpressId());
+ /**
+ * 事件类型
+ *
+ * GOT 收件 网点揽收
+ * DEPARTURE 发件 从网点或分拨中心发出
+ * ARRIVAL 到件 到达网点或分拨中心
+ * DISPATCH 派件 业务员派送
+ * RETURN_SCAN 退件 发生退回或改地址,具体类型见退改类型(returnType)字段
+ * RETURN_SIGNED 退件签收 已经退回至寄件客户
+ * INBOUND 入站 放入快递超市/自提柜/第三方代理点等
+ * HANDOVERSCAN_SIGNED 第三方妥投 入站后妥投成功
+ * DEPARTURE_SIGNED 出站签收 客户从快递超市/自提柜/第三方代理点等取出
+ * SIGNED 签收 客户正常签收
+ * PROBLEM 问题件 网点或中心登记的问题件,问题件类型在问题件编码(problemCode)字段体现
+ */
+ switch (ztTrack.getAction()) {
+ case "GOT":
+ dto.setExpressStatus(EExpressStatus.PICKED_UP);
+ break;
+ case "SIGNED":
+ dto.setExpressStatus(EExpressStatus.COMPLETED);
+ break;
+ }
+ return dto;
+ }
+
+ private StoreOrderExpressTrackAddDTO trans2OrderTrack(YtoTrackObj.Info ytTrack) {
+ StoreOrderExpressTrackAddDTO dto = new StoreOrderExpressTrackAddDTO();
+ dto.setExpressWaybillNo(ytTrack.getMailNo());
+ dto.setAction(ytTrack.getInfoContent());
+ dto.setDescription(DateUtil.formatDateTime(ytTrack.getAcceptTime()) + " " + ytTrack.getRemark());
+ dto.setExpressId(EExpressChannel.YTO.getExpressId());
+ switch (ytTrack.getInfoContent()) {
+ case "GOT":
+ dto.setExpressStatus(EExpressStatus.PICKED_UP);
+ break;
+ case "SIGNED":
+ dto.setExpressStatus(EExpressStatus.COMPLETED);
+ break;
+ }
+ return dto;
+ }
+
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java
index e98c1dbb37..373d9e44f6 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java
@@ -26,8 +26,8 @@ import com.ruoyi.xkt.service.IStoreOrderService;
import io.jsonwebtoken.lang.Assert;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ResponseHeader;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -53,7 +53,6 @@ public class StoreOrderController extends XktBaseController {
@Autowired
private List paymentManagers;
- @PreAuthorize("@ss.hasPermi('system:order:add')")
@Log(title = "订单", businessType = BusinessType.INSERT)
@ApiOperation("创建订单")
@PostMapping("create")
@@ -69,7 +68,6 @@ public class StoreOrderController extends XktBaseController {
return success(respVO);
}
- @PreAuthorize("@ss.hasPermi('system:order:edit')")
@Log(title = "订单", businessType = BusinessType.UPDATE)
@ApiOperation("修改订单")
@PostMapping("edit")
@@ -81,7 +79,6 @@ public class StoreOrderController extends XktBaseController {
return success(result.getOrder().getId());
}
- @PreAuthorize("@ss.hasPermi('system:order:add')")
@Log(title = "订单", businessType = BusinessType.OTHER)
@ApiOperation("支付订单")
@PostMapping("pay")
@@ -96,7 +93,6 @@ public class StoreOrderController extends XktBaseController {
return success(respVO);
}
- @PreAuthorize("@ss.hasPermi('system:order:edit')")
@Log(title = "订单", businessType = BusinessType.UPDATE)
@ApiOperation("取消订单")
@PostMapping("cancel")
@@ -110,7 +106,6 @@ public class StoreOrderController extends XktBaseController {
return success();
}
- @PreAuthorize("@ss.hasPermi('system:order:query')")
@ApiOperation(value = "订单详情")
@GetMapping(value = "/{id}")
public R getInfo(@PathVariable("id") Long id) {
@@ -120,9 +115,9 @@ public class StoreOrderController extends XktBaseController {
}
- @PreAuthorize("@ss.hasPermi('system:order:list')")
@ApiOperation(value = "订单分页查询")
@PostMapping("/page")
+ @ResponseHeader
public R> page(@Validated @RequestBody StoreOrderQueryVO vo) {
StoreOrderQueryDTO queryDTO = BeanUtil.toBean(vo, StoreOrderQueryDTO.class);
if (1 == vo.getSrcPage()) {
@@ -134,7 +129,6 @@ public class StoreOrderController extends XktBaseController {
return success(PageVO.of(pageDTO, StoreOrderPageItemVO.class));
}
- @PreAuthorize("@ss.hasPermi('system:order:add')")
@Log(title = "订单", businessType = BusinessType.OTHER)
@ApiOperation("发货-平台物流")
@PostMapping("ship-platform")
@@ -151,7 +145,6 @@ public class StoreOrderController extends XktBaseController {
return success(respList);
}
- @PreAuthorize("@ss.hasPermi('system:order:add')")
@Log(title = "订单", businessType = BusinessType.OTHER)
@ApiOperation("发货-档口物流")
@PostMapping("ship-store")
@@ -168,7 +161,6 @@ public class StoreOrderController extends XktBaseController {
return success(respList);
}
- @PreAuthorize("@ss.hasPermi('system:order:add')")
@Log(title = "订单", businessType = BusinessType.OTHER)
@ApiOperation("打印面单")
@PostMapping("print")
@@ -184,7 +176,6 @@ public class StoreOrderController extends XktBaseController {
return success(rtnList);
}
- @PreAuthorize("@ss.hasPermi('system:order:add')")
@Log(title = "订单", businessType = BusinessType.OTHER)
@ApiOperation("确认收货")
@PostMapping("receipt")
@@ -194,7 +185,6 @@ public class StoreOrderController extends XktBaseController {
return success();
}
- @PreAuthorize("@ss.hasPermi('system:order:add')")
@Log(title = "订单", businessType = BusinessType.OTHER)
@ApiOperation("申请售后(创建售后订单)")
@PostMapping("refund/apply")
@@ -230,7 +220,6 @@ public class StoreOrderController extends XktBaseController {
return success(afterSaleApplyResult.getStoreOrderId());
}
- @PreAuthorize("@ss.hasPermi('system:order:add')")
@Log(title = "订单", businessType = BusinessType.OTHER)
@ApiOperation("确认退款")
@PostMapping("refund/confirm")
@@ -250,7 +239,6 @@ public class StoreOrderController extends XktBaseController {
return success();
}
- @PreAuthorize("@ss.hasPermi('system:order:add')")
@Log(title = "订单", businessType = BusinessType.OTHER)
@ApiOperation("拒绝退款")
@PostMapping("refund/reject")
diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/express/ExpressTrackDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/express/ExpressTrackDTO.java
new file mode 100644
index 0000000000..9240549b92
--- /dev/null
+++ b/xkt/src/main/java/com/ruoyi/xkt/dto/express/ExpressTrackDTO.java
@@ -0,0 +1,48 @@
+package com.ruoyi.xkt.dto.express;
+
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 代发订单物流轨迹
+ *
+ * @author liangyq
+ * @date 2025-05-07 11:57:52.599
+ **/
+@Data
+public class ExpressTrackDTO {
+ /**
+ * 物流运单号(快递单号)
+ */
+ private String expressWaybillNo;
+ /**
+ * 物流ID
+ */
+ private Long expressId;
+ /**
+ * 详情
+ */
+ private List- items;
+
+ @Data
+ public static class Item {
+ /**
+ * 节点事件
+ */
+ private String action;
+ /**
+ * 描述
+ */
+ private String description;
+ /**
+ * 备注
+ */
+ private String remark;
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+ }
+}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/express/ExpressTrackSubReqDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/express/ExpressTrackSubReqDTO.java
new file mode 100644
index 0000000000..faa7bd592d
--- /dev/null
+++ b/xkt/src/main/java/com/ruoyi/xkt/dto/express/ExpressTrackSubReqDTO.java
@@ -0,0 +1,34 @@
+package com.ruoyi.xkt.dto.express;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author liangyq
+ * @date 2025-04-16 15:24
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ExpressTrackSubReqDTO {
+ /**
+ * 请求号
+ */
+ private String expressReqNo;
+ /**
+ * 物流运单号(快递单号)
+ */
+ private String expressWaybillNo;
+ /**
+ * 发货人-电话
+ */
+ private String originContactPhoneNumber;
+ /**
+ * 收货人-电话
+ */
+ private String destinationContactPhoneNumber;
+}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderExpressTrackAddDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderExpressTrackAddDTO.java
new file mode 100644
index 0000000000..0500cd6b64
--- /dev/null
+++ b/xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderExpressTrackAddDTO.java
@@ -0,0 +1,40 @@
+package com.ruoyi.xkt.dto.order;
+
+import com.ruoyi.xkt.enums.EExpressStatus;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 代发订单物流轨迹
+ *
+ * @author liangyq
+ * @date 2025-04-01 11:57:52.599
+ **/
+@Data
+public class StoreOrderExpressTrackAddDTO {
+ /**
+ * 物流运单号(快递单号)
+ */
+ private String expressWaybillNo;
+ /**
+ * 节点事件
+ */
+ private String action;
+ /**
+ * 描述
+ */
+ private String description;
+ /**
+ * 备注
+ */
+ private String remark;
+ /**
+ * 物流ID
+ */
+ private Long expressId;
+ /**
+ * 物流状态
+ */
+ private EExpressStatus expressStatus;
+}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderInfoDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderInfoDTO.java
index a5a78df96b..3579ade3a6 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderInfoDTO.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/dto/order/StoreOrderInfoDTO.java
@@ -1,5 +1,6 @@
package com.ruoyi.xkt.dto.order;
+import com.ruoyi.xkt.dto.express.ExpressTrackDTO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@@ -34,4 +35,6 @@ public class StoreOrderInfoDTO extends StoreOrderDTO {
private String destinationCountyName;
private List orderDetails;
+
+ private List expressTracks;
}
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 0aee1798b0..af30a83d51 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/manager/ExpressManager.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/manager/ExpressManager.java
@@ -1,9 +1,6 @@
package com.ruoyi.xkt.manager;
-import com.ruoyi.xkt.dto.express.ExpressCancelReqDTO;
-import com.ruoyi.xkt.dto.express.ExpressInterceptReqDTO;
-import com.ruoyi.xkt.dto.express.ExpressPrintDTO;
-import com.ruoyi.xkt.dto.express.ExpressShipReqDTO;
+import com.ruoyi.xkt.dto.express.*;
import com.ruoyi.xkt.enums.EExpressChannel;
import java.util.Collection;
@@ -52,4 +49,11 @@ public interface ExpressManager {
*/
List printOrder(Collection waybillNos);
+ /**
+ * 订阅轨迹
+ *
+ * @param trackSubReq
+ * @return
+ */
+ boolean subscribeTrack(ExpressTrackSubReqDTO trackSubReq);
}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/manager/impl/YtoExpressManagerImpl.java b/xkt/src/main/java/com/ruoyi/xkt/manager/impl/YtoExpressManagerImpl.java
index 6d9eae6bbb..6a023ba8c1 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/manager/impl/YtoExpressManagerImpl.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/manager/impl/YtoExpressManagerImpl.java
@@ -1,15 +1,13 @@
package com.ruoyi.xkt.manager.impl;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ruoyi.common.exception.ServiceException;
-import com.ruoyi.xkt.dto.express.ExpressCancelReqDTO;
-import com.ruoyi.xkt.dto.express.ExpressInterceptReqDTO;
-import com.ruoyi.xkt.dto.express.ExpressPrintDTO;
-import com.ruoyi.xkt.dto.express.ExpressShipReqDTO;
+import com.ruoyi.xkt.dto.express.*;
import com.ruoyi.xkt.enums.EExpressChannel;
import com.ruoyi.xkt.manager.ExpressManager;
import com.ruoyi.xkt.thirdpart.yto.*;
@@ -166,6 +164,39 @@ public class YtoExpressManagerImpl implements ExpressManager {
return list;
}
+ @Override
+ public boolean subscribeTrack(ExpressTrackSubReqDTO trackSubReq) {
+ Assert.notNull(trackSubReq.getExpressWaybillNo());
+ YtoSubTrackParam.LogisticsInterface logisticsInterface = new YtoSubTrackParam.LogisticsInterface();
+ logisticsInterface.setClientId(appKey2);
+ logisticsInterface.setWaybillNo(trackSubReq.getExpressWaybillNo());
+ YtoSubTrackParam ytoSubTrackParam = new YtoSubTrackParam();
+ ytoSubTrackParam.setClient_id(appKey2);
+ ytoSubTrackParam.setMsg_type("online");
+ ytoSubTrackParam.setLogistics_interface(JSONUtil.toJsonStr(logisticsInterface));
+ try {
+ String param = JSONUtil.toJsonStr(ytoSubTrackParam);
+ String sign = YtoSignUtil.sign("subscribe_adapter", "v1", param, appSecret2);
+ YtoPublicRequest request = YtoPublicRequest.builder()
+ .timestamp(System.currentTimeMillis())
+ .param(param)
+ .format(YtoPublicRequest.EFormat.JSON)
+ .sign(sign).build();
+ String rtnStr = HttpUtil.post(gatewayUrl + "open/subscribe_adapter/v1/N364gM/" + appKey2,
+ JSONUtil.toJsonStr(request));
+ log.info("圆通轨迹订阅返回信息: {}", rtnStr);
+ JSONObject rtnJson = JSONUtil.parseObj(rtnStr);
+ Boolean success = rtnJson.getBool("success");
+ if (BooleanUtil.isTrue(success)) {
+ return true;
+ }
+ } catch (Exception e) {
+ log.error("圆通轨迹订阅异常", e);
+ }
+ log.warn("圆通轨迹订阅失败: {}", trackSubReq);
+ return false;
+ }
+
private YtoCreateOrderParam trans2CreateOrderReq(ExpressShipReqDTO shipReqDTO) {
YtoCreateOrderParam reqDTO = new YtoCreateOrderParam();
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
index c4a6a2b225..e6d37893dd 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/manager/impl/ZtoExpressManagerImpl.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/manager/impl/ZtoExpressManagerImpl.java
@@ -2,16 +2,14 @@ package com.ruoyi.xkt.manager.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
-import com.ruoyi.xkt.dto.express.ExpressCancelReqDTO;
-import com.ruoyi.xkt.dto.express.ExpressInterceptReqDTO;
-import com.ruoyi.xkt.dto.express.ExpressPrintDTO;
-import com.ruoyi.xkt.dto.express.ExpressShipReqDTO;
+import com.ruoyi.xkt.dto.express.*;
import com.ruoyi.xkt.enums.EExpressChannel;
import com.ruoyi.xkt.manager.ExpressManager;
import com.ruoyi.xkt.thirdpart.zto.*;
@@ -44,6 +42,8 @@ public class ZtoExpressManagerImpl implements ExpressManager, InitializingBean {
public static final String ORDER_PRINT_URI = "zto.open.order.print";
+ public static final String TRACK_SUB_URI = "zto.merchant.waybill.track.subsrcibe";
+
@Value("${zto.appKey:}")
private String appKey;
@@ -82,7 +82,7 @@ public class ZtoExpressManagerImpl implements ExpressManager, InitializingBean {
String bodyStr = client.execute(request);
log.info("中通订单创建返回信息: {}", bodyStr);
JSONObject bodyJson = JSONUtil.parseObj(bodyStr);
- boolean success = bodyJson.getBool("status");
+ boolean success = BooleanUtil.isTrue(bodyJson.getBool("status"));
if (success) {
return bodyJson.getJSONObject("result").getStr("billCode");
}
@@ -108,7 +108,7 @@ public class ZtoExpressManagerImpl implements ExpressManager, InitializingBean {
String bodyStr = client.execute(request);
log.info("中通订单取消返回信息: {}", bodyStr);
JSONObject bodyJson = JSONUtil.parseObj(bodyStr);
- boolean success = bodyJson.getBool("status");
+ boolean success = BooleanUtil.isTrue(bodyJson.getBool("status"));
if (success) {
return true;
}
@@ -137,120 +137,46 @@ public class ZtoExpressManagerImpl implements ExpressManager, InitializingBean {
String bodyStr = client.execute(request);
log.info("中通订单拦截返回信息: {}", bodyStr);
/**
- * E23101
- * 快件已签收
- * 更换未签收运单发起
- * E23102
- * 订单信息不存在
- * 联系管理员处理
- * E23108
- * 无效的拦截、退改方式
- * 检查入参destinationType
- * E23110
- * 无揽收与面单使用网点
- * 联系管理员处理
- * E23111
- * 时效件,无法拦截
- * 更换运单
- * E23112
- * 已第三方进站不允许拦截
- * 更换运单
- * E23113
- * 有转单信息不允许拦截
- * 更换运单
- * E23114
- * 派件网点不支持到付业务
- * 更换运单
- * E23115
- * 派件网点不支持代收业务
- * 更换运单
- * E23116
- * 渠道不一致,不允许重复发起拦截/超过最大拦截次数
- * 更换运单
- * E23118
- * 订购渠道不存在/该渠道不允许拦截
- * 检查入参platform,如确认无误,联系管理员处理
- * E23119
- * 无任何轨迹不允许发起拦截
- * 揽收后再重试
- * E23120
- * 有派件扫描不允许拦截
- * 更换运单
- * E23121
- * 存在相同渠道的拦截件,不允许再次发起
- * 更换运单
- * E23122
- * 目的地停发不允许拦截
- * 更换运单
- * E23123
- * 存在退改件信息不允许拦截
- * 更换运单
- * E23124
- * 轨迹异常不允许拦截
- * 更换运单
- * E23127
- * 已经有相同诉求,不允许再次发起
- * 更换运单或更改入参destinationType或改地址的收件地址
- * E23130
- * 非拦截中拦截件,不能取消
- * 此运单已无法取消拦截,如仍需取消,请再次拦截改回原址
- * E23131
- * 取消网点与订购网点或原单揽收网点不一致,不能取消
- * 联系管理员处理
- * E23135
- * 同拦截发起方不一致,无权取消
- * 联系管理员处理
- * E23136
- * 已发往港澳台,暂不允许拦截
- * 更换运单
- * E23137
- * 有派件网点到件不允许拦截
- * 更换运单
- * E23138
- * 国际件不允许发起拦截
- * 更换运单
- * E23139
- * 不满足拦截发起业务规则
- * 更换运单
- * E23140
- * 包裹已确认遗失
- * 更换运单
- * E23141
- * 包裹已确认弃件
- * 更换运单
- * E23144
- * CRM无权限
- * 联系合作网点在客户管理系统开通拦截权限
- * E23146
- * 跨境客户,不允许发起改地址/退回指定地址
- * 更换运单
- * E23148
- * 联系方式格式错误,请检查后提交或不填写
- * 检查入参receivePhone
- * E23149
- * 联系方式是虚拟号,请补全分机号或不填写
- * 检查入参receivePhone
- * E23150
- * 联系方式超过16位,请删减
- * 检查入参receivePhone
- * E23151
- * 已经有相同诉求,不允许再次发起
- * 更换运单或更改入参destinationType或改地址的收件地址
- * E23153
- * 发起人不属于发起网点
- * 联系管理员处理
- * E23154
- * 单号不属于当前网点,不允许发起
- * 联系管理员处理
- * E23157
- * 该渠道不支持重复发起拦截
- * 更换运单
- * E23160
- * 已存在外部平台发起的拦截,不允许发起
- * 更换运单
+ * E23101 快件已签收 更换未签收运单发起
+ * E23102 订单信息不存在 联系管理员处理
+ * E23108 无效的拦截、退改方式 检查入参destinationType
+ * E23110 无揽收与面单使用网点 联系管理员处理
+ * E23111 时效件,无法拦截 更换运单
+ * E23112 已第三方进站不允许拦截 更换运单
+ * E23113 有转单信息不允许拦截 更换运单
+ * E23114 派件网点不支持到付业务 更换运单
+ * E23115 派件网点不支持代收业务 更换运单
+ * E23116 渠道不一致,不允许重复发起拦截/超过最大拦截次数 更换运单
+ * E23118 订购渠道不存在/该渠道不允许拦截 检查入参platform,如确认无误,联系管理员处理
+ * E23119 无任何轨迹不允许发起拦截 揽收后再重试
+ * E23120 有派件扫描不允许拦截 更换运单
+ * E23121 存在相同渠道的拦截件,不允许再次发起 更换运单
+ * E23122 目的地停发不允许拦截 更换运单
+ * E23123 存在退改件信息不允许拦截 更换运单
+ * E23124 轨迹异常不允许拦截 更换运单
+ * E23127 已经有相同诉求,不允许再次发起 更换运单或更改入参destinationType或改地址的收件地址
+ * E23130 非拦截中拦截件,不能取消 此运单已无法取消拦截,如仍需取消,请再次拦截改回原址
+ * E23131 取消网点与订购网点或原单揽收网点不一致,不能取消 联系管理员处理
+ * E23135 同拦截发起方不一致,无权取消 联系管理员处理
+ * E23136 已发往港澳台,暂不允许拦截 更换运单
+ * E23137 有派件网点到件不允许拦截 更换运单
+ * E23138 国际件不允许发起拦截 更换运单
+ * E23139 不满足拦截发起业务规则 更换运单
+ * E23140 包裹已确认遗失 更换运单
+ * E23141 包裹已确认弃件 更换运单
+ * E23144 CRM无权限 联系合作网点在客户管理系统开通拦截权限
+ * E23146 跨境客户,不允许发起改地址/退回指定地址 更换运单
+ * E23148 联系方式格式错误,请检查后提交或不填写 检查入参receivePhone
+ * E23149 联系方式是虚拟号,请补全分机号或不填写 检查入参receivePhone
+ * E23150 联系方式超过16位,请删减 检查入参receivePhone
+ * E23151 已经有相同诉求,不允许再次发起 更换运单或更改入参destinationType或改地址的收件地址
+ * E23153 发起人不属于发起网点 联系管理员处理
+ * E23154 单号不属于当前网点,不允许发起 联系管理员处理
+ * E23157 该渠道不支持重复发起拦截 更换运单
+ * E23160 已存在外部平台发起的拦截,不允许发起 更换运单
*/
JSONObject bodyJson = JSONUtil.parseObj(bodyStr);
- boolean success = bodyJson.getBool("status");
+ boolean success = BooleanUtil.isTrue(bodyJson.getBool("status"));
if (success) {
return true;
}
@@ -274,7 +200,7 @@ public class ZtoExpressManagerImpl implements ExpressManager, InitializingBean {
String bodyStr = client.execute(request);
log.info("中通订单打印返回信息: {}", bodyStr);
JSONObject bodyJson = JSONUtil.parseObj(bodyStr);
- boolean success = bodyJson.getBool("status");
+ boolean success = BooleanUtil.isTrue(bodyJson.getBool("status"));
if (success) {
continue;
}
@@ -301,6 +227,34 @@ public class ZtoExpressManagerImpl implements ExpressManager, InitializingBean {
return list;
}
+ @Override
+ public boolean subscribeTrack(ExpressTrackSubReqDTO trackSubReq) {
+ Assert.notNull(trackSubReq);
+ Assert.notEmpty(trackSubReq.getExpressWaybillNo());
+ Assert.notEmpty(trackSubReq.getDestinationContactPhoneNumber());
+ ZtoSubTrackParam ztoSubTrackParam = new ZtoSubTrackParam();
+ ztoSubTrackParam.setBillCode(trackSubReq.getExpressWaybillNo());
+ ztoSubTrackParam.setMobilePhone(StrUtil.sub(trackSubReq.getDestinationContactPhoneNumber(),
+ -1, 4));
+ ZopPublicRequest request = new ZopPublicRequest();
+ request.setBody(JSONUtil.toJsonStr(ztoSubTrackParam));
+ request.setUrl(gatewayUrl + TRACK_SUB_URI);
+ request.setEncryptionType(EncryptionType.MD5);
+ try {
+ String bodyStr = client.execute(request);
+ log.info("中通轨迹订阅返回信息: {}", bodyStr);
+ JSONObject bodyJson = JSONUtil.parseObj(bodyStr);
+ boolean success = BooleanUtil.isTrue(bodyJson.getBool("status"));
+ if (success) {
+ return true;
+ }
+ } catch (Exception e) {
+ log.error("中通轨迹订阅异常", e);
+ }
+ log.warn("中通轨迹订阅失败: {}", trackSubReq);
+ return false;
+ }
+
/**
* 智能解析
*
@@ -318,7 +272,7 @@ public class ZtoExpressManagerImpl implements ExpressManager, InitializingBean {
String bodyStr = client.execute(request);
log.info("中通智能解析返回信息: {}", bodyStr);
JSONObject bodyJson = JSONUtil.parseObj(bodyStr);
- boolean success = bodyJson.getBool("status");
+ boolean success = BooleanUtil.isTrue(bodyJson.getBool("status"));
if (success) {
return bodyJson.getJSONObject("result");
}
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 cc41d82274..cabe9b1ca8 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/service/IStoreOrderService.java
@@ -169,5 +169,10 @@ public interface IStoreOrderService {
*/
void refundSuccess(Long storeOrderId, List storeOrderDetailIds, Long operatorId);
-
+ /**
+ * 添加轨迹信息
+ *
+ * @param trackAddDTO
+ */
+ void addTrack(StoreOrderExpressTrackAddDTO trackAddDTO);
}
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 903b233573..6792e47c72 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,10 +17,7 @@ import com.ruoyi.common.core.domain.XktBaseEntity;
import com.ruoyi.common.exception.ServiceException;
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.ExpressPrintDTO;
-import com.ruoyi.xkt.dto.express.ExpressRegionDTO;
-import com.ruoyi.xkt.dto.express.ExpressShipReqDTO;
+import com.ruoyi.xkt.dto.express.*;
import com.ruoyi.xkt.dto.order.*;
import com.ruoyi.xkt.dto.storeProductFile.StoreProdMainPicDTO;
import com.ruoyi.xkt.enums.*;
@@ -55,6 +52,8 @@ public class StoreOrderServiceImpl implements IStoreOrderService {
@Autowired
private StoreOrderDetailMapper storeOrderDetailMapper;
@Autowired
+ private StoreOrderExpressTrackMapper storeOrderExpressTrackMapper;
+ @Autowired
private StoreMapper storeMapper;
@Autowired
private StoreProductMapper storeProductMapper;
@@ -627,6 +626,11 @@ public class StoreOrderServiceImpl implements IStoreOrderService {
//发货
ExpressShipReqDTO shipReq = createShipReq(order, orderDetails);
String expressWaybillNo = expressManager.shipStoreOrder(shipReq);
+ //订阅轨迹
+ ExpressTrackSubReqDTO trackSubReq = new ExpressTrackSubReqDTO(shipReq.getExpressReqNo(),
+ expressWaybillNo, shipReq.getOriginContactPhoneNumber(), shipReq.getDestinationContactPhoneNumber());
+ boolean trackSubSuccess = expressManager.subscribeTrack(trackSubReq);
+ Assert.isTrue(trackSubSuccess, "物流轨迹订阅失败");
List orderDetailIdList = new ArrayList<>(orderDetails.size());
for (StoreOrderDetail orderDetail : orderDetails) {
@@ -1182,6 +1186,58 @@ public class StoreOrderServiceImpl implements IStoreOrderService {
financeBillService.entryRefundOrderPaymentBill(order.getId());
}
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void addTrack(StoreOrderExpressTrackAddDTO trackAddDTO) {
+ Assert.notNull(trackAddDTO.getExpressId());
+ Assert.notEmpty(trackAddDTO.getExpressWaybillNo());
+ List storeOrderDetails = storeOrderDetailMapper.selectList(
+ Wrappers.lambdaQuery(StoreOrderDetail.class)
+ .eq(StoreOrderDetail::getExpressId, trackAddDTO.getExpressId())
+ .eq(StoreOrderDetail::getExpressWaybillNo, trackAddDTO.getExpressWaybillNo())
+ .eq(SimpleEntity::getDelFlag, Constants.UNDELETED)
+ );
+ for (StoreOrderDetail storeOrderDetail : storeOrderDetails) {
+ if (storeOrderDetail.getDetailStatus() > 20) {
+ //售后订单
+ continue;
+ }
+ if (EExpressStatus.PICKED_UP == trackAddDTO.getExpressStatus()) {
+ if (storeOrderDetail.getExpressStatus() == null) {
+ storeOrderDetail.setExpressStatus(EExpressStatus.PICKED_UP.getValue());
+ int orderDetailSuccess = storeOrderDetailMapper.updateById(storeOrderDetail);
+ if (orderDetailSuccess == 0) {
+ throw new ServiceException(Constants.VERSION_LOCK_ERROR_COMMON_MSG);
+ }
+ } else {
+ if (storeOrderDetail.getExpressStatus() < EExpressStatus.PICKED_UP.getValue()) {
+ storeOrderDetail.setExpressStatus(EExpressStatus.PICKED_UP.getValue());
+ int orderDetailSuccess = storeOrderDetailMapper.updateById(storeOrderDetail);
+ if (orderDetailSuccess == 0) {
+ throw new ServiceException(Constants.VERSION_LOCK_ERROR_COMMON_MSG);
+ }
+ }
+ }
+ }
+ if (EExpressStatus.COMPLETED == trackAddDTO.getExpressStatus()) {
+ storeOrderDetail.setExpressStatus(EExpressStatus.COMPLETED.getValue());
+ int orderDetailSuccess = storeOrderDetailMapper.updateById(storeOrderDetail);
+ if (orderDetailSuccess == 0) {
+ throw new ServiceException(Constants.VERSION_LOCK_ERROR_COMMON_MSG);
+ }
+ }
+ StoreOrderExpressTrack storeOrderExpressTrack = new StoreOrderExpressTrack();
+ storeOrderExpressTrack.setStoreOrderId(storeOrderDetail.getStoreOrderId());
+ storeOrderExpressTrack.setStoreOrderIdDetailId(storeOrderDetail.getId());
+ storeOrderExpressTrack.setSort(0);
+ storeOrderExpressTrack.setAction(trackAddDTO.getAction());
+ storeOrderExpressTrack.setDescription(trackAddDTO.getDescription());
+ storeOrderExpressTrack.setRemark(trackAddDTO.getRemark());
+ storeOrderExpressTrack.setDelFlag(Constants.UNDELETED);
+ storeOrderExpressTrackMapper.insert(storeOrderExpressTrack);
+ }
+ }
+
/**
* 添加操作记录
*
diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/yto/YtoSignUtil.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/yto/YtoSignUtil.java
index f4c33b095a..327f1dc468 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/yto/YtoSignUtil.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/yto/YtoSignUtil.java
@@ -1,6 +1,7 @@
package com.ruoyi.xkt.thirdpart.yto;
import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
/**
@@ -22,10 +23,23 @@ public class YtoSignUtil {
}
- public static String sign(String data, String secret) {
+ public static String sign(String data1, String data2) {
//进行md5加密,然后对数组进行base64编码
- byte[] bytes = DigestUtil.md5(data + secret);
+ byte[] bytes = DigestUtil.md5(data1 + data2);
return Base64.encodeStr(bytes, false, false);
}
+ /**
+ * 假设xml内容为: , partnerId(客户密钥)为123456,
+ * 则要签名的内容为123456,然后对123456先进行MD5加密,再转换为base64字符串。
+ * 即经过md5(16位byte)和base64后的内容就为LghTkEmsD2tbQ3fsIBRcBg== 。
+ *
+ * @param digest
+ * @param param
+ * @param secret
+ * @return
+ */
+ public static boolean verify(String digest, String param, String secret) {
+ return StrUtil.equals(digest, sign(StrUtil.emptyIfNull(param), StrUtil.emptyIfNull(secret)));
+ }
}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/yto/YtoSubTrackParam.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/yto/YtoSubTrackParam.java
new file mode 100644
index 0000000000..f83b0c7007
--- /dev/null
+++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/yto/YtoSubTrackParam.java
@@ -0,0 +1,40 @@
+package com.ruoyi.xkt.thirdpart.yto;
+
+import lombok.Data;
+
+/**
+ * @author liangyq
+ * @date 2025-04-29 18:11
+ */
+@Data
+public class YtoSubTrackParam {
+ /**
+ * 客户端id, 由物流公司提供,物流公司开通新渠道后获得
+ */
+ private String client_id;
+ /**
+ * 请求体
+ */
+ private String logistics_interface;
+ /**
+ * 类型(一般填写online)
+ */
+ private String msg_type;
+
+ @Data
+ public static class LogisticsInterface{
+ /**
+ * 客户端id, 由物流公司提供,物流公司开通新渠道后获得
+ */
+ private String clientId;
+ /**
+ * 运单号
+ */
+ private String waybillNo;
+ /**
+ * 订单号
+ */
+ private String txLogisticId;
+ }
+
+}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/yto/YtoTrackObj.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/yto/YtoTrackObj.java
new file mode 100644
index 0000000000..25982da418
--- /dev/null
+++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/yto/YtoTrackObj.java
@@ -0,0 +1,164 @@
+package com.ruoyi.xkt.thirdpart.yto;
+
+import cn.hutool.core.util.XmlUtil;
+import com.ruoyi.common.core.text.CharsetKit;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * @author liangyq
+ * @date 2025-05-06 23:00
+ */
+@Data
+public class YtoTrackObj {
+
+ private Info UpdateInfo;
+
+ @Data
+ public static class Info {
+ /**
+ * 物流公司ID(YTO)
+ */
+ private String logisticProviderID;
+ /**
+ * vip客户标识(客户编号)
+ */
+ private String clientID;
+ /**
+ * 运单号
+ */
+ private String mailNo;
+ /**
+ * 物流号
+ */
+ private String txLogisticID;
+ /**
+ * 通知类型STATUS:物流状态
+ */
+ private String infoType;
+ /**
+ * 操作,固定为:ACCEPT 接单;ORDER_BOOKING 修改预约取件时间;GOT 已揽收;NOT_SEND 揽收失败;ARRIVAL 已收入;DEPARTURE 已发出;SENT_SCAN 派件;INBOUND 自提柜入柜;SIGNED 签收成功;FAILED 签收失败;TMS_RETURN 退回;FORWARDING 转寄;AIRSEND 航空发货;AIRPICK 航空提货
+ */
+ private String infoContent;
+ /**
+ * 事件发生时间
+ */
+ private Date acceptTime;
+ /**
+ * 备注或失败原因(值为中文原因或备注)
+ */
+ private String remark;
+ /**
+ * 揽收重量
+ */
+ private String weight;
+ /**
+ * 网点名称
+ */
+ private String orgName;
+ /**
+ * 网点编号
+ */
+ private String orgCode;
+ /**
+ * 网点客服电话
+ */
+ private String orgPhone;
+ /**
+ * 业务员名称
+ */
+ private String empName;
+ /**
+ * 业务员编号
+ */
+ private String empCode;
+ /**
+ * 当前操作城市
+ */
+ private String city;
+ /**
+ * 当前操作区或者县
+ */
+ private String district;
+ /**
+ * 签收人
+ */
+ private String signedName;
+ /**
+ * 该状态操作人员,签收、派送、揽件
+ */
+ private String deliveryName;
+ /**
+ * 联系方式(包括手机,电话等)
+ */
+ private String contactInfo;
+ /**
+ * 异常原因
+ */
+ private String questionCause;
+ /**
+ * 新的预约取件开始时间
+ */
+ private Date bookingStartTime;
+ /**
+ * 新的预约取件结束时间
+ */
+ private Date bookingEndTime;
+ /**
+ * 扩展字段
+ */
+ private String extendFields;
+ }
+
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Response {
+ /**
+ * 物流公司ID
+ */
+ private String logisticProviderID;
+ /**
+ * 物流号
+ */
+ private String txLogisticID;
+ /**
+ * 成功失败标识 true-成功;false-失败
+ */
+ private Boolean success;
+ /**
+ * 失败原因
+ */
+ private String reason;
+
+ public String toXmlStr() {
+ return XmlUtil.toStr(XmlUtil.beanToXml(this), CharsetKit.UTF_8, false, true);
+ }
+ }
+
+ @Data
+ public static class Request {
+ /**
+ * 消息内容
+ */
+ private String logistics_interface;
+ /**
+ * 消息签名
+ */
+ private String data_digest;
+ /**
+ * 客户编码(电商标识)
+ */
+ private String clientId;
+ /**
+ * 订单类型(online:在线下单,offline:线下下单)
+ */
+ private String type;
+ }
+}
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
index a973a555cd..3d9ff19355 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopDigestUtil.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZopDigestUtil.java
@@ -1,5 +1,6 @@
package com.ruoyi.xkt.thirdpart.zto;
+import cn.hutool.core.util.StrUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
@@ -57,4 +58,9 @@ public class ZopDigestUtil {
return mac.doFinal(body.getBytes(StandardCharsets.UTF_8));
}
+ public static boolean verify(String digest, String param, String secret) {
+ return StrUtil.equals(digest, Base64.encodeBase64String(DigestUtils.md5(StrUtil.emptyIfNull(param) +
+ StrUtil.emptyIfNull(secret))));
+ }
+
}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoSubTrackParam.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoSubTrackParam.java
new file mode 100644
index 0000000000..2df7b7c4ad
--- /dev/null
+++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoSubTrackParam.java
@@ -0,0 +1,20 @@
+package com.ruoyi.xkt.thirdpart.zto;
+
+import lombok.Data;
+
+/**
+ * @author liangyq
+ * @date 2025-04-29 18:11
+ */
+@Data
+public class ZtoSubTrackParam {
+ /**
+ * 运单号
+ */
+ private String billCode;
+ /**
+ * 收寄人任一方电话号码后4位(手机或座机)。通过电话号码鉴权时必填,鉴权方式可以电子面单账号或电话号码二选一。
+ * 选择电子面单账号鉴权时,该字段非必填;选择电话号码鉴权时,可以不绑定下单电子面单账号。
+ */
+ private String mobilePhone;
+}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoTrackObj.java b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoTrackObj.java
new file mode 100644
index 0000000000..4526eeedb9
--- /dev/null
+++ b/xkt/src/main/java/com/ruoyi/xkt/thirdpart/zto/ZtoTrackObj.java
@@ -0,0 +1,174 @@
+package com.ruoyi.xkt.thirdpart.zto;
+
+import cn.hutool.json.JSONUtil;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author liangyq
+ * @date 2025-05-06 23:00
+ */
+@Data
+public class ZtoTrackObj {
+
+ @Data
+ public static class Info {
+ /**
+ * 运单号
+ */
+ private String billCode;
+ /**
+ * 事件类型
+ *
+ * GOT 收件 网点揽收
+ * DEPARTURE 发件 从网点或分拨中心发出
+ * ARRIVAL 到件 到达网点或分拨中心
+ * DISPATCH 派件 业务员派送
+ * RETURN_SCAN 退件 发生退回或改地址,具体类型见退改类型(returnType)字段
+ * RETURN_SIGNED 退件签收 已经退回至寄件客户
+ * INBOUND 入站 放入快递超市/自提柜/第三方代理点等
+ * HANDOVERSCAN_SIGNED 第三方妥投 入站后妥投成功
+ * DEPARTURE_SIGNED 出站签收 客户从快递超市/自提柜/第三方代理点等取出
+ * SIGNED 签收 客户正常签收
+ * PROBLEM 问题件 网点或中心登记的问题件,问题件类型在问题件编码(problemCode)字段体现
+ */
+ private String action;
+ /**
+ * 操作节点编码
+ */
+ private String facilityCode;
+ /**
+ * 操作节点名称
+ */
+ private String facilityName;
+ /**
+ * 操作节点服务电话
+ */
+ private String facilityContactPhone;
+ /**
+ * 操作节点所属城市
+ */
+ private String city;
+ /**
+ * 操作时间
+ */
+ private String actionTime;
+ /**
+ * 下一站编码
+ */
+ private String nextNodeCode;
+ /**
+ * 下一站名称
+ */
+ private String nextNodeName;
+ /**
+ * 下一站城市
+ */
+ private String nextCity;
+ /**
+ * 末端品牌编码
+ */
+ private String comCode;
+ /**
+ * 末端品牌名称
+ */
+ private String comName;
+ /**
+ * 物流详情描述
+ */
+ private String desc;
+ /**
+ * 快递员名称
+ */
+ private String courier;
+ /**
+ * 快递员电话
+ */
+ private String courierPhone;
+ /**
+ * 签收人名称
+ */
+ private String expressSigner;
+ /**
+ * 地址
+ */
+ private String address;
+ /**
+ * 问题件编码
+ */
+ private String problemCode;
+ /**
+ * (旧)退改类型
+ */
+ private String returnType;
+ /**
+ * (新)退改类型
+ */
+ private String returnUpdateType;
+ /**
+ * 备注
+ */
+ private String remark;
+ /**
+ * 备注1
+ */
+ private String remark1;
+ /**
+ * 备注2
+ */
+ private String remark2;
+
+ }
+
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Response {
+ /**
+ * 默认false,中通根据status=true来判断是否推送成功
+ */
+ private Boolean status;
+ /**
+ * 响应说明
+ */
+ private String message;
+ /**
+ * 响应结果
+ */
+ private String result;
+ /**
+ * 响应状态码
+ */
+ private String statusCode;
+
+ public String toJsonStr() {
+ return JSONUtil.toJsonStr(this);
+ }
+ }
+
+ @Data
+ public static class Request {
+ /**
+ * 消息内容
+ */
+ private String data;
+ /**
+ * 消息签名
+ *
+ * data_digest=base64(md5(data+AppSecret))
+ */
+ private String data_digest;
+ /**
+ * 消息类型(Traces)
+ */
+ private String msg_type;
+ /**
+ * 应用appKey
+ */
+ private String company_id;
+ }
+}