购物车系统 - 承载用户购买意愿的智能容器

# 购物车系统 - 承载用户购买意愿的智能容器

# 📖 故事继续

小明在搜索推荐引擎的帮助下,找到了几款心仪的瑞士手表。他点击了一款欧米茄海马系列手表的"加入购物车"按钮,页面右上角的购物车图标立即显示了"1"的红色数字。接着,他又浏览了推荐的手表配件,将一条真皮表带也加入了购物车。

当小明点击购物车图标时,一个侧边栏滑出,清晰地展示了他选择的商品:手表$1,299、表带$89,系统还贴心地计算出了总价$1,388,并提示他"再购买$12即可享受免费国际配送"。更智能的是,系统还推荐了一个手表保养套装,正好$15,既满足了免邮条件,又是实用的配件。

小红作为一个经常海淘的用户,她的购物车更加复杂。里面有来自不同国家的商品:美国的化妆品、日本的护肤品、德国的保健品。系统智能地按照发货地进行了分组,并分别计算了每组的运费和预计到达时间。当她在手机和电脑之间切换时,购物车的内容始终保持同步。

这个看似简单的购物车,实际上是一个复杂的分布式系统,需要处理高并发、数据一致性、跨设备同步、实时计算等多种技术挑战。今天,我们就来深入了解这个"智能容器"的设计奥秘。

# 🎯 系统概述

购物车系统是跨境电商平台的核心交易组件,它不仅要存储用户的购买意愿,还要提供实时的价格计算、库存检查、物流预估、优惠券应用等功能。系统需要支持高并发访问、跨设备同步、离线操作等复杂场景。

# 核心功能

  • 商品添加/删除/修改
  • 实时价格计算
  • 库存状态检查
  • 跨设备数据同步
  • 购物车合并
  • 优惠券应用
  • 物流费用计算
  • 智能推荐

# 🏗️ 系统架构设计

# 整体架构图

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   客户端        │    │   API网关       │    │   购物车服务    │
│                 │────│                 │────│                 │
│ - Web端         │    │ - 请求路由      │    │ - 购物车管理    │
│ - 移动端        │    │ - 身份验证      │    │ - 价格计算      │
│ - 小程序        │    │ - 限流熔断      │    │ - 库存检查      │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                                                        │
                       ┌─────────────────┐    ┌─────────────────┐
                       │   消息队列      │    │   数据存储层    │
                       │                 │    │                 │
                       │ - 异步处理      │    │ - Redis缓存     │
                       │ - 事件通知      │    │ - MySQL持久化   │
                       │ - 数据同步      │    │ - 分布式锁      │
                       └─────────────────┘    └─────────────────┘
                                                        │
                                               ┌─────────────────┐
                                               │   外部服务      │
                                               │                 │
                                               │ - 商品服务      │
                                               │ - 库存服务      │
                                               │ - 价格服务      │
                                               │ - 物流服务      │
                                               └─────────────────┘

# 💻 核心代码实现

# 1. 购物车实体模型设计

/**
 * 购物车实体类
 * 设计思路:支持多设备、多会话的购物车管理
 */
@Entity
@Table(name = "shopping_carts")
public class ShoppingCart {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    /**
     * 用户ID - 登录用户的唯一标识
     */
    @Column(name = "user_id")
    private String userId;
    
    /**
     * 会话ID - 未登录用户的临时标识
     */
    @Column(name = "session_id")
    private String sessionId;
    
    /**
     * 设备类型:WEB, MOBILE, MINIPROGRAM
     */
    @Column(name = "device_type")
    private String deviceType;
    
    /**
     * 购物车状态:ACTIVE, MERGED, EXPIRED
     */
    @Column(name = "status")
    private String status;
    
    /**
     * 创建时间
     */
    @CreationTimestamp
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    /**
     * 最后更新时间
     */
    @UpdateTimestamp
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    /**
     * 过期时间(未登录用户30天,登录用户90天)
     */
    @Column(name = "expires_at")
    private LocalDateTime expiresAt;
    
    // 构造函数、getter、setter省略...
}

/**
 * 购物车商品项实体
 */
@Entity
@Table(name = "cart_items")
public class CartItem {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    /**
     * 购物车ID
     */
    @Column(name = "cart_id")
    private Long cartId;
    
    /**
     * 商品SKU
     */
    @Column(name = "product_sku")
    private String productSku;
    
    /**
     * 商品数量
     */
    @Column(name = "quantity")
    private Integer quantity;
    
