Commit f7fe9ad7b0c19f2c2fc12bc518b34ee991ac5283
1 parent
da162853
1. 商城:调整购物车的失效逻辑实现,基于 SPU 是否下架
2. 商城:增加购物车失效的重选逻辑
Showing
11 changed files
with
103 additions
and
56 deletions
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<TradeCartDO> { |
| 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 | } | ... | ... |