Commit 45e5578cb44392e8e59c2da4296034365e25148c

Authored by jason
1 parent 5ffc1ac9

快递查询、 通过快递鸟查询实现

Showing 18 changed files with 665 additions and 1 deletions
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
... ... @@ -10,11 +10,14 @@ import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
10 10 import org.springframework.beans.factory.annotation.Value;
11 11 import org.springframework.boot.autoconfigure.AutoConfiguration;
12 12 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  13 +import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
13 14 import org.springframework.boot.context.properties.EnableConfigurationProperties;
  15 +import org.springframework.boot.web.client.RestTemplateBuilder;
14 16 import org.springframework.boot.web.servlet.FilterRegistrationBean;
15 17 import org.springframework.context.annotation.Bean;
16 18 import org.springframework.util.AntPathMatcher;
17 19 import org.springframework.web.bind.annotation.RestController;
  20 +import org.springframework.web.client.RestTemplate;
18 21 import org.springframework.web.cors.CorsConfiguration;
19 22 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
20 23 import org.springframework.web.filter.CorsFilter;
... ... @@ -107,6 +110,15 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
107 110 return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
108 111 }
109 112  
  113 + /**
  114 + * 创建 RestTemplate 实例
  115 + * @param restTemplateBuilder {@link RestTemplateAutoConfiguration#restTemplateBuilder}
  116 + */
  117 + @Bean
  118 + public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
  119 + return restTemplateBuilder.build();
  120 + }
  121 +
110 122 public static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
111 123 FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
112 124 bean.setOrder(order);
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
... ... @@ -53,7 +53,8 @@ public interface ErrorCodeConstants {
53 53 ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1011003003, "已经存在该运费模板名");
54 54 ErrorCode DELIVERY_EXPRESS_USER_ADDRESS_IS_EMPTY = new ErrorCode(1011003004, "计算快递运费时,收件人地址编号为空");
55 55 ErrorCode PRODUCT_EXPRESS_TEMPLATE_NOT_FOUND = new ErrorCode(1011003005, "找不到到商品对应的运费模板");
56   - ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011003006, "自提门店不存在");
  56 + ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1011003006, "快递接口查询失败");
  57 + ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011003007, "自提门店不存在");
