mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 10:18:42 +08:00 
			
		
		
		
	订单:
1. 增加查询物流接口
This commit is contained in:
		| @@ -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()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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())); | ||||
|  | ||||
| } | ||||
| @@ -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}} | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
| } | ||||
| @@ -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); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -59,7 +59,7 @@ public class TradeExpressProperties { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 快递100 配置项 | ||||
|      * 快递 100 配置项 | ||||
|      */ | ||||
|     @Data | ||||
|     public static class Kd100Config { | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -29,20 +29,5 @@ public class Kd100ExpressQueryReqDTO { | ||||
|      * 收、寄件人的电话号码 | ||||
|      */ | ||||
|     private String phone; | ||||
|     /** | ||||
|      * 出发地城市 | ||||
|      */ | ||||
|     private String from; | ||||
|     /** | ||||
|      * 目的地城市,到达目的地后会加大监控频率 | ||||
|      */ | ||||
|     private String to; | ||||
|  | ||||
|     /** | ||||
|      * 返回结果排序 | ||||
|      * | ||||
|      * desc 降序(默认), asc 升序 | ||||
|      */ | ||||
|     private String order; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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": "暂无轨迹信息" | ||||
| //    } | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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 | ||||
|      */ | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
| } | ||||
| @@ -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())); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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 { | ||||
|     } | ||||
| } | ||||
| @@ -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)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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 { | ||||
|     } | ||||
| } | ||||
| @@ -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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV