Commit 5cfcaa1a6ea6a80e307fc667364b5f20cd4bf6a9

Authored by puhui999
2 parents 9ff873fe dcb16608

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

# Conflicts:
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/AppCommentController.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppProductCommentBaseVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppProductCommentRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
#	yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java
Showing 57 changed files with 1539 additions and 98 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-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperX.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.mybatis.core.query;
2 2  
3 3 import cn.hutool.core.util.ArrayUtil;
4 4 import cn.hutool.core.util.ObjectUtil;
5   -import cn.hutool.core.util.StrUtil;
6 5 import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
7 6 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
8 7 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
... ...
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;
... ... @@ -113,4 +116,13 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
113 116 return bean;
114 117 }
115 118  
  119 + /**
  120 + * 创建 RestTemplate 实例
  121 + *
  122 + * @param restTemplateBuilder {@link RestTemplateAutoConfiguration#restTemplateBuilder}
  123 + */
  124 + @Bean
  125 + public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
  126 + return restTemplateBuilder.build();
  127 + }
116 128 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppProductCommentBaseVO.java
1 1 package cn.iocoder.yudao.module.product.controller.app.comment.vo;
2 2  
  3 +import cn.iocoder.yudao.module.product.controller.app.property.vo.value.AppProductPropertyValueDetailRespVO;
3 4 import io.swagger.v3.oas.annotations.media.Schema;
4 5 import lombok.Data;
5 6  
... ... @@ -27,6 +28,9 @@ public class AppProductCommentBaseVO {
27 28 @NotNull(message = "商品SKU编号不能为空")
28 29 private Long skuId;
29 30  
  31 + @Schema(description = "商品 SKU 属性", required = true)
  32 + private List<AppProductPropertyValueDetailRespVO> skuProperties; // TODO puhui999:这个需要从数据库查询哈
  33 +
30 34 @Schema(description = "评分星级 1-5分", required = true, example = "5")
31 35 @NotNull(message = "评分星级 1-5分不能为空")
32 36 private Integer scores;
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppProductCommentRespVO.java
... ... @@ -9,8 +9,6 @@ import org.springframework.format.annotation.DateTimeFormat;
9 9 import java.time.LocalDateTime;
10 10 import java.util.List;
11 11  
12   -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
13   -
14 12 @Schema(description = "用户APP - 商品评价 Response VO")
15 13 @Data
16 14 @EqualsAndHashCode(callSuper = true)
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
... ... @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.product.controller.app.spu;
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
6   -import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
  6 +import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageRespVO;
7 7 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
8 8 import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
9 9 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
... ... @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
13 13 import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
14 14 import io.swagger.v3.oas.annotations.Operation;
15 15 import io.swagger.v3.oas.annotations.Parameter;
  16 +import io.swagger.v3.oas.annotations.Parameters;
16 17 import io.swagger.v3.oas.annotations.tags.Tag;
17 18 import org.springframework.validation.annotation.Validated;
18 19 import org.springframework.web.bind.annotation.GetMapping;
... ... @@ -40,9 +41,22 @@ public class AppProductSpuController {
40 41 @Resource
41 42 private ProductSkuService productSkuService;
42 43  
  44 + @GetMapping("/list")
  45 + @Operation(summary = "获得商品 SPU 列表")
  46 + @Parameters({
  47 + @Parameter(name = "recommendType", description = "推荐类型", required = true), // 参见 AppProductSpuPageReqVO.RECOMMEND_TYPE_XXX 常量
  48 + @Parameter(name = "count", description = "数量", required = true)
  49 + })
  50 + public CommonResult<List<AppProductSpuPageRespVO>> getSpuList(
  51 + @RequestParam("recommendType") String recommendType,
  52 + @RequestParam(value = "count", defaultValue = "10") Integer count) {
  53 + List<ProductSpuDO> list = productSpuService.getSpuList(recommendType, count);
  54 + return success(ProductSpuConvert.INSTANCE.convertListForGetSpuList(list));
  55 + }
  56 +
43 57 @GetMapping("/page")
44 58 @Operation(summary = "获得商品 SPU 分页")
45   - public CommonResult<PageResult<AppProductSpuPageItemRespVO>> getSpuPage(@Valid AppProductSpuPageReqVO pageVO) {
  59 + public CommonResult<PageResult<AppProductSpuPageRespVO>> getSpuPage(@Valid AppProductSpuPageReqVO pageVO) {
46 60 PageResult<ProductSpuDO> pageResult = productSpuService.getSpuPage(pageVO);
47 61 return success(ProductSpuConvert.INSTANCE.convertPageForGetSpuPage(pageResult));
48 62 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuDetailRespVO.java
... ... @@ -39,6 +39,11 @@ public class AppProductSpuDetailRespVO {
39 39 @Schema(description = "单位名", required = true, example = "个")
40 40 private String unitName;
41 41  
  42 + // ========== 营销相关字段 =========
  43 +
  44 + @Schema(description = "活动排序数组", required = true, example = "1024")
  45 + private List<Integer> activityOrders;
  46 +
42 47 // ========== SKU 相关字段 =========
43 48  
44 49 @Schema(description = "规格类型", required = true, example = "true")
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageReqVO.java
... ... @@ -20,6 +20,7 @@ public class AppProductSpuPageReqVO extends PageParam {
20 20 public static final String SORT_FIELD_SALES_COUNT = "salesCount";
21 21  
22 22 public static final String RECOMMEND_TYPE_HOT = "hot";
  23 + public static final String RECOMMEND_TYPE_GOOD = "good";
23 24  
24 25 @Schema(description = "分类编号", example = "1")
25 26 private Long categoryId;
... ... @@ -33,7 +34,7 @@ public class AppProductSpuPageReqVO extends PageParam {
33 34 @Schema(description = "排序方式", example = "true")
34 35 private Boolean sortAsc;
35 36  
36   - @Schema(description = "推荐类型", example = "hot") // 参见 AppProductSpuPageReqVO.RECOMMEND_TYPE_XXX 常亮
  37 + @Schema(description = "推荐类型", example = "hot") // 参见 AppProductSpuPageReqVO.RECOMMEND_TYPE_XXX 常量
37 38 private String recommendType;
38 39  
39 40 @AssertTrue(message = "排序字段不合法")
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java renamed to yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageRespVO.java
... ... @@ -5,9 +5,9 @@ import lombok.Data;
5 5  
6 6 import java.util.List;
7 7  
8   -@Schema(description = "用户 App - 商品 SPU 分页项 Response VO")
  8 +@Schema(description = "用户 App - 商品 SPU Response VO")
9 9 @Data
10   -public class AppProductSpuPageItemRespVO {
  10 +public class AppProductSpuPageRespVO {
11 11  
12 12 @Schema(description = "商品 SPU 编号", required = true, example = "1")
13 13 private Long id;
... ... @@ -35,6 +35,11 @@ public class AppProductSpuPageItemRespVO {
35 35 @Schema(description = "库存", required = true, example = "666")
36 36 private Integer stock;
37 37  
  38 + // ========== 营销相关字段 =========
  39 +
  40 + @Schema(description = "活动排序数组", required = true, example = "1024")
  41 + private List<Integer> activityOrders;
  42 +
38 43 // ========== 统计相关字段 =========
39 44  
40 45 @Schema(description = "商品销量", required = true, example = "1024")
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
1 1 package cn.iocoder.yudao.module.product.convert.spu;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.hutool.core.util.StrUtil;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
7 6 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
8   -import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
9 7 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
10 8 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
11   -import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
  9 +import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageRespVO;
12 10 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
13 11 import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
14 12 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
... ... @@ -80,14 +78,15 @@ public interface ProductSpuConvert {
80 78  
81 79 // ========== 用户 App 相关 ==========
82 80  
83   - default PageResult<AppProductSpuPageItemRespVO> convertPageForGetSpuPage(PageResult<ProductSpuDO> page) {
84   - // 累加虚拟销量
85   - page.getList().forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount()));
86   - // 然后进行转换
87   - return convertPageForGetSpuPage0(page);
88   - }
  81 + PageResult<AppProductSpuPageRespVO> convertPageForGetSpuPage(PageResult<ProductSpuDO> page);
89 82  
90   - PageResult<AppProductSpuPageItemRespVO> convertPageForGetSpuPage0(PageResult<ProductSpuDO> page);
  83 + default List<AppProductSpuPageRespVO> convertListForGetSpuList(List<ProductSpuDO> list) {
  84 + // 处理虚拟销量
  85 + list.forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount()));
  86 + return convertListForGetSpuList0(list);
  87 + }
  88 + @Named("convertListForGetSpuList0")
  89 + List<AppProductSpuPageRespVO> convertListForGetSpuList0(List<ProductSpuDO> list);
91 90  
92 91 default AppProductSpuDetailRespVO convertForGetSpuDetail(ProductSpuDO spu, List<ProductSkuDO> skus) {
93 92 // 处理 SPU
... ... @@ -109,15 +108,9 @@ public interface ProductSpuConvert {
109 108 List<AppProductSpuDetailRespVO.Sku> convertListForGetSpuDetail(List<ProductSkuDO> skus);
110 109  
111 110 default ProductSpuDetailRespVO convertForSpuDetailRespVO(ProductSpuDO spu, List<ProductSkuDO> skus) {
112   - ProductSpuDetailRespVO productSpuDetailRespVO = convert03(spu);
113   - // skus 为空直接返回
114   - if (CollUtil.isEmpty(skus)) {
115   - return productSpuDetailRespVO;
116   - }
117   - List<ProductSkuRespVO> skuVOs = ProductSkuConvert.INSTANCE.convertList(skus);
118   - // fix: 因为现在已改为 sku 属性列表 属性 已包含 属性名字 属性值名字 所以不需要再额外处理,属性更新时更新 sku 中的属性相关冗余即可
119   - productSpuDetailRespVO.setSkus(skuVOs);
120   - return productSpuDetailRespVO;
  111 + ProductSpuDetailRespVO detailRespVO = convert03(spu);
  112 + detailRespVO.setSkus(ProductSkuConvert.INSTANCE.convertList(skus));
  113 + return detailRespVO;
121 114 }
122 115  
123 116 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/comment/ProductCommentDO.java
... ... @@ -40,7 +40,7 @@ public class ProductCommentDO extends BaseDO {
40 40 private Long id;
41 41  
42 42 /**
43   - * 评价人 用户编号
  43 + * 评价人的用户编号
44 44 *
45 45 * 关联 MemberUserDO 的 id 编号
46 46 */
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java
... ... @@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
7 7 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  8 +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
8 9 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuExportReqVO;
9 10 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO;
10 11 import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
... ... @@ -66,7 +67,10 @@ public interface ProductSpuMapper extends BaseMapperX&lt;ProductSpuDO&gt; {
66 67 // 推荐类型的过滤条件
67 68 if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_HOT)) {
68 69 query.eq(ProductSpuDO::getRecommendHot, true);
  70 + } else if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_GOOD)) {
  71 + query.eq(ProductSpuDO::getRecommendGood, true);
69 72 }
  73 +
70 74 // 排序逻辑
71 75 if (Objects.equals(pageReqVO.getSortField(), AppProductSpuPageReqVO.SORT_FIELD_SALES_COUNT)) {
72 76 query.last(String.format(" ORDER BY (sales_count + virtual_sales_count) %s, sort DESC, id DESC",
... ... @@ -80,6 +84,21 @@ public interface ProductSpuMapper extends BaseMapperX&lt;ProductSpuDO&gt; {
80 84 return selectPage(pageReqVO, query);
81 85 }
82 86  
  87 + default List<ProductSpuDO> selectListByRecommendType(String recommendType, Integer count) {
  88 + QueryWrapperX<ProductSpuDO> query = new QueryWrapperX<>();
  89 + // 上架状态 且有库存
  90 + query.eq("status", ProductSpuStatusEnum.ENABLE.getStatus()).gt("stock", 0);
  91 + // 推荐类型的过滤条件
  92 + if (ObjUtil.equal(recommendType, AppProductSpuPageReqVO.RECOMMEND_TYPE_HOT)) {
  93 + query.eq("recommend_hot", true);
  94 + } else if (ObjUtil.equal(recommendType, AppProductSpuPageReqVO.RECOMMEND_TYPE_GOOD)) {
  95 + query.eq("recommend_good", true);
  96 + }
  97 + // 设置最大长度
  98 + query.limitN(count);
  99 + return selectList(query);
  100 + }
  101 +
83 102 /**
84 103 * 更新商品 SPU 库存
85 104 *
... ... @@ -111,33 +130,34 @@ public interface ProductSpuMapper extends BaseMapperX&lt;ProductSpuDO&gt; {
111 130 }
112 131  
113 132 /**
114   - * 验证选项卡类型构建条件
  133 + * 添加后台 Tab 选项的查询条件
115 134 *
116 135 * @param tabType 标签类型
117   - * @param queryWrapper 查询条件
  136 + * @param query 查询条件
118 137 */
119   - static void appendTabQuery(Integer tabType, LambdaQueryWrapperX<ProductSpuDO> queryWrapper) {
  138 + static void appendTabQuery(Integer tabType, LambdaQueryWrapperX<ProductSpuDO> query) {
120 139 // 出售中商品
121 140 if (ObjectUtil.equals(ProductSpuPageReqVO.FOR_SALE, tabType)) {
122   - queryWrapper.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus());
  141 + query.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus());
123 142 }
124 143 // 仓储中商品
125 144 if (ObjectUtil.equals(ProductSpuPageReqVO.IN_WAREHOUSE, tabType)) {
126   - queryWrapper.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus());
  145 + query.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus());
127 146 }
128 147 // 已售空商品
129 148 if (ObjectUtil.equals(ProductSpuPageReqVO.SOLD_OUT, tabType)) {
130   - queryWrapper.eqIfPresent(ProductSpuDO::getStock, 0);
  149 + query.eqIfPresent(ProductSpuDO::getStock, 0);
131 150 }
132 151 // 警戒库存
133 152 if (ObjectUtil.equals(ProductSpuPageReqVO.ALERT_STOCK, tabType)) {
134   - queryWrapper.le(ProductSpuDO::getStock, ProductConstants.ALERT_STOCK)
  153 + query.le(ProductSpuDO::getStock, ProductConstants.ALERT_STOCK)
135 154 // 如果库存触发警戒库存且状态为回收站的话则不在警戒库存列表展示
136 155 .notIn(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus());
137 156 }
138 157 // 回收站
139 158 if (ObjectUtil.equals(ProductSpuPageReqVO.RECYCLE_BIN, tabType)) {
140   - queryWrapper.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus());
  159 + query.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus());
141 160 }
142 161 }
  162 +
143 163 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
... ... @@ -99,6 +99,15 @@ public interface ProductSpuService {
99 99 PageResult<ProductSpuDO> getSpuPage(AppProductSpuPageReqVO pageReqVO);
100 100  
101 101 /**
  102 + * 获得商品 SPU 列表,提供给用户 App 使用
  103 + *
  104 + * @param recommendType 推荐类型
  105 + * @param count 数量
  106 + * @return 商品 SPU 列表
  107 + */
  108 + List<ProductSpuDO> getSpuList(String recommendType, Integer count);
  109 +
  110 + /**
102 111 * 更新商品 SPU 库存(增量)
103 112 *
104 113 * @param stockIncrCounts SPU 编号与库存变化(增量)的映射
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
... ... @@ -202,6 +202,11 @@ public class ProductSpuServiceImpl implements ProductSpuService {
202 202 }
203 203  
204 204 @Override
  205 + public List<ProductSpuDO> getSpuList(String recommendType, Integer count) {
  206 + return productSpuMapper.selectListByRecommendType(recommendType, count);
  207 + }
  208 +
  209 + @Override
205 210 @Transactional(rollbackFor = Exception.class)
206 211 public void updateSpuStock(Map<Long, Integer> stockIncrCounts) {
207 212 stockIncrCounts.forEach((id, incCount) -> productSpuMapper.updateStock(id, incCount));
... ...
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java
... ... @@ -192,9 +192,4 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
192 192 assertEquals("测试", productCommentDO.getReplyContent());
193 193 }
194 194  
195   - @Test
196   - public void testCreateComment_success() {
197   - // mock 测试
198   - }
199   -
200 195 }
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/common/PromotionTypeEnum.java
... ... @@ -15,11 +15,15 @@ import java.util.Arrays;
15 15 @AllArgsConstructor
16 16 public enum PromotionTypeEnum implements IntArrayValuable {
17 17  
18   - DISCOUNT_ACTIVITY(1, "限时折扣"),
19   - REWARD_ACTIVITY(2, "满减送"),
  18 + SECKILL_ACTIVITY(1, "秒杀活动"),
  19 + BARGAIN_ACTIVITY(2, "拼团活动"),
  20 + COMBINATION_ACTIVITY(3, "砍价活动"),
20 21  
21   - MEMBER(3, "会员折扣"), // TODO 芋艿:待实现 StrUtil.format("会员折扣:省 {} 元", formatPrice(orderItem.getPayPrice() - memberPrice)
22   - COUPON(4, "优惠劵")
  22 + DISCOUNT_ACTIVITY(4, "限时折扣"),
  23 + REWARD_ACTIVITY(5, "满减送"),
  24 +
  25 + MEMBER(6, "会员折扣"),
  26 + COUPON(7, "优惠劵")
23 27 ;
24 28  
25 29 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionTypeEnum::getType).toArray();
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityBaseVO.java
... ... @@ -58,7 +58,7 @@ public class SeckillActivityBaseVO {
58 58  
59 59 @Schema(description = "每人限购", example = "10") // 如果为 0 则不限购
60 60 @Min(value = 0, message = "每人限购需要大于等于 0")
61   - private Integer limitBuyCount;
  61 + private Integer limitCount;
62 62  
63 63 }
64 64  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/AppMarketTestController.java deleted
1   -package cn.iocoder.yudao.module.promotion.controller.app;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import io.swagger.v3.oas.annotations.Operation;
5   -import io.swagger.v3.oas.annotations.tags.Tag;
6   -import org.springframework.validation.annotation.Validated;
7   -import org.springframework.web.bind.annotation.GetMapping;
8   -import org.springframework.web.bind.annotation.RequestMapping;
9   -import org.springframework.web.bind.annotation.RestController;
10   -
11   -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
12   -
13   -@Tag(name = "用户 App - 营销")
14   -@RestController
15   -@RequestMapping("/market/test")
16   -@Validated
17   -public class AppMarketTestController {
18   -
19   - @GetMapping("/get")
20   - @Operation(summary = "获取 market 信息")
21   - public CommonResult<String> get() {
22   - return success("true");
23   - }
24   -}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.app.activity;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO;
  5 +import io.swagger.v3.oas.annotations.Operation;
  6 +import io.swagger.v3.oas.annotations.Parameter;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import org.springframework.validation.annotation.Validated;
  9 +import org.springframework.web.bind.annotation.GetMapping;
  10 +import org.springframework.web.bind.annotation.RequestMapping;
  11 +import org.springframework.web.bind.annotation.RequestParam;
  12 +import org.springframework.web.bind.annotation.RestController;
  13 +
  14 +import java.time.LocalDateTime;
  15 +import java.util.*;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  18 +
  19 +@Tag(name = "用户 APP - 营销活动") // 用于提供跨多个活动的 HTTP 接口
  20 +@RestController
  21 +@RequestMapping("/promotion/activity")
  22 +@Validated
  23 +public class AppActivityController {
  24 +
  25 + @GetMapping("/list-by-spu-id")
  26 + @Operation(summary = "获得单个商品,近期参与的每个活动") // 每种活动,只返回一个
  27 + @Parameter(name = "spuId", description = "商品编号", required = true)
  28 + public CommonResult<List<AppActivityRespVO>> getActivityListBySpuId(@RequestParam("spuId") Long spuId) {
  29 + // TODO 芋艿,实现
  30 + List<AppActivityRespVO> randomList = new ArrayList<>();
  31 + Random random = new Random();
  32 + for (int i = 0; i < 3; i++) { // 生成5个随机对象
  33 + AppActivityRespVO vo = new AppActivityRespVO();
  34 + vo.setId(random.nextLong()); // 随机生成一个长整型 ID
  35 + vo.setType(i + 1); // 随机生成一个介于0到2之间的整数,对应枚举类型的三种类型之一
  36 + vo.setName(String.format("活动%d", random.nextInt(100))); // 随机生成一个类似于“活动XX”的活动名称,XX为0到99之间的随机整数
  37 + vo.setStartTime(LocalDateTime.now()); // 随机生成一个在过去的一年内的开始时间(以毫秒为单位)
  38 + vo.setEndTime(LocalDateTime.now()); // 随机生成一个在未来的一年内的结束时间(以毫秒为单位)
  39 + randomList.add(vo);
  40 + }
  41 + return success(randomList);
  42 + }
  43 +
  44 + @GetMapping("/list-by-spu-ids")
  45 + @Operation(summary = "获得多个商品,近期参与的每个活动") // 每种活动,只返回一个;key 为 SPU 编号
  46 + @Parameter(name = "spuIds", description = "商品编号数组", required = true)
  47 + public CommonResult<Map<Long, List<AppActivityRespVO>>> getActivityListBySpuIds(@RequestParam("spuIds") List<Long> spuIds) {
  48 + // TODO 芋艿,实现
  49 + List<AppActivityRespVO> randomList = new ArrayList<>();
  50 + Random random = new Random();
  51 + for (int i = 0; i < 5; i++) { // 生成5个随机对象
  52 + AppActivityRespVO vo = new AppActivityRespVO();
  53 + vo.setId(random.nextLong()); // 随机生成一个长整型 ID
  54 + vo.setType(random.nextInt(3)); // 随机生成一个介于0到2之间的整数,对应枚举类型的三种类型之一
  55 + vo.setName(String.format("活动%d", random.nextInt(100))); // 随机生成一个类似于“活动XX”的活动名称,XX为0到99之间的随机整数
  56 + vo.setStartTime(LocalDateTime.now()); // 随机生成一个在过去的一年内的开始时间(以毫秒为单位)
  57 + vo.setEndTime(LocalDateTime.now()); // 随机生成一个在未来的一年内的结束时间(以毫秒为单位)
  58 + randomList.add(vo);
  59 + }
  60 + Map<Long, List<AppActivityRespVO>> map = new HashMap<>();
  61 + map.put(109L, randomList);
  62 + return success(map);
  63 + }
  64 +
  65 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/vo/AppActivityRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.app.activity.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "用户 App - 营销活动 Response VO")
  9 +@Data
  10 +public class AppActivityRespVO {
  11 +
  12 + @Schema(description = "活动编号", required = true, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "活动类型", required = true, example = "1") // 对应 PromotionTypeEnum 枚举
  16 + private Integer type;
  17 +
  18 + @Schema(description = "活动名称", required = true, example = "618 大促")
  19 + private String name;
  20 +
  21 + @Schema(description = "活动开始时间", required = true)
  22 + private LocalDateTime startTime;
  23 +
  24 + @Schema(description = "活动结束时间", required = true)
  25 + private LocalDateTime endTime;
  26 +
  27 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java 0 → 100755
  1 +package cn.iocoder.yudao.module.promotion.controller.app.coupon;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO;
  5 +import io.swagger.v3.oas.annotations.Operation;
  6 +import io.swagger.v3.oas.annotations.tags.Tag;
  7 +import org.springframework.validation.annotation.Validated;
  8 +import org.springframework.web.bind.annotation.PostMapping;
  9 +import org.springframework.web.bind.annotation.RequestBody;
  10 +import org.springframework.web.bind.annotation.RequestMapping;
  11 +import org.springframework.web.bind.annotation.RestController;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  14 +
  15 +@Tag(name = "用户 App - 优惠劵")
  16 +@RestController
  17 +@RequestMapping("/promotion/coupon")
  18 +@Validated
  19 +public class AppCouponController {
  20 +
  21 + // TODO 芋艿:待实现
  22 + @PostMapping("/take")
  23 + @Operation(summary = "领取优惠劵")
  24 + public CommonResult<Long> takeCoupon(@RequestBody AppCouponTemplatePageReqVO pageReqVO) {
  25 + return success(1L);
  26 + }
  27 +
  28 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java 0 → 100755
  1 +package cn.iocoder.yudao.module.promotion.controller.app.coupon;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplatePageReqVO;
  6 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template.AppCouponTemplateRespVO;
  7 +import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
  8 +import io.swagger.v3.oas.annotations.Operation;
  9 +import io.swagger.v3.oas.annotations.Parameter;
  10 +import io.swagger.v3.oas.annotations.Parameters;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import org.springframework.validation.annotation.Validated;
  13 +import org.springframework.web.bind.annotation.GetMapping;
  14 +import org.springframework.web.bind.annotation.RequestMapping;
  15 +import org.springframework.web.bind.annotation.RequestParam;
  16 +import org.springframework.web.bind.annotation.RestController;
  17 +
  18 +import javax.annotation.Resource;
  19 +import java.time.LocalDateTime;
  20 +import java.util.ArrayList;
  21 +import java.util.List;
  22 +import java.util.Random;
  23 +
  24 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  25 +
  26 +@Tag(name = "用户 App - 优惠劵模板")
  27 +@RestController
  28 +@RequestMapping("/promotion/coupon-template")
  29 +@Validated
  30 +public class AppCouponTemplateController {
  31 +
  32 + @Resource
  33 + private CouponTemplateService couponTemplateService;
  34 +
  35 + // TODO 芋艿:待实现
  36 + @GetMapping("/list")
  37 + @Operation(summary = "获得优惠劵模版列表") // 目前主要给商品详情使用
  38 + @Parameters({
  39 + @Parameter(name = "spuId", description = "商品 SPU 编号", required = true),
  40 + @Parameter(name = "useType", description = "使用类型"),
  41 + @Parameter(name = "count", description = "数量", required = true)
  42 + })
  43 + public CommonResult<List<AppCouponTemplateRespVO>> getCouponTemplateList(@RequestParam("spuId") Long spuId,
  44 + @RequestParam(value = "useType", required = false) Integer useType) {
  45 + List<AppCouponTemplateRespVO> list = new ArrayList<>();
  46 + Random random = new Random();
  47 + for (int i = 0; i < 10; i++) {
  48 + AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO();
  49 + vo.setId(i + 1L);
  50 + vo.setName("优惠劵" + (i + 1));
  51 + vo.setTakeLimitCount(random.nextInt(10) + 1);
  52 + vo.setUsePrice(random.nextInt(100) * 100);
  53 + vo.setValidityType(random.nextInt(2) + 1);
  54 + if (vo.getValidityType() == 1) {
  55 + vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
  56 + vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
  57 + } else {
  58 + vo.setFixedStartTerm(random.nextInt(10));
  59 + vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1);
  60 + }
  61 + vo.setDiscountType(random.nextInt(2) + 1);
  62 + if (vo.getDiscountType() == 1) {
  63 + vo.setDiscountPercent(null);
  64 + vo.setDiscountPrice(random.nextInt(50) * 100);
  65 + vo.setDiscountLimitPrice(null);
  66 + } else {
  67 + vo.setDiscountPercent(random.nextInt(90) + 10);
  68 + vo.setDiscountPrice(null);
  69 + vo.setDiscountLimitPrice(random.nextInt(200) * 100);
  70 + }
  71 + vo.setTakeStatus(random.nextBoolean());
  72 + list.add(vo);
  73 + }
  74 + return success(list);
  75 + }
  76 +
  77 + // TODO 芋艿:待实现
  78 + @GetMapping("/page")
  79 + @Operation(summary = "获得优惠劵模版分页")
  80 + public CommonResult<PageResult<AppCouponTemplateRespVO>> getCouponTemplatePage(AppCouponTemplatePageReqVO pageReqVO) {
  81 + return null;
  82 + }
  83 +
  84 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponTakeReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotNull;
  7 +
  8 +@Schema(description = "用户 App - 优惠劵领取 Request VO")
  9 +@Data
  10 +public class AppCouponTakeReqVO {
  11 +
  12 + @Schema(description = "优惠劵模板编号", example = "1")
  13 + @NotNull(message = "优惠劵模板编号不能为空")
  14 + private Long templateId;
  15 +
  16 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplatePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +@Schema(description = "用户 App - 优惠劵模板分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class AppCouponTemplatePageReqVO extends PageParam {
  14 +
  15 + @Schema(description = "使用类型", example = "1")
  16 + // TODO 芋艿:这里要限制下枚举的使用
  17 + private Integer useType;
  18 +
  19 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplateRespVO.java 0 → 100755
  1 +package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.Min;
  7 +import java.time.LocalDateTime;
  8 +
  9 +@Schema(description = "用户 App - 优惠劵模板 Response VO")
  10 +@Data
  11 +public class AppCouponTemplateRespVO {
  12 +
  13 + @Schema(description = "优惠劵模板编号", required = true, example = "1")
  14 + private Long id;
  15 +
  16 + @Schema(description = "优惠劵名", required = true, example = "春节送送送")
  17 + private String name;
  18 +
  19 + @Schema(description = "每人限领个数", required = true, example = "66") // -1 - 则表示不限制
  20 + private Integer takeLimitCount;
  21 +
  22 + @Schema(description = "是否设置满多少金额可用", required = true, example = "100") // 单位:分;0 - 不限制
  23 + private Integer usePrice;
  24 +
  25 + // TODO 芋艿:这两要改的
  26 +// @Schema(description = "商品范围", required = true, example = "1")
  27 +// @InEnum(PromotionProductScopeEnum.class)
  28 +// private Integer productScope;
  29 +//
  30 +// @Schema(description = "商品 SPU 编号的数组", example = "1,3")
  31 +// private List<Long> productSpuIds;
  32 +
  33 + @Schema(description = "生效日期类型", required = true, example = "1")
  34 + private Integer validityType;
  35 +
  36 + @Schema(description = "固定日期 - 生效开始时间")
  37 + private LocalDateTime validStartTime;
  38 +
  39 + @Schema(description = "固定日期 - 生效结束时间")
  40 + private LocalDateTime validEndTime;
  41 +
  42 + @Schema(description = "领取日期 - 开始天数")
  43 + @Min(value = 0L, message = "开始天数必须大于 0")
  44 + private Integer fixedStartTerm;
  45 +
  46 + @Schema(description = "领取日期 - 结束天数")
  47 + @Min(value = 1L, message = "开始天数必须大于 1")
  48 + private Integer fixedEndTerm;
  49 +
  50 + @Schema(description = "优惠类型", required = true, example = "1")
  51 + private Integer discountType;
  52 +
  53 + @Schema(description = "折扣百分比", example = "80") // 例如说,80% 为 80
  54 + private Integer discountPercent;
  55 +
  56 + @Schema(description = "优惠金额", example = "10")
  57 + @Min(value = 0, message = "优惠金额需要大于等于 0")
  58 + private Integer discountPrice;
  59 +
  60 + @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
  61 + private Integer discountLimitPrice;
  62 +
  63 + // ========== 用户相关字段 ==========
  64 +
  65 + @Schema(description = "是否已领取", required = true, example = "true")
  66 + private Boolean takeStatus;
  67 +
  68 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillActivityController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.app.seckill;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.AppSeckillActivitiDetailRespVO;
  5 +import io.swagger.v3.oas.annotations.Operation;
  6 +import io.swagger.v3.oas.annotations.Parameter;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import org.springframework.validation.annotation.Validated;
  9 +import org.springframework.web.bind.annotation.GetMapping;
  10 +import org.springframework.web.bind.annotation.RequestMapping;
  11 +import org.springframework.web.bind.annotation.RequestParam;
  12 +import org.springframework.web.bind.annotation.RestController;
  13 +
  14 +import java.time.LocalDateTime;
  15 +import java.util.ArrayList;
  16 +import java.util.List;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "用户 App - 秒杀活动")
  21 +@RestController
  22 +@RequestMapping("/promotion/seckill-activity")
  23 +@Validated
  24 +public class AppSeckillActivityController {
  25 +
  26 + @GetMapping("/get-detail")
  27 + @Operation(summary = "获得秒杀活动明细")
  28 + @Parameter(name = "id", description = "活动编号", required = true, example = "1024")
  29 + public CommonResult<AppSeckillActivitiDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
  30 + // TODO 芋艿:如果禁用的时候,需要抛出异常;
  31 + AppSeckillActivitiDetailRespVO obj = new AppSeckillActivitiDetailRespVO();
  32 + // 设置其属性的值
  33 + obj.setId(id);
  34 + obj.setName("晚九点限时秒杀");
  35 + obj.setStatus(1);
  36 + obj.setStartTime(LocalDateTime.of(2023, 6, 11, 0, 0, 0));
  37 + obj.setEndTime(LocalDateTime.of(2023, 6, 11, 23, 59, 0));
  38 + obj.setSpuId(633L);
  39 + // 创建一个Product对象的列表
  40 + List<AppSeckillActivitiDetailRespVO.Product> productList = new ArrayList<>();
  41 + // 创建三个新的Product对象并设置其属性的值
  42 + AppSeckillActivitiDetailRespVO.Product product1 = new AppSeckillActivitiDetailRespVO.Product();
  43 + product1.setSkuId(1L);
  44 + product1.setSeckillPrice(100);
  45 + product1.setQuota(50);
  46 + product1.setLimitCount(3);
  47 + // 将第一个Product对象添加到列表中
  48 + productList.add(product1);
  49 + // 创建第二个Product对象并设置其属性的值
  50 + AppSeckillActivitiDetailRespVO.Product product2 = new AppSeckillActivitiDetailRespVO.Product();
  51 + product2.setSkuId(2L);
  52 + product2.setSeckillPrice(200);
  53 + product2.setQuota(100);
  54 + product2.setLimitCount(4);
  55 + // 将第二个Product对象添加到列表中
  56 + productList.add(product2);
  57 + // 创建第三个Product对象并设置其属性的值
  58 + AppSeckillActivitiDetailRespVO.Product product3 = new AppSeckillActivitiDetailRespVO.Product();
  59 + product3.setSkuId(3L);
  60 + product3.setSeckillPrice(300);
  61 + product3.setQuota(150);
  62 + product3.setLimitCount(5);
  63 + // 将第三个Product对象添加到列表中
  64 + productList.add(product3);
  65 + // 将Product列表设置为对象的属性值
  66 + obj.setProducts(productList);
  67 + return success(obj);
  68 + }
  69 +
  70 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/vo/AppSeckillActivitiDetailRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.app.seckill.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "用户 App - 秒杀活动 Response VO")
  10 +@Data
  11 +public class AppSeckillActivitiDetailRespVO {
  12 +
  13 + @Schema(description = "秒杀活动编号", required = true, example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "秒杀活动名称", required = true, example = "晚九点限时秒杀")
  17 + private String name;
  18 +
  19 + @Schema(description = "活动状态", required = true, example = "1")
  20 + private Integer status;
  21 +
  22 + // TODO @芋艿:开始时间、结束时间,要和场次结合起来;就是要算到当前场次,是几点哈;
  23 +
  24 + @Schema(description = "活动开始时间", required = true)
  25 + private LocalDateTime startTime;
  26 +
  27 + @Schema(description = "活动结束时间", required = true)
  28 + private LocalDateTime endTime;
  29 +
  30 + @Schema(description = "商品 SPU 编号", required = true, example = "2048")
  31 + private Long spuId;
  32 +
  33 + @Schema(description = "商品 SPU 名字", required = true)
  34 + private List<Product> products;
  35 +
  36 + @Schema(description = "商品信息")
  37 + @Data
  38 + public static class Product {
  39 +
  40 + @Schema(description = "商品 SKU 编号", required = true, example = "4096")
  41 + private Long skuId;
  42 +
  43 + @Schema(description = "秒杀金额,单位:分", required = true, example = "100")
  44 + private Integer seckillPrice;
  45 +
  46 + @Schema(description = "秒杀限量库存", required = true, example = "50")
  47 + private Integer quota;
  48 +
  49 + @Schema(description = "limitCount", required = true, example = "10")
  50 + private Integer limitCount;
  51 +
  52 + }
  53 +
  54 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/seckillactivity/SeckillActivityConvert.java
... ... @@ -56,7 +56,7 @@ public interface SeckillActivityConvert {
56 56 && ObjectUtil.equals(productDO.getSkuId(), productVO.getSkuId())
57 57 && ObjectUtil.equals(productDO.getSeckillPrice(), productVO.getSeckillPrice())
58 58 && ObjectUtil.equals(productDO.getStock(), productVO.getStock())
59   - && ObjectUtil.equals(productDO.getLimitBuyCount(), productVO.getLimitBuyCount());
  59 + && ObjectUtil.equals(productDO.getLimitCount(), productVO.getLimitCount());
60 60 }
61 61  
62 62 /**
... ... @@ -71,7 +71,7 @@ public interface SeckillActivityConvert {
71 71 && ObjectUtil.equals(productDO.getSkuId(), productVO.getSkuId())
72 72 && ObjectUtil.equals(productDO.getSeckillPrice(), productVO.getSeckillPrice())
73 73 && ObjectUtil.equals(productDO.getStock(), productVO.getStock())
74   - && ObjectUtil.equals(productDO.getLimitBuyCount(), productVO.getLimitBuyCount());
  74 + && ObjectUtil.equals(productDO.getLimitCount(), productVO.getLimitCount());
75 75  
76 76 }
77 77  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/seckill/seckillactivity/SeckillActivityDO.java
... ... @@ -37,7 +37,7 @@ public class SeckillActivityDO extends BaseDO {
37 37 private String name;
38 38 /**
39 39 * 活动状态
40   - * <p>
  40 + *
41 41 * 枚举 {@link PromotionActivityStatusEnum 对应的类}
42 42 */
43 43 private Integer status;
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/seckill/seckillactivity/SeckillProductDO.java
... ... @@ -53,6 +53,7 @@ public class SeckillProductDO extends BaseDO {
53 53 */
54 54 private Integer seckillPrice;
55 55  
  56 + // TODO @芋艿:改成 quota 限量库存;每次购买时,需要减小;
56 57 /**
57 58 * 秒杀库存
58 59 */
... ... @@ -61,5 +62,6 @@ public class SeckillProductDO extends BaseDO {
61 62 /**
62 63 * 每人限购
63 64 */
64   - private Integer limitBuyCount;
65   -}
66 65 \ No newline at end of file
  66 + private Integer limitCount;
  67 +
  68 +}
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
... ... @@ -51,9 +51,11 @@ public interface ErrorCodeConstants {
51 51 ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1011003001, "已经存在该编码的快递公司");
52 52 ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1011003002, "运费模板不存在");
53 53 ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1011003003, "已经存在该运费模板名");
54   - ErrorCode DELIVERY_EXPRESS_USER_ADDRESS_IS_EMPTY = new ErrorCode(1011003004, "计算快递运费时,收件人地址编号为空");
55   - ErrorCode PRODUCT_EXPRESS_TEMPLATE_NOT_FOUND = new ErrorCode(1011003005, "找不到到商品对应的运费模板");
56   - ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011003006, "自提门店不存在");
  54 + ErrorCode DELIVERY_EXPRESS_USER_ADDRESS_IS_EMPTY = new ErrorCode(1011003004, "计算快递运费时,收件人地址编号为空"); // TODO @jaosn:这个错误码,放到 Price 这块
  55 + ErrorCode PRODUCT_EXPRESS_TEMPLATE_NOT_FOUND = new ErrorCode(1011003005, "找不到到商品对应的运费模板"); // TODO @jaosn:这个错误码,放到 Price 这块
  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-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeOrderTypeEnum.java
... ... @@ -17,10 +17,8 @@ public enum TradeOrderTypeEnum implements IntArrayValuable {
17 17  
18 18 NORMAL(0, "普通订单"),
19 19 SECKILL(1, "秒杀订单"),
20   - // TODO 芋艿:如下三个字段,名字需要改下,等后面表设计完成后。
21   - KANJIA(2, "砍价订单"),
22   - PINTUAN(3, "拼团订单"),
23   - YUSHOU(4, "预售订单"),
  20 + BARGAIN(2, "砍价订单"),
  21 + COMBINATION(3, "拼团订单"),
24 22 ;
25 23  
26 24 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TradeOrderTypeEnum::getType).toArray();
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
... ... @@ -71,7 +71,7 @@ public class AppTradeOrderController {
71 71 }
72 72  
73 73 @PostMapping("/update-paid")
74   - @Operation(description = "更新订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
  74 + @Operation(summary = "更新订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
75 75 public CommonResult<Boolean> updateOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
76 76 tradeOrderService.updateOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
77 77 notifyReqDTO.getPayOrderId());
... ...
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 +// TODO @jason:TradeExpressProperties;更通用哈
  13 +// TODO @芋艿:未来要不要放数据库中?考虑 saas 多租户时,不同租户使用不同的配置?
  14 +/**
  15 + * 交易快递查询的配置项
  16 + *
  17 + * @author jason
  18 + */
  19 +@Component
  20 +@ConfigurationProperties(prefix = "yudao.trade.express.query")
  21 +@Data
  22 +@Validated
  23 +public class TradeExpressQueryProperties {
  24 +
  25 + /**
  26 + * 快递查询服务商
  27 + *
  28 + * 如果未配置,默认使用快递鸟
  29 + */
  30 + // TODO @jason:可以把 expressQueryProvider 改成 client 变量,更简洁一点;
  31 + private ExpressQueryProviderEnum expressQueryProvider; // TODO @jaosn:默认值可以通过属性直接赋值哈;
  32 + // TODO @jason:需要考虑下,用户只配置了其中一个;
  33 + /**
  34 + * 快递鸟配置
  35 + */
  36 + @Valid
  37 + private KdNiaoConfig kdNiao;
  38 + /**
  39 + * 快递 100 配置
  40 + */
  41 + @Valid
  42 + private Kd100Config kd100;
  43 +
  44 + /**
  45 + * 快递鸟配置项目
  46 + */
  47 + @Data
  48 + public static class KdNiaoConfig {
  49 +
  50 + /**
  51 + * 快递鸟用户 ID
  52 + */
  53 + @NotEmpty(message = "快递鸟用户 ID 配置项不能为空")
  54 + private String businessId;
  55 + /**
  56 + * 快递鸟 API Key
  57 + */
  58 + @NotEmpty(message = "快递鸟 Api Key 配置项不能为空")
  59 + private String apiKey;
  60 +
  61 + }
  62 +
  63 + /**
  64 + * 快递100 配置项
  65 + */
  66 + @Data
  67 + public static class Kd100Config {
  68 +
  69 + /**
  70 + * 快递 100 授权码
  71 + */
  72 + @NotEmpty(message = "快递 100 授权码配置项不能为空")
  73 + private String customer;
  74 + /**
  75 + * 快递 100 授权 key
  76 + */
  77 + @NotEmpty(message = "快递 100 授权 Key 配置项不能为空")
  78 + private String key;
  79 +
  80 + }
  81 +
  82 +}
... ...
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 +// TODO @jason:可以改成 ExpressClient,未来可能还对接别的接口噢
  9 +/**
  10 + * 快递查询客户端
  11 + *
  12 + * @author jason
  13 + */
  14 +public interface ExpressQueryClient {
  15 +
  16 + /**
  17 + * 快递实时查询
  18 + *
  19 + * @param reqDTO 查询请求参数
  20 + */
  21 + // TODO @jason:可以改成 getExpressTrackList。返回字段可以参考 https://doc.youzanyun.com/detail/API/0/5 响应的 data
  22 + List<ExpressQueryRespDTO> realTimeQuery(ExpressQueryReqDTO reqDTO);
  23 +
  24 +}
... ...
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 + *
  18 + * @param reqDTO 查询请求参数
  19 + */
  20 + List<ExpressQueryRespDTO> realTimeQueryExpress(ExpressQueryReqDTO reqDTO);
  21 +
  22 +}
... ...
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 +
  12 +public enum ExpressQueryProviderEnum {
  13 +
  14 + KD_NIAO("kd-niao", "快递鸟"),
  15 + KD_100("kd-100", "快递100");
  16 +
  17 + /**
  18 + * 快递服务商唯一编码
  19 + */
  20 + private final String code;
  21 +
  22 + /**
  23 + * 快递服务商名称
  24 + */
  25 + private final String name;
  26 +
  27 + // TODO @jaosn:@AllArgsConstructor 可以替代哈
  28 + ExpressQueryProviderEnum(String code, String name) {
  29 + this.code = code;
  30 + this.name = name;
  31 + }
  32 +
  33 +}
... ...
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 + *
  6 + * @author jason
  7 + */
  8 +public interface ExpressQueryProviderFactory {
  9 +
  10 + /**
  11 + * 通过枚举获取快递查询服务商
  12 + *
  13 + * 如果不存在,就创建一个对应的快递查询服务商
  14 + *
  15 + * @param queryProviderEnum 快递服务商枚举
  16 + */
  17 + ExpressQueryProvider getOrCreateExpressQueryProvider(ExpressQueryProviderEnum queryProviderEnum);
  18 +
  19 +}
... ...
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 +
  27 +}
... ...
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 + // TODO @jaosn:要不改成 expressCode;项目里使用这个哈
  20 + private String expressCompanyCode;
  21 +
  22 + /**
  23 + * 发货快递单号
  24 + */
  25 + private String logisticsNo;
  26 +
  27 + /**
  28 + * 收、寄件人的电话号码
  29 + */
  30 + private String phone;
  31 +
  32 +}
... ...
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 + // TODO @jason:LocalDateTime
  14 + /**
  15 + * 发生时间
  16 + */
  17 + private String time;
  18 + // TODO @jason:其它字段可能要补充下
  19 + /**
  20 + * 快递状态
  21 + */
  22 + private String state;
  23 +
  24 +}
... ...
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 + // TODO @jaosn:要不改成 expressCode;项目里使用这个哈
  17 + /**
  18 + * 快递公司编码
  19 + */
  20 + @JsonProperty("com")
  21 + private String expressCompanyCode;
  22 +
  23 + /**
  24 + * 快递单号
  25 + */
  26 + @JsonProperty("num")
  27 + private String logisticsNo;
  28 +
  29 + /**
  30 + * 收、寄件人的电话号码
  31 + */
  32 + private String phone;
  33 + /**
  34 + * 出发地城市
  35 + */
  36 + private String from;
  37 + /**
  38 + * 目的地城市,到达目的地后会加大监控频率
  39 + */
  40 + private String to;
  41 +
  42 + /**
  43 + * 返回结果排序
  44 + *
  45 + * desc 降序(默认), asc 升序
  46 + */
  47 + private String order;
  48 +
  49 +}
... ...
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 + @JsonProperty("nu")
  25 + private String logisticsNo;
  26 + /**
  27 + * 快递单当前状态
  28 + */
  29 + private String state;
  30 +
  31 + /**
  32 + * 查询结果
  33 + *
  34 + * 失败返回 "false"
  35 + */
  36 + private String result;
  37 + /**
  38 + * 查询结果失败时的错误信息
  39 + */
  40 + private String message;
  41 +
  42 + @JsonProperty("data")
  43 + private List<ExpressTrack> tracks;
  44 +
  45 + @Data
  46 + public static class ExpressTrack {
  47 + /**
  48 + * 轨迹发生时间
  49 + */
  50 + @JsonProperty("time")
  51 + private String time;
  52 + /**
  53 + * 轨迹描述
  54 + */
  55 + @JsonProperty("context")
  56 + private String state;
  57 + }
  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 + // TODO @jaosn:要不改成 expressCode;项目里使用这个哈
  17 + /**
  18 + * 快递公司编码
  19 + */
  20 + @JsonProperty("ShipperCode")
  21 + private String expressCompanyCode;
  22 + /**
  23 + * 快递单号
  24 + */
  25 + @JsonProperty("LogisticCode")
  26 + private String logisticsNo;
  27 + /**
  28 + * 订单编号
  29 + */
  30 + @JsonProperty("OrderCode")
  31 + private String orderNo;
  32 +
  33 +}
... ...
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 +// TODO @jason:可以把整体包结构调整下;参考 sms client 的方式;
  21 +// + config
  22 +// + core
  23 +// client
  24 +// + dto
  25 +// + impl:里面可以放 kdniaoclient、kd100client
  26 +// ExpressClient
  27 +// ExpressClientFactory: 通过它直接获取默认和创建默认的 Client
  28 +// enums
  29 +/**
  30 + * 快递查询客户端实现
  31 + *
  32 + * @author jason
  33 + */
  34 +@Component
  35 +@Slf4j
  36 +public class ExpressQueryClientImpl implements ExpressQueryClient {
  37 +
  38 + @Resource
  39 + private ExpressQueryProviderFactory expressQueryProviderFactory;
  40 + @Resource
  41 + private TradeExpressQueryProperties tradeExpressQueryProperties;
  42 +
  43 + private ExpressQueryProvider expressQueryProvider;
  44 +
  45 + @PostConstruct
  46 + private void init() {
  47 + // 如果未设置,默认使用快递鸟
  48 + ExpressQueryProviderEnum queryProvider = tradeExpressQueryProperties.getExpressQueryProvider();
  49 + if (queryProvider == null) {
  50 + queryProvider = KD_NIAO;
  51 + }
  52 + // 创建客户端
  53 + expressQueryProvider = expressQueryProviderFactory.getOrCreateExpressQueryProvider(queryProvider);
  54 + if (expressQueryProvider == null) {
  55 + log.error("获取创建快递查询服务商{}失败,请检查相关配置", queryProvider);
  56 + }
  57 + Assert.notNull(expressQueryProvider, "快递查询服务商不能为空");
  58 + }
  59 +
  60 + @Override
  61 + public List<ExpressQueryRespDTO> realTimeQuery(ExpressQueryReqDTO reqDTO) {
  62 + return expressQueryProvider.realTimeQueryExpress(reqDTO);
  63 + }
  64 +
  65 +}
