Commit 0c283ded5dd67e656e6abe2c112ac7bc2a81b5ff

Authored by YunaiV
2 parents b6c7a940 644d976a

Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

Showing 22 changed files with 918 additions and 1 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
... ... @@ -4,6 +4,7 @@ import cn.hutool.core.util.ArrayUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.hutool.json.JSONUtil;
6 6 import com.fasterxml.jackson.core.type.TypeReference;
  7 +import com.fasterxml.jackson.databind.DeserializationFeature;
7 8 import com.fasterxml.jackson.databind.JsonNode;
8 9 import com.fasterxml.jackson.databind.ObjectMapper;
9 10 import com.fasterxml.jackson.databind.SerializationFeature;
... ... @@ -29,6 +30,7 @@ public class JsonUtils {
29 30  
30 31 static {
31 32 objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
  33 + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
32 34 objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
33 35 }
34 36  
... ...
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,9 @@ 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_ERROR = new ErrorCode(1011003006, "快递查询接口异常");
  57 + ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1011003007, "快递查询返回失败, 原因:{}");
  58 + ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011003008, "自提门店不存在");
57 59  
58 60 // ========== Price 相关 1011004000 ============
59 61 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 配置项
  59 + */
  60 + @Data
  61 + public static class Kd100Config {
  62 + /**
  63 + * 快递 100 授权码
  64 + */
  65 + @NotEmpty(message = "快递 100 授权码配置项不能为空")
  66 + private String customer;
  67 + /**
  68 + * 快递 100 授权 key
  69 + */
  70 + @NotEmpty(message = "快递 100 授权 Key 配置项不能为空")
  71 + private String key;
  72 + }
  73 +
  74 +
  75 +}
... ...
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.kd100.Kd100ExpressQueryReqDTO;
  6 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kd100.Kd100ExpressQueryRespDTO;
  7 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kdniao.KdNiaoExpressQueryReqDTO;
  8 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kdniao.KdNiaoExpressQueryRespDTO;
  9 +import org.mapstruct.Mapper;
  10 +import org.mapstruct.factory.Mappers;
  11 +
  12 +import java.util.List;
  13 +
  14 +@Mapper
  15 +public interface ExpressQueryConvert {
  16 +
  17 + ExpressQueryConvert INSTANCE = Mappers.getMapper(ExpressQueryConvert.class);
  18 +
  19 + List<ExpressQueryRespDTO> convertList(List<KdNiaoExpressQueryRespDTO.ExpressTrack> expressTrackList);
  20 +
  21 + List<ExpressQueryRespDTO> convertList2(List<Kd100ExpressQueryRespDTO.ExpressTrack> expressTrackList);
  22 +
  23 + KdNiaoExpressQueryReqDTO convert(ExpressQueryReqDTO dto);
  24 +
  25 + Kd100ExpressQueryReqDTO convert2(ExpressQueryReqDTO dto);
  26 +}
