Browse Source

订单:
1. 增加查询物流接口

YunaiV 1 year ago
parent
commit
e4a2c738b2
22 changed files with 338 additions and 266 deletions
  1. 1 2
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalDateTimeDeserializer.java
  2. 0 21
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalTimeJson.java
  3. 5 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.http
  4. 11 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
  5. 23 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppOrderExpressTrackRespDTO.java
  6. 3 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  7. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/config/TradeExpressProperties.java
  8. 8 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/convert/ExpressQueryConvert.java
  9. 6 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/dto/ExpressTrackRespDTO.java
  10. 0 15
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/dto/kd100/Kd100ExpressQueryReqDTO.java
  11. 17 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/dto/kd100/Kd100ExpressQueryRespDTO.java
  12. 38 14
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/dto/kdniao/KdNiaoExpressQueryRespDTO.java
  13. 19 14
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/kd100/Kd100ExpressClient.java
  14. 23 16
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/kdniao/KdNiaoExpressClient.java
  15. 23 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java
  16. 60 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java
  17. 46 0
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/Kd100ExpressClientIntegrationTest.java
  18. 0 59
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/Kd100ExpressClientTest.java
  19. 46 0
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/KdNiaoExpressClientIntegrationTest.java
  20. 0 59
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/KdNiaoExpressClientTest.java
  21. 0 53
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/NoProvideExpressClientTest.java
  22. 8 0
      yudao-server/src/main/resources/application.yaml

+ 1 - 2
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalDateTimeDeserializer.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.framework.jackson.core.databind;
 
 import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonDeserializer;
 
@@ -20,7 +19,7 @@ public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
     public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();
 
     @Override
-    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
         return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
     }
 }

+ 0 - 21
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/LocalTimeJson.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.framework.jackson.core.databind;
-
-import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
-import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
-
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_HOUR_MINUTE_SECOND;
-
-public class LocalTimeJson {
-
-    public static final LocalTimeSerializer SERIALIZER = new LocalTimeSerializer(DateTimeFormatter
-            .ofPattern(FORMAT_HOUR_MINUTE_SECOND)
-            .withZone(ZoneId.systemDefault()));
-
-    public static final LocalTimeDeserializer DESERIALIZABLE = new LocalTimeDeserializer(DateTimeFormatter
-            .ofPattern(FORMAT_HOUR_MINUTE_SECOND)
-            .withZone(ZoneId.systemDefault()));
-
-}

+ 5 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.http

@@ -35,3 +35,8 @@ tenant-id: {{appTenentId}}
 GET {{appApi}}/trade/order/get-detail?id=21
 Authorization: Bearer {{appToken}}
 tenant-id: {{appTenentId}}
+
+### 获得交易订单的物流轨迹
+GET {{appApi}}/trade/order/get-express-track-list?id=70
+Authorization: Bearer {{appToken}}
+tenant-id: {{appTenentId}}

+ 11 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java

@@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
 import cn.iocoder.yudao.module.trade.service.order.TradeOrderService;
 import com.google.common.collect.Maps;
 import io.swagger.v3.oas.annotations.Operation;
@@ -45,6 +46,8 @@ public class AppTradeOrderController {
     @Resource
     private TradeOrderService tradeOrderService;
     @Resource
+    private TradeOrderQueryService tradeOrderQueryService;
+    @Resource
     private DeliveryExpressService deliveryExpressService;
 
     @Resource
@@ -99,6 +102,14 @@ public class AppTradeOrderController {
                 propertyValueDetails, tradeOrderProperties, express));
     }
 