... ...
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 + * // TODO @jason:注释不全
  16 + * @author jason
  17 + */
  18 +@Component
  19 +public class ExpressQueryProviderFactoryImpl implements ExpressQueryProviderFactory {
  20 +
  21 + private final Map<ExpressQueryProviderEnum, ExpressQueryProvider> providerMap = new ConcurrentHashMap<>(8);
  22 +
  23 + @Resource
  24 + private TradeExpressQueryProperties tradeExpressQueryProperties;
  25 + @Resource
  26 + private RestTemplate restTemplate;
  27 +
  28 + @Override
  29 + public ExpressQueryProvider getOrCreateExpressQueryProvider(ExpressQueryProviderEnum queryProviderEnum) {
  30 + return providerMap.computeIfAbsent(queryProviderEnum,
  31 + provider -> createExpressQueryProvider(provider, tradeExpressQueryProperties));
  32 + }
  33 +
  34 + private ExpressQueryProvider createExpressQueryProvider(ExpressQueryProviderEnum queryProviderEnum,
  35 + TradeExpressQueryProperties tradeExpressQueryProperties) {
  36 + // TODO @jason:是不是直接 return 就好啦,更简洁一点
  37 + ExpressQueryProvider result = null;
  38 + switch (queryProviderEnum) {
  39 + case KD_NIAO:
  40 + result = new KdNiaoExpressQueryProvider(restTemplate, tradeExpressQueryProperties.getKdNiao());
  41 + break;
  42 + case KD_100:
  43 + result = new Kd100ExpressQueryProvider(restTemplate, tradeExpressQueryProperties.getKd100());
  44 + break;
  45 + }
  46 + return result;
  47 + }
  48 +}