    /**
     * 添加时的价格(用于价格变动提醒)
     */
    @Column(name = "added_price", precision = 10, scale = 2)
    private BigDecimal addedPrice;
    
    /**
     * 选中状态(支持部分结算)
     */
    @Column(name = "selected")
    private Boolean selected = true;
    
    /**
     * 商品规格信息(JSON格式)
     * 例如:{"颜色": "黑色", "尺寸": "42mm"}
     */
    @Column(name = "specifications", columnDefinition = "JSON")
    private String specifications;
    
    /**
     * 添加时间
     */
    @CreationTimestamp
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    /**
     * 更新时间
     */
    @UpdateTimestamp
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    // 构造函数、getter、setter省略...
}

# 2. 购物车服务核心实现

/**
 * 购物车服务实现类
 * 核心职责:购物车CRUD操作、价格计算、库存检查、数据同步
 */
@Service
@Transactional
public class ShoppingCartService {
    
    @Autowired
    private ShoppingCartRepository cartRepository;
    
    @Autowired
    private CartItemRepository cartItemRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private ProductService productService;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private PricingService pricingService;
    
    @Autowired
    private ShippingService shippingService;
    
    @Autowired
    private DistributedLockService lockService;
    
    @Autowired
    private CartEventPublisher eventPublisher;
    
    /**
     * 添加商品到购物车
     * 核心逻辑:库存检查 → 价格获取 → 数据存储 → 缓存更新
     */
    public CartOperationResult addToCart(AddToCartRequest request) {
        
        String lockKey = "cart:lock:" + request.getUserIdentifier();
        
        return lockService.executeWithLock(lockKey, Duration.ofSeconds(5), () -> {
            
            // 1. 参数验证
            validateAddToCartRequest(request);
            
            // 2. 获取或创建购物车
            ShoppingCart cart = getOrCreateCart(request.getUserId(), request.getSessionId(), 
                                              request.getDeviceType());
            
            // 3. 检查商品信息
            ProductInfo productInfo = productService.getProductInfo(request.getProductSku());
            if (productInfo == null || !productInfo.isAvailable()) {
                throw new ProductNotAvailableException("商品不可用: " + request.getProductSku());
            }
            
            // 4. 库存检查
            InventoryInfo inventoryInfo = inventoryService.checkInventory(
                request.getProductSku(), request.getQuantity());
            
            if (!inventoryInfo.isAvailable()) {
                return CartOperationResult.failure("库存不足,当前可用库存: " + inventoryInfo.getAvailableQuantity());
            }
            
            // 5. 检查是否已存在相同商品
            Optional<CartItem> existingItem = cartItemRepository
                .findByCartIdAndProductSkuAndSpecifications(
                    cart.getId(), request.getProductSku(), request.getSpecifications());
            
            CartItem cartItem;
            if (existingItem.isPresent()) {
                // 更新数量
                cartItem = existingItem.get();
                int newQuantity = cartItem.getQuantity() + request.getQuantity();
                
                // 再次检查库存
                InventoryInfo newInventoryCheck = inventoryService.checkInventory(
                    request.getProductSku(), newQuantity);
                
                if (!newInventoryCheck.isAvailable()) {
                    return CartOperationResult.failure(
                        "库存不足,当前购物车中已有 " + cartItem.getQuantity() + " 件,最多还能添加 " + 
                        (newInventoryCheck.getAvailableQuantity() - cartItem.getQuantity()) + " 件");
                }
                
                cartItem.setQuantity(newQuantity);
                cartItem.setUpdatedAt(LocalDateTime.now());
                
            } else {
                // 创建新的购物车项
                cartItem = new CartItem();
                cartItem.setCartId(cart.getId());
                cartItem.setProductSku(request.getProductSku());
                cartItem.setQuantity(request.getQuantity());
                cartItem.setAddedPrice(productInfo.getCurrentPrice());
                cartItem.setSpecifications(request.getSpecifications());
                cartItem.setSelected(true);
            }
            
            // 6. 保存到数据库
            cartItem = cartItemRepository.save(cartItem);
            
            // 7. 更新购物车时间
            cart.setUpdatedAt(LocalDateTime.now());
            cartRepository.save(cart);
            
            // 8. 更新缓存
            updateCartCache(cart.getId());
            
            // 9. 发布事件
            eventPublisher.publishCartItemAdded(cart.getId(), cartItem);
            
            log.info("商品添加到购物车成功 - 用户: {}, 商品: {}, 数量: {}", 
                    request.getUserIdentifier(), request.getProductSku(), request.getQuantity());
            
            return CartOperationResult.success("商品已添加到购物车", cartItem.getId());
        });
    }
    
