设计电商系统:商品、购物车与下单
从零设计电商核心域:商品、购物车与下单
本教程面向初学者和转型开发者,带你系统性地理解并落地一个电商平台中最重要的三个核心模块:商品域、购物车域、下单流程。我们不堆砌框架,先讲透抽象建模与业务逻辑,再思考如何映射到代码与数据库。
一、商品建模:从 SPU 与 SKU 开始
商品是电商的根基,建模错误会导致库存、价格管理全面崩塌。首先要区分两个核心概念:
- SPU (Standard Product Unit):标准产品单元,描述一款“可销售商品”的共性信息。例如“iPhone 15 Pro Max”就是一个 SPU。
- SKU (Stock Keeping Unit):库存量单位,描述一个具体的、可被购买与追踪库存的实体。例如“iPhone 15 Pro Max 256GB 原色钛金属”就是一个 SKU。
1.1 属性体系设计
商品属性分为三类,不可混为一谈:
- 关键属性:决定一个 SPU 是什么,如品牌、型号、系列。
- 销售属性 (规格):组合形成 SKU,如颜色、容量、版本。用户必须选择。
- 非销售属性:扩展描述,不影响 SKU,如保修政策、材质描述、生产年份。
设计诀窍:属性模板化。不建议为每个商品建字段,而应抽象为属性名-属性值结构,这样新品录入不需要改表结构。
1.2 价格与库存归属
- 价格必须挂在 SKU 层级。SPU 只展示区间价(由 SKU 聚合得出)。
- 库存也是 SKU 粒度。仓库实际存放的是 SKU,不是 SPU。
- 需要支持多仓库库存时,SKU 与仓库是多对多关系,并产生
库存记录表。
1.3 类目体系
用分层类目树组织商品,前台展示用前台类目(可挂靠多层级,允许跨分支挂载),后台管理用后台类目(严格树状)。每个商品需要绑定至少一个后台叶子类目。
二、购物车:状态机与数据一致性
购物车不是临时的“篮子”,而是用户购买意向的管理器。它的核心难点在于未登录用户、多端同步以及促销实时计算。
2.1 存储策略抉择
| 方案 | 适用场景 | 优缺点 |
|---|---|---|
| 纯浏览器端 (LocalStorage) | 完全不需要登录的轻量级站点 | 无法跨设备,清除缓存即丢失,无法做服务端促销校验 |
| 纯服务端 (DB) | 必须登录才能加购 | 体验割裂,未登录用户无法使用 |
| 混合方案(推荐) | 兼顾未登录使用与登录同步 | 未登录存本地,登录后合并到服务端,后续统一服务端存储 |
2.2 购物车数据结构
服务端购物车表核心字段:
user_id:归属用户sku_id:SKU标识quantity:数量selected:是否勾选created_time/modified_time
重要:购物车不存价格!前端展示价格必须从商品域实时查询(或缓存)。因为价格随时可能变更,不可能通知所有购物车更新。
2.3 合并逻辑(本地→服务端)
当未登录用户登录时,触发合并:
- 将浏览器内购物车条目逐条传给后端。
- 后端对该用户的已有服务端购物车做相同 SKU 数量叠加,不同 SKU 则新增。
- 合并后清空本地存储,所有后续操作走服务端。
- 合并需要有最终一致性设计,避免并发添加导致数量错误。
2.4 购物车选中状态与失效处理
- 用户可勾选/取消勾选条项,选中状态应持久化,刷新不丢失。
- 对于下架、无库存、价格变动的 SKU,购物车需做失效标记:显示“已失效”但保留记录,允许“清理失效”操作。
- 这些失效检测在下单前会二次校验。
三、下单流程:从确认页到订单生成
下单不是简单的插入一条订单,而是一个包含多步确认与分布式事务的核心流程。
3.1 订单确认页 (Order Preview)
在真正创建订单之前,需要构建订单确认页,供用户最终确认。这一步需调用大量实时服务,并展示计算后的价格摘要。
需要展示的信息结构:
- 收货地址:可编辑选择
- 配送方式与运费:按地区、重量、件数计算
- 商品清单:从购物车选中项获取,展示 SKU、图片、单价(取当前实时价)、数量、小计
- 优惠券与促销:用户可用券列表,自动选中最优
- 价格分解:商品总金额 - 优惠金额 + 运费 = 实付金额
- 发票信息:可选
- 买家留言
该页面必须做到无下单副作用,即纯粹查询,不锁定任何资源。
3.2 下单核心步骤与事务边界
点击“提交订单”后进入核心事务,推荐分解为以下步骤,并采用最终一致性思想:
Step 1: 订单防重与幂等处理
客户端生成一个全局唯一的order_token(可在确认页下发),后端使用该 Token 做幂等校验,防止重复提交。
Step 2: 库存预占 (最关键)
不能直接减库存,否则占用时间长,容易超卖。推荐方案:
- 先去库存服务对每个 SKU 执行预占操作(即扣减可售库存,增加冻结库存)。
- 预占成功意味着库存为你保留,通常设置一个超时时间(如15分钟)。
- 如果任何 SKU 预占失败,整个订单失败,并释放已预占的 SKU。
Step 3: 订单创建与状态设置
预占成功后,创建主订单和子订单(按商家拆单时)。主订单状态设为待支付,记录支付截止时间。此处订单状态机开始运转。
Step 4: 冻结库存超时取消
需要定时任务扫描待支付状态且超过支付截止时间未支付的订单,自动进行解冻库存并标记订单已取消。
Step 5: 异步清理购物车
创建订单成功后,异步清除购物车中已成功结算的选中项,不影响主流程返回。
3.3 订单状态与状态机
标准订单状态流转如下(可根据业务扩展):
待支付 → (支付成功) → 待发货 → (商家发货) → 待收货 → (用户确认收货) → 已完成
↓ ↓ ↓
(取消/超时) (部分退款) (退货/退款)
↓
已取消
每个状态变迁都应记录操作日志,不可直接修改状态字段。
3.4 拆单逻辑
一个购物车可能包含不同商家或不同仓库的商品,系统需要自动拆单:
- 按商家拆:每个商家一个主订单,方便商家管理。
- 按发货仓库拆:同一个商家但不同仓库可能拆成多个物流包裹(子订单/包裹单)。
- 拆单后每个子订单独立支付、独立物流,但需要在前端透明聚合展示,并提供整体操作入口(如合并支付)。
四、数据库设计速览(核心表)
商品相关
spu:id, title, brand_id, category_id, status, description, main_images
sku:id, spu_id, sku_code, price, market_price, cost_price, stock, sale_attrs_json, image
attribute:id, name, type
spu_attribute_value:spu_id, attr_id, attr_value
sku_sales_attribute:sku_id, attr_id, attr_value
购物车
cart_item:id, user_id, sku_id, quantity, selected, created_at, updated_at
订单
order_master:id, order_no, user_id, total_amount, discount_amount, freight, payment_amount, status, create_time, payment_deadline
order_item:id, order_id, sku_id, sku_snapshot_json, quantity, unit_price, subtotal
order_payment:id, order_id, payment_method, transaction_id, paid_amount, paid_time
order_logistics:id, order_id, tracking_number, carrier, status
五、初学者容易踩的坑与总结
- 库存扣减时机错误:绝不能加入购物车就扣库存,那会把库存锁死。标准做法是提交订单时预占,支付后实际扣减,超时释放。
- 价格到处存储:订单必须快照价格,购物车绝不存储价格。
- 订单号生成规则混乱:必须有唯一且趋势递增的订单号,不能依赖数据库自增主键暴露在外部。
- 忽略幂等性:网络重试会导致重复订单,必须引入 Token 机制。
- 状态直接修改:永远通过操作(事件)驱动状态变更,并落日志。
设计电商系统不是做一个简单的 CRUD 应用,而是建立一个高内聚、低耦合的领域模型。掌握上述商品、购物车、下单的核心抽象与流程,你就可以在此之上衍生出促销引擎、退款售后、多仓物流等复杂设施,而不会动摇根基。
本教程为通用电商设计入门,具体实现请根据业务量级选择合适的技术架构(单体 → 微服务)。希望这张模型蓝图对你打通电商系统任督二脉有所帮助。