设计电商系统:商品、购物车与下单

FreeGuideOnline 最新 2026-06-19

从零设计电商核心域:商品、购物车与下单

本教程面向初学者和转型开发者,带你系统性地理解并落地一个电商平台中最重要的三个核心模块:商品域、购物车域、下单流程。我们不堆砌框架,先讲透抽象建模与业务逻辑,再思考如何映射到代码与数据库。


一、商品建模:从 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 合并逻辑(本地→服务端)

当未登录用户登录时,触发合并:

  1. 将浏览器内购物车条目逐条传给后端。
  2. 后端对该用户的已有服务端购物车做相同 SKU 数量叠加,不同 SKU 则新增。
  3. 合并后清空本地存储,所有后续操作走服务端。
  4. 合并需要有最终一致性设计,避免并发添加导致数量错误。

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

五、初学者容易踩的坑与总结

  1. 库存扣减时机错误:绝不能加入购物车就扣库存,那会把库存锁死。标准做法是提交订单时预占,支付后实际扣减,超时释放。
  2. 价格到处存储:订单必须快照价格,购物车绝不存储价格。
  3. 订单号生成规则混乱:必须有唯一且趋势递增的订单号,不能依赖数据库自增主键暴露在外部。
  4. 忽略幂等性:网络重试会导致重复订单,必须引入 Token 机制。
  5. 状态直接修改:永远通过操作(事件)驱动状态变更,并落日志。

设计电商系统不是做一个简单的 CRUD 应用,而是建立一个高内聚、低耦合的领域模型。掌握上述商品、购物车、下单的核心抽象与流程,你就可以在此之上衍生出促销引擎、退款售后、多仓物流等复杂设施,而不会动摇根基。


本教程为通用电商设计入门,具体实现请根据业务量级选择合适的技术架构(单体 → 微服务)。希望这张模型蓝图对你打通电商系统任督二脉有所帮助。