+    @GetMapping("/get-express-track-list")
+    @Operation(summary = "获得交易订单的物流轨迹")
+    @Parameter(name = "id", description = "交易订单编号")
+    public CommonResult<List<?>> getOrderExpressTrackList(@RequestParam("id") Long id) {
+        return success(TradeOrderConvert.INSTANCE.convertList02(
+                tradeOrderQueryService.getExpressTrackList(id, getLoginUserId())));
+    }
+
     @GetMapping("/page")
     @Operation(summary = "获得交易订单分页")
     public CommonResult<PageResult<AppTradeOrderPageItemRespVO>> getOrderPage(AppTradeOrderPageReqVO reqVO) {

+ 23 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppOrderExpressTrackRespDTO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.trade.controller.app.order.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 快递查询的轨迹 Resp DTO
+ *
+ * @author jason
+ */
+@Schema(description = "用户 App - 快递查询的轨迹 Response VO")
+@Data
+public class AppOrderExpressTrackRespDTO {
+
+    @Schema(description = "发生时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime time;
+
+    @Schema(description = "快递状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "已签收")
+    private String content;
+
+}

+ 3 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java

@@ -29,6 +29,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
@@ -340,4 +341,6 @@ public interface TradeOrderConvert {
     CombinationRecordCreateReqDTO convert(TradeOrderDO order, TradeOrderItemDO orderItem,
                                           AppTradeOrderCreateReqVO createReqVO, MemberUserRespDTO user);
 
+    List<AppOrderExpressTrackRespDTO> convertList02(List<ExpressTrackRespDTO> list);
+
 }

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/config/TradeExpressProperties.java

@@ -59,7 +59,7 @@ public class TradeExpressProperties {
     }
 
     /**
-     * 快递100 配置项
+     * 快递 100 配置项
      */
     @Data
     public static class Kd100Config {

+ 8 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/convert/ExpressQueryConvert.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.kd100.Kd
 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.kdniao.KdNiaoExpressQueryReqDTO;
 import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.kdniao.KdNiaoExpressQueryRespDTO;
 import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
 import org.mapstruct.factory.Mappers;
 
 import java.util.List;
@@ -16,9 +17,14 @@ public interface ExpressQueryConvert {
 
     ExpressQueryConvert INSTANCE = Mappers.getMapper(ExpressQueryConvert.class);
 
-    List<ExpressTrackRespDTO> convertList(List<KdNiaoExpressQueryRespDTO.ExpressTrack> expressTrackList);
+    List<ExpressTrackRespDTO> convertList(List<KdNiaoExpressQueryRespDTO.ExpressTrack> list);
+    @Mapping(source = "acceptTime", target = "time")
+    @Mapping(source = "acceptStation", target = "content")
+    ExpressTrackRespDTO convert(KdNiaoExpressQueryRespDTO.ExpressTrack track);
 
-    List<ExpressTrackRespDTO> convertList2(List<Kd100ExpressQueryRespDTO.ExpressTrack> expressTrackList);
+    List<ExpressTrackRespDTO> convertList2(List<Kd100ExpressQueryRespDTO.ExpressTrack> list);
+    @Mapping(source = "context", target = "content")
+    ExpressTrackRespDTO convert(Kd100ExpressQueryRespDTO.ExpressTrack track);
 
     KdNiaoExpressQueryReqDTO convert(ExpressTrackQueryReqDTO dto);
 

+ 6 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/dto/ExpressTrackRespDTO.java

@@ -2,23 +2,24 @@ package cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto;
 
 import lombok.Data;
 
+import java.time.LocalDateTime;
+
 /**
- * 快递查询 Resp DTO
+ * 快递查询的轨迹 Resp DTO
  *
  * @author jason
  */
 @Data
 public class ExpressTrackRespDTO {
 
-    // TODO @jason:LocalDateTime
     /**
      * 发生时间
      */
-    private String time;
-    // TODO @jason:其它字段可能要补充下
+    private LocalDateTime time;
+
     /**
      * 快递状态
      */
-    private String state;
+    private String content;
 
 }

+ 0 - 15
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/dto/kd100/Kd100ExpressQueryReqDTO.java

@@ -29,20 +29,5 @@ public class Kd100ExpressQueryReqDTO {
      * 收、寄件人的电话号码
      */
     private String phone;
-    /**
-     * 出发地城市
-     */
-    private String from;
-    /**
-     * 目的地城市,到达目的地后会加大监控频率
-     */
-    private String to;
-
-    /**
-     * 返回结果排序
-     *
-     * desc 降序(默认), asc 升序
-     */
-    private String order;
 
 }

+ 17 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/dto/kd100/Kd100ExpressQueryRespDTO.java

@@ -1,12 +1,19 @@
 package cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.kd100;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
+
 /**
- * 快递 100 实时快递查询 Resp DTO 参见  <a href="https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc">快递 100 文档</a>
+ * 快递 100 实时快递查询 Resp DTO
+ *
+ * 参见  <a href="https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc">快递 100 文档</a>
  *
  * @author jason
  */
@@ -39,21 +46,26 @@ public class Kd100ExpressQueryRespDTO {
      */
     private String message;
 
+    /**
+     * 轨迹数组
+     */
     @JsonProperty("data")
     private List<ExpressTrack> tracks;
 
     @Data
     public static class ExpressTrack {
+
         /**
          * 轨迹发生时间
          */
-        @JsonProperty("time")
-        private String time;
+        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
+        private LocalDateTime time;
+
         /**
          * 轨迹描述
          */
-        @JsonProperty("context")
-        private String state;
+        private String context;
+
     }
 
 }

+ 38 - 14
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/dto/kdniao/KdNiaoExpressQueryRespDTO.java

@@ -1,12 +1,21 @@
 package cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.kdniao;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
 import lombok.Data;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
+
 /**
- * 快递鸟快递查询 Resp DTO 参见  <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/wugo6k">快递鸟接口文档</a>
+ * 快递鸟快递查询 Resp DTO
+ *
+ * 参见 <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/wugo6k">快递鸟接口文档</a>
  *
  * @author jason
  */
@@ -17,7 +26,7 @@ public class KdNiaoExpressQueryRespDTO {
      * 快递公司编码
      */
     @JsonProperty("ShipperCode")
-    private String expressCompanyCode;
+    private String shipperCode;
 
     /**
      * 快递单号
@@ -31,10 +40,26 @@ public class KdNiaoExpressQueryRespDTO {
     @JsonProperty("OrderCode")
     private String orderNo;
 
+    /**
+     * 用户 ID
+     */
     @JsonProperty("EBusinessID")
     private String businessId;
+
+    /**
+     * 普通物流状态
+     *
+     * 0 - 暂无轨迹信息
+     * 1 - 已揽收
+     * 2 - 在途中
+     * 3 - 签收
+     * 4 - 问题件
+     * 5 - 转寄
+     * 6 - 清关
+     */
     @JsonProperty("State")
     private String state;
+
     /**
      * 成功与否
      */
@@ -46,30 +71,29 @@ public class KdNiaoExpressQueryRespDTO {
     @JsonProperty("Reason")
     private String reason;
 
+    /**
+     * 轨迹数组
+     */
     @JsonProperty("Traces")
     private List<ExpressTrack> tracks;
 
     @Data
     public static class ExpressTrack {
+
         /**
-         * 轨迹发生时间
+         * 发生时间
          */
         @JsonProperty("AcceptTime")
-        private String time;
+        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
+        @JsonDeserialize(using = LocalDateTimeDeserializer.class)
+        private LocalDateTime acceptTime;
+
         /**
          * 轨迹描述
          */
         @JsonProperty("AcceptStation")
-        private String state;
+        private String acceptStation;
+
     }
 
-//    {
-//        "EBusinessID": "1237100",
-//            "Traces": [],
-//        "State": "0",
-//            "ShipperCode": "STO",
-//            "LogisticCode": "638650888018",
-//            "Success": true,
-//            "Reason": "暂无轨迹信息"
-//    }
 }

+ 19 - 14
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/kd100/Kd100ExpressClient.java

@@ -40,18 +40,24 @@ public class Kd100ExpressClient implements ExpressClient {
     private final RestTemplate restTemplate;
     private final TradeExpressProperties.Kd100Config config;
 
+    /**
+     * 查询快递轨迹
+     *
+     * @see <a href="https://api.kuaidi100.com/debug-tool/query/">接口文档</a>
+     *
+     * @param reqDTO 查询请求参数
+     * @return 快递轨迹
+     */
     @Override
     public List<ExpressTrackRespDTO> getExpressTrackList(ExpressTrackQueryReqDTO reqDTO) {
-        // 发起查询
-        Kd100ExpressQueryReqDTO kd100ReqParam = INSTANCE.convert2(reqDTO);
-        kd100ReqParam.setExpressCode(kd100ReqParam.getExpressCode().toLowerCase()); // 快递公司编码需要转成小写
-        Kd100ExpressQueryRespDTO respDTO = requestExpressQuery(REAL_TIME_QUERY_URL, kd100ReqParam,
+        // 发起请求
+        Kd100ExpressQueryReqDTO requestDTO = INSTANCE.convert2(reqDTO)
+                .setExpressCode(reqDTO.getExpressCode().toLowerCase());
+        Kd100ExpressQueryRespDTO respDTO = httpRequest(REAL_TIME_QUERY_URL, requestDTO,
                 Kd100ExpressQueryRespDTO.class);
-        log.debug("[getExpressTrackList][快递 100 接口 查询接口返回 {}]", respDTO);
 
         // 处理结果
         if (Objects.equals("false", respDTO.getResult())) {
-            log.error("[getExpressTrackList][快递 100 接口 返回失败 {}]", respDTO.getMessage());
             throw exception(EXPRESS_API_QUERY_FAILED, respDTO.getMessage());
         }
         if (CollUtil.isEmpty(respDTO.getTracks())) {
@@ -61,7 +67,7 @@ public class Kd100ExpressClient implements ExpressClient {
     }
 
     /**
-     * 发送快递 100 实时快递查询请求,可以作为通用快递 100 通用请求接口。 目前没有其它场景需要使用。暂时放这里
+     * 快递 100 API 请求
      *
      * @param url 请求 url
      * @param req 对应请求的请求参数
@@ -69,24 +75,23 @@ public class Kd100ExpressClient implements ExpressClient {
      * @param <Req> 每个请求的请求结构 Req DTO
      * @param <Resp> 每个请求的响应结构 Resp DTO
      */
-    private <Req, Resp> Resp requestExpressQuery(String url, Req req, Class<Resp> respClass) {
+    private <Req, Resp> Resp httpRequest(String url, Req req, Class<Resp> respClass) {
         // 请求头
         HttpHeaders headers = new HttpHeaders();
         headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
-        // 生成签名
-        String param = JsonUtils.toJsonString(req);
-        String sign = generateReqSign(param, config.getKey(), config.getCustomer());
         // 请求体
+        String param = JsonUtils.toJsonString(req);
+        String sign = generateReqSign(param, config.getKey(), config.getCustomer()); // 签名
         MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
         requestBody.add("customer", config.getCustomer());
         requestBody.add("sign", sign);
         requestBody.add("param", param);
-        log.debug("[sendExpressQueryReq][快递 100 接口的请求参数: {}]", requestBody);
+        log.debug("[httpRequest][请求参数({})]", requestBody);
+
         // 发送请求
         HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
         ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
-        log.debug("[sendExpressQueryReq][快递 100 接口响应结果 {}]", responseEntity);
-
+        log.debug("[httpRequest][的响应结果({})]", responseEntity);
         // 处理响应
         if (!responseEntity.getStatusCode().is2xxSuccessful()) {
             throw exception(EXPRESS_API_QUERY_ERROR);

+ 23 - 16
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/kdniao/KdNiaoExpressClient.java

@@ -41,46 +41,49 @@ public class KdNiaoExpressClient implements ExpressClient {
      * 快递鸟即时查询免费版 RequestType
      */
     private static final String REAL_TIME_FREE_REQ_TYPE = "1002";
+
     private final RestTemplate restTemplate;
     private final TradeExpressProperties.KdNiaoConfig config;
 
     /**
-     * 快递鸟即时查询免费版本
+     * 查询快递轨迹【免费版】
+     *
+     * 仅支持 3 家:申通快递、圆通速递、百世快递
+     *
+     * @see <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/wugo6k">接口文档</a>
      *
-     * @see <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/wugo6k">快递鸟接口文档</a>
      * @param reqDTO 查询请求参数
+     * @return 快递轨迹
      */
     @Override
     public List<ExpressTrackRespDTO> getExpressTrackList(ExpressTrackQueryReqDTO reqDTO) {
-        KdNiaoExpressQueryReqDTO kdNiaoReqData = INSTANCE.convert(reqDTO);
-        // 快递公司编码需要转成大写
-        kdNiaoReqData.setExpressCode(reqDTO.getExpressCode().toUpperCase());
-        KdNiaoExpressQueryRespDTO respDTO = requestKdNiaoApi(REAL_TIME_QUERY_URL, REAL_TIME_FREE_REQ_TYPE,
-                kdNiaoReqData, KdNiaoExpressQueryRespDTO.class);
-        log.debug("[getExpressTrackList][快递鸟即时查询接口返回 {}]", respDTO);
+        // 发起请求
+        KdNiaoExpressQueryReqDTO requestDTO = INSTANCE.convert(reqDTO)
+                .setExpressCode(reqDTO.getExpressCode().toUpperCase());
+        KdNiaoExpressQueryRespDTO respDTO = httpRequest(REAL_TIME_QUERY_URL, REAL_TIME_FREE_REQ_TYPE,
+                requestDTO, KdNiaoExpressQueryRespDTO.class);
 
         // 处理结果
         if (respDTO == null || !respDTO.getSuccess()) {
             throw exception(EXPRESS_API_QUERY_FAILED, respDTO == null ? "" : respDTO.getReason());
         }
-        if (CollUtil.isNotEmpty(respDTO.getTracks())) {
+        if (CollUtil.isEmpty(respDTO.getTracks())) {
             return Collections.emptyList();
         }
         return INSTANCE.convertList(respDTO.getTracks());
     }
 
     /**
-     * 快递鸟 通用的 API 请求,暂时没有其他应用场景, 暂时放这里
+     * 快递鸟 API 请求
      *
      * @param url 请求 url
-     * @param requestType 对应的请求指令 (快递鸟的RequestType)
+     * @param requestType 对应的请求指令 (快递鸟的 RequestType)
      * @param req  对应请求的请求参数
      * @param respClass 对应请求的响应 class
      * @param <Req> 每个请求的请求结构 Req DTO
      * @param <Resp> 每个请求的响应结构 Resp DTO
      */
-    private <Req, Resp> Resp requestKdNiaoApi(String url, String requestType, Req req,
-                                              Class<Resp> respClass){
+    private <Req, Resp> Resp httpRequest(String url, String requestType, Req req, Class<Resp> respClass) {
         // 请求头
         HttpHeaders headers = new HttpHeaders();
         headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
@@ -93,11 +96,12 @@ public class KdNiaoExpressClient implements ExpressClient {
         requestBody.add("EBusinessID", config.getBusinessId());
         requestBody.add("DataSign", dataSign);
         requestBody.add("RequestType", requestType);
-        log.debug("[requestKdNiaoApi][快递鸟接口 RequestType : {}, 的请求参数 {}]", requestType, requestBody);
+        log.debug("[httpRequest][RequestType({}) 的请求参数({})]", requestType, requestBody);
+
         // 发送请求
         HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
         ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
-        log.debug("快递鸟接口 RequestType : {}, 的响应结果 {}", requestType,  responseEntity);
+        log.debug("[httpRequest][RequestType({}) 的响应结果({})", requestType, responseEntity);
         // 处理响应
         if (!responseEntity.getStatusCode().is2xxSuccessful()) {
             throw exception(EXPRESS_API_QUERY_ERROR);
@@ -106,7 +110,10 @@ public class KdNiaoExpressClient implements ExpressClient {
     }
 
     /**
-     * 快递鸟生成请求签名 参见 <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/zes04h">签名说明</a>
+     * 快递鸟生成请求签名
+     *
+     * 参见 <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/zes04h">签名说明</a>
+     *
      * @param reqData 请求实体
      * @param apiKey  api Key
      */

+ 23 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.trade.service.order;
+
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
+
+import java.util.List;
+
+/**
+ * 交易订单【读】 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface TradeOrderQueryService {
+
+    /**
+     * 获得订单的物流轨迹
+     *
+     * @param id 订单编号
+     * @param userId 用户编号
+     * @return 物流轨迹数组
+     */
+    List<ExpressTrackRespDTO> getExpressTrackList(Long id, Long userId);
+
+}

+ 60 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java

@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.trade.service.order;
+
+import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClientFactory;
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
+import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_NOT_EXISTS;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_NOT_FOUND;
+
+/**
+ * 交易订单【读】 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
+
+    @Resource
+    private ExpressClientFactory expressClientFactory;
+
+    @Resource
+    private TradeOrderMapper tradeOrderMapper;
+
+    @Resource
+    private DeliveryExpressService deliveryExpressService;
+
+    @Override
+    public List<ExpressTrackRespDTO> getExpressTrackList(Long id, Long userId) {
+        // 查询订单
+        TradeOrderDO order = tradeOrderMapper.selectByIdAndUserId(id, userId);
+        if (order == null) {
+            throw exception(ORDER_NOT_FOUND);
+        }
+
+        // 查询物流公司
+        if (order.getLogisticsId() == null) {
+            return Collections.emptyList();
+        }
+        DeliveryExpressDO express = deliveryExpressService.getDeliveryExpress(order.getLogisticsId());
+        if (express == null) {
+            throw exception(EXPRESS_NOT_EXISTS);
+        }
+
+        // 查询物流轨迹
+        return expressClientFactory.getDefaultExpressClient().getExpressTrackList(
+                new ExpressTrackQueryReqDTO().setExpressCode(express.getCode()).setLogisticsNo(order.getLogisticsNo())
+                        .setPhone(order.getReceiverMobile()));
+    }
+
+}

+ 46 - 0
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/Kd100ExpressClientIntegrationTest.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl;
+
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressProperties;
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl.kd100.Kd100ExpressClient;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+
+/**
+ * {@link Kd100ExpressClient} 的集成测试
+ *
+ * @author jason
+ */
+@Slf4j
+public class Kd100ExpressClientIntegrationTest {
+
+    private Kd100ExpressClient client;
+
+    @BeforeEach
+    public void init() {
+        RestTemplate restTemplate = new RestTemplateBuilder().build();
+        TradeExpressProperties.Kd100Config config = new TradeExpressProperties.Kd100Config()
+                .setKey("pLXUGAwK5305")
+                .setCustomer("E77DF18BE109F454A5CD319E44BF5177");
+        client = new Kd100ExpressClient(restTemplate, config);
+    }
+
+    @Test
+    @Disabled("集成测试,暂时忽略")
+    public void testGetExpressTrackList() {
+        ExpressTrackQueryReqDTO reqDTO = new ExpressTrackQueryReqDTO();
+        reqDTO.setExpressCode("STO");
+        reqDTO.setLogisticsNo("773220402764314");
+        List<ExpressTrackRespDTO> tracks = client.getExpressTrackList(reqDTO);
+        System.out.println(JsonUtils.toJsonPrettyString(tracks));
+    }
+
+}

+ 0 - 59
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/Kd100ExpressClientTest.java

@@ -1,59 +0,0 @@
-package cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl;
-
-import cn.iocoder.yudao.framework.common.exception.ServiceException;
-import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressProperties;
-import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
-import cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl.kd100.Kd100ExpressClient;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Import;
-import org.springframework.test.context.ActiveProfiles;
-
-import javax.annotation.Resource;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-// TODO @jason:可以参考 AliyunSmsClientTest 写,纯 mockito,无需启动 spring 容器
-/**
- * @author jason
- */
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = Kd100ExpressClientTest.Application.class)
-@ActiveProfiles("unit-test") // 设置使用 trade-delivery-query 配置文件
-public class Kd100ExpressClientTest {
-
-    @Resource
-    private RestTemplateBuilder builder;
-    @Resource
-    private TradeExpressProperties expressQueryProperties;
-
-    private Kd100ExpressClient kd100ExpressClient;
-
-    @BeforeEach
-    public void init(){
-        kd100ExpressClient = new Kd100ExpressClient(builder.build(),expressQueryProperties.getKd100());
-    }
-    @Test
-    @Disabled("需要 授权 key. 暂时忽略")
-    void testRealTimeQueryExpressFailed() {
-        ServiceException t =  assertThrows(ServiceException.class, () -> {
-            ExpressTrackQueryReqDTO reqDTO = new ExpressTrackQueryReqDTO();
-            reqDTO.setExpressCode("yto");
-            reqDTO.setLogisticsNo("YT9383342193097");
-            kd100ExpressClient.getExpressTrackList(reqDTO);
-        });
-        assertEquals(1011003005, t.getCode());
-    }
-
-    @Import({
-            RestTemplateAutoConfiguration.class
-    })
-    @EnableConfigurationProperties(TradeExpressProperties.class)
-    public static class Application {
-    }
-}

+ 46 - 0
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/KdNiaoExpressClientIntegrationTest.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl;
+
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressProperties;
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO;
+import cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl.kdniao.KdNiaoExpressClient;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+
+/**
+ * {@link KdNiaoExpressClient} 的集成测试
+ *
+ * @author jason
+ */
+@Slf4j
+public class KdNiaoExpressClientIntegrationTest {
+
+    private KdNiaoExpressClient client;
+
+    @BeforeEach
+    public void init() {
+        RestTemplate restTemplate = new RestTemplateBuilder().build();
+        TradeExpressProperties.KdNiaoConfig config = new TradeExpressProperties.KdNiaoConfig()
+                .setApiKey("cb022f1e-48f1-4c4a-a723-9001ac9676b8")
+                .setBusinessId("1809751");
+        client = new KdNiaoExpressClient(restTemplate, config);
+    }
+
+    @Test
+    @Disabled("集成测试,暂时忽略")
+    public void testGetExpressTrackList() {
+        ExpressTrackQueryReqDTO reqDTO = new ExpressTrackQueryReqDTO();
+        reqDTO.setExpressCode("STO");
+        reqDTO.setLogisticsNo("663220402764314");
+        List<ExpressTrackRespDTO> tracks = client.getExpressTrackList(reqDTO);
+        System.out.println(JsonUtils.toJsonPrettyString(tracks));
+    }
+
+}

+ 0 - 59
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/KdNiaoExpressClientTest.java

@@ -1,59 +0,0 @@
-package cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl;
-
-import cn.iocoder.yudao.framework.common.exception.ServiceException;
-import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressProperties;
-import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO;
-import cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl.kdniao.KdNiaoExpressClient;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Import;
-import org.springframework.test.context.ActiveProfiles;
-
-import javax.annotation.Resource;
-
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-// TODO @jason:可以参考 AliyunSmsClientTest 写,纯 mockito,无需启动 spring 容器
-/**
- * {@link KdNiaoExpressClient} 的单元测试
- *
- * @author jason
- */
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = KdNiaoExpressClientTest.Application.class)
-@ActiveProfiles("unit-test")
-public class KdNiaoExpressClientTest {
-
-    @Resource
-    private RestTemplateBuilder builder;
-    @Resource
-    private TradeExpressProperties expressQueryProperties;
-
-    private KdNiaoExpressClient kdNiaoExpressClient;
-
-    @BeforeEach
-    public void init(){
-        kdNiaoExpressClient = new KdNiaoExpressClient(builder.build(),expressQueryProperties.getKdNiao());
-    }
-    @Test
-    @Disabled("需要 授权 key. 暂时忽略")
-    void testRealTimeQueryExpressFailed() {
-        assertThrows(ServiceException.class,() ->{
-            ExpressTrackQueryReqDTO reqDTO = new ExpressTrackQueryReqDTO();
-            reqDTO.setExpressCode("yy");
-            reqDTO.setLogisticsNo("YT9383342193097");
-            kdNiaoExpressClient.getExpressTrackList(reqDTO);
-        });
-    }
-
-    @Import({
-            RestTemplateAutoConfiguration.class
-    })
-    @EnableConfigurationProperties(TradeExpressProperties.class)
-    public static class Application {
-    }
-}

+ 0 - 53
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/NoProvideExpressClientTest.java

@@ -1,53 +0,0 @@
-package cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl;
-
-import cn.iocoder.yudao.framework.common.exception.ServiceException;
-import cn.iocoder.yudao.module.trade.framework.delivery.config.ExpressClientConfig;
-import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressProperties;
-import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClient;
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Import;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.web.client.RestTemplate;
-
-import javax.annotation.Resource;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-// TODO @jason:可以参考 AliyunSmsClientTest 写,纯 mockito,无需启动 spring 容器
-/**
- * @author jason
- */
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = NoProvideExpressClientTest.Application.class)
-@ActiveProfiles("unit-test") // 设置使用 trade-delivery-query 配置文件
-@Import({ExpressClientConfig.class})
-public class NoProvideExpressClientTest {
-
-    @Resource
-    private ExpressClient expressClient;
-
-    @Test
-    void getExpressTrackList() {
-        ServiceException t =  assertThrows(ServiceException.class, () -> {
-            expressClient.getExpressTrackList(null);
-        });
-        assertEquals(1011003006, t.getCode());
-    }
-
-    @Import({
-            RestTemplateAutoConfiguration.class,
-    })
-    @EnableConfigurationProperties(TradeExpressProperties.class)
-    public static class Application {
-
-        @Bean
-        private RestTemplate restTemplate(RestTemplateBuilder builder) {
-            return builder.build();
-        }
-    }
-}

+ 8 - 0
yudao-server/src/main/resources/application.yaml

@@ -204,6 +204,14 @@ yudao:
     order:
       app-id: 1 # 商户编号
       expire-time: 2h # 支付的过期时间
+    express:
+      client: kd_niao
+      kd-niao:
+        api-key: cb022f1e-48f1-4c4a-a723-9001ac9676b8
+        business-id: 1809751
+      kd100:
+        key: pLXUGAwK5305
+        customer: E77DF18BE109F454A5CD319E44BF5177
 
 debug: false