    /**
     * 获取购物车详情
     * 包含实时价格、库存状态、物流信息等
     */
    public CartDetailDTO getCartDetail(String userId, String sessionId, 
                                     String currencyCode, String countryCode) {
        
        // 1. 获取购物车
        ShoppingCart cart = getActiveCart(userId, sessionId);
        if (cart == null) {
            return CartDetailDTO.empty();
        }
        
        // 2. 尝试从缓存获取
        String cacheKey = buildCartCacheKey(cart.getId(), currencyCode, countryCode);
        CartDetailDTO cachedDetail = (CartDetailDTO) redisTemplate.opsForValue().get(cacheKey);
        
        if (cachedDetail != null && !isCartCacheExpired(cachedDetail)) {
            log.debug("从缓存获取购物车详情,cartId: {}", cart.getId());
            return cachedDetail;
        }
        
        // 3. 从数据库构建详情
        CartDetailDTO cartDetail = buildCartDetail(cart, currencyCode, countryCode);
        
        // 4. 缓存结果(5分钟过期)
        redisTemplate.opsForValue().set(cacheKey, cartDetail, Duration.ofMinutes(5));
        
        return cartDetail;
    }
    
    /**
     * 构建购物车详情
     * 核心逻辑:商品信息获取 → 价格计算 → 库存检查 → 物流计算
     */
    private CartDetailDTO buildCartDetail(ShoppingCart cart, String currencyCode, String countryCode) {
        
        // 1. 获取购物车商品列表
        List<CartItem> cartItems = cartItemRepository.findByCartIdAndStatus(cart.getId(), "ACTIVE");
        
        if (cartItems.isEmpty()) {
            return CartDetailDTO.empty();
        }
        
        // 2. 批量获取商品信息(优化性能)
        List<String> productSkus = cartItems.stream()
                .map(CartItem::getProductSku)
                .collect(Collectors.toList());
        
        Map<String, ProductInfo> productInfoMap = productService.batchGetProductInfo(productSkus);
        
        // 3. 批量检查库存
        Map<String, InventoryInfo> inventoryInfoMap = inventoryService.batchCheckInventory(productSkus);
        
        // 4. 构建购物车项详情列表
        List<CartItemDetailDTO> itemDetails = new ArrayList<>();
        BigDecimal totalAmount = BigDecimal.ZERO;
        int totalQuantity = 0;
        boolean hasUnavailableItems = false;
        
        for (CartItem cartItem : cartItems) {
            CartItemDetailDTO itemDetail = buildCartItemDetail(
                cartItem, productInfoMap, inventoryInfoMap, currencyCode, countryCode);
            
            itemDetails.add(itemDetail);
            
            if (itemDetail.isSelected() && itemDetail.isAvailable()) {
                totalAmount = totalAmount.add(itemDetail.getTotalPrice());
                totalQuantity += itemDetail.getQuantity();
            }
            
            if (!itemDetail.isAvailable()) {
                hasUnavailableItems = true;
            }
        }
        
        // 5. 按发货地分组(跨境电商特有逻辑)
        Map<String, List<CartItemDetailDTO>> itemsByOrigin = groupItemsByOrigin(itemDetails);
        
        // 6. 计算物流费用
        ShippingCalculationResult shippingResult = calculateShippingCosts(
            itemsByOrigin, countryCode, totalAmount);
        
        // 7. 构建购物车详情
        CartDetailDTO cartDetail = new CartDetailDTO();
        cartDetail.setCartId(cart.getId());
        cartDetail.setItems(itemDetails);
        cartDetail.setItemsByOrigin(itemsByOrigin);
        cartDetail.setTotalQuantity(totalQuantity);
        cartDetail.setSubtotal(totalAmount);
        cartDetail.setShippingInfo(shippingResult);
        cartDetail.setGrandTotal(totalAmount.add(shippingResult.getTotalShippingCost()));
        cartDetail.setCurrencyCode(currencyCode);
        cartDetail.setHasUnavailableItems(hasUnavailableItems);
        cartDetail.setLastUpdated(LocalDateTime.now());
        
        // 8. 添加优惠信息
        addPromotionInfo(cartDetail);
        
        // 9. 添加推荐商品
        addRecommendedItems(cartDetail, cart.getUserId());
        
        return cartDetail;
    }
    
