在技术开发者的实战视角下,反向海淘购物车的设计逻辑与普通电商有着显著差异。用户常常跨淘宝、1688等多个店铺挑选商品,许多商品还设有最低起批量;更棘手的是,用户往往隔几天才提交代购集运请求,这就要求购物车数据既实现持久化,又不能无限膨胀。我们在Taocarts系统中采用Redis存储购物车数据,设计了按商家分组的存储结构,并支持自动过期清理。以下直接展示实现方案。

一、反向海淘购物车的特殊需求
反向海淘场景下的购物车,与普通电商最核心的区别在于:用户需跨店铺选品、商品存在最低起批量限制、提交代购时间不固定。因此购物车数据必须持久化存储,但不能无限膨胀——设置7天自动过期是一个合理且实用的策略。
二、数据结构设计
首先来看购物车商品项的数据结构定义:
// 购物车商品项
@Data
public class CartItem {
private Long itemId; // 商品ID
private String title;
private String skuProperties; // 所选规格JSON
private Integer quantity;
private BigDecimal price;
private Long shopId; // 店铺ID
private Integer minOrderQty; // 最低起批量
}
// 购物车整体结构:Hash存储,key=cart:userId,field=shopId,value=该店铺下的商品列表JSON
采用Hash结构存储,key以用户ID为前缀,field为店铺ID,value则是对应店铺下的商品列表。这种按店铺分组的模式,便于后续进行批量操作与管理。
三、核心操作实现
核心操作涵盖添加商品、移除商品以及获取按店铺分组的购物车列表。具体代码实现如下:
@Service
public class CartService {
@Autowired
private RedisTemplate redisTemplate;
private static final String CART_KEY_PREFIX = "cart:";
// 添加商品到购物车
public void addItem(Long userId, CartItem item) {
String key = CART_KEY_PREFIX + userId;
String shopKey = String.valueOf(item.getShopId());
// 获取该店铺现有商品列表
List shopItems = getShopItems(userId, item.getShopId());
Optional existing = shopItems.stream()
.filter(i -> i.getItemId().equals(item.getItemId())
&& i.getSkuProperties().equals(item.getSkuProperties()))
.findFirst();
if (existing.isPresent()) {
existing.get().setQuantity(existing.get().getQuantity() + item.getQuantity());
} else {
shopItems.add(item);
}
// 序列化存储
String json = JSON.toJSONString(shopItems);
redisTemplate.opsForHash().put(key, shopKey, json);
// 设置整个购物车的过期时间为7天
redisTemplate.expire(key, Duration.ofDays(7));
}
// 移除商品
public void removeItem(Long userId, Long shopId, Long itemId, String sku) {
String key = CART_KEY_PREFIX + userId;
List shopItems = getShopItems(userId, shopId);
shopItems.removeIf(i -> i.getItemId().equals(itemId) && i.getSkuProperties().equals(sku));
if (shopItems.isEmpty()) {
redisTemplate.opsForHash().delete(key, String.valueOf(shopId));
} else {
redisTemplate.opsForHash().put(key, String.valueOf(shopId), JSON.toJSONString(shopItems));
}
}
// 获取购物车所有商品(按店铺分组)
public Map> getCart(Long userId) {
String key = CART_KEY_PREFIX + userId;
Map
在添加商品时,先检查同一店铺内是否已存在相同规格的商品,若有则累加数量,否则新增条目。同时,每次操作都会刷新整个购物车的过期时间,从而确保活跃用户的购物车不会因过期而失效。
四、最低起批量校验机制
在结算阶段,需要对每个店铺内商品的数量进行最低起批量校验。校验逻辑十分直观:遍历所有店铺下的商品,若某件商品的数量低于其最低起批量,则直接抛出异常。
public void validateMinOrderQty(Long userId) {
Map> cart = getCart(userId);
for (Map.Entry> entry : cart.entrySet()) {
for (CartItem item : entry.getValue()) {
if (item.getQuantity() < item.getMinOrderQty()) {
throw new BusinessException(
String.format("商品【%s】最少需要购买%d件", item.getTitle(), item.getMinOrderQty()));
}
}
}
}
五、过期清理与主动用户通知
购物车默认7天自动过期,但用户很可能忘记已添加的商品。为了减少用户流失,我们在过期前1天主动发送站内信提醒,这依托于Redis的键过期事件来实现。
@Component
public class CartExpirationListener implements KeyExpirationEventListener {
@Override
public void onKeyExpired(String key) {
if (key.startsWith("cart:")) {
Long userId = Long.valueOf(key.substring(5));
// 发送站内信提醒
notificationService.sendCartExpireWarning(userId);
}
}
}
需要特别注意:Redis的键过期事件默认并未开启,需在 redis.conf 配置文件中设置 notify-keyspace-events Ex 才能启用该功能。
六、与Taocarts系统的集成应用
上述购物车逻辑已在Taocarts系统中得到完整实现。作为专业的代购系统,该购物车模块还额外支持“代购集运”的批量选择、运费预估等高级功能。对于搭建反向海淘独立站的开发者而言,可以直接复用这套成熟方案。
