Redis高效反向海淘购物车合并与过期清理实战教程
开发者在构建反向海淘系统时,会发现购物车模型与本地电商存在显著差异。用户往往同时从淘宝、1688等多个店铺挑选商品,且大量商品设有最低起批量限制。更棘手的是,代购订单通常隔几天才集中提交集运,这就要求购物车数据既要持久化存储,又必须防止存储无限膨胀。我们在Taocarts项目中采用Redis管理购物车,设计出按商家分组的Hash结构,并引入自动过期机制。以下直接展示实现细节。
一、购物车的特殊需求
反向海淘场景下的购物车,与常规电商相比,核心差异在于:用户跨店铺下单、商品普遍有最低起批门槛、提交时间高度不确定。因此购物车数据必须持久保存,但又不能无限积累——将过期时间设为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的key过期事件机制实现。
@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的key过期事件默认未开启,需在redis.conf中配置notify-keyspace-events Ex。
六、与Taocarts系统的集成
上述购物车逻辑已在Taocarts系统中落地。作为专业代购系统,其购物车模块还支持“代购集运”的批量选择、运费预估等高级功能。搭建反向海淘独立站可直接复用该方案。