    /**
     * 构建单个购物车项详情
     */
    private CartItemDetailDTO buildCartItemDetail(CartItem cartItem, 
                                                 Map<String, ProductInfo> productInfoMap,
                                                 Map<String, InventoryInfo> inventoryInfoMap,
                                                 String currencyCode, String countryCode) {
        
        ProductInfo productInfo = productInfoMap.get(cartItem.getProductSku());
        InventoryInfo inventoryInfo = inventoryInfoMap.get(cartItem.getProductSku());
        
        CartItemDetailDTO itemDetail = new CartItemDetailDTO();
        itemDetail.setId(cartItem.getId());
        itemDetail.setProductSku(cartItem.getProductSku());
        itemDetail.setQuantity(cartItem.getQuantity());
        itemDetail.setSelected(cartItem.getSelected());
        itemDetail.setSpecifications(parseSpecifications(cartItem.getSpecifications()));
        itemDetail.setAddedAt(cartItem.getCreatedAt());
        
        if (productInfo != null) {
            itemDetail.setProductName(productInfo.getName());
            itemDetail.setProductImage(productInfo.getMainImage());
            itemDetail.setBrand(productInfo.getBrand());
            
            // 价格计算(考虑汇率和税费)
            PriceCalculationResult priceResult = pricingService.calculatePrice(
                productInfo.getBasePrice(), currencyCode, countryCode, cartItem.getQuantity());
            
            itemDetail.setUnitPrice(priceResult.getUnitPrice());
            itemDetail.setTotalPrice(priceResult.getTotalPrice());
            itemDetail.setOriginalPrice(cartItem.getAddedPrice());
            
            // 价格变动提醒
            if (priceResult.getUnitPrice().compareTo(cartItem.getAddedPrice()) != 0) {
                itemDetail.setPriceChanged(true);
                itemDetail.setPriceChangeAmount(
                    priceResult.getUnitPrice().subtract(cartItem.getAddedPrice()));
            }
        }
        
        if (inventoryInfo != null) {
            itemDetail.setAvailable(inventoryInfo.isAvailable());
            itemDetail.setAvailableQuantity(inventoryInfo.getAvailableQuantity());
            
            // 库存不足提醒
            if (cartItem.getQuantity() > inventoryInfo.getAvailableQuantity()) {
                itemDetail.setQuantityExceedsStock(true);
                itemDetail.setMaxAvailableQuantity(inventoryInfo.getAvailableQuantity());
            }
        }
        
        return itemDetail;
    }
    
    /**
     * 按发货地分组商品
     * 跨境电商需要根据商品发货地计算不同的物流费用
     */
    private Map<String, List<CartItemDetailDTO>> groupItemsByOrigin(List<CartItemDetailDTO> items) {
        
        return items.stream()
                .filter(item -> item.isSelected() && item.isAvailable())
                .collect(Collectors.groupingBy(
                    item -> getProductOriginCountry(item.getProductSku()),
                    LinkedHashMap::new,
                    Collectors.toList()
                ));
    }
    
    /**
     * 计算物流费用
     */
    private ShippingCalculationResult calculateShippingCosts(
            Map<String, List<CartItemDetailDTO>> itemsByOrigin, 
            String destinationCountry, BigDecimal totalAmount) {
        
        ShippingCalculationResult result = new ShippingCalculationResult();
        BigDecimal totalShippingCost = BigDecimal.ZERO;
        List<ShippingGroupInfo> shippingGroups = new ArrayList<>();
        
        for (Map.Entry<String, List<CartItemDetailDTO>> entry : itemsByOrigin.entrySet()) {
            String originCountry = entry.getKey();
            List<CartItemDetailDTO> items = entry.getValue();
            
            // 计算该发货地的物流费用
            ShippingCostCalculation shippingCalc = shippingService.calculateShippingCost(
                originCountry, destinationCountry, items);
            
            ShippingGroupInfo groupInfo = new ShippingGroupInfo();
            groupInfo.setOriginCountry(originCountry);
            groupInfo.setItems(items);
            groupInfo.setShippingCost(shippingCalc.getCost());
            groupInfo.setEstimatedDeliveryDays(shippingCalc.getEstimatedDays());
            groupInfo.setFreeShippingThreshold(shippingCalc.getFreeShippingThreshold());
            
            // 检查是否满足免邮条件
            BigDecimal groupTotal = items.stream()
                    .map(CartItemDetailDTO::getTotalPrice)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            
            if (groupTotal.compareTo(shippingCalc.getFreeShippingThreshold()) >= 0) {
                groupInfo.setShippingCost(BigDecimal.ZERO);
                groupInfo.setFreeShippingApplied(true);
            }
            
            shippingGroups.add(groupInfo);
            totalShippingCost = totalShippingCost.add(groupInfo.getShippingCost());
        }
        
        result.setShippingGroups(shippingGroups);
        result.setTotalShippingCost(totalShippingCost);
        
        // 计算距离免邮还需多少金额
        calculateFreeShippingGap(result, totalAmount);
        
        return result;
    }
    