... ...
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/kd100/Kd100ExpressQueryReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kd100;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonInclude;
  4 +import com.fasterxml.jackson.annotation.JsonProperty;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * 快递 100 快递查询 Req DTO
  9 + *
  10 + * @author jason
  11 + */
  12 +@Data
  13 +@JsonInclude(JsonInclude.Include.NON_NULL)
  14 +public class Kd100ExpressQueryReqDTO {
  15 +
  16 + /**
  17 + * 快递公司编码
  18 + */
  19 + @JsonProperty("com")
  20 + private String expressCompanyCode;
  21 +
  22 + /**
  23 + * 快递单号
  24 + */
  25 + @JsonProperty("num")
  26 + private String logisticsNo;
  27 +
  28 + /**
  29 + * 收、寄件人的电话号码
  30 + */
  31 + private String phone;
  32 +
  33 + /**
  34 + * 出发地城市
  35 + */
  36 + private String from;
  37 +
  38 + /**
  39 + * 目的地城市,到达目的地后会加大监控频率
  40 + */
  41 + private String to;
  42 +
  43 + /**
  44 + * 返回结果排序:desc降序(默认),asc 升序
  45 + */
  46 + private String order;
  47 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/delivery/core/dto/provider/kd100/Kd100ExpressQueryRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kd100;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * 快递 100 实时快递查询 Resp DTO 参见 <a href="https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc">快递 100 文档</a>
  10 + *
  11 + * @author jason
  12 + */
  13 +@Data
  14 +public class Kd100ExpressQueryRespDTO {
  15 +
  16 + /**
  17 + * 快递公司编码
  18 + */
  19 + @JsonProperty("com")
  20 + private String expressCompanyCode;
  21 +
  22 + /**
  23 + * 快递单号
  24 + */
  25 + @JsonProperty("nu")
  26 + private String logisticsNo;
  27 +
  28 + /**
  29 + * 快递单当前状态
  30 + */
  31 + private String state;
  32 +
  33 + /**
  34 + * 查询结果, 失败返回 "false"
  35 + */
  36 + private String result;
  37 +
  38 + /**
  39 + * 查询结果失败时的错误信息
  40 + */
  41 + private String message;
  42 +
  43 + @JsonProperty("data")
  44 + private List<ExpressTrack> tracks;
  45 +
  46 + @Data
  47 + public static class ExpressTrack {
  48 + /**
  49 + * 轨迹发生时间
  50 + */
  51 + @JsonProperty("time")
  52 + private String time;
  53 + /**
  54 + * 轨迹描述
  55 + */
  56 + @JsonProperty("context")
  57 + private String state;
  58 + }
  59 +}
... ...
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.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.HexUtil;
  5 +import cn.hutool.crypto.digest.DigestUtil;
  6 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  7 +import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressQueryProperties;
  8 +import cn.iocoder.yudao.module.trade.framework.delivery.core.ExpressQueryProvider;
  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 cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kd100.Kd100ExpressQueryReqDTO;
  12 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.provider.kd100.Kd100ExpressQueryRespDTO;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +import org.springframework.http.*;
  15 +import org.springframework.util.LinkedMultiValueMap;
  16 +import org.springframework.util.MultiValueMap;
  17 +import org.springframework.web.client.RestTemplate;
  18 +
  19 +import java.util.Collections;
  20 +import java.util.List;
  21 +import java.util.Objects;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  24 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_API_QUERY_FAILED;
  25 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_API_QUERY_ERROR;
  26 +import static cn.iocoder.yudao.module.trade.framework.delivery.core.convert.ExpressQueryConvert.INSTANCE;
  27 +
  28 +/**
  29 + * 快递 100 服务商
  30 + *
  31 + * @author jason
  32 + */
  33 +@Slf4j
  34 +public class Kd100ExpressQueryProvider implements ExpressQueryProvider {
  35 +
  36 + private static final String REAL_TIME_QUERY_URL = "https://poll.kuaidi100.com/poll/query.do";
  37 + private final RestTemplate restTemplate;
  38 +
  39 + private final TradeExpressQueryProperties.Kd100Config config;
  40 +
  41 + public Kd100ExpressQueryProvider(RestTemplate restTemplate, TradeExpressQueryProperties.Kd100Config config) {
  42 + this.restTemplate = restTemplate;
  43 + this.config = config;
  44 + }
  45 +
  46 + @Override
  47 + public List<ExpressQueryRespDTO> realTimeQueryExpress(ExpressQueryReqDTO reqDTO) {
  48 + Kd100ExpressQueryReqDTO kd100ReqParam = INSTANCE.convert2(reqDTO);
  49 + // 快递公司编码需要转成小写
  50 + kd100ReqParam.setExpressCompanyCode(kd100ReqParam.getExpressCompanyCode().toLowerCase());
  51 + Kd100ExpressQueryRespDTO respDTO = sendExpressQueryReq(REAL_TIME_QUERY_URL, kd100ReqParam,
  52 + Kd100ExpressQueryRespDTO.class);
  53 + log.debug("快递 100 接口 查询接口返回 {}", respDTO);
  54 + if (Objects.equals("false", respDTO.getResult())) {
  55 + log.error("快递 100 接口 返回失败 {} ", respDTO.getMessage());
  56 + throw exception(EXPRESS_API_QUERY_FAILED, respDTO.getMessage());
  57 + } else {
  58 + if (CollUtil.isNotEmpty(respDTO.getTracks())) {
  59 + return INSTANCE.convertList2(respDTO.getTracks());
  60 + } else {
  61 + return Collections.emptyList();
  62 + }
  63 + }
  64 + }
  65 +
  66 + /**
  67 + * 发送快递 100 实时快递查询请求,可以作为通用快递 100 通用请求接口。 目前没有其它场景需要使用。暂时放这里
  68 + * @param url 请求 url
  69 + * @param req 对应请求的请求参数
  70 + * @param respClass 对应请求的响应 class
  71 + * @param <Req> 每个请求的请求结构 Req DTO
  72 + * @param <Resp> 每个请求的响应结构 Resp DTO
  73 + */
  74 + private <Req, Resp> Resp sendExpressQueryReq(String url, Req req, Class<Resp> respClass) {
  75 + // 请求头
  76 + HttpHeaders headers = new HttpHeaders();
  77 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  78 + // 生成签名
  79 + String param = JsonUtils.toJsonString(req);
  80 + String sign = generateReqSign(param, config.getKey(), config.getCustomer());
  81 + log.debug("快递 100 快递 接口生成签名的: {}", sign);
  82 + // 请求体
  83 + MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
  84 + requestBody.add("customer", config.getCustomer());
  85 + requestBody.add("sign", sign);
  86 + requestBody.add("param", param);
  87 + log.debug("快递 100 接口的请求参数: {}", requestBody);
  88 +
  89 + HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
  90 + // 发送请求
  91 + ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
  92 + log.debug("快递 100 接口响应结果 {}", responseEntity);
  93 + // 处理响应
  94 + if (responseEntity.getStatusCode().is2xxSuccessful()) {
  95 + String response = responseEntity.getBody();
  96 + return JsonUtils.parseObject(response, respClass);
  97 + } else {
  98 + throw exception(EXPRESS_API_QUERY_ERROR);
  99 + }
  100 + }
  101 +
  102 + private String generateReqSign(String param, String key, String customer) {
  103 + String plainText = String.format("%s%s%s", param, key, customer);
  104 + log.debug("快递 100 接口待签名的数据 {}", plainText);
  105 + return HexUtil.encodeHexStr(DigestUtil.md5(plainText), false);
  106 + }
  107 +}
... ...
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 lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.http.*;
  16 +import org.springframework.util.LinkedMultiValueMap;
  17 +import org.springframework.util.MultiValueMap;
  18 +import org.springframework.web.client.RestTemplate;
  19 +
  20 +import java.util.Collections;
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  24 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_API_QUERY_FAILED;
  25 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_API_QUERY_ERROR;
  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()){
  61 + throw exception(EXPRESS_API_QUERY_FAILED, respDTO.getReason());
  62 + }else{
  63 + if (CollUtil.isNotEmpty(respDTO.getTracks())) {
  64 + return INSTANCE.convertList(respDTO.getTracks());
  65 + }else{
  66 + return Collections.emptyList();
  67 + }
  68 + }
  69 + }
  70 +
  71 + /**
  72 + * 快递鸟 通用的 API 请求, 暂时没有其他应用场景, 暂时放这里
  73 + * @param url 请求 url
  74 + * @param requestType 对应的请求指令 (快递鸟的RequestType)
  75 + * @param req 对应请求的请求参数
  76 + * @param respClass 对应请求的响应 class
  77 + * @param <Req> 每个请求的请求结构 Req DTO
  78 + * @param <Resp> 每个请求的响应结构 Resp DTO
  79 + */
  80 + private <Req, Resp> Resp sendKdNiaoApiRequest(String url, String requestType, Req req,
  81 + Class<Resp> respClass){
  82 + // 请求头
  83 + HttpHeaders headers = new HttpHeaders();
  84 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  85 + // 请求体
  86 + String reqData = JsonUtils.toJsonString(req);
  87 + String dataSign = generateDataSign(reqData, config.getApiKey());
  88 + log.trace("得到快递鸟接口 RequestType : {} 的 签名: {}", requestType, dataSign);
  89 + MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
  90 + requestBody.add("RequestData", reqData);
  91 + requestBody.add("DataType", "2");
  92 + requestBody.add("EBusinessID", config.getBusinessId());
  93 + requestBody.add("DataSign", dataSign);
  94 + requestBody.add("RequestType", requestType);
  95 + log.debug("快递鸟接口 RequestType : {}, 的请求参数 {}", requestType, requestBody);
  96 + HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
  97 + // 发送请求
  98 + ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
  99 + log.debug("快递鸟接口 RequestType : {}, 的响应结果 {}", requestType, responseEntity);
  100 + // 处理响应
  101 + if (responseEntity.getStatusCode().is2xxSuccessful()) {
  102 + String response = responseEntity.getBody();
  103 + return JsonUtils.parseObject(response, respClass);
  104 + } else {
  105 + throw exception(EXPRESS_API_QUERY_ERROR);
  106 + }
  107 + }
  108 +
  109 + /**
  110 + * 快递鸟生成请求签名 参见 <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/zes04h">签名说明</a>
  111 + * @param reqData 请求实体
  112 + * @param apiKey api Key
  113 + */
  114 + private String generateDataSign(String reqData, String apiKey) {
  115 + String plainText = String.format("%s%s", reqData, apiKey);
  116 + log.trace("签名前的数据 {}", plainText);
  117 + return URLEncodeUtil.encode(Base64.encode(DigestUtil.md5Hex(plainText)));
  118 + }
  119 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/impl/Kd100ExpressQueryProviderTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.framework.delivery.core.impl;
  2 +
  3 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
  4 +import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressQueryProperties;
  5 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryReqDTO;
  6 +import org.junit.jupiter.api.BeforeEach;
  7 +import org.junit.jupiter.api.Disabled;
  8 +import org.junit.jupiter.api.Test;
  9 +import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
  10 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  11 +import org.springframework.boot.test.context.SpringBootTest;
  12 +import org.springframework.boot.web.client.RestTemplateBuilder;
  13 +import org.springframework.context.annotation.Import;
  14 +import org.springframework.test.context.ActiveProfiles;
  15 +
  16 +import javax.annotation.Resource;
  17 +
  18 +import static org.junit.jupiter.api.Assertions.assertEquals;
  19 +import static org.junit.jupiter.api.Assertions.assertThrows;
  20 +
  21 +/**
  22 + * @author jason
  23 + */
  24 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = Kd100ExpressQueryProviderTest.Application.class)
  25 +@ActiveProfiles("trade-delivery-query") // 设置使用 trade-delivery-query 配置文件
  26 +public class Kd100ExpressQueryProviderTest {
  27 + @Resource
  28 + private RestTemplateBuilder builder;
  29 + @Resource
  30 + private TradeExpressQueryProperties expressQueryProperties;
  31 +
  32 + private Kd100ExpressQueryProvider kd100ExpressQueryProvider;
  33 +
  34 + @BeforeEach
  35 + public void init(){
  36 + kd100ExpressQueryProvider = new Kd100ExpressQueryProvider(builder.build(),expressQueryProperties.getKd100());
  37 + }
  38 + @Test
  39 + @Disabled("需要 授权 key. 暂时忽略")
  40 + void testRealTimeQueryExpressFailed() {
  41 + ServiceException t = assertThrows(ServiceException.class, () -> {
  42 + ExpressQueryReqDTO reqDTO = new ExpressQueryReqDTO();
  43 + reqDTO.setExpressCompanyCode("yto");
  44 + reqDTO.setLogisticsNo("YT9383342193097");
  45 + kd100ExpressQueryProvider.realTimeQueryExpress(reqDTO);
  46 + });
  47 + assertEquals(1011003007, t.getCode());
  48 + }
  49 +
  50 + @Import({
  51 + RestTemplateAutoConfiguration.class
  52 + })
  53 + @EnableConfigurationProperties(TradeExpressQueryProperties.class)
  54 + public static class Application {
  55 + }
  56 +}
