2026-06-26 20b96473f2520590a0dca6b775b81e3ea06a77a0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package cn.iocoder.yudao.module.trade.service.cart;
 
import cn.hutool.core.collection.CollUtil;
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.*;
import cn.iocoder.yudao.module.trade.convert.cart.TradeCartConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
import cn.iocoder.yudao.module.trade.dal.mysql.cart.CartMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
 
import jakarta.annotation.Resource;
import java.util.*;
 
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.CARD_ITEM_NOT_FOUND;
import static java.util.Collections.emptyList;
 
/**
 * 购物车 Service 实现类
 *
 * // TODO 芋艿:未来优化:购物车的价格计算,支持营销信息;目前不支持的原因,前端界面需要前端 pr 支持下;例如说:会员价格;
 *
 * @author 芋道源码
 */
@Service
@Validated
public class CartServiceImpl implements CartService {
 
    @Resource
    private CartMapper cartMapper;
 
    @Resource
    private ProductSpuApi productSpuApi;
    @Resource
    private ProductSkuApi productSkuApi;
 
    @Override
    public Long addCart(Long userId, AppCartAddReqVO addReqVO) {
        // 查询 TradeCartDO
        CartDO cart = cartMapper.selectByUserIdAndSkuId(userId, addReqVO.getSkuId());
        // 校验 SKU
        Integer count = addReqVO.getCount();
        ProductSkuRespDTO sku = checkProductSku(addReqVO.getSkuId(), count);
 
        // 情况一:存在,则进行数量更新
        if (cart != null) {
            cartMapper.updateById(new CartDO().setId(cart.getId()).setSelected(true)
                    .setCount(cart.getCount() + count));
            return cart.getId();
        // 情况二:不存在,则进行插入
        } else {
            cart = new CartDO().setUserId(userId).setSelected(true)
                    .setSpuId(sku.getSpuId()).setSkuId(sku.getId()).setCount(count);
            cartMapper.insert(cart);
        }
        return cart.getId();
    }
 
    @Override
    public void updateCartCount(Long userId, AppCartUpdateCountReqVO updateReqVO) {
        // 校验 TradeCartDO 存在
        CartDO cart = cartMapper.selectById(updateReqVO.getId(), userId);
        if (cart == null) {
            throw exception(CARD_ITEM_NOT_FOUND);
        }
        // 校验商品 SKU
        checkProductSku(cart.getSkuId(), updateReqVO.getCount());
 
        // 更新数量
        cartMapper.updateById(new CartDO().setId(cart.getId())
                .setCount(updateReqVO.getCount()));
    }
 
    @Override
    public void updateCartSelected(Long userId, AppCartUpdateSelectedReqVO updateSelectedReqVO) {
        cartMapper.updateByIds(updateSelectedReqVO.getIds(), userId,
                new CartDO().setSelected(updateSelectedReqVO.getSelected()));
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resetCart(Long userId, AppCartResetReqVO resetReqVO) {
        // 第一步:删除原本的购物项
        CartDO oldCart = cartMapper.selectById(resetReqVO.getId(), userId);
        if (oldCart == null) {
            throw exception(CARD_ITEM_NOT_FOUND);
        }
        cartMapper.deleteById(oldCart.getId());
 
        // 第二步:添加新的购物项
        CartDO newCart = cartMapper.selectByUserIdAndSkuId(userId, resetReqVO.getSkuId());
        if (newCart != null) {
            updateCartCount(userId, new AppCartUpdateCountReqVO()
                    .setId(newCart.getId()).setCount(resetReqVO.getCount()));
        } else {
            addCart(userId, new AppCartAddReqVO().setSkuId(resetReqVO.getSkuId())
                    .setCount(resetReqVO.getCount()));
        }
    }
 
    /**
     * 购物车删除商品
     *
     * @param userId 用户编号
     * @param ids 商品 SKU 编号的数组
     */
    @Override
    public void deleteCart(Long userId, Collection<Long> ids) {
        // 查询 TradeCartDO 列表
        List<CartDO> carts = cartMapper.selectListByIds(ids, userId);
        if (CollUtil.isEmpty(carts)) {
            return;
        }
 
        // 批量标记删除
        cartMapper.deleteByIds(convertSet(carts, CartDO::getId));
    }
 
    @Override
    public Integer getCartCount(Long userId) {
        // TODO 芋艿:需要算上 selected
        return cartMapper.selectSumByUserId(userId);
    }
 
    @Override
    public AppCartListRespVO getCartList(Long userId) {
        // 获得购物车的商品
        List<CartDO> carts = cartMapper.selectListByUserId(userId);
        carts.sort(Comparator.comparing(CartDO::getId).reversed());
        // 如果未空,则返回空结果
        if (CollUtil.isEmpty(carts)) {
            return new AppCartListRespVO().setValidList(emptyList())
                    .setInvalidList(emptyList());
        }
 
        // 查询 SPU、SKU 列表
        List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(carts, CartDO::getSpuId));
        List<ProductSkuRespDTO> skus = productSkuApi.getSkuList(convertSet(carts, CartDO::getSkuId));
 
        // 如果 SPU 被删除,则删除购物车对应的商品。延迟删除
        // 为什么不是 SKU 被删除呢?因为 SKU 被删除时,还可以通过 SPU 选择其它 SKU
        deleteCartIfSpuDeleted(carts, spus);
 
        // 拼接数据
        return TradeCartConvert.INSTANCE.convertList(carts, spus, skus);
    }
 
    @Override
    public List<CartDO> getCartList(Long userId, Set<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return cartMapper.selectListByUserId(userId, ids);
    }
 
    private void deleteCartIfSpuDeleted(List<CartDO> 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. 是否存在
     * 2. 是否下架
     * 3. 库存不足
     *
     * @param skuId 商品 SKU 编号
     * @param count 商品数量
     * @return 商品 SKU
     */
    private ProductSkuRespDTO checkProductSku(Long skuId, Integer count) {
        ProductSkuRespDTO sku = productSkuApi.getSku(skuId);
        if (sku == null) {
            throw exception(SKU_NOT_EXISTS);
        }
        if (count > sku.getStock()) {
            throw exception(SKU_STOCK_NOT_ENOUGH);
        }
        return sku;
    }
 
}