    /**
     * 更新购物车商品数量
     */
    public CartOperationResult updateQuantity(UpdateQuantityRequest request) {
        
        String lockKey = "cart:item:lock:" + request.getCartItemId();
        
        return lockService.executeWithLock(lockKey, Duration.ofSeconds(3), () -> {
            
            // 1. 获取购物车项
            CartItem cartItem = cartItemRepository.findById(request.getCartItemId())
                    .orElseThrow(() -> new CartItemNotFoundException("购物车项不存在"));
            
            // 2. 权限检查
            validateCartItemOwnership(cartItem, request.getUserId(), request.getSessionId());
            
            // 3. 数量验证
            if (request.getQuantity() <= 0) {
                // 数量为0或负数,删除商品
                return removeFromCart(cartItem.getId(), request.getUserId(), request.getSessionId());
            }
            
            // 4. 库存检查
            InventoryInfo inventoryInfo = inventoryService.checkInventory(
                cartItem.getProductSku(), request.getQuantity());
            
            if (!inventoryInfo.isAvailable()) {
                return CartOperationResult.failure(
                    "库存不足,当前可用库存: " + inventoryInfo.getAvailableQuantity());
            }
            
            // 5. 更新数量
            int oldQuantity = cartItem.getQuantity();
            cartItem.setQuantity(request.getQuantity());
            cartItem.setUpdatedAt(LocalDateTime.now());
            cartItemRepository.save(cartItem);
            
            // 6. 更新缓存
            updateCartCache(cartItem.getCartId());
            
            // 7. 发布事件
            eventPublisher.publishCartItemQuantityUpdated(cartItem.getCartId(), cartItem, oldQuantity);
            
            log.info("购物车商品数量更新成功 - 商品: {}, 原数量: {}, 新数量: {}", 
                    cartItem.getProductSku(), oldQuantity, request.getQuantity());
            
            return CartOperationResult.success("数量更新成功");
        });
    }
    
    /**
     * 购物车合并(用户登录时)
     * 将未登录时的购物车合并到用户账户
     */
    public CartMergeResult mergeCart(String userId, String sessionId) {
        
        String lockKey = "cart:merge:lock:" + userId;
        
        return lockService.executeWithLock(lockKey, Duration.ofSeconds(10), () -> {
            
            // 1. 获取用户购物车和会话购物车
            ShoppingCart userCart = cartRepository.findByUserIdAndStatus(userId, "ACTIVE")
                    .orElse(null);
            
            ShoppingCart sessionCart = cartRepository.findBySessionIdAndStatus(sessionId, "ACTIVE")
                    .orElse(null);
            
            if (sessionCart == null) {
                return CartMergeResult.noSessionCart();
            }
            
            List<CartItem> sessionItems = cartItemRepository.findByCartIdAndStatus(
                sessionCart.getId(), "ACTIVE");
            
            if (sessionItems.isEmpty()) {
                return CartMergeResult.emptySessionCart();
            }
            
            // 2. 创建或获取用户购物车
            if (userCart == null) {
                userCart = createUserCart(userId, sessionCart.getDeviceType());
            }
            
            // 3. 合并商品项
            List<CartItem> userItems = cartItemRepository.findByCartIdAndStatus(
                userCart.getId(), "ACTIVE");
            
            Map<String, CartItem> userItemMap = userItems.stream()
                    .collect(Collectors.toMap(
                        item -> item.getProductSku() + ":" + item.getSpecifications(),
                        item -> item
                    ));
            
            int mergedCount = 0;
            int conflictCount = 0;
            
            for (CartItem sessionItem : sessionItems) {
                String itemKey = sessionItem.getProductSku() + ":" + sessionItem.getSpecifications();
                
                if (userItemMap.containsKey(itemKey)) {
                    // 存在相同商品,合并数量
                    CartItem userItem = userItemMap.get(itemKey);
                    int newQuantity = userItem.getQuantity() + sessionItem.getQuantity();
                    
                    // 检查库存
                    InventoryInfo inventoryInfo = inventoryService.checkInventory(
                        userItem.getProductSku(), newQuantity);
                    
                    if (inventoryInfo.isAvailable()) {
                        userItem.setQuantity(newQuantity);
                        userItem.setUpdatedAt(LocalDateTime.now());
                        cartItemRepository.save(userItem);
                        mergedCount++;
                    } else {
                        // 库存不足,保持原数量
                        conflictCount++;
                    }
                    
                } else {
                    // 新商品,直接添加
                    sessionItem.setCartId(userCart.getId());
                    sessionItem.setUpdatedAt(LocalDateTime.now());
                    cartItemRepository.save(sessionItem);
                    mergedCount++;
                }
            }
            
            // 4. 标记会话购物车为已合并
            sessionCart.setStatus("MERGED");
            sessionCart.setUpdatedAt(LocalDateTime.now());
            cartRepository.save(sessionCart);
            
            // 5. 更新用户购物车时间
            userCart.setUpdatedAt(LocalDateTime.now());
            cartRepository.save(userCart);
            
            // 6. 更新缓存
            updateCartCache(userCart.getId());
            clearCartCache(sessionCart.getId());
            
            // 7. 发布事件
            eventPublisher.publishCartMerged(userCart.getId(), sessionCart.getId(), mergedCount);
            
            log.info("购物车合并完成 - 用户: {}, 合并商品数: {}, 冲突数: {}", 
                    userId, mergedCount, conflictCount);
            
            return CartMergeResult.success(mergedCount, conflictCount);
        });
    }
    
    /**
     * 清理过期购物车
     * 定时任务,清理过期的购物车数据
     */
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    public void cleanExpiredCarts() {
        
        log.info("开始清理过期购物车...");
        
        LocalDateTime expireTime = LocalDateTime.now();
        
        // 1. 查找过期的购物车
        List<ShoppingCart> expiredCarts = cartRepository.findExpiredCarts(expireTime);
        
        if (expiredCarts.isEmpty()) {
            log.info("没有过期的购物车需要清理");
            return;
        }
        
        int cleanedCount = 0;
        
        for (ShoppingCart cart : expiredCarts) {
            try {
                // 2. 删除购物车项
                cartItemRepository.deleteByCartId(cart.getId());
                
                // 3. 删除购物车
                cartRepository.delete(cart);
                
                // 4. 清理缓存
                clearCartCache(cart.getId());
                
                cleanedCount++;
                
            } catch (Exception e) {
                log.error("清理购物车失败,cartId: {}", cart.getId(), e);
            }
        }
        
        log.info("过期购物车清理完成,清理数量: {}", cleanedCount);
    }
    
    // 辅助方法实现...
    private void updateCartCache(Long cartId) {
        // 清除相关缓存,下次访问时重新构建
        String pattern = "cart:detail:" + cartId + ":*";
        Set<String> keys = redisTemplate.keys(pattern);
        if (!keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }
    
    private String buildCartCacheKey(Long cartId, String currencyCode, String countryCode) {
        return String.format("cart:detail:%d:%s:%s", cartId, currencyCode, countryCode);
    }
    
    private String getProductOriginCountry(String productSku) {
        // 根据商品SKU获取发货地,实际项目中从商品服务获取
        return "US"; // 示例
    }
}

# 3. 分布式锁实现

/**
 * 分布式锁服务
 * 基于Redis实现,确保购物车操作的数据一致性
 */
@Service
public class DistributedLockService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String LOCK_PREFIX = "lock:";
    private static final String LOCK_VALUE = "locked";
    
    /**
     * 执行带锁的操作
     */
    public <T> T executeWithLock(String lockKey, Duration timeout, Supplier<T> operation) {
        
        String fullLockKey = LOCK_PREFIX + lockKey;
        String lockValue = UUID.randomUUID().toString();
        
        try {
            // 1. 尝试获取锁
            boolean acquired = acquireLock(fullLockKey, lockValue, timeout);
            
            if (!acquired) {
                throw new LockAcquisitionException("获取锁失败: " + lockKey);
            }
            
            // 2. 执行业务操作
            return operation.get();
            
        } finally {
            // 3. 释放锁
            releaseLock(fullLockKey, lockValue);
        }
    }
    
