Commit f7fe9ad7b0c19f2c2fc12bc518b34ee991ac5283

Authored by YunaiV
1 parent da162853

1. 商城:调整购物车的失效逻辑实现,基于 SPU 是否下架

2. 商城:增加购物车失效的重选逻辑
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java
1 1 package cn.iocoder.yudao.module.product.api.sku.dto;
2 2  
3   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 3 import lombok.Data;
5 4  
6 5 import java.util.List;
... ... @@ -22,10 +21,6 @@ public class ProductSkuRespDTO {
22 21 * SPU 编号
23 22 */
24 23 private Long spuId;
25   - /**
26   - * SPU 名字
27   - */
28   - private String spuName;
29 24  
30 25 /**
31 26 * 属性数组
... ... @@ -52,12 +47,6 @@ public class ProductSkuRespDTO {
52 47 */
53 48 private String picUrl;
54 49 /**
55   - * SKU 状态
56   - * <p>
57   - * 枚举 {@link CommonStatusEnum}
58   - */
59   - private Integer status;
60   - /**
61 50 * 库存
62 51 */
63 52 private Integer stock;
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
... ... @@ -14,7 +14,6 @@ public interface ErrorCodeConstants {
14 14 // ========== Order 模块 1-011-000-000 ==========
15 15 ErrorCode ORDER_CREATE_SKU_NOT_FOUND = new ErrorCode(1011000001, "商品 SKU 不存在");
16 16 ErrorCode ORDER_CREATE_SPU_NOT_SALE = new ErrorCode(1011000002, "商品 SPU 不可售卖");
17   - ErrorCode ORDER_CREATE_SKU_NOT_SALE = new ErrorCode(1011000003, "商品 SKU 不可售卖");
18 17 ErrorCode ORDER_CREATE_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1011000004, "商品 SKU 库存不足");
19 18 ErrorCode ORDER_CREATE_SPU_NOT_FOUND = new ErrorCode(1011000005, "商品 SPU 不可售卖");
20 19 ErrorCode ORDER_CREATE_ADDRESS_NOT_FOUND = new ErrorCode(1011000006, "收货地址不存在");
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/sku/AppProductSkuBaseRespVO.java
... ... @@ -17,12 +17,12 @@ public class AppProductSkuBaseRespVO {
17 17 @Schema(description = "主键", required = true, example = "1024")
18 18 private Long id;
19 19  
20   - @Schema(description = "商品 SKU 名字", required = true, example = "芋道")
21   - private String name;
22   -
23 20 @Schema(description = "图片地址", example = "https://www.iocoder.cn/xx.png")
24 21 private String picUrl;
25 22  
  23 + @Schema(description = "销售价格,单位:分", required = true, example = "100")
  24 + private Integer price;
  25 +
26 26 @Schema(description = "库存", required = true, example = "1")
27 27 private Integer stock;
28 28  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/TradeCartController.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
5 5 import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
6 6 import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
  7 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartResetReqVO;
7 8 import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
8 9 import cn.iocoder.yudao.module.trade.service.cart.TradeCartService;
9 10 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -42,11 +43,19 @@ public class TradeCartController {
42 43 @PutMapping("/update")
43 44 @Operation(summary = "更新购物车商品")
44 45 @PreAuthenticated
45   - public CommonResult<Boolean> updateCartItemQuantity(@Valid @RequestBody AppTradeCartUpdateReqVO updateReqVO) {
  46 + public CommonResult<Boolean> updateCart(@Valid @RequestBody AppTradeCartUpdateReqVO updateReqVO) {
46 47 cartService.updateCart(getLoginUserId(), updateReqVO);
47 48 return success(true);
48 49 }
49 50  
  51 + @PutMapping("/reset")
  52 + @Operation(summary = "重置购物车商品")
  53 + @PreAuthenticated
  54 + public CommonResult<Boolean> resetCart(@Valid @RequestBody AppTradeCartResetReqVO updateReqVO) {
  55 + cartService.resetCart(getLoginUserId(), updateReqVO);
  56 + return success(true);
  57 + }
  58 +
50 59 @DeleteMapping("/delete")
51 60 @Operation(summary = "删除购物车商品")
52 61 @Parameter(name = "ids", description = "购物车商品编号", required = true, example = "1024,2048")
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartItemUpdateSelectedReqVO.java deleted
1   -package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
2   -
3   -import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.Data;
5   -
6   -import javax.validation.constraints.NotNull;
7   -import java.util.Collection;
8   -
9   -@Schema(description = "用户 App - 购物车更新是否选中 Request VO")
10   -@Data
11   -public class AppTradeCartItemUpdateSelectedReqVO {
12   -
13   - @Schema(description = "商品 SKU 编号列表", required = true, example = "1024,2048")
14   - @NotNull(message = "商品 SKU 编号列表不能为空")
15   - private Collection<Long> skuIds;
16   -
17   - @Schema(description = "是否选中", required = true, example = "true")
18   - @NotNull(message = "是否选中不能为空")
19   - private Boolean selected;
20   -
21   -}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppTradeCartResetReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.Min;
  7 +import javax.validation.constraints.NotNull;
  8 +
  9 +@Schema(description = "用户 App - 购物车重置 Request VO")
  10 +@Data
  11 +public class AppTradeCartResetReqVO {
  12 +
  13 + @Schema(description = "编号", required = true, example = "1024")
  14 + @NotNull(message = "编号不能为空")
  15 + private Long id;
  16 +
  17 + @Schema(description = "商品 SKU 编号", required = true,example = "1024")
  18 + @NotNull(message = "商品 SKU 编号不能为空")
  19 + private Long skuId;
  20 +
  21 + @Schema(description = "商品数量", required = true, example = "1")
  22 + @NotNull(message = "数量不能为空")
  23 + @Min(message = "数量必须大于 0", value = 1L)
  24 + private Integer count;
  25 +
  26 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java
... ... @@ -34,17 +34,17 @@ public interface TradeCartConvert {
34 34 ProductSpuRespDTO spu = spuMap.get(cart.getSpuId());
35 35 ProductSkuRespDTO sku = skuMap.get(cart.getSkuId());
36 36 cartVO.setSpu(convert(spu)).setSku(convert(sku));
37   - // 如果 spu 或 sku 不存在,或者 spu 被禁用,说明是非法的,或者 sku 库存不足
  37 + // 如果 SPU 不存在,或者下架,或者库存不足,说明是无效的
38 38 if (spu == null
39   - || sku == null
40 39 || !ProductSpuStatusEnum.isEnable(spu.getStatus())
41   - || sku.getStock() <= 0) {
  40 + || spu.getStock() <= 0) {
42 41 invalidList.add(cartVO);
43 42 } else {
  43 + // 虽然 SKU 可能也会不存在,但是可以通过购物车重新选择
44 44 validList.add(cartVO);
45 45 }
46 46 });
47   - return new AppTradeCartListRespVO().setValidList(validList).setValidList(invalidList);
  47 + return new AppTradeCartListRespVO().setValidList(validList).setInvalidList(invalidList);
48 48 }
49 49 AppProductSpuBaseRespVO convert(ProductSpuRespDTO spu);
50 50 AppProductSkuBaseRespVO convert(ProductSkuRespDTO sku);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/cart/TradeCartMapper.java
... ... @@ -45,8 +45,11 @@ public interface TradeCartMapper extends BaseMapperX&lt;TradeCartDO&gt; {
45 45 .eq(TradeCartDO::getUserId, userId));
46 46 }
47 47  
48   - default List<TradeCartDO> selectListByUserId(Long userId) {
49   - return selectList(TradeCartDO::getUserId, userId);
  48 + default List<TradeCartDO> selectListByUserId(Long userId, Boolean addStatus, Boolean orderStatus) {
  49 + return selectList(new LambdaQueryWrapper<TradeCartDO>()
  50 + .eq(TradeCartDO::getUserId, userId)
  51 + .eq(TradeCartDO::getAddStatus, addStatus)
  52 + .eq(TradeCartDO::getOrderStatus, orderStatus));
50 53 }
51 54  
52 55 default void updateByIds(Collection<Long> ids, TradeCartDO updateObject) {
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartService.java
1 1 package cn.iocoder.yudao.module.trade.service.cart;
2 2  
3   -import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
4 3 import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
  4 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
  5 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartResetReqVO;
5 6 import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
6 7  
7 8 import javax.validation.Valid;
... ... @@ -32,6 +33,16 @@ public interface TradeCartService {
32 33 void updateCart(Long userId, AppTradeCartUpdateReqVO updateCountReqVO);
33 34  
34 35 /**
  36 + * 重置购物车商品
  37 + *
  38 + * 使用场景:在一个购物车项对应的商品失效(例如说 SPU 被下架),可以重新选择对应的 SKU
  39 + *
  40 + * @param userId 用户编号
  41 + * @param updateReqVO 重置信息
  42 + */
  43 + void resetCart(Long userId, AppTradeCartResetReqVO updateReqVO);
  44 +
  45 + /**
35 46 * 删除购物车商品
36 47 *
37 48 * @param userId 用户编号
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/cart/TradeCartServiceImpl.java
1 1 package cn.iocoder.yudao.module.trade.service.cart;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 4 import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
6 5 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
7 6 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
8 7 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
9 8 import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
10 9 import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
  10 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartResetReqVO;
11 11 import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
12 12 import cn.iocoder.yudao.module.trade.convert.cart.TradeCartConvert;
13 13 import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
14 14 import cn.iocoder.yudao.module.trade.dal.mysql.cart.TradeCartMapper;
15 15 import org.springframework.stereotype.Service;
  16 +import org.springframework.transaction.annotation.Transactional;
16 17 import org.springframework.validation.annotation.Validated;
17 18  
18 19 import javax.annotation.Resource;
... ... @@ -86,6 +87,28 @@ public class TradeCartServiceImpl implements TradeCartService {
86 87 .setCount(updateReqVO.getCount()));
87 88 }
88 89  
  90 + @Override
  91 + @Transactional(rollbackFor = Exception.class)
  92 + public void resetCart(Long userId, AppTradeCartResetReqVO resetReqVO) {
  93 + // 第一步:删除原本的购物项
  94 + TradeCartDO oldCart = cartMapper.selectById(resetReqVO.getId(), userId);
  95 + if (oldCart == null) {
  96 + throw exception(CARD_ITEM_NOT_FOUND);
  97 + }
  98 + cartMapper.deleteById(oldCart.getId());
  99 +
  100 + // 第二步:添加新的购物项
  101 + TradeCartDO newCart = cartMapper.selectByUserIdAndSkuId(userId, resetReqVO.getSkuId(),
  102 + true, false);
  103 + if (newCart != null) {
  104 + updateCart(userId, new AppTradeCartUpdateReqVO()
  105 + .setId(newCart.getId()).setCount(resetReqVO.getCount()));
  106 + } else {
  107 + addCart(userId, new AppTradeCartAddReqVO().setAddStatus(true)
  108 + .setSkuId(resetReqVO.getSkuId()).setCount(resetReqVO.getCount()));
  109 + }
  110 + }
  111 +
89 112 /**
90 113 * 购物车删除商品
91 114 *
... ... @@ -111,8 +134,8 @@ public class TradeCartServiceImpl implements TradeCartService {
111 134  
112 135 @Override
113 136 public AppTradeCartListRespVO getCartList(Long userId) {
114   - // 获得购物车的商品
115   - List<TradeCartDO> carts = cartMapper.selectListByUserId(userId);
  137 + // 获得购物车的商品,只查询未下单的
  138 + List<TradeCartDO> carts = cartMapper.selectListByUserId(userId, true, false);
116 139 carts.sort(Comparator.comparing(TradeCartDO::getId).reversed());
117 140 // 如果未空,则返回空结果
118 141 if (CollUtil.isEmpty(carts)) {
... ... @@ -122,12 +145,26 @@ public class TradeCartServiceImpl implements TradeCartService {
122 145  
123 146 // 查询 SPU、SKU 列表
124 147 List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(carts, TradeCartDO::getSpuId));
125   - List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(carts, TradeCartDO::getSpuId));
  148 + List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(carts, TradeCartDO::getSkuId));
  149 +
  150 + // 如果 SPU 被删除,则删除购物车对应的商品。延迟删除
  151 + deleteCartIfSpuDeleted(carts, spus);
126 152  
127 153 // 拼接数据
128 154 return TradeCartConvert.INSTANCE.convertList(carts, spus, skus);
129 155 }
130 156  
  157 + private void deleteCartIfSpuDeleted(List<TradeCartDO> carts, List<ProductSpuRespDTO> spus) {
  158 + // 如果 SPU 被删除,则删除购物车对应的商品。延迟删除
  159 + carts.removeIf(cart -> {
  160 + if (spus.stream().noneMatch(spu -> spu.getId().equals(cart.getSpuId()))) {
  161 + cartMapper.deleteById(cart.getId());
  162 + return true;
  163 + }
  164 + return false;
  165 + });
  166 + }
  167 +
131 168 /**
132 169 * 校验商品 SKU 是否合法
133 170 * 1. 是否存在
... ... @@ -140,7 +177,7 @@ public class TradeCartServiceImpl implements TradeCartService {
140 177 */
141 178 private ProductSkuRespDTO checkProductSku(Long skuId, Integer count) {
142 179 ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
143   - if (sku == null || CommonStatusEnum.DISABLE.getStatus().equals(sku.getStatus())) {
  180 + if (sku == null) {
144 181 throw exception(SKU_NOT_EXISTS);
145 182 }
146 183 if (count > sku.getStock()) {
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
... ... @@ -5,7 +5,6 @@ import cn.hutool.core.util.IdUtil;
5 5 import cn.hutool.core.util.ObjectUtil;
6 6 import cn.hutool.core.util.StrUtil;
7 7 import cn.iocoder.yudao.framework.common.core.KeyValue;
8   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
9 8 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
10 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
11 10 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
... ... @@ -125,15 +124,10 @@ public class TradeOrderServiceImpl implements TradeOrderService {
125 124 if (items.size() != skus.size()) {
126 125 throw exception(ORDER_CREATE_SKU_NOT_FOUND);
127 126 }
128   - // 校验是否禁用 or 库存不足
  127 + // 校验库存不足
129 128 Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
130 129 items.forEach(item -> {
131 130 ProductSkuRespDTO sku = skuMap.get(item.getSkuId());
132   - // SKU 禁用
133   - if (ObjectUtil.notEqual(CommonStatusEnum.ENABLE.getStatus(), sku.getStatus())) {
134   - throw exception(ORDER_CREATE_SKU_NOT_SALE);
135   - }
136   - // SKU 库存不足
137 131 if (item.getCount() > sku.getStock()) {
138 132 throw exception(ErrorCodeConstants.ORDER_CREATE_SKU_STOCK_NOT_ENOUGH);
139 133 }
... ...