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

2. 商城:增加购物车失效的重选逻辑
pull/2/head
YunaiV 2023-05-01 16:00:37 +08:00
parent da162853ec
commit f7fe9ad7b0
11 changed files with 103 additions and 56 deletions

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.product.api.sku.dto;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import lombok.Data;
import java.util.List;
@ -22,10 +21,6 @@ public class ProductSkuRespDTO {
* SPU
*/
private Long spuId;
/**
* SPU
*/
private String spuName;
/**
*
@ -51,12 +46,6 @@ public class ProductSkuRespDTO {
*
*/
private String picUrl;
/**
* SKU
* <p>
* {@link CommonStatusEnum}
*/
private Integer status;
/**
*
*/

View File

@ -14,7 +14,6 @@ public interface ErrorCodeConstants {
// ========== Order 模块 1-011-000-000 ==========
ErrorCode ORDER_CREATE_SKU_NOT_FOUND = new ErrorCode(1011000001, "商品 SKU 不存在");
ErrorCode ORDER_CREATE_SPU_NOT_SALE = new ErrorCode(1011000002, "商品 SPU 不可售卖");
ErrorCode ORDER_CREATE_SKU_NOT_SALE = new ErrorCode(1011000003, "商品 SKU 不可售卖");
ErrorCode ORDER_CREATE_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1011000004, "商品 SKU 库存不足");
ErrorCode ORDER_CREATE_SPU_NOT_FOUND = new ErrorCode(1011000005, "商品 SPU 不可售卖");
ErrorCode ORDER_CREATE_ADDRESS_NOT_FOUND = new ErrorCode(1011000006, "收货地址不存在");

View File

@ -17,12 +17,12 @@ public class AppProductSkuBaseRespVO {
@Schema(description = "主键", required = true, example = "1024")
private Long id;
@Schema(description = "商品 SKU 名字", required = true, example = "芋道")
private String name;
@Schema(description = "图片地址", example = "https://www.iocoder.cn/xx.png")
private String picUrl;
@Schema(description = "销售价格,单位:分", required = true, example = "100")
private Integer price;
@Schema(description = "库存", required = true, example = "1")
private Integer stock;

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartResetReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
import cn.iocoder.yudao.module.trade.service.cart.TradeCartService;
import io.swagger.v3.oas.annotations.Operation;
@ -42,11 +43,19 @@ public class TradeCartController {
@PutMapping("/update")
@Operation(summary = "更新购物车商品")
@PreAuthenticated
public CommonResult<Boolean> updateCartItemQuantity(@Valid @RequestBody AppTradeCartUpdateReqVO updateReqVO) {
public CommonResult<Boolean> updateCart(@Valid @RequestBody AppTradeCartUpdateReqVO updateReqVO) {
cartService.updateCart(getLoginUserId(), updateReqVO);
return success(true);
}
@PutMapping("/reset")
@Operation(summary = "重置购物车商品")
@PreAuthenticated
public CommonResult<Boolean> resetCart(@Valid @RequestBody AppTradeCartResetReqVO updateReqVO) {
cartService.resetCart(getLoginUserId(), updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除购物车商品")
@Parameter(name = "ids", description = "购物车商品编号", required = true, example = "1024,2048")

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.Collection;
@Schema(description = "用户 App - 购物车更新是否选中 Request VO")
@Data
public class AppTradeCartItemUpdateSelectedReqVO {
@Schema(description = "商品 SKU 编号列表", required = true, example = "1024,2048")
@NotNull(message = "商品 SKU 编号列表不能为空")
private Collection<Long> skuIds;
@Schema(description = "是否选中", required = true, example = "true")
@NotNull(message = "是否选中不能为空")
private Boolean selected;
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.trade.controller.app.cart.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Schema(description = "用户 App - 购物车重置 Request VO")
@Data
public class AppTradeCartResetReqVO {
@Schema(description = "编号", required = true, example = "1024")
@NotNull(message = "编号不能为空")
private Long id;
@Schema(description = "商品 SKU 编号", required = true,example = "1024")
@NotNull(message = "商品 SKU 编号不能为空")
private Long skuId;
@Schema(description = "商品数量", required = true, example = "1")
@NotNull(message = "数量不能为空")
@Min(message = "数量必须大于 0", value = 1L)
private Integer count;
}

View File

@ -34,17 +34,17 @@ public interface TradeCartConvert {
ProductSpuRespDTO spu = spuMap.get(cart.getSpuId());
ProductSkuRespDTO sku = skuMap.get(cart.getSkuId());
cartVO.setSpu(convert(spu)).setSku(convert(sku));
// 如果 spu 或 sku 不存在,或者 spu 被禁用,说明是非法的,或者 sku 库存不足
// 如果 SPU 不存在,或者下架,或者库存不足,说明是无效的
if (spu == null
|| sku == null
|| !ProductSpuStatusEnum.isEnable(spu.getStatus())
|| sku.getStock() <= 0) {
|| spu.getStock() <= 0) {
invalidList.add(cartVO);
} else {
// 虽然 SKU 可能也会不存在,但是可以通过购物车重新选择
validList.add(cartVO);
}
});
return new AppTradeCartListRespVO().setValidList(validList).setValidList(invalidList);
return new AppTradeCartListRespVO().setValidList(validList).setInvalidList(invalidList);
}
AppProductSpuBaseRespVO convert(ProductSpuRespDTO spu);
AppProductSkuBaseRespVO convert(ProductSkuRespDTO sku);

View File

@ -45,8 +45,11 @@ public interface TradeCartMapper extends BaseMapperX<TradeCartDO> {
.eq(TradeCartDO::getUserId, userId));
}
default List<TradeCartDO> selectListByUserId(Long userId) {
return selectList(TradeCartDO::getUserId, userId);
default List<TradeCartDO> selectListByUserId(Long userId, Boolean addStatus, Boolean orderStatus) {
return selectList(new LambdaQueryWrapper<TradeCartDO>()
.eq(TradeCartDO::getUserId, userId)
.eq(TradeCartDO::getAddStatus, addStatus)
.eq(TradeCartDO::getOrderStatus, orderStatus));
}
default void updateByIds(Collection<Long> ids, TradeCartDO updateObject) {

View File

@ -1,7 +1,8 @@
package cn.iocoder.yudao.module.trade.service.cart;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartResetReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
import javax.validation.Valid;
@ -31,6 +32,16 @@ public interface TradeCartService {
*/
void updateCart(Long userId, AppTradeCartUpdateReqVO updateCountReqVO);
/**
*
*
* 使 SPU SKU
*
* @param userId
* @param updateReqVO
*/
void resetCart(Long userId, AppTradeCartResetReqVO updateReqVO);
/**
*
*

View File

@ -1,18 +1,19 @@
package cn.iocoder.yudao.module.trade.service.cart;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartAddReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartListRespVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartResetReqVO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppTradeCartUpdateReqVO;
import cn.iocoder.yudao.module.trade.convert.cart.TradeCartConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.TradeCartDO;
import cn.iocoder.yudao.module.trade.dal.mysql.cart.TradeCartMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
@ -86,6 +87,28 @@ public class TradeCartServiceImpl implements TradeCartService {
.setCount(updateReqVO.getCount()));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void resetCart(Long userId, AppTradeCartResetReqVO resetReqVO) {
// 第一步:删除原本的购物项
TradeCartDO oldCart = cartMapper.selectById(resetReqVO.getId(), userId);
if (oldCart == null) {
throw exception(CARD_ITEM_NOT_FOUND);
}
cartMapper.deleteById(oldCart.getId());
// 第二步:添加新的购物项
TradeCartDO newCart = cartMapper.selectByUserIdAndSkuId(userId, resetReqVO.getSkuId(),
true, false);
if (newCart != null) {
updateCart(userId, new AppTradeCartUpdateReqVO()
.setId(newCart.getId()).setCount(resetReqVO.getCount()));
} else {
addCart(userId, new AppTradeCartAddReqVO().setAddStatus(true)
.setSkuId(resetReqVO.getSkuId()).setCount(resetReqVO.getCount()));
}
}
/**
*
*
@ -111,8 +134,8 @@ public class TradeCartServiceImpl implements TradeCartService {
@Override
public AppTradeCartListRespVO getCartList(Long userId) {
// 获得购物车的商品
List<TradeCartDO> carts = cartMapper.selectListByUserId(userId);
// 获得购物车的商品,只查询未下单的
List<TradeCartDO> carts = cartMapper.selectListByUserId(userId, true, false);
carts.sort(Comparator.comparing(TradeCartDO::getId).reversed());
// 如果未空,则返回空结果
if (CollUtil.isEmpty(carts)) {
@ -122,12 +145,26 @@ public class TradeCartServiceImpl implements TradeCartService {
// 查询 SPU、SKU 列表
List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(carts, TradeCartDO::getSpuId));
List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(carts, TradeCartDO::getSpuId));
List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(carts, TradeCartDO::getSkuId));
// 如果 SPU 被删除,则删除购物车对应的商品。延迟删除
deleteCartIfSpuDeleted(carts, spus);
// 拼接数据
return TradeCartConvert.INSTANCE.convertList(carts, spus, skus);
}
private void deleteCartIfSpuDeleted(List<TradeCartDO> carts, List<ProductSpuRespDTO> spus) {
// 如果 SPU 被删除,则删除购物车对应的商品。延迟删除
carts.removeIf(cart -> {
if (spus.stream().noneMatch(spu -> spu.getId().equals(cart.getSpuId()))) {
cartMapper.deleteById(cart.getId());
return true;
}
return false;
});
}
/**
* SKU
* 1.
@ -140,7 +177,7 @@ public class TradeCartServiceImpl implements TradeCartService {
*/
private ProductSkuRespDTO checkProductSku(Long skuId, Integer count) {
ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
if (sku == null || CommonStatusEnum.DISABLE.getStatus().equals(sku.getStatus())) {
if (sku == null) {
throw exception(SKU_NOT_EXISTS);
}
if (count > sku.getStock()) {

View File

@ -5,7 +5,6 @@ import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@ -125,15 +124,10 @@ public class TradeOrderServiceImpl implements TradeOrderService {
if (items.size() != skus.size()) {
throw exception(ORDER_CREATE_SKU_NOT_FOUND);
}
// 校验是否禁用 or 库存不足
// 校验库存不足
Map<Long, ProductSkuRespDTO> skuMap = convertMap(skus, ProductSkuRespDTO::getId);
items.forEach(item -> {
ProductSkuRespDTO sku = skuMap.get(item.getSkuId());
// SKU 禁用
if (ObjectUtil.notEqual(CommonStatusEnum.ENABLE.getStatus(), sku.getStatus())) {
throw exception(ORDER_CREATE_SKU_NOT_SALE);
}
// SKU 库存不足
if (item.getCount() > sku.getStock()) {
throw exception(ErrorCodeConstants.ORDER_CREATE_SKU_STOCK_NOT_ENOUGH);
}