    /**
     * 获取分布式锁
     */
    private boolean acquireLock(String lockKey, String lockValue, Duration timeout) {
        
        long timeoutMillis = timeout.toMillis();
        long startTime = System.currentTimeMillis();
        
        while (System.currentTimeMillis() - startTime < timeoutMillis) {
            
            // 使用SET命令的NX和EX选项实现原子性加锁
            Boolean result = redisTemplate.opsForValue().setIfAbsent(
                lockKey, lockValue, Duration.ofSeconds(30));
            
            if (Boolean.TRUE.equals(result)) {
                return true;
            }
            
            // 短暂等待后重试
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        
        return false;
    }
    
    /**
     * 释放分布式锁
     */
    private void releaseLock(String lockKey, String lockValue) {
        
        // 使用Lua脚本确保原子性释放锁
        String luaScript = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    return redis.call('del', KEYS[1]) " +
            "else " +
            "    return 0 " +
            "end";
        
        redisTemplate.execute(
            new DefaultRedisScript<>(luaScript, Long.class),
            Collections.singletonList(lockKey),
            lockValue
        );
    }
}

# 4. 购物车事件处理

/**
 * 购物车事件发布器
 * 用于发布购物车相关事件,支持异步处理和系统解耦
 */
@Component
public class CartEventPublisher {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 发布商品添加事件
     */
    public void publishCartItemAdded(Long cartId, CartItem cartItem) {
        
        CartItemAddedEvent event = new CartItemAddedEvent();
        event.setCartId(cartId);
        event.setProductSku(cartItem.getProductSku());
        event.setQuantity(cartItem.getQuantity());
        event.setTimestamp(LocalDateTime.now());
        
        // 1. 本地事件发布(同步处理)
        eventPublisher.publishEvent(event);
        
        // 2. 消息队列发布(异步处理)
        rabbitTemplate.convertAndSend("cart.exchange", "cart.item.added", event);
        
        log.debug("购物车商品添加事件已发布,cartId: {}, productSku: {}", cartId, cartItem.getProductSku());
    }
    
    /**
     * 发布数量更新事件
     */
    public void publishCartItemQuantityUpdated(Long cartId, CartItem cartItem, int oldQuantity) {
        
        CartItemQuantityUpdatedEvent event = new CartItemQuantityUpdatedEvent();
        event.setCartId(cartId);
        event.setProductSku(cartItem.getProductSku());
        event.setOldQuantity(oldQuantity);
        event.setNewQuantity(cartItem.getQuantity());
        event.setTimestamp(LocalDateTime.now());
        
        eventPublisher.publishEvent(event);
        rabbitTemplate.convertAndSend("cart.exchange", "cart.item.quantity.updated", event);
    }
    
    /**
     * 发布购物车合并事件
     */
    public void publishCartMerged(Long userCartId, Long sessionCartId, int mergedItemCount) {
        
        CartMergedEvent event = new CartMergedEvent();
        event.setUserCartId(userCartId);
        event.setSessionCartId(sessionCartId);
        event.setMergedItemCount(mergedItemCount);
        event.setTimestamp(LocalDateTime.now());
        
        eventPublisher.publishEvent(event);
        rabbitTemplate.convertAndSend("cart.exchange", "cart.merged", event);
    }
}

/**
 * 购物车事件监听器
 * 处理购物车相关的业务逻辑
 */
@Component
public class CartEventListener {
    
    @Autowired
    private UserBehaviorService userBehaviorService;
    
    @Autowired
    private RecommendationService recommendationService;
    
    @Autowired
    private InventoryService inventoryService;
    
    /**
     * 处理商品添加事件
     */
    @EventListener
    @Async
    public void handleCartItemAdded(CartItemAddedEvent event) {
        
        try {
            // 1. 记录用户行为
            userBehaviorService.recordCartAction(
                event.getCartId(), "ADD_TO_CART", event.getProductSku(), event.getQuantity());
            
            // 2. 更新推荐模型
            recommendationService.updateUserPreference(
                event.getCartId(), event.getProductSku(), "CART_ADD");
            
            // 3. 预占库存(可选,根据业务需求)
            // inventoryService.reserveInventory(event.getProductSku(), event.getQuantity());
            
        } catch (Exception e) {
            log.error("处理购物车商品添加事件失败", e);
        }
    }
    
