设计购物车功能的步骤说明
1. 用户状态处理
未登录用户:
- 存储方式:使用浏览器本地存储(
localStorage
或Cookie
),键为guest_cart
,值为商品列表的JSON字符串。 - 数据迁移:用户登录后,合并本地购物车数据到服务端(通过API发送合并请求)。
- 存储方式:使用浏览器本地存储(
已登录用户:
- 存储方式:服务端使用Redis Hash或数据库表存储,键为用户ID(如
cart:user123
),字段为商品ID,值为商品详情。
- 存储方式:服务端使用Redis Hash或数据库表存储,键为用户ID(如
2. 数据结构设计
前端本地存储示例(未登录用户):
json[ { "skuId": "1001", "quantity": 2, "selected": true, "price": 199.00, "image": "https://example.com/image.jpg", "title": "商品名称", "addTime": 1620000000000 } ]
Redis存储示例(已登录用户):
bash# 用户购物车Hash结构 HSET cart:user123 1001 '{"quantity":2,"selected":1,"price":199.00}'
数据库表设计(持久化备份):
sqlCREATE TABLE cart ( user_id VARCHAR(36) NOT NULL, sku_id VARCHAR(20) NOT NULL, quantity INT DEFAULT 1, selected TINYINT(1) DEFAULT 1, price DECIMAL(10,2), created_time BIGINT, updated_time BIGINT, PRIMARY KEY (user_id, sku_id) );
3. 核心功能实现
3.1 添加商品到购物车
请求示例:
httpPOST /api/cart/add { "skuId": "1001", "quantity": 1 }
逻辑流程:
- 校验商品状态:调用商品服务验证SKU是否存在、是否上架。
- 更新数量:
- 未登录:前端合并本地数据(相同SKU数量累加)。
- 已登录:Redis
HINCRBY
原子增加数量。bashHINCRBY cart:user123 1001 1 # 数量+1
- 返回结果:前端提示“添加成功”并更新本地数据或跳转购物车页。
3.2 修改商品数量
请求示例:
httpPOST /api/cart/update { "skuId": "1001", "quantity": 3 }
逻辑流程:
- 校验合法性:数量必须≥1且≤库存上限(调用库存服务)。
- 更新数据:
- 未登录:遍历本地数组修改对应SKU数量。
- 已登录:Redis
HSET
直接覆盖数量。bashHSET cart:user123 1001 '{"quantity":3,"selected":1}'
- 前端响应:重新计算总价并刷新显示。
3.3 删除商品
请求示例:
httpPOST /api/cart/delete { "skuIds": ["1001", "1002"] }
逻辑流程:
- 批量处理:
- 未登录:过滤本地数组,移除指定SKU。
- 已登录:Redis
HDEL
删除字段。bashHDEL cart:user123 1001 1002
- 更新视图:前端移除对应商品DOM节点。
3.4 选中/取消选中商品
请求示例:
httpPOST /api/cart/select { "skuIds": ["1001"], "selected": false }
逻辑流程:
- 更新选中状态:
- 未登录:遍历本地数组修改
selected
字段。 - 已登录:读取Redis Hash中的JSON,修改后重新写入。bash
HSET cart:user123 1001 '{"quantity":3,"selected":0}'
- 前端计算:重新统计选中商品的总价。
4. 性能优化与扩展
读写分离:
- 读操作优先访问Redis,写操作同步更新数据库(异步队列)。
java// 异步写入数据库的RocketMQ消费者 @RocketMQMessageListener(topic = "cart_update", consumerGroup = "cart_group") public class CartDBConsumer implements RocketMQListener<CartItem> { @Override public void onMessage(CartItem item) { cartMapper.insertOrUpdate(item); // 插入或更新数据库 } }
缓存预热:
- 用户登录时将其数据库中的购物车数据加载到Redis。
sqlSELECT * FROM cart WHERE user_id = 'user123'; -- 结果转换为Redis HSET命令批量执行
分片策略:
- 大商户或高活跃用户单独分片(如
cart:user123_shard1
)。
- 大商户或高活跃用户单独分片(如
5. 安全与容错
输入校验:
- SKU ID格式校验(防止SQL注入)、数量范围校验(≥1且≤99)。
限流防护:
- 接口添加令牌桶限流(如Guava RateLimiter),防止恶意刷接口。
javaRateLimiter limiter = RateLimiter.create(100); // 每秒100次请求 if (limiter.tryAcquire()) { processRequest(); } else { throw new RateLimitException(); }
数据备份:
- 每日定时任务将Redis购物车数据持久化到数据库。
6. 用户体验增强
实时计算总价:
- 前端监听选中状态和数量变化,实时调用API计算优惠和总价。
javascript// 前端示例:计算选中商品总价 const total = cartItems .filter(item => item.selected) .reduce((sum, item) => sum + (item.price * item.quantity), 0);
多端同步:
- Web端与App端通过长连接(WebSocket)同步购物车变更。
javascriptconst ws = new WebSocket('wss://example.com/cart-sync'); ws.onmessage = (event) => { const data = JSON.parse(event.data); updateLocalCart(data); // 合并本地购物车 };
7. 架构图
用户请求 → 负载均衡 → 网关(鉴权/限流)
↓
购物车服务 → Redis(读写购物车数据)
↓ ↗ 异步
数据库(持久化) ← 消息队列(RocketMQ)
总结
通过区分登录状态、优化存储结构、实现核心操作逻辑,并结合异步持久化、限流防护、多端同步等策略,可构建一个高效、稳定且用户体验良好的购物车系统。关键点包括:
- 状态分离:未登录用户依赖本地存储,已登录用户使用Redis+数据库。
- 原子操作:利用Redis的原子命令保证数据一致性。
- 性能优先:读写分离、缓存预热、异步处理提升响应速度。
- 安全兜底:输入校验、限流、备份机制保障系统健壮性。