... ...
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_ERROR;
  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 +// TODO @jason:可以参考 KdNiaoExpressQueryProvider 建议改改哈
  29 +/**
  30 + * 快递 100 服务商
  31 + *
  32 + * @author jason
  33 + */
  34 +@Slf4j
  35 +public class Kd100ExpressQueryProvider implements ExpressQueryProvider {
  36 +
  37 + private static final String REAL_TIME_QUERY_URL = "https://poll.kuaidi100.com/poll/query.do";
  38 +
  39 + private final RestTemplate restTemplate;
  40 + private final TradeExpressQueryProperties.Kd100Config config;
  41 +
  42 + public Kd100ExpressQueryProvider(RestTemplate restTemplate, TradeExpressQueryProperties.Kd100Config config) {
  43 + this.restTemplate = restTemplate;
  44 + this.config = config;
  45 + }
  46 +
  47 + @Override
  48 + public List<ExpressQueryRespDTO> realTimeQueryExpress(ExpressQueryReqDTO reqDTO) {
  49 + // 发起查询
  50 + Kd100ExpressQueryReqDTO kd100ReqParam = INSTANCE.convert2(reqDTO);
  51 + kd100ReqParam.setExpressCompanyCode(kd100ReqParam.getExpressCompanyCode().toLowerCase()); // 快递公司编码需要转成小写
  52 + Kd100ExpressQueryRespDTO respDTO = sendExpressQueryReq(REAL_TIME_QUERY_URL, kd100ReqParam,
  53 + Kd100ExpressQueryRespDTO.class);
  54 + log.debug("[realTimeQueryExpress][快递 100 接口 查询接口返回 {}]", respDTO);
  55 + // 处理结果
  56 + if (Objects.equals("false", respDTO.getResult())) {
  57 + log.error("[realTimeQueryExpress][快递 100 接口 返回失败 {}]", respDTO.getMessage());
  58 + throw exception(EXPRESS_API_QUERY_FAILED, respDTO.getMessage());
  59 + // TODO @json:else 可以不用写哈;
  60 + } else {
  61 + // TODO @jason:convertList2 如果空,应该返回 list 了;
  62 + if (CollUtil.isNotEmpty(respDTO.getTracks())) {
  63 + return INSTANCE.convertList2(respDTO.getTracks());
  64 + } else {
  65 + return Collections.emptyList();
  66 + }
  67 + }
  68 + }
  69 +
  70 + /**
  71 + * 发送快递 100 实时快递查询请求,可以作为通用快递 100 通用请求接口。 目前没有其它场景需要使用。暂时放这里
  72 + *
  73 + * @param url 请求 url
  74 + * @param req 对应请求的请求参数
  75 + * @param respClass 对应请求的响应 class
  76 + * @param <Req> 每个请求的请求结构 Req DTO
  77 + * @param <Resp> 每个请求的响应结构 Resp DTO
  78 + */
  79 + // TODO @jason:可以改成 request,发起请求哈;
  80 + private <Req, Resp> Resp sendExpressQueryReq(String url, Req req, Class<Resp> respClass) {
  81 + // 请求头
  82 + HttpHeaders headers = new HttpHeaders();
  83 + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  84 + // 生成签名
  85 + String param = JsonUtils.toJsonString(req);
  86 + String sign = generateReqSign(param, config.getKey(), config.getCustomer());
  87 + // 请求体
  88 + MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
  89 + requestBody.add("customer", config.getCustomer());
  90 + requestBody.add("sign", sign);
  91 + requestBody.add("param", param);
  92 + log.debug("[sendExpressQueryReq][快递 100 接口的请求参数: {}]", requestBody);
  93 + // 发送请求
  94 + HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
  95 + // TODO @jason:可以使用 restTemplate 的 post 方法哇?
  96 + ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
  97 + log.debug("[sendExpressQueryReq][快递 100 接口响应结果 {}]", responseEntity);
  98 +
  99 + // 处理响应
  100 + // TODO @jason:if return 原则;if (!responseEntity.getStatusCode().is2xxSuccessful()) 抛出异常;接着处理成功的
  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 + private String generateReqSign(String param, String key, String customer) {
  110 + String plainText = String.format("%s%s%s", param, key, customer);
  111 + // TODO @jason:DigestUtil.md5Hex(plainText);
  112 + return HexUtil.encodeHexStr(DigestUtil.md5(plainText), false);
  113 + }
  114 +
  115 +}
... ...
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 +
  36 + private static final String REAL_TIME_QUERY_URL = "https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx";
  37 +
  38 + /**
  39 + * 快递鸟即时查询免费版 RequestType
  40 + */
  41 + private static final String REAL_TIME_FREE_REQ_TYPE = "1002";
  42 +
  43 + private final RestTemplate restTemplate;
  44 + private final TradeExpressQueryProperties.KdNiaoConfig config;
  45 +
  46 + // TODO @jason:可以改成 lombok 哈
  47 + public KdNiaoExpressQueryProvider(RestTemplate restTemplate, TradeExpressQueryProperties.KdNiaoConfig config) {
  48 + this.restTemplate = restTemplate;
  49 + this.config = config;
  50 + }
  51 +
  52 + /**
  53 + * 快递鸟即时查询免费版本
  54 + *
  55 + * @see <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/wugo6k">快递鸟接口文档</a>
  56 + * @param reqDTO 查询请求参数
  57 + */
  58 + @Override
  59 + public List<ExpressQueryRespDTO> realTimeQueryExpress(ExpressQueryReqDTO reqDTO) {
  60 + KdNiaoExpressQueryReqDTO kdNiaoReqData = INSTANCE.convert(reqDTO);
  61 + // 快递公司编码需要转成大写
  62 + kdNiaoReqData.setExpressCompanyCode(reqDTO.getExpressCompanyCode().toUpperCase());
  63 + KdNiaoExpressQueryRespDTO respDTO = sendKdNiaoApiRequest(REAL_TIME_QUERY_URL, REAL_TIME_FREE_REQ_TYPE,
  64 + kdNiaoReqData, KdNiaoExpressQueryRespDTO.class);
  65 + log.debug("[realTimeQueryExpress][快递鸟即时查询接口返回 {}]", respDTO);
  66 + if(!respDTO.getSuccess()){
  67 + throw exception(EXPRESS_API_QUERY_FAILED, respDTO.getReason());
  68 + }else{
  69 + if (CollUtil.isNotEmpty(respDTO.getTracks())) {
  70 + return INSTANCE.convertList(respDTO.getTracks());
  71 + }else{
  72 + return Collections.emptyList();
  73 + }
  74 + }
  75 + }
  76 +
  77 + /**
  78 + * 快递鸟 通用的 API 请求, 暂时没有其他应用场景, 暂时放这里
  79 + * @param url 请求 url
  80 + * @param requestType 对应的请求指令 (快递鸟的RequestType)
  81 + * @param req 对应请求的请求参数
  82 + * @param respClass 对应请求的响应 class
  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){
  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 + MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
  95 + requestBody.add("RequestData", reqData);
  96 + requestBody.add("DataType", "2");
  97 + requestBody.add("EBusinessID", config.getBusinessId());
  98 + requestBody.add("DataSign", dataSign);
  99 + requestBody.add("RequestType", requestType);
  100 + log.debug("[sendKdNiaoApiRequest][快递鸟接口 RequestType : {}, 的请求参数 {}]", requestType, requestBody);
  101 +
  102 + // 发送请求
  103 + HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);
  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 + return JsonUtils.parseObject(response, respClass);
  110 + } else {
  111 + throw exception(EXPRESS_API_QUERY_ERROR);
  112 + }
  113 + }
  114 +
  115 + /**
  116 + * 快递鸟生成请求签名 参见 <a href="https://www.yuque.com/kdnjishuzhichi/dfcrg1/zes04h">签名说明</a>
  117 + * @param reqData 请求实体
  118 + * @param apiKey api Key
  119 + */
  120 + private String generateDataSign(String reqData, String apiKey) {
  121 + String plainText = String.format("%s%s", reqData, apiKey);
  122 + return URLEncodeUtil.encode(Base64.encode(DigestUtil.md5Hex(plainText)));
  123 + }
  124 +
  125 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateService.java
... ... @@ -75,7 +75,7 @@ public interface DeliveryExpressTemplateService {
75 75  
76 76 /**
77 77 * 校验快递运费模板
78   - * <p>
  78 + *
79 79 * 如果校验不通过,抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 异常
80 80 *
81 81 * @param templateId 模板编号
... ... @@ -83,6 +83,7 @@ public interface DeliveryExpressTemplateService {
83 83 */
84 84 DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId);
85 85  
  86 + // TODO @jason:可以把 spuIds 改成传递 ids 么?价格计算那,在 TradePriceCalculateRespBO 冗余好 templateId 字段。目的是,减少重复的查询
86 87 /**
87 88 * 基于指定的 SPU 编号数组和收件人地址区域编号. 获取匹配运费模板
88 89 *
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java
... ... @@ -242,10 +242,9 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
242 242 if (spu == null) {
243 243 return;
244 244 }
  245 + // TODO @jason:避免循环查询;最好类似 expressTemplateMapper.selectBatchIds(spuMap.keySet()); 批量查询,内存组合;
245 246 SpuDeliveryExpressTemplateRespBO bo = new SpuDeliveryExpressTemplateRespBO()
246 247 .setChargeMode(item.getChargeMode())
247   - // TODO @jason:是不是只要查询到一个,就不用查询下一个了;TemplateCharge 和 TemplateFree
248   - // @芋艿 包邮的优先级> 费用的优先级 所以两个都要查询
249 248 .setTemplateCharge(findMatchExpressTemplateCharge(item.getId(), areaId))
250 249 .setTemplateFree(findMatchExpressTemplateFree(item.getId(), areaId));
251 250 result.put(spu.getId(), bo);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/SpuDeliveryExpressTemplateRespBO.java
... ... @@ -18,6 +18,8 @@ public class SpuDeliveryExpressTemplateRespBO {
18 18 */
19 19 private Integer chargeMode;
20 20  
  21 + // TODO @jaosn:可以把 DeliveryExpressTemplateChargeBO 和 DeliveryExpressTemplateFreeBO 搞成内嵌的类。这样简洁一点
  22 +
21 23 /**
22 24 * 运费模板快递运费设置
23 25 */
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
... ... @@ -164,16 +164,7 @@ public class TradePriceCalculateRespBO {
164 164 */
165 165 private Integer payPrice;
166 166  
167   - /**
168   - * 商品重量,单位:kg 千克
169   - */
170   - private Double weight;
171   - /**
172   - * 商品体积,单位:m^3 平米
173   - */
174   - private Double volume;
175   -
176   - // ========== 商品信息 ==========
  167 + // ========== 商品 SPU 信息 ==========
177 168 /**
178 169 * 商品名
179 170 */
... ... @@ -189,6 +180,16 @@ public class TradePriceCalculateRespBO {
189 180 */
190 181 private Long categoryId;
191 182  
  183 + // ========== 商品 SKU 信息 ==========
  184 + /**
  185 + * 商品重量,单位:kg 千克
  186 + */
  187 + private Double weight;
  188 + /**
  189 + * 商品体积,单位:m^3 平米
  190 + */
  191 + private Double volume;
  192 +
192 193 /**
193 194 * 商品属性数组
194 195 */
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
... ... @@ -62,7 +62,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
62 62 deliveryExpressTemplateService.getExpressTemplateMapBySpuIdsAndArea(spuIds, address.getAreaId());
63 63 // 3. 计算配送费用
64 64 if (CollUtil.isEmpty(spuExpressTemplateMap)) {
65   - log.error("找不到商品 SPU ID {}, area Id {} ,对应的运费模板", spuIds, address.getAreaId());
  65 + log.error("[calculate][找不到商品 spuId{} areaId{} 对应的运费模板]", spuIds, address.getAreaId());
66 66 throw exception(PRODUCT_EXPRESS_TEMPLATE_NOT_FOUND);
67 67 }
68 68 calculateDeliveryPrice(selectedItem, spuExpressTemplateMap, result);
... ... @@ -170,7 +170,8 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
170 170 */
171 171 private void divideDeliveryPrice(int deliveryPrice, List<OrderItem> orderItems) {
172 172 // TODO @jason:分摊的话,是不是要按照比例呀?重量、价格、数量等等,
173   - // 按比例是不是有点复杂。后面看看是否需要
  173 + // 按比例是不是有点复杂。后面看看是否需要;
  174 + // TODO 可以看看别的项目怎么搞的哈。
174 175 int dividePrice = deliveryPrice / orderItems.size();
175 176 for (OrderItem item : orderItems) {
176 177 // 更新快递运费
... ... @@ -207,6 +208,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
207 208 // freeCount 是不是应该是 double ??
208 209 // TODO @jason:要不配置的时候,把它的单位和商品对齐?到底是 kg、还是斤
209 210 // TODO @芋艿 目前 包邮 件数/重量/体积 都用的是这个字段
  211 + // TODO @jason:那要不快递模版也改成 kg?这样是不是就不用 double ?
210 212 if (totalWeight >= templateFree.getFreeCount()
211 213 && totalPrice >= templateFree.getFreePrice()) {
212 214 return true;
... ...
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 +// TODO @芋艿:单测最后 review
  21 +/**
  22 + * @author jason
  23 + */
  24 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = KdNiaoExpressQueryProviderTest.Application.class)
  25 +@ActiveProfiles("trade-delivery-query") // 设置使用 trade-delivery-query 配置文件 TODO @jason:可以直接写到 application-unit-test.yaml 配置文件里
  26 +public class KdNiaoExpressQueryProviderTest {
  27 + @Resource
  28 + private RestTemplateBuilder builder;
  29 + @Resource
  30 + private TradeExpressQueryProperties expressQueryProperties;
  31 +
  32 + private KdNiaoExpressQueryProvider kdNiaoExpressQueryProvider;
  33 +
  34 + @BeforeEach
  35 + public void init(){
  36 + kdNiaoExpressQueryProvider = new KdNiaoExpressQueryProvider(builder.build(),expressQueryProperties.getKdNiao());
  37 + }
  38 + @Test
  39 + @Disabled("需要 授权 key. 暂时忽略")
  40 + void testRealTimeQueryExpressFailed() {
  41 + assertThrows(ServiceException.class,() ->{
  42 + ExpressQueryReqDTO reqDTO = new ExpressQueryReqDTO();
  43 + reqDTO.setExpressCompanyCode("yy");
  44 + reqDTO.setLogisticsNo("YT9383342193097");
  45 + kdNiaoExpressQueryProvider.realTimeQueryExpress(reqDTO);
  46 + });
  47 + }
  48 +
  49 + @Import({
  50 + RestTemplateAutoConfiguration.class
  51 + })
  52 + @EnableConfigurationProperties(TradeExpressQueryProperties.class)
  53 + public static class Application {
  54 + }
  55 +}
... ...
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
... ...