0 57 \ No newline at end of file
... ...
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.framework.common.exception.ServiceException;
  4 +import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressQueryProperties;
  5 +import cn.iocoder.yudao.module.trade.framework.delivery.core.dto.ExpressQueryReqDTO;
  6 +import org.junit.jupiter.api.BeforeEach;
  7 +import org.junit.jupiter.api.Disabled;
  8 +import org.junit.jupiter.api.Test;
  9 +import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
  10 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  11 +import org.springframework.boot.test.context.SpringBootTest;
  12 +import org.springframework.boot.web.client.RestTemplateBuilder;
  13 +import org.springframework.context.annotation.Import;
  14 +import org.springframework.test.context.ActiveProfiles;
  15 +
  16 +import javax.annotation.Resource;
  17 +
  18 +import static org.junit.jupiter.api.Assertions.assertThrows;
  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 + @Disabled("需要 授权 key. 暂时忽略")
  39 + void testRealTimeQueryExpressFailed() {
  40 + assertThrows(ServiceException.class,() ->{
  41 + ExpressQueryReqDTO reqDTO = new ExpressQueryReqDTO();
  42 + reqDTO.setExpressCompanyCode("yy");
  43 + reqDTO.setLogisticsNo("YT9383342193097");
  44 + kdNiaoExpressQueryProvider.realTimeQueryExpress(reqDTO);
  45 + });
  46 + }
  47 +
  48 + @Import({
  49 + RestTemplateAutoConfiguration.class
  50 + })
  51 + @EnableConfigurationProperties(TradeExpressQueryProperties.class)
  52 + public static class Application {
  53 + }
  54 +}
0 55 \ 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: xxx
  15 + business-id: xxxxxxxx
  16 + kd100:
  17 + customer: xxxx
  18 + key: xxxxx
... ...