    /**
     * 处理数量更新事件
     */
    @EventListener
    @Async
    public void handleCartItemQuantityUpdated(CartItemQuantityUpdatedEvent event) {
        
        try {
            // 1. 记录用户行为
            userBehaviorService.recordCartAction(
                event.getCartId(), "UPDATE_QUANTITY", event.getProductSku(), 
                event.getNewQuantity() - event.getOldQuantity());
            
            // 2. 调整库存预占(如果启用了预占机制)
            int quantityDiff = event.getNewQuantity() - event.getOldQuantity();
            if (quantityDiff > 0) {
                // 增加预占
                // inventoryService.reserveInventory(event.getProductSku(), quantityDiff);
            } else if (quantityDiff < 0) {
                // 释放预占
                // inventoryService.releaseReservation(event.getProductSku(), Math.abs(quantityDiff));
            }
            
        } catch (Exception e) {
            log.error("处理购物车数量更新事件失败", e);
        }
    }
}

# 🔧 技术要点解析

# 1. 数据一致性保证

分布式锁机制

  • 使用Redis实现分布式锁
  • 确保并发操作的数据一致性
  • 支持锁超时和自动释放

事务管理

  • 数据库事务确保ACID特性
  • 补偿机制处理分布式事务
  • 幂等性设计避免重复操作

# 2. 高性能设计

缓存策略

  • Redis缓存购物车详情
  • 批量操作减少数据库访问
  • 缓存预热和智能失效

异步处理

  • 事件驱动的异步处理
  • 消息队列解耦系统组件
  • 非关键路径异步执行

# 3. 跨设备同步

数据同步机制

  • 基于用户ID的数据关联
  • 实时缓存更新
  • 购物车合并算法

# 📊 监控与运维

# 1. 关键指标监控

/**
 * 购物车监控指标
 */
@Component
public class CartMetricsCollector {
    
    private final Counter cartOperationCounter;
    private final Timer cartOperationTimer;
    private final Gauge activeCartGauge;
    
    public CartMetricsCollector(MeterRegistry meterRegistry) {
        this.cartOperationCounter = Counter.builder("cart.operations.total")
                .description("购物车操作总数")
                .register(meterRegistry);
                
        this.cartOperationTimer = Timer.builder("cart.operation.duration")
                .description("购物车操作耗时")
                .register(meterRegistry);
                
        this.activeCartGauge = Gauge.builder("cart.active.count")
                .description("活跃购物车数量")
                .register(meterRegistry, this, CartMetricsCollector::getActiveCartCount);
    }
    
    public void recordCartOperation(String operation, boolean success, long duration) {
        cartOperationCounter.increment(
            Tags.of(
                "operation", operation,
                "success", String.valueOf(success)
            )
        );
        
        cartOperationTimer.record(duration, TimeUnit.MILLISECONDS);
    }
    
    private double getActiveCartCount() {
        // 实现获取活跃购物车数量的逻辑
        return 0.0;
    }
}

# 2. 性能优化配置

# application.yml
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
  
  redis:
    lettuce:
      pool:
        max-active: 20
        max-idle: 10
        min-idle: 5
    timeout: 2000ms
  
  rabbitmq:
    listener:
      simple:
        concurrency: 5
        max-concurrency: 10
        prefetch: 10

cart:
  cache:
    ttl: 300 # 5分钟
  lock:
    timeout: 5000 # 5秒
  cleanup:
    batch-size: 100
    enabled: true

# 🎉 总结

通过本文的深入学习,我们全面了解了跨境电商购物车系统的设计与实现。从小明添加手表到购物车的简单操作,到小红复杂的多国商品购物车管理,背后是一套高度复杂的分布式系统在支撑。

# 关键收获

  1. 系统架构:分布式架构设计,支持高并发和高可用
  2. 数据一致性:分布式锁和事务管理确保数据准确性
  3. 性能优化:多级缓存、批量操作、异步处理等技术手段
  4. 业务逻辑:价格计算、库存检查、物流计算等核心算法
  5. 用户体验:跨设备同步、智能推荐、实时更新等功能

# 最佳实践

  • 使用分布式锁确保并发操作的数据一致性
  • 采用事件驱动架构实现系统解耦
  • 实现多级缓存策略提升系统性能
  • 设计合理的数据模型支持复杂业务场景
  • 建立完善的监控和告警机制

# 技术演进方向

  • 智能化:AI驱动的购物车优化和推荐
  • 实时性:更快的数据同步和状态更新
  • 个性化:基于用户行为的个性化购物车体验
  • 全球化:更好的跨境电商支持和本地化

下一篇文章,我们将继续探讨跨境电商交易链路的下一个关键环节:价格计算引擎,看看如何处理复杂的跨境定价逻辑。