订单:

1. 增加查询物流接口
This commit is contained in:
YunaiV 2023-08-15 20:18:22 +08:00
parent 36da5d69b0
commit e4a2c738b2
22 changed files with 338 additions and 266 deletions

View File

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

View File

@ -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()));
}

View File

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

View File

@ -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.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; 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.delivery.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderService;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@ -45,6 +46,8 @@ public class AppTradeOrderController {
@Resource @Resource
private TradeOrderService tradeOrderService; private TradeOrderService tradeOrderService;
@Resource @Resource
private TradeOrderQueryService tradeOrderQueryService;
@Resource
private DeliveryExpressService deliveryExpressService; private DeliveryExpressService deliveryExpressService;
@Resource @Resource
@ -99,6 +102,14 @@ public class AppTradeOrderController {
propertyValueDetails, tradeOrderProperties, express)); 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") @GetMapping("/page")
@Operation(summary = "获得交易订单分页") @Operation(summary = "获得交易订单分页")
public CommonResult<PageResult<AppTradeOrderPageItemRespVO>> getOrderPage(AppTradeOrderPageReqVO reqVO) { public CommonResult<PageResult<AppTradeOrderPageItemRespVO>> getOrderPage(AppTradeOrderPageReqVO reqVO) {

View File

@ -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;
}

View File

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

View File

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

View File

@ -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.KdNiaoExpressQueryReqDTO;
import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.kdniao.KdNiaoExpressQueryRespDTO; import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.kdniao.KdNiaoExpressQueryRespDTO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import java.util.List; import java.util.List;
@ -16,9 +17,14 @@ public interface ExpressQueryConvert {
ExpressQueryConvert INSTANCE = Mappers.getMapper(ExpressQueryConvert.class); 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); KdNiaoExpressQueryReqDTO convert(ExpressTrackQueryReqDTO dto);

View File

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

View File

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

View File

@ -1,12 +1,19 @@
package cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.kd100; 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 com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime;
import java.util.List; 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 * @author jason
*/ */
@ -39,21 +46,26 @@ public class Kd100ExpressQueryRespDTO {
*/ */
private String message; private String message;
/**
* 轨迹数组
*/
@JsonProperty("data") @JsonProperty("data")
private List<ExpressTrack> tracks; private List<ExpressTrack> tracks;
@Data @Data
public static class ExpressTrack { public static class ExpressTrack {
/** /**
* 轨迹发生时间 * 轨迹发生时间
*/ */
@JsonProperty("time") @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private String time; private LocalDateTime time;
/** /**
* 轨迹描述 * 轨迹描述
*/ */
@JsonProperty("context") private String context;
private String state;
} }
} }

View File

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

View File

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

View File

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

View File

@ -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);
}

View File

@ -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()));
}
}

View File

@ -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));
}
}

View File

@ -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 {
}
}

View File

@ -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));
}
}

View File

@ -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 {
}
}

View File

@ -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();
}
}
}

View File

@ -204,6 +204,14 @@ yudao:
order: order:
app-id: 1 # 商户编号 app-id: 1 # 商户编号
expire-time: 2h # 支付的过期时间 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 debug: false