57 58  
58 59 // ========== Price 相关 1011004000 ============
59 60 ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1011004000, "支付价格计算异常,原因:价格小于等于 0");
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/config/TradeExpressQueryProperties.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.config;
  2 +
  3 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProviderEnum;
  4 +import lombok.Data;
  5 +import org.springframework.boot.context.properties.ConfigurationProperties;
  6 +import org.springframework.stereotype.Component;
  7 +import org.springframework.validation.annotation.Validated;
  8 +
  9 +import javax.validation.Valid;
  10 +import javax.validation.constraints.NotEmpty;
  11 +
  12 +/**
  13 + * 交易快递查询的配置项
  14 + *
  15 + * @author jason
  16 + */
  17 +@Component
  18 +@ConfigurationProperties(prefix = "yudao.trade.express.query")
  19 +@Data
  20 +@Validated
  21 +public class TradeExpressQueryProperties {
  22 +
  23 + /**
  24 + * 快递查询服务商, 如果未配置,默认使用快递鸟
  25 + */
  26 + private ExpressQueryProviderEnum expressQueryProvider;
  27 + /**
  28 + * 快递鸟配置
  29 + */
  30 + @Valid
  31 + private KdNiaoConfig kdNiao;
  32 + /**
  33 + * 快递 100 配置
  34 + */
  35 + @Valid
  36 + private Kd100Config kd100;
  37 +
  38 + /**
  39 + * 快递鸟配置项目
  40 + */
  41 + @Data
  42 + public static class KdNiaoConfig {
  43 +
  44 + /**
  45 + * 快递鸟用户 ID
  46 + */
  47 + @NotEmpty(message = "快递鸟用户 ID 配置项不能为空")
  48 + private String businessId;
  49 +
  50 + /**
  51 + * 快递鸟 API Key
  52 + */
  53 + @NotEmpty(message = "快递鸟 Api Key 配置项不能为空")
  54 + private String apiKey;
  55 + }
  56 +
  57 + /**
  58 + * 快递100 配置项 TODO
  59 + */
  60 + public static class Kd100Config {
  61 +
  62 + }
  63 +
  64 +
  65 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/ExpressQueryClient.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core;
  2 +
  3 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryReqDTO;
  4 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryRespDTO;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * 快递查询客户端
  10 + *
  11 + * @author jason
  12 + */
  13 +public interface ExpressQueryClient {
  14 +
  15 + /**
  16 + * 快递实时查询
  17 + *
  18 + * @param reqDTO 查询请求参数
  19 + */
  20 + List<ExpressQueryRespDTO> realTimeQuery(ExpressQueryReqDTO reqDTO);
  21 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/ExpressQueryProvider.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core;
  2 +
  3 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryReqDTO;
  4 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryRespDTO;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * 快递查询服务商
  10 + *
  11 + * @author jason
  12 + */
  13 +public interface ExpressQueryProvider {
  14 + /**
  15 + * 快递实时查询
  16 + *
  17 + * @param reqDTO 查询请求参数
  18 + */
  19 + List<ExpressQueryRespDTO> realTimeQueryExpress(ExpressQueryReqDTO reqDTO);
  20 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/ExpressQueryProviderEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core;
  2 +
  3 +import lombok.Getter;
  4 +
  5 +/**
  6 + * 快递查询服务商枚举
  7 + *
  8 + * @author jason
  9 + */
  10 +@Getter
  11 +public enum ExpressQueryProviderEnum {
  12 + KD_NIAO("kd-niao", "快递鸟"),
  13 + KD_100("kd-100", "快递100");
  14 + /**
  15 + * 快递服务商唯一编码
  16 + */
  17 + private final String code;
  18 +
  19 + /**
  20 + * 快递服务商名称
  21 + */
  22 + private final String name;
  23 +
  24 + ExpressQueryProviderEnum(String code, String name) {
  25 + this.code = code;
  26 + this.name = name;
  27 + }
  28 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/ExpressQueryProviderFactory.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core;
  2 +
  3 +/**
  4 + * 快递服务商工厂, 用于创建和缓存快递服务商服务
  5 + * @author jason
  6 + */
  7 +public interface ExpressQueryProviderFactory {
  8 +
  9 + /**
  10 + * 通过枚举获取快递查询服务商, 如果不存在。就创建一个对应的快递查询服务商
  11 + * @param queryProviderEnum 快递服务商枚举
  12 + */
  13 + ExpressQueryProvider getOrCreateExpressQueryProvider(ExpressQueryProviderEnum queryProviderEnum);
  14 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/convert/ExpressQueryConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.convert;
  2 +
  3 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryReqDTO;
  4 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryRespDTO;
  5 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kdniao.KdNiaoExpressQueryReqDTO;
  6 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kdniao.KdNiaoExpressQueryRespDTO;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.factory.Mappers;
  9 +
  10 +import java.util.List;
  11 +
  12 +@Mapper
  13 +public interface ExpressQueryConvert {
  14 +
  15 + ExpressQueryConvert INSTANCE = Mappers.getMapper(ExpressQueryConvert.class);
  16 +
  17 + List<ExpressQueryRespDTO> convertList(List<KdNiaoExpressQueryRespDTO.ExpressTrack> expressTrackList);
  18 +
  19 + KdNiaoExpressQueryReqDTO convert(ExpressQueryReqDTO dto);
  20 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/dto/ExpressQueryReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.dto;
  2 +
  3 +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 快递查询 Req DTO
  8 + *
  9 + * @author jason
  10 + */
  11 +@Data
  12 +public class ExpressQueryReqDTO {
  13 +
  14 + /**
  15 + * 快递公司编码
  16 + *
  17 + * 对应 {@link DeliveryExpressDO#getCode()} }
  18 + */
  19 + private String expressCompanyCode;
  20 +
  21 + /**
  22 + * 发货快递单号
  23 + */
  24 + private String logisticsNo;
  25 +
  26 + /**
  27 + * 收、寄件人的电话号码
  28 + */
  29 + private String phone;
  30 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/dto/ExpressQueryRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * 快递查询 Resp DTO
  7 + *
  8 + * @author jason
  9 + */
  10 +@Data
  11 +public class ExpressQueryRespDTO {
  12 +
  13 + /**
  14 + * 发生时间
  15 + */
  16 + private String time;
  17 +
  18 + /**
  19 + * 快递状态
  20 + */
  21 + private String state;
  22 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/dto/provider/kdniao/KdNiaoExpressQueryReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kdniao;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonInclude;
  4 +import com.fasterxml.jackson.annotation.JsonProperty;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * 快递鸟快递查询 Req DTO
  9 + *
  10 + * @author jason
  11 + */
  12 +@Data
  13 +@JsonInclude(JsonInclude.Include.NON_NULL)
  14 +public class KdNiaoExpressQueryReqDTO {
  15 + /**
  16 + * 快递公司编码
  17 + */
  18 + @JsonProperty("ShipperCode")
  19 + private String expressCompanyCode;
  20 +
  21 + /**
  22 + * 快递单号
  23 + */
  24 + @JsonProperty("LogisticCode")
  25 + private String logisticsNo;
  26 +
  27 + /**
  28 + * 订单编号
  29 + */
  30 + @JsonProperty("OrderCode")
  31 + private String orderNo;
  32 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/dto/provider/kdniao/KdNiaoExpressQueryRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kdniao;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * 快递鸟快递查询 Resp DTO 参见 <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/wugo6k">快递鸟接口文档</a>
  10 + *
  11 + * @author jason
  12 + */
  13 +@Data
  14 +public class KdNiaoExpressQueryRespDTO {
  15 +
  16 + /**
  17 + * 快递公司编码
  18 + */
  19 + @JsonProperty("ShipperCode")
  20 + private String expressCompanyCode;
  21 +
  22 + /**
  23 + * 快递单号
  24 + */
  25 + @JsonProperty("LogisticCode")
  26 + private String logisticsNo;
  27 +
  28 + /**
  29 + * 订单编号
  30 + */
  31 + @JsonProperty("OrderCode")
  32 + private String orderNo;
  33 +
  34 + @JsonProperty("EBusinessID")
  35 + private String businessId;
  36 + @JsonProperty("State")
  37 + private String state;
  38 + /**
  39 + * 成功与否
  40 + */
  41 + @JsonProperty("Success")
  42 + private Boolean success;
  43 + /**
  44 + * 失败原因
  45 + */
  46 + @JsonProperty("Reason")
  47 + private String reason;
  48 +
  49 + @JsonProperty("Traces")
  50 + private List<ExpressTrack> tracks;
  51 +
  52 + @Data
  53 + public static class ExpressTrack {
  54 + /**
  55 + * 轨迹发生时间
  56 + */
  57 + @JsonProperty("AcceptTime")
  58 + private String time;
  59 + /**
  60 + * 轨迹描述
  61 + */
  62 + @JsonProperty("AcceptStation")
  63 + private String state;
  64 + }
  65 +
  66 +// {
  67 +// "EBusinessID": "1237100",
  68 +// "Traces": [],
  69 +// "State": "0",
  70 +// "ShipperCode": "STO",
  71 +// "LogisticCode": "638650888018",
  72 +// "Success": true,
  73 +// "Reason": "暂无轨迹信息"
  74 +// }
  75 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/impl/ExpressQueryClientImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.impl;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressQueryProperties;
  5 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryClient;
  6 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProvider;
  7 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProviderEnum;
  8 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProviderFactory;
  9 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryReqDTO;
  10 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryRespDTO;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.springframework.stereotype.Component;
  13 +
  14 +import javax.annotation.PostConstruct;
  15 +import javax.annotation.Resource;
  16 +import java.util.List;
  17 +
  18 +import static cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProviderEnum.KD_NIAO;
  19 +
  20 +/**
  21 + * 快递查询客户端实现
  22 + *
  23 + * @author jason
  24 + */
  25 +@Component
  26 +@Slf4j
  27 +public class ExpressQueryClientImpl implements ExpressQueryClient {
  28 + @Resource
  29 + private ExpressQueryProviderFactory expressQueryProviderFactory;
  30 + @Resource
  31 + private TradeExpressQueryProperties tradeExpressQueryProperties;
  32 +
  33 + private ExpressQueryProvider expressQueryProvider;
  34 + @PostConstruct
  35 + private void init(){
  36 + ExpressQueryProviderEnum queryProvider = tradeExpressQueryProperties.getExpressQueryProvider();
  37 + if (queryProvider == null) {
  38 + // 如果未设置,默认使用快递鸟
  39 + queryProvider = KD_NIAO;
  40 + }
  41 + expressQueryProvider = expressQueryProviderFactory.getOrCreateExpressQueryProvider(queryProvider);
  42 + if (expressQueryProvider == null) {
  43 + // 记录错误日志
  44 + log.error("获取创建快递查询服务商{}失败,请检查相关配置", queryProvider);
  45 + }
  46 + Assert.notNull(expressQueryProvider, "快递查询服务商不能为空");
  47 +
  48 + }
  49 + @Override
  50 + public List<ExpressQueryRespDTO> realTimeQuery(ExpressQueryReqDTO reqDTO) {
  51 + return expressQueryProvider.realTimeQueryExpress(reqDTO);
  52 + }
  53 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/impl/ExpressQueryProviderFactoryImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.impl;
  2 +
  3 +import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressQueryProperties;
  4 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProvider;
  5 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProviderEnum;
  6 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProviderFactory;
  7 +import org.springframework.stereotype.Component;
  8 +import org.springframework.web.client.RestTemplate;
  9 +
  10 +import javax.annotation.Resource;
  11 +import java.util.Map;
  12 +import java.util.concurrent.ConcurrentHashMap;
  13 +
  14 +/**
  15 + * @author jason
  16 + */
  17 +@Component
  18 +public class ExpressQueryProviderFactoryImpl implements ExpressQueryProviderFactory {
  19 +
  20 + private final Map<ExpressQueryProviderEnum, ExpressQueryProvider> providerMap = new ConcurrentHashMap<>(8);
  21 + @Resource
  22 + private TradeExpressQueryProperties tradeExpressQueryProperties;
  23 + @Resource
  24 + private RestTemplate restTemplate;
  25 +
  26 + @Override
  27 + public ExpressQueryProvider getOrCreateExpressQueryProvider(ExpressQueryProviderEnum queryProviderEnum) {
  28 + return providerMap.computeIfAbsent(queryProviderEnum,
  29 + provider -> createExpressQueryProvider(provider, tradeExpressQueryProperties));
  30 + }
  31 +
  32 + private ExpressQueryProvider createExpressQueryProvider(ExpressQueryProviderEnum queryProviderEnum,
  33 + TradeExpressQueryProperties tradeExpressQueryProperties) {
  34 + ExpressQueryProvider result = null;
  35 + switch (queryProviderEnum) {
  36 + case KD_NIAO:
  37 + result = new KdNiaoExpressQueryProvider(restTemplate, tradeExpressQueryProperties.getKdNiao());
  38 + break;
  39 + case KD_100:
  40 + result = new Kd100ExpressQueryProvider(restTemplate, tradeExpressQueryProperties.getKd100());
  41 + break;
  42 + }
  43 + return result;
  44 + }
  45 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/impl/Kd100ExpressQueryProvider.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.impl;
  2 +
  3 +import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressQueryProperties;
  4 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProvider;
  5 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryReqDTO;
  6 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryRespDTO;
  7 +import org.springframework.web.client.RestTemplate;
  8 +
  9 +import java.util.List;
  10 +
  11 +/**
  12 + * 快递 100 服务商 TODO
  13 + * @author jason
  14 + */
  15 +public class Kd100ExpressQueryProvider implements ExpressQueryProvider {
  16 +
  17 + private final RestTemplate restTemplate;
  18 +
  19 + private final TradeExpressQueryProperties.Kd100Config config;
  20 +
  21 + public Kd100ExpressQueryProvider(RestTemplate restTemplate, TradeExpressQueryProperties.Kd100Config config) {
  22 + this.restTemplate = restTemplate;
  23 + this.config = config;
  24 + }
  25 +
  26 + @Override
  27 + public List<ExpressQueryRespDTO> realTimeQueryExpress(ExpressQueryReqDTO reqDTO) {
  28 + return null;
  29 + }
  30 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/impl/KdNiaoExpressQueryProvider.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.impl;
  2 +
  3 +import cn.hutool.core.codec.Base64;
  4 +import cn.hutool.core.collection.CollUtil;
  5 +import cn.hutool.core.net.URLEncodeUtil;
  6 +import cn.hutool.crypto.digest.DigestUtil;
  7 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  8 +import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressQueryProperties;
  9 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProvider;
  10 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryReqDTO;
  11 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryRespDTO;
  12 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kdniao.KdNiaoExpressQueryReqDTO;
  13 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kdniao.KdNiaoExpressQueryRespDTO;
  14 +import com.fasterxml.jackson.core.type.TypeReference;
  15 +import lombok.extern.slf4j.Slf4j;
  16 +import org.springframework.http.*;
  17 +import org.springframework.util.LinkedMultiValueMap;
  18 +import org.springframework.util.MultiValueMap;
  19 +import org.springframework.web.client.RestTemplate;
  20 +
  21 +import java.util.Collections;
  22 +import java.util.List;
  23 +
  24 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  25 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_API_QUERY_FAILED;
  26 +import static cn.iocoder.yudao.module.trade.framework.delivery.core.convert.ExpressQueryConvert.INSTANCE;
  27 +
  28 +/**
  29 + * 快递鸟服务商
  30 + *
  31 + * @author jason
  32 + */
  33 +@Slf4j
  34 +public class KdNiaoExpressQueryProvider implements ExpressQueryProvider {
  35 + private static final String REAL_TIME_QUERY_URL = "https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx";
  36 + /**
  37 + * 快递鸟即时查询免费版 RequestType
  38 + */
  39 + private static final String REAL_TIME_FREE_REQ_TYPE = "1002";
  40 + private final RestTemplate restTemplate;
  41 + private final TradeExpressQueryProperties.KdNiaoConfig config;
  42 +
  43 + public KdNiaoExpressQueryProvider(RestTemplate restTemplate, TradeExpressQueryProperties.KdNiaoConfig config) {
  44 + this.restTemplate = restTemplate;
  45 + this.config = config;
  46 + }
  47 +
  48 + /**
  49 + * 快递鸟即时查询免费版本 参见 <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/wugo6k">快递鸟接口文档</a>
  50 + * @param reqDTO 查询请求参数
  51 + */
  52 + @Override
  53 + public List<ExpressQueryRespDTO> realTimeQueryExpress(ExpressQueryReqDTO reqDTO) {
  54 + KdNiaoExpressQueryReqDTO kdNiaoReqData = INSTANCE.convert(reqDTO);
  55 + // 快递公司编码需要转成大写
  56 + kdNiaoReqData.setExpressCompanyCode(reqDTO.getExpressCompanyCode().toUpperCase());
  57 + KdNiaoExpressQueryRespDTO respDTO = sendKdNiaoApiRequest(REAL_TIME_QUERY_URL, REAL_TIME_FREE_REQ_TYPE,
  58 + kdNiaoReqData, KdNiaoExpressQueryRespDTO.class);
  59 + log.debug("快递鸟即时查询接口返回 {}", respDTO);
  60 + if (respDTO.getSuccess() && CollUtil.isNotEmpty(respDTO.getTracks())) {
  61 + return INSTANCE.convertList(respDTO.getTracks());
  62 + }else{
  63 + return Collections.emptyList();
  64 + }
  65 + }
  66 +
  67 + private <Req, Resp> Resp sendKdNiaoApiRequest(String url, String requestType, Req req,
  68 + Class<Resp> respClass){
  69 + return sendKdNiaoApiRequest(url, requestType, req, respClass, null);
  70 + }
  71 + private <Req, Resp> Resp sendKdNiaoApiRequest(String url, String requestType, Req req,
  72 + TypeReference<Resp> typeRefResp){
  73 + return sendKdNiaoApiRequest(url, requestType, req, null, typeRefResp);
  74 + }
  75 +
  76 + /**
  77 + * 快递鸟 通用的 API 请求
  78 + * @param url 请求 url
  79 + * @param requestType 对应的请求指令 (快递鸟的RequestType)
  80 + * @param req 对应请求的请求参数
  81 + * @param respClass 对应请求的响应 class
  82 + * @param typeRefResp 对应请求的响应 TypeReference
  83 + * @param <Req> 每个请求的请求结构 Req DTO
  84 + * @param <Resp> 每个请求的响应结构 Resp DTO
  85 + */
  86 + private <Req, Resp> Resp sendKdNiaoApiRequest(String url, String requestType, Req req,
  87 + Class<Resp> respClass, TypeReference<Resp> typeRefResp){
  88 + // 请求头
  89 + HttpHeaders headers = new HttpHeaders();
  90 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  91 + // 请求体
  92 + String reqData = JsonUtils.toJsonString(req);
  93 + String dataSign = generateDataSign(reqData, config.getApiKey());
  94 + log.trace("得到快递鸟接口 RequestType : {} 的 签名: {}", requestType, dataSign);
  95 + MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
  96 + requestBody.add("RequestData", reqData);
  97 + requestBody.add("DataType", "2");
  98 + requestBody.add("EBusinessID", config.getBusinessId());
  99 + requestBody.add("DataSign", dataSign);
  100 + requestBody.add("RequestType", requestType);
  101 + log.debug("快递鸟接口 RequestType : {}, 的请求参数 {}", requestType, requestBody);
  102 + HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
  103 + // 发送请求
  104 + ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
  105 + log.debug("快递鸟接口 RequestType : {}, 的响应结果 {}", requestType, responseEntity);
  106 + // 处理响应
  107 + if (responseEntity.getStatusCode().is2xxSuccessful()) {
  108 + String response = responseEntity.getBody();
  109 + if (respClass != null && typeRefResp == null) {
  110 + return JsonUtils.parseObject(response, respClass);
  111 + } else {
  112 + return JsonUtils.parseObject(response, typeRefResp);
  113 + }
  114 + } else {
  115 + throw exception(EXPRESS_API_QUERY_FAILED);
  116 + }
  117 + }
  118 +
  119 + /**
  120 + * 快递鸟生成请求签名 参见 <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/zes04h">签名说明</a>
  121 + * @param reqData 请求实体
  122 + * @param apiKey api Key
  123 + */
  124 + private String generateDataSign(String reqData, String apiKey) {
  125 + String plainText = String.format("%s%s", reqData, apiKey);
  126 + log.trace("签名前的数据 {}", plainText);
  127 + return URLEncodeUtil.encode(Base64.encode(DigestUtil.md5Hex(plainText)));
  128 + }
  129 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/impl/KdNiaoExpressQueryProviderTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.impl;
  2 +
  3 +import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressQueryProperties;
  4 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryReqDTO;
  5 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryRespDTO;
  6 +import org.junit.jupiter.api.BeforeEach;
  7 +import org.junit.jupiter.api.Test;
  8 +import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
  9 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  10 +import org.springframework.boot.test.context.SpringBootTest;
  11 +import org.springframework.boot.web.client.RestTemplateBuilder;
  12 +import org.springframework.context.annotation.Import;
  13 +import org.springframework.test.context.ActiveProfiles;
  14 +
  15 +import javax.annotation.Resource;
  16 +import java.util.List;
  17 +
  18 +import static org.junit.jupiter.api.Assertions.*;
  19 +
  20 +/**
  21 + * @author jason
  22 + */
  23 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = KdNiaoExpressQueryProviderTest.Application.class)
  24 +@ActiveProfiles("trade-delivery-query") // 设置使用 trade-delivery-query 配置文件
  25 +public class KdNiaoExpressQueryProviderTest {
  26 + @Resource
  27 + private RestTemplateBuilder builder;
  28 + @Resource
  29 + private TradeExpressQueryProperties expressQueryProperties;
  30 +
  31 + private KdNiaoExpressQueryProvider kdNiaoExpressQueryProvider;
  32 +
  33 + @BeforeEach
  34 + public void init(){
  35 + kdNiaoExpressQueryProvider = new KdNiaoExpressQueryProvider(builder.build(),expressQueryProperties.getKdNiao());
  36 + }
  37 + @Test
  38 + void queryExpress() {
  39 + ExpressQueryReqDTO reqDTO = new ExpressQueryReqDTO();
  40 + reqDTO.setExpressCompanyCode("yto");
  41 + reqDTO.setLogisticsNo("YT1764381060802");
  42 + List<ExpressQueryRespDTO> expressQueryRespDTOS = kdNiaoExpressQueryProvider.realTimeQueryExpress(reqDTO);
  43 + assertNotNull(expressQueryRespDTOS);
  44 + }
  45 +
  46 + @Import({
  47 + RestTemplateAutoConfiguration.class
  48 + })
  49 + @EnableConfigurationProperties(TradeExpressQueryProperties.class)
  50 + public static class Application {
  51 + }
  52 +}
0 53 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/resources/application-trade-delivery-query.yaml 0 → 100644
  1 +spring:
  2 + main:
  3 + lazy-initialization: true # 开启懒加载,加快速度
  4 + banner-mode: off # 单元测试,禁用 Banner
  5 +
  6 +--- #################### 数据库相关配置 ####################
  7 +
  8 +yudao:
  9 + trade:
  10 + express:
  11 + query:
  12 + express-query-provider: kd_niao
  13 + kd-niao:
  14 + api-key: 8e22d97d-6a3d-442e-b243-2190c9f0cfdd
  15 + business-id: 1801700
0 16 \ No newline at end of file
... ...