Commit 15798cea108390075f788ec491ecc3a1f5988d76

Authored by YunaiV
1 parent 67d36547

mall:价格计算的 API 简单实现

Showing 39 changed files with 1017 additions and 375 deletions
yudao-module-mall/yudao-module-market-api/src/main/java/cn/iocoder/yudao/module/market/api/price/dto/PriceCalculateRespDTO.java
@@ -20,14 +20,9 @@ public class PriceCalculateRespDTO { @@ -20,14 +20,9 @@ public class PriceCalculateRespDTO {
20 private Order order; 20 private Order order;
21 21
22 /** 22 /**
23 - * 商品 SKU 数组  
24 - */  
25 - private List<Item> items;  
26 -  
27 - /**  
28 * 营销活动数组 23 * 营销活动数组
29 * 24 *
30 - * 只对应 {@link #items} 商品匹配的活动 25 + * 只对应 {@link Order#items} 商品匹配的活动
31 */ 26 */
32 private List<Promotion> promotions; 27 private List<Promotion> promotions;
33 28
@@ -40,13 +35,13 @@ public class PriceCalculateRespDTO { @@ -40,13 +35,13 @@ public class PriceCalculateRespDTO {
40 /** 35 /**
41 * 商品原价(总),单位:分 36 * 商品原价(总),单位:分
42 * 37 *
43 - * 基于 {@link Item#getTotalOriginalPrice()} 求和 38 + * 基于 {@link OrderItem#getTotalOriginalPrice()} 求和
44 */ 39 */
45 private Integer skuOriginalPrice; 40 private Integer skuOriginalPrice;
46 /** 41 /**
47 * 商品优惠(总),单位:分 42 * 商品优惠(总),单位:分
48 * 43 *
49 - * 基于 {@link Item#getTotalPromotionPrice()} 求和 44 + * 基于 {@link OrderItem#getTotalPromotionPrice()} 求和
50 */ 45 */
51 private Integer skuPromotionPrice; 46 private Integer skuPromotionPrice;
52 /** 47 /**
@@ -69,6 +64,10 @@ public class PriceCalculateRespDTO { @@ -69,6 +64,10 @@ public class PriceCalculateRespDTO {
69 */ 64 */
70 // * - {@link #couponPrice} // TODO 芋艿:靠营销表记录 65 // * - {@link #couponPrice} // TODO 芋艿:靠营销表记录
71 private Integer payPrice; 66 private Integer payPrice;
  67 + /**
  68 + * 商品 SKU 数组
  69 + */
  70 + private List<OrderItem> items;
72 71
73 // ========== 营销基本信息 ========== 72 // ========== 营销基本信息 ==========
74 /** 73 /**
@@ -85,10 +84,15 @@ public class PriceCalculateRespDTO { @@ -85,10 +84,15 @@ public class PriceCalculateRespDTO {
85 } 84 }
86 85
87 /** 86 /**
88 - * 商品 SKU 87 + * 订单商品 SKU
89 */ 88 */
90 @Data 89 @Data
91 - public static class Item extends PriceCalculateReqDTO.Item { 90 + public static class OrderItem extends PriceCalculateReqDTO.Item {
  91 +
  92 + /**
  93 + * 购买数量
  94 + */
  95 + private Integer count;
92 96
93 /** 97 /**
94 * 商品原价(单),单位:分 98 * 商品原价(单),单位:分
@@ -141,6 +145,10 @@ public class PriceCalculateRespDTO { @@ -141,6 +145,10 @@ public class PriceCalculateRespDTO {
141 */ 145 */
142 private Long id; 146 private Long id;
143 /** 147 /**
  148 + * 营销名字
  149 + */
  150 + private String name;
  151 + /**
144 * 营销类型 152 * 营销类型
145 * 153 *
146 * 枚举 {@link PromotionTypeEnum} 154 * 枚举 {@link PromotionTypeEnum}
@@ -153,10 +161,6 @@ public class PriceCalculateRespDTO { @@ -153,10 +161,6 @@ public class PriceCalculateRespDTO {
153 */ 161 */
154 private Integer level; 162 private Integer level;
155 /** 163 /**
156 - * 匹配的商品 SKU 数组  
157 - */  
158 - private List<Item> items;  
159 - /**  
160 * 计算时的原价(总),单位:分 164 * 计算时的原价(总),单位:分
161 */ 165 */
162 private Integer totalOriginalPrice; 166 private Integer totalOriginalPrice;
@@ -165,6 +169,13 @@ public class PriceCalculateRespDTO { @@ -165,6 +169,13 @@ public class PriceCalculateRespDTO {
165 */ 169 */
166 private Integer totalPromotionPrice; 170 private Integer totalPromotionPrice;
167 /** 171 /**
  172 + * 匹配的商品 SKU 数组
  173 + */
  174 + private List<PromotionItem> items;
  175 +
  176 + // ========== 匹配情况 ==========
  177 +
  178 + /**
168 * 是否满足优惠条件 179 * 是否满足优惠条件
169 */ 180 */
170 private Boolean meet; 181 private Boolean meet;
@@ -176,26 +187,26 @@ public class PriceCalculateRespDTO { @@ -176,26 +187,26 @@ public class PriceCalculateRespDTO {
176 */ 187 */
177 private String meetTip; 188 private String meetTip;
178 189
179 - /**  
180 - * 匹配的商品 SKU  
181 - */  
182 - @Data  
183 - public static class Item { 190 + }
184 191
185 - /**  
186 - * 商品 SKU 编号  
187 - */  
188 - private Long skuId;  
189 - /**  
190 - * 计算时的原价(总),单位:分  
191 - */  
192 - private Integer totalOriginalPrice;  
193 - /**  
194 - * 计算时的优惠(总),单位:分  
195 - */  
196 - private Integer totalPromotionPrice; 192 + /**
  193 + * 营销匹配的商品 SKU
  194 + */
  195 + @Data
  196 + public static class PromotionItem {
197 197
198 - } 198 + /**
  199 + * 商品 SKU 编号
  200 + */
  201 + private Long skuId;
  202 + /**
  203 + * 计算时的原价(总),单位:分
  204 + */
  205 + private Integer totalOriginalPrice;
  206 + /**
  207 + * 计算时的优惠(总),单位:分
  208 + */
  209 + private Integer totalPromotionPrice;
199 210
200 } 211 }
201 212
yudao-module-mall/yudao-module-market-biz/pom.xml
@@ -24,6 +24,11 @@ @@ -24,6 +24,11 @@
24 <artifactId>yudao-module-market-api</artifactId> 24 <artifactId>yudao-module-market-api</artifactId>
25 <version>${revision}</version> 25 <version>${revision}</version>
26 </dependency> 26 </dependency>
  27 + <dependency>
  28 + <groupId>cn.iocoder.boot</groupId>
  29 + <artifactId>yudao-module-product-api</artifactId>
  30 + <version>${revision}</version>
  31 + </dependency>
27 32
28 <!-- 业务组件 --> 33 <!-- 业务组件 -->
29 <dependency> 34 <dependency>
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/api/discount/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.market.api.discount;
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/price/PriceApiImpl.java renamed to yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/api/price/PriceApiImpl.java
1 -package cn.iocoder.yudao.module.market.service.price; 1 +package cn.iocoder.yudao.module.market.api.price;
2 2
3 -import cn.iocoder.yudao.module.market.api.price.PriceApi;  
4 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO; 3 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
5 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO; 4 import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
  5 +import cn.iocoder.yudao.module.market.service.price.PriceService;
6 import org.springframework.stereotype.Service; 6 import org.springframework.stereotype.Service;
7 7
  8 +import javax.annotation.Resource;
  9 +
8 /** 10 /**
9 * 价格 API 实现类 11 * 价格 API 实现类
10 * 12 *
11 - * TODO 完善注释  
12 - *  
13 - * @author TODO 13 + * @author 芋道源码
14 */ 14 */
15 @Service 15 @Service
16 public class PriceApiImpl implements PriceApi { 16 public class PriceApiImpl implements PriceApi {
17 17
  18 + @Resource
  19 + private PriceService priceService;
  20 +
18 @Override 21 @Override
19 public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) { 22 public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) {
20 - // TODO fixme:实现逻辑  
21 - return new PriceCalculateRespDTO(); 23 + return priceService.calculatePrice(calculateReqDTO);
22 } 24 }
23 25
24 } 26 }
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/convert/price/PriceConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.market.convert.price;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  4 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
  5 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
  6 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.factory.Mappers;
  9 +
  10 +import java.util.ArrayList;
  11 +import java.util.List;
  12 +import java.util.Map;
  13 +
  14 +@Mapper
  15 +public interface PriceConvert {
  16 +
  17 + PriceConvert INSTANCE = Mappers.getMapper(PriceConvert.class);
  18 +
  19 + default PriceCalculateRespDTO convert(PriceCalculateReqDTO calculateReqDTO, List<ProductSkuRespDTO> skuList) {
  20 + // 创建 PriceCalculateRespDTO 对象
  21 + PriceCalculateRespDTO priceCalculate = new PriceCalculateRespDTO();
  22 + priceCalculate.setOrder(new PriceCalculateRespDTO.Order().setSkuOriginalPrice(0).setSkuPromotionPrice(0)
  23 + .setOrderPromotionPrice(0).setDeliveryPrice(0).setPayPrice(0).setItems(new ArrayList<>())
  24 + .setCouponId(calculateReqDTO.getCouponId()));
  25 + priceCalculate.setPromotions(new ArrayList<>());
  26 + // 创建它的 OrderItem 属性
  27 + Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(),
  28 + PriceCalculateReqDTO.Item::getSkuId, PriceCalculateReqDTO.Item::getCount);
  29 + skuList.forEach(sku -> {
  30 + Integer count = skuIdCountMap.get(sku.getId());
  31 + PriceCalculateRespDTO.OrderItem orderItem = new PriceCalculateRespDTO.OrderItem().setCount(count)
  32 + .setOriginalPrice(sku.getPrice()).setTotalOriginalPrice(sku.getPrice() * count).setTotalPromotionPrice(0);
  33 + orderItem.setTotalPresentPrice(orderItem.getTotalPresentPrice()).setPresentPrice(orderItem.getOriginalPrice())
  34 + .setTotalPayPrice(orderItem.getTotalPayPrice());
  35 + priceCalculate.getOrder().getItems().add(orderItem);
  36 + });
  37 + return priceCalculate;
  38 + }
  39 +
  40 +}
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/price/PriceService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.market.service.price;
  2 +
  3 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
  4 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
  5 +
  6 +/**
  7 + * 价格计算 Service 接口
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +public interface PriceService {
  12 +
  13 + /**
  14 + * 计算商品的价格
  15 + *
  16 + * @param calculateReqDTO 价格请求
  17 + * @return 价格相应
  18 + */
  19 + PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO);
  20 +
  21 +}
yudao-module-mall/yudao-module-market-biz/src/main/java/cn/iocoder/yudao/module/market/service/price/PriceServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.market.service.price;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  4 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
  5 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
  6 +import cn.iocoder.yudao.module.market.convert.price.PriceConvert;
  7 +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
  8 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
  9 +import org.springframework.stereotype.Service;
  10 +import org.springframework.validation.annotation.Validated;
  11 +
  12 +import javax.annotation.Resource;
  13 +import java.util.List;
  14 +import java.util.Map;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  17 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
  18 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
  19 +
  20 +/**
  21 + * 价格计算 Service 实现类
  22 + *
  23 + * @author 芋道源码
  24 + */
  25 +@Service
  26 +@Validated
  27 +public class PriceServiceImpl implements PriceService {
  28 +
  29 + @Resource
  30 + private ProductSkuApi productSkuApi;
  31 +
  32 + @Override
  33 + public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) {
  34 + // 获得商品 SKU 数组
  35 + List<ProductSkuRespDTO> skuList = checkProductSkus(calculateReqDTO);
  36 + // 初始化 PriceCalculateRespDTO 对象
  37 + PriceCalculateRespDTO priceCalculate = PriceConvert.INSTANCE.convert(calculateReqDTO, skuList);
  38 +
  39 + // 计算【限时折扣】促销 TODO 待实现
  40 + // 计算【满减送】促销 TODO 待实现
  41 + // 计算【优惠劵】促销 TODO 待实现
  42 + return priceCalculate;
  43 + }
  44 +
  45 + private List<ProductSkuRespDTO> checkProductSkus(PriceCalculateReqDTO calculateReqDTO) {
  46 + // 获得商品 SKU 数组
  47 + Map<Long, Integer> skuIdCountMap = CollectionUtils.convertMap(calculateReqDTO.getItems(),
  48 + PriceCalculateReqDTO.Item::getSkuId, PriceCalculateReqDTO.Item::getCount);
  49 + List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(skuIdCountMap.keySet());
  50 +
  51 + // 校验商品 SKU
  52 + skus.forEach(sku -> {
  53 + Integer count = skuIdCountMap.get(sku.getId());
  54 + if (count == null) {
  55 + throw exception(SKU_NOT_EXISTS);
  56 + }
  57 + if (count > sku.getStock()) {
  58 + throw exception(SKU_STOCK_NOT_ENOUGH);
  59 + }
  60 + });
  61 + return skus;
  62 + }
  63 +
  64 +}
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
1 package cn.iocoder.yudao.module.product.api.sku; 1 package cn.iocoder.yudao.module.product.api.sku;
2 2
3 import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO; 3 import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
4 -import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO; 4 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
5 5
6 import java.util.Collection; 6 import java.util.Collection;
7 import java.util.List; 7 import java.util.List;
8 8
9 /** 9 /**
  10 + * 商品 SKU API 接口
  11 + *
10 * @author LeeYan9 12 * @author LeeYan9
11 * @since 2022-08-26 13 * @since 2022-08-26
12 */ 14 */
13 public interface ProductSkuApi { 15 public interface ProductSkuApi {
14 16
  17 + /**
  18 + * 查询 SKU 信息
  19 + *
  20 + * @param id SKU 编号
  21 + * @return SKU 信息
  22 + */
  23 + ProductSkuRespDTO getSku(Long id);
15 24
16 /** 25 /**
17 - * 根据skuId列表 查询sku信息 26 + * 批量查询 SKU 数组
18 * 27 *
19 - * @param skuIds sku ID列表  
20 - * @return sku信息列表 28 + * @param ids SKU 编号列表
  29 + * @return SKU 数组
21 */ 30 */
22 - List<SkuInfoRespDTO> getSkusByIds(Collection<Long> skuIds); 31 + List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
23 32
24 /** 33 /**
25 * 批量扣减sku库存 34 * 批量扣减sku库存
@@ -27,4 +36,5 @@ public interface ProductSkuApi { @@ -27,4 +36,5 @@ public interface ProductSkuApi {
27 * @param batchReqDTO sku库存信息列表 36 * @param batchReqDTO sku库存信息列表
28 */ 37 */
29 void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO); 38 void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO);
  39 +
30 } 40 }
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuInfoRespDTO.java renamed to yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java
@@ -6,11 +6,13 @@ import lombok.Data; @@ -6,11 +6,13 @@ import lombok.Data;
6 import java.util.List; 6 import java.util.List;
7 7
8 /** 8 /**
  9 + * 商品 SKU 信息 Response DTO
  10 + *
9 * @author LeeYan9 11 * @author LeeYan9
10 * @since 2022-08-26 12 * @since 2022-08-26
11 */ 13 */
12 @Data 14 @Data
13 -public class SkuInfoRespDTO { 15 +public class ProductSkuRespDTO {
14 16
15 /** 17 /**
16 * 商品 SKU 编号,自增 18 * 商品 SKU 编号,自增
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
1 package cn.iocoder.yudao.module.product.api.spu; 1 package cn.iocoder.yudao.module.product.api.spu;
2 2
3 -import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;  
4 import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO; 3 import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
5 4
6 import java.util.Collection; 5 import java.util.Collection;
7 import java.util.List; 6 import java.util.List;
8 7
9 /** 8 /**
  9 + * 商品 SPU API 接口
  10 + *
10 * @author LeeYan9 11 * @author LeeYan9
11 * @since 2022-08-26 12 * @since 2022-08-26
12 */ 13 */
13 public interface ProductSpuApi { 14 public interface ProductSpuApi {
14 15
15 -  
16 /** 16 /**
17 - * 根据spuId列表 查询spu信息 17 + * 批量查询 SPU 数组
18 * 18 *
19 - * @param spuIds spu ID列表  
20 - * @return spu信息列表 19 + * @param ids SPU 编号列表
  20 + * @return SPU 数组
21 */ 21 */
22 - List<SpuInfoRespDTO> getSpusByIds(Collection<Long> spuIds); 22 + List<SpuInfoRespDTO> getSpuList(Collection<Long> ids);
  23 +
23 } 24 }
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/SpuInfoRespDTO.java
1 package cn.iocoder.yudao.module.product.api.spu.dto; 1 package cn.iocoder.yudao.module.product.api.spu.dto;
2 2
3 -import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO; 3 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
4 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum; 4 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
5 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; 5 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
6 import lombok.Data; 6 import lombok.Data;
@@ -80,25 +80,25 @@ public class SpuInfoRespDTO { @@ -80,25 +80,25 @@ public class SpuInfoRespDTO {
80 /** 80 /**
81 * 最小价格,单位使用:分 81 * 最小价格,单位使用:分
82 * <p> 82 * <p>
83 - * 基于其对应的 {@link SkuInfoRespDTO#getPrice()} 最小值 83 + * 基于其对应的 {@link ProductSkuRespDTO#getPrice()} 最小值
84 */ 84 */
85 private Integer minPrice; 85 private Integer minPrice;
86 /** 86 /**
87 * 最大价格,单位使用:分 87 * 最大价格,单位使用:分
88 * <p> 88 * <p>
89 - * 基于其对应的 {@link SkuInfoRespDTO#getPrice()} 最大值 89 + * 基于其对应的 {@link ProductSkuRespDTO#getPrice()} 最大值
90 */ 90 */
91 private Integer maxPrice; 91 private Integer maxPrice;
92 /** 92 /**
93 * 市场价,单位使用:分 93 * 市场价,单位使用:分
94 * <p> 94 * <p>
95 - * 基于其对应的 {@link SkuInfoRespDTO#getMarketPrice()} 最大值 95 + * 基于其对应的 {@link ProductSkuRespDTO#getMarketPrice()} 最大值
96 */ 96 */
97 private Integer marketPrice; 97 private Integer marketPrice;
98 /** 98 /**
99 * 总库存 99 * 总库存
100 * <p> 100 * <p>
101 - * 基于其对应的 {@link SkuInfoRespDTO#getStock()} 求和 101 + * 基于其对应的 {@link ProductSkuRespDTO#getStock()} 求和
102 */ 102 */
103 private Integer totalStock; 103 private Integer totalStock;
104 /** 104 /**
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
@@ -36,5 +36,6 @@ public interface ErrorCodeConstants { @@ -36,5 +36,6 @@ public interface ErrorCodeConstants {
36 ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品 SKU 的属性组合存在重复"); 36 ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品 SKU 的属性组合存在重复");
37 ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU,其规格数必须一致"); 37 ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU,其规格数必须一致");
38 ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU,必须不重复"); 38 ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU,必须不重复");
  39 + ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1008006004, "商品 SKU 库存不足");
39 40
40 } 41 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
1 package cn.iocoder.yudao.module.product.api.sku; 1 package cn.iocoder.yudao.module.product.api.sku;
2 2
3 import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO; 3 import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
4 -import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO; 4 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
  5 +import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
  6 +import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
  7 +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
5 import org.springframework.stereotype.Service; 8 import org.springframework.stereotype.Service;
6 9
  10 +import javax.annotation.Resource;
7 import java.util.Collection; 11 import java.util.Collection;
8 import java.util.List; 12 import java.util.List;
9 13
10 /** 14 /**
11 - * todo 注释 15 + * 商品 SKU API 实现类
  16 + *
  17 + * @author 芋道源码
  18 + * @since 2022-08-26
12 */ 19 */
13 @Service 20 @Service
14 public class ProductSkuApiImpl implements ProductSkuApi { 21 public class ProductSkuApiImpl implements ProductSkuApi {
15 22
  23 + @Resource
  24 + private ProductSkuService productSkuService;
  25 +
  26 + @Override
  27 + public ProductSkuRespDTO getSku(Long id) {
  28 + ProductSkuDO skuDO = productSkuService.getSku(id);
  29 + return ProductSkuConvert.INSTANCE.convert02(skuDO);
  30 + }
  31 +
16 @Override 32 @Override
17 - public List<SkuInfoRespDTO> getSkusByIds(Collection<Long> skuIds) {  
18 - return null; 33 + public List<ProductSkuRespDTO> getSkuList(Collection<Long> ids) {
  34 + List<ProductSkuDO> list = productSkuService.getSkuList(ids);
  35 + return ProductSkuConvert.INSTANCE.convertList02(list);
19 } 36 }
20 37
21 @Override 38 @Override
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java
@@ -7,13 +7,16 @@ import java.util.Collection; @@ -7,13 +7,16 @@ import java.util.Collection;
7 import java.util.List; 7 import java.util.List;
8 8
9 /** 9 /**
10 - * todo 注释 10 + * 商品 SPU API 实现类
  11 + *
  12 + * @author LeeYan9
  13 + * @since 2022-08-26
11 */ 14 */
12 @Service 15 @Service
13 public class ProductSpuApiImpl implements ProductSpuApi { 16 public class ProductSpuApiImpl implements ProductSpuApi {
14 17
15 @Override 18 @Override
16 - public List<SpuInfoRespDTO> getSpusByIds(Collection<Long> spuIds) { 19 + public List<SpuInfoRespDTO> getSpuList(Collection<Long> spuIds) {
17 return null; 20 return null;
18 } 21 }
19 22
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java
@@ -33,7 +33,7 @@ public class ProductSkuBaseVO { @@ -33,7 +33,7 @@ public class ProductSkuBaseVO {
33 @ApiModelProperty(value = "条形码", example = "haha") 33 @ApiModelProperty(value = "条形码", example = "haha")
34 private String barCode; 34 private String barCode;
35 35
36 - @ApiModelProperty(value = "图片地址") 36 + @ApiModelProperty(value = "图片地址", required = true, example = "https://www.iocoder.cn/xx.png")
37 @NotNull(message = "图片地址不能为空") 37 @NotNull(message = "图片地址不能为空")
38 private String picUrl; 38 private String picUrl;
39 39
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
1 package cn.iocoder.yudao.module.product.convert.sku; 1 package cn.iocoder.yudao.module.product.convert.sku;
2 2
  3 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
3 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO; 4 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
4 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO; 5 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
5 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; 6 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
6 import org.mapstruct.Mapper; 7 import org.mapstruct.Mapper;
7 -import org.mapstruct.Mapping;  
8 import org.mapstruct.factory.Mappers; 8 import org.mapstruct.factory.Mappers;
9 9
10 import java.util.List; 10 import java.util.List;
@@ -21,11 +21,14 @@ public interface ProductSkuConvert { @@ -21,11 +21,14 @@ public interface ProductSkuConvert {
21 21
22 ProductSkuDO convert(ProductSkuCreateOrUpdateReqVO bean); 22 ProductSkuDO convert(ProductSkuCreateOrUpdateReqVO bean);
23 23
24 - @Mapping(source = "properties", target = "properties")  
25 ProductSkuRespVO convert(ProductSkuDO bean); 24 ProductSkuRespVO convert(ProductSkuDO bean);
26 25
27 List<ProductSkuRespVO> convertList(List<ProductSkuDO> list); 26 List<ProductSkuRespVO> convertList(List<ProductSkuDO> list);
28 27
29 List<ProductSkuDO> convertSkuDOList(List<ProductSkuCreateOrUpdateReqVO> list); 28 List<ProductSkuDO> convertSkuDOList(List<ProductSkuCreateOrUpdateReqVO> list);
30 29
  30 + ProductSkuRespDTO convert02(ProductSkuDO bean);
  31 +
  32 + List<ProductSkuRespDTO> convertList02(List<ProductSkuDO> list);
  33 +
31 } 34 }
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/enums/ErrorCodeConstants.java renamed to yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
1 -package cn.iocoder.yudao.module.trade.enums.enums; 1 +package cn.iocoder.yudao.module.trade.enums;
2 2
3 import cn.iocoder.yudao.framework.common.exception.ErrorCode; 3 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
4 4
5 /** 5 /**
6 - * Trade-Order 错误码 Core 枚举类  
7 - * <p>  
8 - * Trade-Order 系统,使用 1-011-000-000 段 6 + * 交易 错误码枚举类
  7 + * 交易系统,使用 1-011-000-000 段
9 * 8 *
10 * @author LeeYan9 9 * @author LeeYan9
11 * @since 2022-08-26 10 * @since 2022-08-26
12 */ 11 */
13 public interface ErrorCodeConstants { 12 public interface ErrorCodeConstants {
14 13
15 - /**  
16 - * ========== Order 模块 1-011-000-000 ==========  
17 - */ 14 + // ========== Order 模块 1-011-000-000 ==========
18 ErrorCode ORDER_SKU_NOT_FOUND = new ErrorCode(1011000001, "商品不存在"); 15 ErrorCode ORDER_SKU_NOT_FOUND = new ErrorCode(1011000001, "商品不存在");
19 ErrorCode ORDER_SPU_NOT_SALE = new ErrorCode(1011000002, "商品不可售卖"); 16 ErrorCode ORDER_SPU_NOT_SALE = new ErrorCode(1011000002, "商品不可售卖");
20 ErrorCode ORDER_SKU_NOT_SALE = new ErrorCode(1011000003, "商品Sku不可售卖"); 17 ErrorCode ORDER_SKU_NOT_SALE = new ErrorCode(1011000003, "商品Sku不可售卖");
21 ErrorCode ORDER_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1011000004, "商品库存不足"); 18 ErrorCode ORDER_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1011000004, "商品库存不足");
22 19
  20 + // ========== Cart 模块 1-011-001-000 ==========
  21 + ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1001001000, "购物车项不存在");
  22 +
23 } 23 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/package-info.java 0 → 100644
  1 +/**
  2 + * 基础包,放一些通用的 VO 类
  3 + */
  4 +package cn.iocoder.yudao.module.trade.controller.app.base;
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/property/AppProductPropertyValueDetailRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.base.property;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +@ApiModel("用户 App - 规格 + 规格值 Response VO")
  8 +@Data
  9 +public class AppProductPropertyValueDetailRespVO {
  10 +
  11 + @ApiModelProperty(value = "属性的编号", required = true, example = "1")
  12 + private Long propertyId;
  13 +
  14 + @ApiModelProperty(value = "属性的名称", required = true, example = "颜色")
  15 + private String propertyName;
  16 +
  17 + @ApiModelProperty(value = "属性值的编号", required = true, example = "1024")
  18 + private Long valueId;
  19 +
  20 + @ApiModelProperty(value = "属性值的名称", required = true, example = "红色")
  21 + private String valueName;
  22 +
  23 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/sku/AppProductSkuBaseRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.base.sku;
  2 +
  3 +import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * 商品 SKU 基础 Response VO
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +public class AppProductSkuBaseRespVO {
  16 +
  17 + @ApiModelProperty(value = "主键", required = true, example = "1024")
  18 + private Long id;
  19 +
  20 + @ApiModelProperty(value = "商品 SKU 名字", required = true, example = "芋道")
  21 + private String name;
  22 +
  23 + @ApiModelProperty(value = "图片地址", example = "https://www.iocoder.cn/xx.png")
  24 + private String picUrl;
  25 +
  26 + @ApiModelProperty(value = "库存", required = true, example = "1")
  27 + private Integer stock;
  28 +
  29 + /**
  30 + * 规格数组
  31 + */
  32 + private List<AppProductPropertyValueDetailRespVO> properties;
  33 +
  34 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.base.spu;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * 商品 SPU 基础 Response VO
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Data
  14 +public class AppProductSpuBaseRespVO {
  15 +
  16 + @ApiModelProperty(value = "主键", required = true, example = "1024")
  17 + private Long id;
  18 +
  19 + @ApiModelProperty(value = "商品 SPU 名字", required = true, example = "芋道")
  20 + private String name;
  21 +
  22 + @ApiModelProperty(value = "商品主图地址", example = "https://www.iocoder.cn/xx.png")
  23 + private List<String> picUrls;
  24 +
  25 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/CartController.java deleted
1 -package cn.iocoder.yudao.module.trade.controller.app.cart;  
2 -  
3 -import io.swagger.annotations.Api;  
4 -import org.springframework.validation.annotation.Validated;  
5 -import org.springframework.web.bind.annotation.RequestMapping;  
6 -import org.springframework.web.bind.annotation.RestController;  
7 -  
8 -@Api(tags = "购物车 API")  
9 -@RestController  
10 -@RequestMapping("/cart")  
11 -@Validated  
12 -public class CartController {  
13 -  
14 -// @Autowired  
15 -// private CartManager cartManager;  
16 -//  
17 -// @PostMapping("add")  
18 -// @ApiOperation("添加商品到购物车")  
19 -// @ApiImplicitParams({  
20 -// @ApiImplicitParam(name = "skuId", value = "商品 SKU 编号", required = true, example = "1"),  
21 -// @ApiImplicitParam(name = "quantity", value = "增加数量", required = true, example = "1024")  
22 -// })  
23 -// @RequiresAuthenticate  
24 -// public CommonResult<Boolean> addCartItem(@RequestParam("skuId") Integer skuId,  
25 -// @RequestParam("quantity") Integer quantity) {  
26 -// cartManager.addCartItem(UserSecurityContextHolder.getUserId(), skuId, quantity);  
27 -// return success(true);  
28 -// }  
29 -//  
30 -// @GetMapping("sum-quantity")  
31 -// @ApiOperation("查询用户在购物车中的商品数量")  
32 -// @RequiresAuthenticate  
33 -// public CommonResult<Integer> sumCartItemQuantity() {  
34 -// return success(cartManager.sumCartItemQuantity(UserSecurityContextHolder.getUserId()));  
35 -// }  
36 -//  
37 -// @GetMapping("/get-detail")  
38 -// @ApiOperation("查询用户的购物车的商品列表")  
39 -// @RequiresAuthenticate  
40 -// public CommonResult<CartDetailVO> getCartDetail() {  
41 -// return success(cartManager.getCartDetail(UserSecurityContextHolder.getUserId()));  
42 -// }  
43 -//  
44 -// @PostMapping("update-quantity")  
45 -// @ApiOperation("更新购物车商品数量")  
46 -// @ApiImplicitParams({  
47 -// @ApiImplicitParam(name = "skuId", value = "商品 SKU 编号", required = true, example = "1"),  
48 -// @ApiImplicitParam(name = "quantity", value = "增加数量", required = true, example = "1024")  
49 -// })  
50 -// @RequiresAuthenticate  
51 -// public CommonResult<Boolean> updateCartItemQuantity(@RequestParam("skuId") Integer skuId,  
52 -// @RequestParam("quantity") Integer quantity) {  
53 -// cartManager.updateCartItemQuantity(UserSecurityContextHolder.getUserId(), skuId, quantity);  
54 -// return success(true);  
55 -// }  
56 -//  
57 -// @PostMapping("update-selected")  
58 -// @ApiOperation("更新购物车商品是否选中")  
59 -// @ApiImplicitParams({  
60 -// @ApiImplicitParam(name = "skuIds", value = "商品 SKU 编号数组", required = true, example = "1,3"),  
61 -// @ApiImplicitParam(name = "selected", value = "是否选中", required = true, example = "true")  
62 -// })  
63 -// @RequiresAuthenticate  
64 -// public CommonResult<Boolean> updateCartItemSelected(@RequestParam("skuIds") Set<Integer> skuIds,  
65 -// @RequestParam("selected") Boolean selected) {  
66 -// cartManager.updateCartItemSelected(UserSecurityContextHolder.getUserId(), skuIds, selected);  
67 -// // 获得目前购物车明细  
68 -// return success(true);  
69 -// }  
70 -  
71 -}  
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/TradeCartController.http 0 → 100644
  1 +### 请求 /trade/cart/add-count 接口 => 成功
  2 +POST {{appApi}}/trade/cart/add-count
  3 +tenant-id: {{appTenentId}}
  4 +Authorization: Bearer {{appToken}}
  5 +Content-Type: application/json
  6 +
  7 +{
  8 + "skuId": 1,
  9 + "count": 1
  10 +}
  11 +
  12 +### 请求 /trade/cart/update-count 接口 => 成功
  13 +PUT {{appApi}}/trade/cart/update-count
  14 +tenant-id: {{appTenentId}}
  15 +Authorization: Bearer {{appToken}}
  16 +Content-Type: application/json
  17 +
  18 +{
  19 + "skuId": 1,
  20 + "count": 5
  21 +}
  22 +
  23 +### 请求 /trade/cart/update-selected 接口 => 成功
  24 +PUT {{appApi}}/trade/cart/update-selected
  25 +tenant-id: {{appTenentId}}
  26 +Authorization: Bearer {{appToken}}
  27 +Content-Type: application/json
  28 +
  29 +{
  30 + "skuIds": [1],
  31 + "selected": false
  32 +}
  33 +
  34 +### 请求 /trade/cart/delete 接口 => 成功
  35 +DELETE {{appApi}}/trade/cart/delete?skuIds=1
  36 +tenant-id: {{appTenentId}}
  37 +Authorization: Bearer {{appToken}}
  38 +
  39 +### 请求 /trade/cart/get-count 接口 => 成功
  40 +GET {{appApi}}/trade/cart/get-count
  41 +tenant-id: {{appTenentId}}
  42 +Authorization: Bearer {{appToken}}
  43 +
  44 +### 请求 /trade/cart/get-detail 接口 => 成功
  45 +GET {{appApi}}/trade/cart/get-detail
  46 +tenant-id: {{appTenentId}}
  47 +Authorization: Bearer {{appToken}}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/TradeCartController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.cart;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
  5 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO;
  6 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO;
  7 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO;
  8 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO;
  9 +import cn.iocoder.yudao.module.trade.service.cart.TradeCartService;
  10 +import io.swagger.annotations.Api;
  11 +import io.swagger.annotations.ApiImplicitParam;
  12 +import io.swagger.annotations.ApiOperation;
  13 +import lombok.RequiredArgsConstructor;
  14 +import lombok.extern.slf4j.Slf4j;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.*;
  17 +
  18 +import javax.annotation.Resource;
  19 +import javax.validation.Valid;
  20 +import java.util.List;
  21 +
  22 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  23 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  24 +
  25 +@Api(tags = "用户 App - 购物车")
  26 +@RestController
  27 +@RequestMapping("/trade/cart")
  28 +@RequiredArgsConstructor
  29 +@Validated
  30 +@Slf4j
  31 +public class TradeCartController {
  32 +
  33 + @Resource
  34 + private TradeCartService cartService;
  35 +
  36 + @PostMapping("/add-count")
  37 + @ApiOperation("添加商品到购物车")
  38 + @PreAuthenticated
  39 + public CommonResult<Boolean> addCartItemCount(@Valid @RequestBody AppTradeCartItemAddCountReqVO addCountReqVO) {
  40 + cartService.addCartItemCount(getLoginUserId(), addCountReqVO);
  41 + return success(true);
  42 + }
  43 +
  44 + @PutMapping("update-count")
  45 + @ApiOperation("更新购物车商品数量")
  46 + @PreAuthenticated
  47 + public CommonResult<Boolean> updateCartItemQuantity(@Valid @RequestBody AppTradeCartItemUpdateCountReqVO updateCountReqVO) {
  48 + cartService.updateCartItemCount(getLoginUserId(), updateCountReqVO);
  49 + return success(true);
  50 + }
  51 +
  52 + @PutMapping("update-selected")
  53 + @ApiOperation("更新购物车商品是否选中")
  54 + @PreAuthenticated
  55 + public CommonResult<Boolean> updateCartItemSelected(@Valid @RequestBody AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO) {
  56 + cartService.updateCartItemSelected(getLoginUserId(), updateSelectedReqVO);
  57 + // 获得目前购物车明细
  58 + return success(true);
  59 + }
  60 +
  61 + @DeleteMapping("/delete")
  62 + @ApiOperation("删除购物车商品")
  63 + @ApiImplicitParam(name = "skuId", value = "商品 SKU 编号的数组", required = true, example = "1024,2048", dataTypeClass = List.class)
  64 + @PreAuthenticated
  65 + public CommonResult<Boolean> deleteCartItem(@RequestParam("skuIds") List<Long> skuIds) {
  66 + cartService.deleteCartItems(getLoginUserId(), skuIds);
  67 + return success(true);
  68 + }
  69 +
  70 + @GetMapping("get-count")
  71 + @ApiOperation("查询用户在购物车中的商品数量")
  72 + @PreAuthenticated
  73 + public CommonResult<Integer> getCartCount() {
  74 + return success(cartService.getCartCount(getLoginUserId()));
  75 + }
  76 +
  77 + @GetMapping("/get-detail")
  78 + @ApiOperation("查询用户的购物车的详情")
  79 + @PreAuthenticated
  80 + public CommonResult<AppTradeCartDetailRespVO> getCartDetail() {
  81 + return success(cartService.getCartDetail(getLoginUserId()));
  82 + }
  83 +
  84 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartDetailRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
  2 +
  3 +import cn.iocoder.yudao.module.trade.controller.app.base.sku.AppProductSkuBaseRespVO;
  4 +import io.swagger.annotations.ApiModel;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@ApiModel(value = "用户 App - 用户的购物车明细 Response VO")
  11 +@Data
  12 +public class AppTradeCartDetailRespVO {
  13 +
  14 + /**
  15 + * 商品分组数组
  16 + */
  17 + private List<ItemGroup> itemGroups;
  18 +
  19 + /**
  20 + * 费用
  21 + */
  22 + private Order order;
  23 +
  24 + @ApiModel(value = "商品分组", description = "多个商品,参加同一个活动,从而形成分组")
  25 + @Data
  26 + public static class ItemGroup {
  27 +
  28 + /**
  29 + * 商品数组
  30 + */
  31 + private List<Sku> items;
  32 + /**
  33 + * 营销活动,订单级别
  34 + */
  35 + private Promotion promotion;
  36 +
  37 + }
  38 +
  39 + @ApiModel(value = "商品 SKU")
  40 + @Data
  41 + public static class Sku extends AppProductSkuBaseRespVO {
  42 +
  43 + /**
  44 + * SPU 信息
  45 + */
  46 + private AppProductSkuBaseRespVO spu;
  47 +
  48 + // ========== 购物车相关的字段 ==========
  49 +
  50 + @ApiModelProperty(value = "商品数量", required = true, example = "1")
  51 + private Integer count;
  52 + @ApiModelProperty(value = "是否选中", required = true, example = "true")
  53 + private Boolean selected;
  54 +
  55 + // ========== 价格相关的字段,对应 PriceCalculateRespDTO.OrderItem 的属性 ==========
  56 +
  57 + // TODO 芋艿:后续可以去除一些无用的字段
  58 +
  59 + @ApiModelProperty(value = "商品原价(单)", required = true, example = "100")
  60 + private Integer originalPrice;
  61 + @ApiModelProperty(value = "商品原价(总)", required = true, example = "200")
  62 + private Integer totalOriginalPrice;
  63 + @ApiModelProperty(value = "商品级优惠(总)", required = true, example = "300")
  64 + private Integer totalPromotionPrice;
  65 + @ApiModelProperty(value = "最终购买金额(总)", required = true, example = "400")
  66 + private Integer totalPresentPrice;
  67 + @ApiModelProperty(value = "最终购买金额(单)", required = true, example = "500")
  68 + private Integer presentPrice;
  69 + @ApiModelProperty(value = "应付金额(总)", required = true, example = "600")
  70 + private Integer totalPayPrice;
  71 +
  72 + // ========== 营销相关的字段 ==========
  73 + /**
  74 + * 营销活动,商品级别
  75 + */
  76 + private Promotion promotion;
  77 +
  78 + }
  79 +
  80 + @ApiModel(value = "订单", description = "对应 PriceCalculateRespDTO.Order 类,用于费用(合计)")
  81 + @Data
  82 + public static class Order {
  83 +
  84 + // TODO 芋艿:后续可以去除一些无用的字段
  85 +
  86 + @ApiModelProperty(value = "商品原价(总)", required = true, example = "100")
  87 + private Integer skuOriginalPrice;
  88 + @ApiModelProperty(value = "商品优惠(总)", required = true, example = "200")
  89 + private Integer skuPromotionPrice;
  90 + @ApiModelProperty(value = "订单优惠(总)", required = true, example = "300")
  91 + private Integer orderPromotionPrice;
  92 + @ApiModelProperty(value = "运费金额", required = true, example = "400")
  93 + private Integer deliveryPrice;
  94 + @ApiModelProperty(value = "应付金额(总)", required = true, example = "500")
  95 + private Integer payPrice;
  96 +
  97 + }
  98 +
  99 + @ApiModel(value = "营销活动", description = "对应 PriceCalculateRespDTO.Promotion 类的属性")
  100 + @Data
  101 + public static class Promotion {
  102 +
  103 + @ApiModelProperty(value = "营销编号", required = true, example = "1024", notes = "营销活动的编号、优惠劵的编号")
  104 + private Long id;
  105 + @ApiModelProperty(value = "营销名字", required = true, example = "xx 活动")
  106 + private String name;
  107 + @ApiModelProperty(value = "营销类型", required = true, example = "1", notes = "参见 PromotionTypeEnum 枚举类")
  108 + private Integer type;
  109 +
  110 + // ========== 匹配情况 ==========
  111 + @ApiModelProperty(value = "是否满足优惠条件", required = true, example = "true")
  112 + private Boolean meet;
  113 + @ApiModelProperty(value = "满足条件的提示", required = true, example = "圣诞价:省 150.00 元")
  114 + private String meetTip;
  115 +
  116 + }
  117 +
  118 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartItemAddCountReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.Min;
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +@ApiModel(value = "用户 App - 购物车添加购物项 Request VO")
  11 +@Data
  12 +public class AppTradeCartItemAddCountReqVO {
  13 +
  14 + @ApiModelProperty(value = "商品 SKU 编号", required = true,example = "1024")
  15 + @NotNull(message = "商品 SKU 编号不能为空")
  16 + private Long skuId;
  17 +
  18 + @ApiModelProperty(value = "商品数量", required = true, example = "1", notes = "注意,这是新增数量")
  19 + @NotNull(message = "数量不能为空")
  20 + @Min(message = "数量必须大于 0", value = 1L)
  21 + private Integer count;
  22 +
  23 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartItemUpdateCountReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.Min;
  8 +import javax.validation.constraints.NotNull;
  9 +
  10 +@ApiModel(value = "用户 App - 购物车更新数量 Request VO")
  11 +@Data
  12 +public class AppTradeCartItemUpdateCountReqVO {
  13 +
  14 + @ApiModelProperty(value = "商品 SKU 编号", required = true, example = "1024")
  15 + @NotNull(message = "商品 SKU 编号不能为空")
  16 + private Long skuId;
  17 +
  18 + @ApiModelProperty(value = "商品数量", required = true, example = "1")
  19 + @NotNull(message = "数量不能为空")
  20 + @Min(message = "数量必须大于 0", value = 1L)
  21 + private Integer count;
  22 +
  23 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartItemUpdateSelectedReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import javax.validation.constraints.NotNull;
  8 +import java.util.Collection;
  9 +
  10 +@ApiModel(value = "用户 App - 购物车更新是否选中 Request VO")
  11 +@Data
  12 +public class AppTradeCartItemUpdateSelectedReqVO {
  13 +
  14 + @ApiModelProperty(value = "商品 SKU 编号列表", required = true, example = "1024,2048")
  15 + @NotNull(message = "商品 SKU 编号列表不能为空")
  16 + private Collection<Long> skuIds;
  17 +
  18 + @ApiModelProperty(value = "是否选中", required = true, example = "true")
  19 + @NotNull(message = "是否选中不能为空")
  20 + private Boolean selected;
  21 +
  22 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/CartDetailVO.java deleted
1 -package cn.iocoder.yudao.module.trade.controller.app.cart.vo;  
2 -  
3 -import io.swagger.annotations.ApiModel;  
4 -import lombok.Data;  
5 -import lombok.experimental.Accessors;  
6 -  
7 -import java.util.List;  
8 -  
9 -@ApiModel(value = "用户的购物车明细 Response VO") // TODO 芋艿:swagger 文档完善  
10 -@Data  
11 -@Accessors(chain = true)  
12 -public class CartDetailVO {  
13 -  
14 - /**  
15 - * 商品分组数组  
16 - */  
17 - private List<ItemGroup> itemGroups;  
18 - /**  
19 - * 费用  
20 - */  
21 - private Fee fee;  
22 -  
23 - /**  
24 - * 商品分组  
25 - *  
26 - * 多个商品,参加同一个活动,从而形成分组。  
27 - */  
28 - @Data  
29 - @Accessors(chain = true)  
30 - public static class ItemGroup {  
31 -  
32 -// /**  
33 -// * 优惠活动  
34 -// */  
35 -// private PromotionActivityRespDTO activity; // TODO 芋艿,偷懒  
36 - /**  
37 - * 促销减少的金额  
38 - *  
39 - * 1. 若未参与促销活动,或不满足促销条件,返回 null  
40 - * 2. 该金额,已经分摊到每个 Item 的 discountTotal ,需要注意。  
41 - */  
42 - private Integer activityDiscountTotal;  
43 - /**  
44 - * 商品数组  
45 - */  
46 - private List<Sku> items;  
47 -  
48 - }  
49 -  
50 - @Data  
51 - @Accessors(chain = true)  
52 - public static class Sku {  
53 -  
54 - // SKU 自带信息  
55 - /**  
56 - * sku 编号  
57 - */  
58 - private Integer id;  
59 - /**  
60 - * SPU 信息  
61 - */  
62 - private Spu spu;  
63 - /**  
64 - * 图片地址  
65 - */  
66 - private String picURL;  
67 -// /**  
68 -// * 规格值数组  
69 -// */  
70 -// private List<ProductAttrKeyValueRespVO> attrs; // TODO 后面改下  
71 - /**  
72 - * 价格,单位:分  
73 - */  
74 - private Integer price;  
75 - /**  
76 - * 库存数量  
77 - */  
78 - private Integer quantity;  
79 -  
80 - // 非 SKU 自带信息  
81 -  
82 - /**  
83 - * 购买数量  
84 - */  
85 - private Integer buyQuantity;  
86 - /**  
87 - * 是否选中  
88 - */  
89 - private Boolean selected;  
90 -// /**  
91 -// * 优惠活动  
92 -// */  
93 -// private PromotionActivityRespDTO activity; // TODO 芋艿,偷懒  
94 - /**  
95 - * 原始单价,单位:分。  
96 - */  
97 - private Integer originPrice;  
98 - /**  
99 - * 购买单价,单位:分  
100 - */  
101 - private Integer buyPrice;  
102 - /**  
103 - * 最终价格,单位:分。  
104 - */  
105 - private Integer presentPrice;  
106 - /**  
107 - * 购买总金额,单位:分  
108 - *  
109 - * 用途类似 {@link #presentTotal}  
110 - */  
111 - private Integer buyTotal;  
112 - /**  
113 - * 优惠总金额,单位:分。  
114 - */  
115 - private Integer discountTotal;  
116 - /**  
117 - * 最终总金额,单位:分。  
118 - *  
119 - * 注意,presentPrice * quantity 不一定等于 presentTotal 。  
120 - * 因为,存在无法整除的情况。  
121 - * 举个例子,presentPrice = 8.33 ,quantity = 3 的情况,presentTotal 有可能是 24.99 ,也可能是 25 。  
122 - * 所以,需要存储一个该字段。  
123 - */  
124 - private Integer presentTotal;  
125 -  
126 - }  
127 -  
128 - @Data  
129 - @Accessors(chain = true)  
130 - public static class Spu {  
131 -  
132 - /**  
133 - * SPU 编号  
134 - */  
135 - private Integer id;  
136 -  
137 - // ========== 基本信息 =========  
138 - /**  
139 - * SPU 名字  
140 - */  
141 - private String name;  
142 - /**  
143 - * 分类编号  
144 - */  
145 - private Integer cid;  
146 - /**  
147 - * 商品主图地址  
148 - *  
149 - * 数组,以逗号分隔  
150 - *  
151 - * 建议尺寸:800*800像素,你可以拖拽图片调整顺序,最多上传15张  
152 - */  
153 - private List<String> picUrls;  
154 -  
155 - }  
156 -  
157 - /**  
158 - * 费用(合计)  
159 - */  
160 - @Data  
161 - @Accessors(chain = true)  
162 - public static class Fee {  
163 -  
164 - /**  
165 - * 购买总价  
166 - */  
167 - private Integer buyTotal;  
168 - /**  
169 - * 优惠总价  
170 - *  
171 - * 注意,满多少元包邮,不算在优惠中。  
172 - */  
173 - private Integer discountTotal;  
174 - /**  
175 - * 邮费  
176 - */  
177 - private Integer postageTotal;  
178 - /**  
179 - * 最终价格  
180 - *  
181 - * 计算公式 = 总价 - 优惠总价 + 邮费  
182 - */  
183 - private Integer presentTotal;  
184 -  
185 - public Fee() {  
186 - }  
187 -  
188 - public Fee(Integer buyTotal, Integer discountTotal, Integer postageTotal, Integer presentTotal) {  
189 - this.buyTotal = buyTotal;  
190 - this.discountTotal = discountTotal;  
191 - this.postageTotal = postageTotal;  
192 - this.presentTotal = presentTotal;  
193 - }  
194 -  
195 - }  
196 -  
197 - /**  
198 - * 邮费信息 TODO 芋艿,未完成  
199 - */  
200 - @Data  
201 - @Accessors(chain = true)  
202 - public static class Postage {  
203 -  
204 - /**  
205 - * 需要满足多少钱,可以包邮。单位:分  
206 - */  
207 - private Integer threshold;  
208 -  
209 - }  
210 -  
211 -}  
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
@@ -60,8 +60,8 @@ public class AppTradeOrderController { @@ -60,8 +60,8 @@ public class AppTradeOrderController {
60 60
61 @GetMapping("/get") 61 @GetMapping("/get")
62 @ApiOperation("获得交易订单") 62 @ApiOperation("获得交易订单")
63 - @ApiImplicitParam(name = "tradeOrderId", value = "交易订单编号", required = true)  
64 - public CommonResult<TradeOrderRespVO> getTradeOrder(@RequestParam("tradeOrderId") Integer tradeOrderId) { 63 + @ApiImplicitParam(name = "id", value = "交易订单编号", required = true, dataTypeClass = Long.class)
  64 + public CommonResult<TradeOrderRespVO> getTradeOrder(@RequestParam("id") Integer id) {
65 // return success(tradeOrderService.getTradeOrder(tradeOrderId)); 65 // return success(tradeOrderService.getTradeOrder(tradeOrderId));
66 return null; 66 return null;
67 } 67 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.convert.cart;
  2 +
  3 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
  4 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
  5 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO;
  6 +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartItemDO;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.Mapping;
  9 +import org.mapstruct.Mappings;
  10 +import org.mapstruct.factory.Mappers;
  11 +
  12 +import java.util.Collections;
  13 +import java.util.List;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  16 +
  17 +@Mapper
  18 +public interface TradeCartConvert {
  19 +
  20 + TradeCartConvert INSTANCE = Mappers.getMapper(TradeCartConvert.class);
  21 +
  22 + default AppTradeCartDetailRespVO buildEmptyAppTradeCartDetailRespVO() {
  23 + return new AppTradeCartDetailRespVO().setItemGroups(Collections.emptyList())
  24 + .setOrder(new AppTradeCartDetailRespVO.Order().setSkuOriginalPrice(0).setSkuPromotionPrice(0)
  25 + .setOrderPromotionPrice(0).setDeliveryPrice(0).setPayPrice(0));
  26 + }
  27 +
  28 + default PriceCalculateReqDTO convert(Long userId, List<TradeCartItemDO> cartItems) {
  29 + return new PriceCalculateReqDTO().setUserId(userId)
  30 + .setItems(convertList(cartItems, cartItem -> new PriceCalculateReqDTO.Item().setSkuId(cartItem.getSkuId())
  31 + .setCount(cartItem.getSelected() ? cartItem.getCount() : 0)));
  32 + }
  33 +
  34 + // ========== AppTradeCartDetailRespVO 相关 ==========
  35 +
  36 + AppTradeCartDetailRespVO.Promotion convert(PriceCalculateRespDTO.Promotion bean);
  37 +
  38 + @Mappings({
  39 + @Mapping(source = "cartItem.count", target = "count")
  40 + })
  41 + AppTradeCartDetailRespVO.Sku convert(PriceCalculateRespDTO.OrderItem orderItem, TradeCartItemDO cartItem);
  42 +
  43 + AppTradeCartDetailRespVO.Order convert(PriceCalculateRespDTO.Order bean);
  44 +
  45 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderItemConvert.java
@@ -29,7 +29,7 @@ public interface TradeOrderItemConvert { @@ -29,7 +29,7 @@ public interface TradeOrderItemConvert {
29 @Mapping(source = "tradeOrder.userId", target = "userId"), 29 @Mapping(source = "tradeOrder.userId", target = "userId"),
30 @Mapping(source = "tradeOrder.orderId", target = "orderId") 30 @Mapping(source = "tradeOrder.orderId", target = "orderId")
31 }) 31 })
32 - default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrder, List<PriceCalculateRespDTO.Item> items) { 32 + default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrder, List<PriceCalculateRespDTO.OrderItem> items) {
33 // TODO @Com: Mapstruct 生成会报错 33 // TODO @Com: Mapstruct 生成会报错
34 throw new UnsupportedOperationException("无法实现"); 34 throw new UnsupportedOperationException("无法实现");
35 } 35 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/sku/ProductSkuConvert.java
@@ -7,6 +7,7 @@ import org.mapstruct.factory.Mappers; @@ -7,6 +7,7 @@ import org.mapstruct.factory.Mappers;
7 7
8 import java.util.List; 8 import java.util.List;
9 9
  10 +// TODO @LeeYan9:挪到 OrderConvert 那
10 /** 11 /**
11 * @author LeeYan9 12 * @author LeeYan9
12 * @since 2022-08-26 13 * @since 2022-08-26
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/cart/CartItemDO.java renamed to yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/cart/TradeCartItemDO.java
@@ -6,16 +6,16 @@ import lombok.Data; @@ -6,16 +6,16 @@ import lombok.Data;
6 import lombok.EqualsAndHashCode; 6 import lombok.EqualsAndHashCode;
7 import lombok.experimental.Accessors; 7 import lombok.experimental.Accessors;
8 8
9 -import java.util.Date;  
10 -  
11 /** 9 /**
12 * 购物车的商品信息 DO 10 * 购物车的商品信息 DO
  11 + *
  12 + * @author 芋道源码
13 */ 13 */
14 @TableName("trade_cart_item") 14 @TableName("trade_cart_item")
15 @Data 15 @Data
16 @EqualsAndHashCode(callSuper = true) 16 @EqualsAndHashCode(callSuper = true)
17 @Accessors(chain = true) 17 @Accessors(chain = true)
18 -public class CartItemDO extends BaseDO { 18 +public class TradeCartItemDO extends BaseDO {
19 19
20 // ========= 基础字段 BEGIN ========= 20 // ========= 基础字段 BEGIN =========
21 21
@@ -27,10 +27,6 @@ public class CartItemDO extends BaseDO { @@ -27,10 +27,6 @@ public class CartItemDO extends BaseDO {
27 * 是否选中 27 * 是否选中
28 */ 28 */
29 private Boolean selected; 29 private Boolean selected;
30 - /**  
31 - * 购物时间  
32 - */  
33 - private Date buyTime; // basket_date  
34 30
35 // ========= 基础字段 END ========= 31 // ========= 基础字段 END =========
36 32
@@ -62,7 +58,7 @@ public class CartItemDO extends BaseDO { @@ -62,7 +58,7 @@ public class CartItemDO extends BaseDO {
62 /** 58 /**
63 * 商品购买数量 59 * 商品购买数量
64 */ 60 */
65 - private Integer stock; 61 + private Integer count;
66 62
67 // ========= 商品信息 END ========= 63 // ========= 商品信息 END =========
68 64
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/cart/TradeCartItemMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.mysql.cart;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
  5 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  7 +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartItemDO;
  8 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  9 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  10 +import org.apache.ibatis.annotations.Mapper;
  11 +
  12 +import java.util.Collection;
  13 +import java.util.List;
  14 +import java.util.Map;
  15 +
  16 +@Mapper
  17 +public interface TradeCartItemMapper extends BaseMapperX<TradeCartItemDO> {
  18 +
  19 + default TradeCartItemDO selectByUserIdAndSkuId(Long userId, Long skuId) {
  20 + return selectOne(TradeCartItemDO::getUserId, userId,
  21 + TradeCartItemDO::getSkuId, skuId);
  22 + }
  23 +
  24 + default List<TradeCartItemDO> selectListByUserIdAndSkuIds(Long userId, Collection<Long> skuIds) {
  25 + return selectList(new LambdaQueryWrapper<TradeCartItemDO>().eq(TradeCartItemDO::getUserId, userId)
  26 + .in(TradeCartItemDO::getSkuId, skuIds));
  27 + }
  28 +
  29 + default void updateByIds(Collection<Long> ids, TradeCartItemDO updateObject) {
  30 + update(updateObject, new LambdaQueryWrapper<TradeCartItemDO>().in(TradeCartItemDO::getId, ids));
  31 + }
  32 +
  33 + default Integer selectSumByUserId(Long userId) {
  34 + // SQL sum 查询
  35 + List<Map<String, Object>> result = selectMaps(new QueryWrapper<TradeCartItemDO>()
  36 + .select("SUM(count) AS sumCount")
  37 + .eq("user_id", userId));
  38 + // 获得数量
  39 + return CollUtil.isNotEmpty(result) ? MapUtil.getInt(result.get(0), "sumCount") : 0;
  40 + }
  41 +
  42 + default List<TradeCartItemDO> selectListByUserId(Long userId, Boolean selected) {
  43 + return selectList(new LambdaQueryWrapperX<TradeCartItemDO>().eq(TradeCartItemDO::getUserId, userId)
  44 + .eqIfPresent(TradeCartItemDO::getSelected, selected));
  45 + }
  46 +
  47 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.cart;
  2 +
  3 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO;
  4 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO;
  5 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO;
  6 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO;
  7 +
  8 +import javax.validation.Valid;
  9 +import java.util.Collection;
  10 +
  11 +/**
  12 + * 购物车 Service 接口
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public interface TradeCartService {
  17 +
  18 + /**
  19 + * 添加商品到购物车
  20 + *
  21 + * @param userId 用户编号
  22 + * @param addCountReqVO 添加信息
  23 + */
  24 + void addCartItemCount(Long userId, @Valid AppTradeCartItemAddCountReqVO addCountReqVO);
  25 +
  26 + /**
  27 + * 更新购物车商品数量
  28 + *
  29 + * @param userId 用户编号
  30 + * @param updateCountReqVO 更新信息
  31 + */
  32 + void updateCartItemCount(Long userId, AppTradeCartItemUpdateCountReqVO updateCountReqVO);
  33 +
  34 + /**
  35 + * 更新购物车商品是否选中
  36 + *
  37 + * @param userId 用户编号
  38 + * @param updateSelectedReqVO 更新信息
  39 + */
  40 + void updateCartItemSelected(Long userId, AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO);
  41 +
  42 + /**
  43 + * 删除购物车商品
  44 + *
  45 + * @param userId 用户编号
  46 + * @param skuIds SKU 编号的数组
  47 + */
  48 + void deleteCartItems(Long userId, Collection<Long> skuIds);
  49 +
  50 + /**
  51 + * 查询用户在购物车中的商品数量
  52 + *
  53 + * @param userId 用户编号
  54 + * @return 商品数量
  55 + */
  56 + Integer getCartCount(Long userId);
  57 +
  58 + /**
  59 + * 查询用户的购物车详情
  60 + *
  61 + * @param userId 用户编号
  62 + * @return 购物车详情
  63 + */
  64 + AppTradeCartDetailRespVO getCartDetail(Long userId);
  65 +
  66 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.cart;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  6 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  7 +import cn.iocoder.yudao.module.market.api.price.PriceApi;
  8 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
  9 +import cn.iocoder.yudao.module.market.enums.common.PromotionLevelEnum;
  10 +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
  11 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
  12 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartDetailRespVO;
  13 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemAddCountReqVO;
  14 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateCountReqVO;
  15 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartItemUpdateSelectedReqVO;
  16 +import cn.iocoder.yudao.module.trade.convert.cart.TradeCartConvert;
  17 +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartItemDO;
  18 +import cn.iocoder.yudao.module.trade.dal.mysql.cart.TradeCartItemMapper;
  19 +import org.springframework.stereotype.Service;
  20 +import org.springframework.validation.annotation.Validated;
  21 +
  22 +import javax.annotation.Resource;
  23 +import java.util.ArrayList;
  24 +import java.util.Collection;
  25 +import java.util.List;
  26 +import java.util.Map;
  27 +
  28 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  30 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
  31 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
  32 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.CARD_ITEM_NOT_FOUND;
  33 +
  34 +/**
  35 + * 购物车 Service 实现类
  36 + *
  37 + * @author 芋道源码
  38 + */
  39 +@Service
  40 +@Validated
  41 +public class TradeCartServiceImpl implements TradeCartService {
  42 +
  43 + @Resource
  44 + private TradeCartItemMapper cartItemMapper;
  45 +
  46 + @Resource
  47 + private ProductSkuApi productSkuApi;
  48 + @Resource
  49 + private PriceApi priceApi;
  50 +
  51 + @Override
  52 + public void addCartItemCount(Long userId, AppTradeCartItemAddCountReqVO addCountReqVO) {
  53 + Long skuId = addCountReqVO.getSkuId();
  54 + Integer count = addCountReqVO.getCount();
  55 + // 查询 CartItemDO
  56 + TradeCartItemDO tradeItem = cartItemMapper.selectByUserIdAndSkuId(userId, addCountReqVO.getSkuId());
  57 +
  58 + // 存在,则进行数量更新
  59 + if (tradeItem != null) {
  60 + checkProductSku(skuId, tradeItem.getCount() + count);
  61 + cartItemMapper.updateById(new TradeCartItemDO().setId(tradeItem.getId())
  62 + .setSelected(true).setCount(tradeItem.getCount() + count));
  63 + return;
  64 + }
  65 +
  66 + // 不存在,则进行插入
  67 + ProductSkuRespDTO sku = checkProductSku(skuId, count);
  68 + cartItemMapper.insert(new TradeCartItemDO().setUserId(userId).setSpuId(sku.getSpuId()).setSkuId(sku.getId())
  69 + .setSelected(true).setCount(count));
  70 + }
  71 +
  72 + @Override
  73 + public void updateCartItemCount(Long userId, AppTradeCartItemUpdateCountReqVO updateCountReqVO) {
  74 + // 校验 TradeCartItemDO 存在
  75 + TradeCartItemDO tradeItem = cartItemMapper.selectByUserIdAndSkuId(userId, updateCountReqVO.getSkuId());
  76 + if (tradeItem == null) {
  77 + throw exception(CARD_ITEM_NOT_FOUND);
  78 + }
  79 + // 校验商品 SKU
  80 + checkProductSku(updateCountReqVO.getSkuId(), updateCountReqVO.getCount());
  81 +
  82 + // 更新数量
  83 + cartItemMapper.updateById(new TradeCartItemDO().setId(tradeItem.getId()).setCount(updateCountReqVO.getCount()));
  84 + }
  85 +
  86 + @Override
  87 + public void updateCartItemSelected(Long userId, AppTradeCartItemUpdateSelectedReqVO updateSelectedReqVO) {
  88 + // 查询 CartItemDO 列表
  89 + List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserIdAndSkuIds(userId, updateSelectedReqVO.getSkuIds());
  90 + if (CollUtil.isEmpty(cartItems)) {
  91 + return;
  92 + }
  93 +
  94 + // 更新选中
  95 + cartItemMapper.updateByIds(CollectionUtils.convertList(cartItems, TradeCartItemDO::getId),
  96 + new TradeCartItemDO().setSelected(updateSelectedReqVO.getSelected()));
  97 + }
  98 +
  99 + /**
  100 + * 购物车删除商品
  101 + *
  102 + * @param userId 用户编号
  103 + * @param skuIds 商品 SKU 编号的数组
  104 + */
  105 + @Override
  106 + public void deleteCartItems(Long userId, Collection<Long> skuIds) {
  107 + // 查询 CartItemDO 列表
  108 + List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserIdAndSkuIds(userId, skuIds);
  109 + if (CollUtil.isEmpty(cartItems)) {
  110 + return;
  111 + }
  112 +
  113 + // 批量标记删除
  114 + cartItemMapper.deleteBatchIds(CollectionUtils.convertSet(cartItems, TradeCartItemDO::getId));
  115 + }
  116 +
  117 + @Override
  118 + public Integer getCartCount(Long userId) {
  119 + return cartItemMapper.selectSumByUserId(userId);
  120 + }
  121 +
  122 + @Override
  123 + public AppTradeCartDetailRespVO getCartDetail(Long userId) {
  124 + // 获得购物车的商品
  125 + List<TradeCartItemDO> cartItems = cartItemMapper.selectListByUserId(userId, null);
  126 + // 如果未空,则返回空结果
  127 + if (CollUtil.isEmpty(cartItems)) {
  128 + return TradeCartConvert.INSTANCE.buildEmptyAppTradeCartDetailRespVO();
  129 + }
  130 +
  131 + // 调用价格服务,计算价格
  132 + PriceCalculateRespDTO priceCalculate = priceApi.calculatePrice(TradeCartConvert.INSTANCE.convert(userId, cartItems));
  133 +
  134 + // 转换返回
  135 + Map<Long, TradeCartItemDO> cartItemMap = convertMap(cartItems, TradeCartItemDO::getSkuId);
  136 + Map<Long, PriceCalculateRespDTO.OrderItem> orderItemMap = convertMap(priceCalculate.getOrder().getItems(),
  137 + PriceCalculateRespDTO.OrderItem::getSkuId);
  138 + List<AppTradeCartDetailRespVO.ItemGroup> itemGroups = new ArrayList<>(cartItems.size());
  139 + // ① 场景一,营销活动,订单级别 TODO 芋艿:待测试
  140 + priceCalculate.getPromotions().stream().filter(promotion -> PromotionLevelEnum.ORDER.getLevel().equals(promotion.getLevel()))
  141 + .forEach(promotion -> {
  142 + AppTradeCartDetailRespVO.ItemGroup itemGroup = new AppTradeCartDetailRespVO.ItemGroup().setItems(new ArrayList<>())
  143 + .setPromotion(TradeCartConvert.INSTANCE.convert(promotion));
  144 + itemGroups.add(itemGroup);
  145 + promotion.getItems().forEach(promotionItem -> {
  146 + PriceCalculateRespDTO.OrderItem orderItem = orderItemMap.remove(promotionItem.getSkuId());
  147 + Assert.notNull(orderItem, "商品 SKU({}) 对应的订单项不能为空", promotionItem.getSkuId());
  148 + TradeCartItemDO cartItem = cartItemMap.get(orderItem.getSkuId());
  149 + itemGroup.getItems().add(TradeCartConvert.INSTANCE.convert(orderItem, cartItem)); // TODO spu
  150 + });
  151 + });
  152 + // ② 场景二,营销活动,商品级别
  153 + orderItemMap.values().forEach(orderItem -> {
  154 + AppTradeCartDetailRespVO.ItemGroup itemGroup = new AppTradeCartDetailRespVO.ItemGroup().setItems(new ArrayList<>(1)).setPromotion(null);
  155 + itemGroups.add(itemGroup);
  156 + TradeCartItemDO cartItem = cartItemMap.get(orderItem.getSkuId());
  157 + itemGroup.getItems().add(TradeCartConvert.INSTANCE.convert(orderItem, cartItem)); // TODO spu
  158 + });
  159 + return new AppTradeCartDetailRespVO().setItemGroups(itemGroups)
  160 + .setOrder(TradeCartConvert.INSTANCE.convert(priceCalculate.getOrder()));
  161 + }
  162 +
  163 + /**
  164 + * 校验商品 SKU 是否合法
  165 + * 1. 是否存在
  166 + * 2. 是否下架
  167 + * 3. 库存不足
  168 + *
  169 + * @param skuId 商品 SKU 编号
  170 + * @param count 商品数量
  171 + * @return 商品 SKU
  172 + */
  173 + private ProductSkuRespDTO checkProductSku(Long skuId, Integer count) {
  174 + ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
  175 + if (sku == null || CommonStatusEnum.DISABLE.getStatus().equals(sku.getStatus())) {
  176 + throw exception(SKU_NOT_EXISTS);
  177 + }
  178 + if (count > sku.getStock()) {
  179 + throw exception(SKU_STOCK_NOT_ENOUGH);
  180 + }
  181 + return sku;
  182 + }
  183 +
  184 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
@@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; @@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
11 import cn.iocoder.yudao.module.pay.api.order.PayOrderDataCreateReqDTO; 11 import cn.iocoder.yudao.module.pay.api.order.PayOrderDataCreateReqDTO;
12 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; 12 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
13 import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO; 13 import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
14 -import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO; 14 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
15 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; 15 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
16 import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO; 16 import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
17 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; 17 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
@@ -26,7 +26,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; @@ -26,7 +26,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
26 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; 26 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
27 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; 27 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
28 import cn.iocoder.yudao.module.trade.dal.mysql.orderitem.TradeOrderItemMapper; 28 import cn.iocoder.yudao.module.trade.dal.mysql.orderitem.TradeOrderItemMapper;
29 -import cn.iocoder.yudao.module.trade.enums.enums.ErrorCodeConstants; 29 +import cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants;
30 import lombok.RequiredArgsConstructor; 30 import lombok.RequiredArgsConstructor;
31 import org.springframework.stereotype.Service; 31 import org.springframework.stereotype.Service;
32 import org.springframework.transaction.annotation.Transactional; 32 import org.springframework.transaction.annotation.Transactional;
@@ -62,12 +62,12 @@ public class TradeOrderServiceImpl implements TradeOrderService { @@ -62,12 +62,12 @@ public class TradeOrderServiceImpl implements TradeOrderService {
62 62
63 List<Item> items = createReqVO.getItems(); 63 List<Item> items = createReqVO.getItems();
64 // 商品SKU检查 sku可售状态,库存 64 // 商品SKU检查 sku可售状态,库存
65 - List<SkuInfoRespDTO> skuInfos = productSkuApi.getSkusByIds(CollectionUtils.convertSet(items, Item::getSkuId));  
66 - Map<Long, SkuInfoRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, SkuInfoRespDTO::getId); 65 + List<ProductSkuRespDTO> skuInfos = productSkuApi.getSkuList(CollectionUtils.convertSet(items, Item::getSkuId));
  66 + Map<Long, ProductSkuRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, ProductSkuRespDTO::getId);
67 checkSaleableAndStockFromSpu(skuInfoMap, items); 67 checkSaleableAndStockFromSpu(skuInfoMap, items);
68 68
69 // 商品SPU检查 sku可售状态,库存 69 // 商品SPU检查 sku可售状态,库存
70 - List<SpuInfoRespDTO> spuInfos = productSpuApi.getSpusByIds(CollectionUtils.convertSet(skuInfos, SkuInfoRespDTO::getSpuId)); 70 + List<SpuInfoRespDTO> spuInfos = productSpuApi.getSpuList(CollectionUtils.convertSet(skuInfos, ProductSkuRespDTO::getSpuId));
71 checkSaleableFromSpu(spuInfos); 71 checkSaleableFromSpu(spuInfos);
72 72
73 // 价格计算 73 // 价格计算
@@ -79,7 +79,7 @@ public class TradeOrderServiceImpl implements TradeOrderService { @@ -79,7 +79,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
79 tradeOrderMapper.insert(tradeOrderDO); 79 tradeOrderMapper.insert(tradeOrderDO);
80 80
81 // 订单项信息记录 81 // 订单项信息记录
82 - List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(tradeOrderDO, priceResp.getItems()); 82 + List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(tradeOrderDO, priceResp.getOrder().getItems());
83 //-填充订单项-SKU信息 83 //-填充订单项-SKU信息
84 fillItemsInfoFromSku(tradeOrderItems, skuInfoMap); 84 fillItemsInfoFromSku(tradeOrderItems, skuInfoMap);
85 tradeOrderItemMapper.insertBatch(tradeOrderItems); 85 tradeOrderItemMapper.insertBatch(tradeOrderItems);
@@ -94,10 +94,10 @@ public class TradeOrderServiceImpl implements TradeOrderService { @@ -94,10 +94,10 @@ public class TradeOrderServiceImpl implements TradeOrderService {
94 } 94 }
95 95
96 private void fillItemsInfoFromSku(List<TradeOrderItemDO> tradeOrderItems, 96 private void fillItemsInfoFromSku(List<TradeOrderItemDO> tradeOrderItems,
97 - Map<Long, SkuInfoRespDTO> spuInfos) { 97 + Map<Long, ProductSkuRespDTO> spuInfos) {
98 for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) { 98 for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) {
99 // 填充SKU信息 99 // 填充SKU信息
100 - SkuInfoRespDTO skuInfoRespDTO = spuInfos.get(tradeOrderItem.getSkuId()); 100 + ProductSkuRespDTO skuInfoRespDTO = spuInfos.get(tradeOrderItem.getSkuId());
101 tradeOrderItem.setSpuId(skuInfoRespDTO.getSpuId()); 101 tradeOrderItem.setSpuId(skuInfoRespDTO.getSpuId());
102 tradeOrderItem.setPicUrl(skuInfoRespDTO.getPicUrl()); 102 tradeOrderItem.setPicUrl(skuInfoRespDTO.getPicUrl());
103 tradeOrderItem.setName(skuInfoRespDTO.getName()); 103 tradeOrderItem.setName(skuInfoRespDTO.getName());
@@ -116,14 +116,14 @@ public class TradeOrderServiceImpl implements TradeOrderService { @@ -116,14 +116,14 @@ public class TradeOrderServiceImpl implements TradeOrderService {
116 } 116 }
117 } 117 }
118 118
119 - private void checkSaleableAndStockFromSpu(Map<Long, SkuInfoRespDTO> skuInfoMap, 119 + private void checkSaleableAndStockFromSpu(Map<Long, ProductSkuRespDTO> skuInfoMap,
120 List<Item> items) { 120 List<Item> items) {
121 // sku 不存在 121 // sku 不存在
122 if (items.size() != skuInfoMap.size()) { 122 if (items.size() != skuInfoMap.size()) {
123 throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_FOUND); 123 throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_FOUND);
124 } 124 }
125 for (Item item : items) { 125 for (Item item : items) {
126 - SkuInfoRespDTO skuInfoDTO = skuInfoMap.get(item.getSkuId()); 126 + ProductSkuRespDTO skuInfoDTO = skuInfoMap.get(item.getSkuId());
127 // sku禁用 127 // sku禁用
128 if (!Objects.equals(CommonStatusEnum.ENABLE.getStatus(), skuInfoDTO.getStatus())) { 128 if (!Objects.equals(CommonStatusEnum.ENABLE.getStatus(), skuInfoDTO.getStatus())) {
129 throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_SALE); 129 throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_SALE);
yudao-server/src/main/resources/application-local.yaml
@@ -173,6 +173,7 @@ logging: @@ -173,6 +173,7 @@ logging:
173 cn.iocoder.yudao.module.system.dal.mysql: debug 173 cn.iocoder.yudao.module.system.dal.mysql: debug
174 cn.iocoder.yudao.module.tool.dal.mysql: debug 174 cn.iocoder.yudao.module.tool.dal.mysql: debug
175 cn.iocoder.yudao.module.member.dal.mysql: debug 175 cn.iocoder.yudao.module.member.dal.mysql: debug
  176 + cn.iocoder.yudao.module.trade.dal.mysql: debug
176 177
177 --- #################### 微信公众号、小程序相关配置 #################### 178 --- #################### 微信公众号、小程序相关配置 ####################
178 wx: 179 wx: