返回架构笔记
Arch Day 69

Arch Day 69: 订单系统设计

Arch Day 69: 订单系统设计

2026-06-07
第三阶段 - 零售域深度
电商订单系统状态机DDD拆单

日期: 2026-06-07 (Day 69) 阶段: 第三阶段 - 零售域深度 标签: #电商 #订单系统 #状态机 #DDD #拆单


核心概念

一句话定义

订单系统是电商的"中枢神经"——它连接用户、商品、库存、支付、物流、售后等所有核心域,承载着从"用户下单意图"到"交易完成"的全部业务逻辑。

为什么关注

2025年全球电商交易额达6.42万亿美元,预计2026年将达6.88万亿美元(Shopify Global Report)。每一笔交易的背后,都是一次完整的订单生命周期管理。订单系统的设计质量,直接决定了:

  • 用户体验:下单流程是否顺畅、退款是否及时
  • 运营效率:拆单逻辑是否合理、履约是否高效
  • 财务准确性:金额计算、优惠分摊、对账是否一致
  • 系统可靠性:高并发下单、异常恢复、幂等保障

误区与反模式

误区现实
"订单就是一张表"订单是一个聚合根,包含订单项、支付、物流、优惠等多个实体
"状态就是一个字段"复杂业务需要多维状态机(主订单+子订单+物流+支付+售后)
"先设计数据库再写业务"应先建立领域模型,再映射到存储
"逆向流程是附加功能"退款/退货复杂度往往超过正向流程
"订单号用自增ID就行"自增ID暴露业务量、不支持分库分表、无法分布式生成

知识点详解

一、订单状态机设计

状态机是订单系统的"骨骼",可以拆为三个要素:现态(State)、动作(Action)、次态(Next State)

1.1 正向流程状态机

┌──────────┐  用户提交   ┌──────────┐  支付成功   ┌──────────┐
│  待支付   │ ─────────→ │  已支付   │ ─────────→ │  待发货   │
│ PENDING  │            │  PAID    │            │ TO_SHIP  │
└──────────┘            └──────────┘            └──────────┘
                                                     │
                                                     │ 商家发货
                                                     ▼
┌──────────┐  确认收货   ┌──────────┐  物流签收   ┌──────────┐
│  已完成   │ ←───────── │  已签收   │ ←───────── │  已发货   │
│ COMPLETED│            │ RECEIVED │            │ SHIPPED  │
└──────────┘            └──────────┘            └──────────┘

1.2 逆向流程状态机

任意可退状态
     │
     │ 用户申请退款/退货
     ▼
┌──────────┐  商家审核   ┌──────────┐  退货寄回   ┌──────────┐
│ 退款申请  │ ─────────→ │ 退款审核中 │ ─────────→ │ 退货中    │
│ REFUND_  │  通过       │ REFUND_  │            │ RETURNING│
│ APPLIED  │            │ APPROVED │            │          │
└──────────┘            └──────────┘            └──────────┘
                              │                       │
                              │ 仅退款                 │ 商家确认收货
                              ▼                       ▼
                        ┌──────────┐            ┌──────────┐
                        │ 退款中    │            │ 退款中    │
                        │ REFUNDING│            │ REFUNDING│
                        └──────────┘            └──────────┘
                              │                       │
                              │ 退款到账               │ 退款到账
                              ▼                       ▼
                        ┌──────────┐            ┌──────────┐
                        │ 退款完成  │            │ 退款完成   │
                        │ REFUNDED │            │ REFUNDED │
                        └──────────┘            └──────────┘

1.3 异常状态处理

异常场景处理策略状态流转
支付超时定时任务扫描,自动关单PENDING → CANCELLED
支付回调延迟主动查询支付结果,补偿机制PENDING → PAID(补偿)
部分发货子订单独立状态机父订单PARTIAL_SHIPPED
物流异常物流状态独立,不影响主流程标记LOGISTICS_EXCEPTION
退款失败重试+人工介入REFUNDING → REFUND_FAILED
并发修改冲突乐观锁+版本号拒绝非法跳转

1.4 状态机代码实现(Spring StateMachine 风格)

@Configuration
public class OrderStateMachineConfig {

    // 定义状态枚举
    public enum OrderState {
        PENDING, PAID, TO_SHIP, SHIPPED, RECEIVED,
        COMPLETED, CANCELLED, REFUND_APPLIED,
        REFUND_APPROVED, RETURNING, REFUNDING, REFUNDED
    }

    // 定义事件枚举
    public enum OrderEvent {
        PAY, SHIP, SIGN, CONFIRM, CANCEL,
        APPLY_REFUND, APPROVE_REFUND, REJECT_REFUND,
        RETURN_GOODS, CONFIRM_RETURN, REFUND_SUCCESS, REFUND_FAIL
    }

    // 状态转换规则
    @Bean
    public StateMachine<OrderState, OrderEvent> orderStateMachine() {
        StateMachineBuilder.Builder<OrderState, OrderEvent> builder =
            StateMachineBuilder.builder();

        // 正向流程
        builder.configureTransitions()
            .withExternal().source(PENDING).target(PAID).event(PAY)
            .withExternal().source(PAID).target(TO_SHIP).event(PAY) // 自动
            .withExternal().source(TO_SHIP).target(SHIPPED).event(SHIP)
            .withExternal().source(SHIPPED).target(RECEIVED).event(SIGN)
            .withExternal().source(RECEIVED).target(COMPLETED).event(CONFIRM)
            // 超时自动关单
            .withExternal().source(PENDING).target(CANCELLED).event(CANCEL)
            // 逆向流程
            .withExternal().source(PAID).target(REFUND_APPLIED).event(APPLY_REFUND)
            .withExternal().source(TO_SHIP).target(REFUND_APPLIED).event(APPLY_REFUND)
            .withExternal().source(SHIPPED).target(REFUND_APPLIED).event(APPLY_REFUND)
            .withExternal().source(RECEIVED).target(REFUND_APPLIED).event(APPLY_REFUND);

        return builder.build();
    }
}

二、拆单逻辑设计

拆单是将用户的一个逻辑订单拆分为多个可独立履约的子订单。

2.1 拆单触发维度

拆单维度场景示例
按商家拆平台型电商,不同商家独立发货淘宝/京东第三方商家
按仓库拆同商家不同仓库分别发货华南仓+华北仓
按配送方式拆普通快递/冷链/大件物流分开生鲜冷链+日用快递
按促销活动拆赠品单独发货、预售与现货分开预售商品+现货商品
按品类拆特殊品类(危险品/液体)单独包装酒类+普通商品
按物流限制拆超出物流重量/体积限制单包裹超30kg

2.2 拆单引擎架构

用户订单(MainOrder)
       │
       ▼
 ┌─────────────┐
 │  拆单规则引擎 │
 │  SplitEngine │
 └──────┬──────┘
        │
   ┌────┼────┬────┬────┐
   ▼    ▼    ▼    ▼    ▼
 商家  仓库  配送  促销  品类
 规则  规则  规则  规则  规则
   │    │    │    │    │
   └────┼────┴────┴────┘
        │
        ▼
 ┌─────────────┐
 │ 合并优化器   │ ← 避免过度拆分(同仓同配送可合并)
 │ MergeOptim  │
 └──────┬──────┘
        │
        ▼
  子订单列表(SubOrders)

2.3 拆单规则引擎伪代码

class OrderSplitEngine:
    def __init__(self):
        self.rules = [
            MerchantSplitRule(),   # 按商家拆
            WarehouseSplitRule(),  # 按仓库拆
            DeliverySplitRule(),   # 按配送方式拆
            PromotionSplitRule(), # 按促销活动拆
            CategorySplitRule(),  # 按品类拆
        ]
        self.merge_optimizer = MergeOptimizer()

    def split(self, main_order: MainOrder) -> List[SubOrder]:
        # 初始:整个订单作为一组
        groups = [main_order.items]

        # 链式拆分:每条规则都可以进一步拆分已有的组
        for rule in self.rules:
            new_groups = []
            for group in groups:
                split_result = rule.apply(group)
                new_groups.extend(split_result)
            groups = new_groups

        # 合并优化:避免过度拆分
        groups = self.merge_optimizer.optimize(groups)

        # 生成子订单
        sub_orders = []
        for i, group in enumerate(groups):
            sub_order = SubOrder(
                sub_order_no=f"{main_order.order_no}-{i+1:02d}",
                items=group,
                warehouse=group[0].warehouse,
                delivery_type=group[0].delivery_type,
            )
            sub_orders.append(sub_order)

        return sub_orders

三、合单逻辑

合单与拆单相反,是将多个逻辑订单合并处理以提高效率。

合单场景条件收益
合并支付同用户同时下多笔订单减少支付次数,提升转化
合并发货同仓库同收货地址节省物流成本
合并包裹同物流商同时间窗发货降低包装和人工成本

四、订单号设计

4.1 设计要求

要求说明
全局唯一分布式环境下不重复
有序递增便于数据库索引,避免页分裂
可读性便于客服查询、用户识别
含业务信息可从订单号推断渠道、时间等
不可猜测不能通过订单号推算业务量
高性能每秒可生成百万级ID

4.2 主流方案对比

方案结构优点缺点
UUID128位随机简单,无中心无序,索引差,可读性差
数据库自增AUTO_INCREMENT有序,简单单点瓶颈,暴露信息
Snowflake时间+机器+序列有序,分布式,高性能时钟回拨问题
Leaf-Segment数据库号段有序,高性能依赖DB,号段浪费
Leaf-Snowflake改进Snowflake解决时钟问题引入ZooKeeper
业务编码渠道+时间+序列+校验可读,含业务信息设计复杂

4.3 推荐方案:业务编码 + Snowflake混合

订单号格式(20位):
  CC   YYMMDD   HH   NNNNNNNNNN   KK
  │     │       │      │           │
  │     │       │      │           └── 校验位(2位)
  │     │       │      └── Snowflake序列(10位)
  │     │       └── 小时(2位)
  │     └── 日期(6位)
  └── 渠道码(2位):10=APP, 20=PC, 30=小程序, 40=H5

示例:10 260607 14 0038291047 35
含义:APP渠道 | 2026-06-07 | 14时 | 序列号 | 校验位

五、订单领域模型(DDD)

5.1 核心聚合

┌─────────────────────────────────────────────────────┐
│                   订单聚合(Order Aggregate)           │
│                                                      │
│  Order (聚合根)                                       │
│  ├── orderId: OrderId (值对象)                        │
│  ├── orderNo: String                                 │
│  ├── buyerId: UserId                                 │
│  ├── status: OrderStatus (值对象)                     │
│  ├── orderItems: List<OrderItem> (实体)               │
│  │   ├── skuId, skuName, quantity, unitPrice         │
│  │   ├── promotionDiscount                           │
│  │   └── actualAmount                                │
│  ├── shippingAddress: Address (值对象)                 │
│  ├── payment: PaymentInfo (值对象)                    │
│  │   ├── paymentMethod, paymentAmount                │
│  │   └── paymentStatus, paymentTime                  │
│  ├── shipment: ShipmentInfo (值对象)                  │
│  │   ├── logisticsCompany, trackingNo                │
│  │   └── shipTime, receiveTime                       │
│  ├── promotion: PromotionInfo (值对象)                │
│  │   ├── couponId, discountAmount                    │
│  │   └── promotionType                               │
│  └── timeline: List<OrderEvent> (值对象)              │
│      ├── eventType, eventTime, operator              │
│      └── remark                                      │
└─────────────────────────────────────────────────────┘

5.2 领域事件

// 订单创建事件
public class OrderCreatedEvent extends DomainEvent {
    private String orderId;
    private String buyerId;
    private BigDecimal totalAmount;
    private List<OrderItemDTO> items;
}

// 订单已支付事件
public class OrderPaidEvent extends DomainEvent {
    private String orderId;
    private String paymentId;
    private BigDecimal paidAmount;
    private String paymentMethod;
}

// 订单已发货事件
public class OrderShippedEvent extends DomainEvent {
    private String orderId;
    private String logisticsCompany;
    private String trackingNo;
}

// 退款申请事件
public class RefundAppliedEvent extends DomainEvent {
    private String orderId;
    private String refundId;
    private BigDecimal refundAmount;
    private String reason;
}

5.3 上下文映射

┌──────────┐     ┌──────────┐     ┌──────────┐
│  商品域   │     │  订单域   │     │  库存域   │
│ Product  │────→│  Order   │←────│ Inventory│
│ Context  │     │ Context  │     │ Context  │
└──────────┘     └────┬─────┘     └──────────┘
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
    ┌──────────┐ ┌──────────┐ ┌──────────┐
    │  支付域   │ │  物流域   │ │  售后域   │
    │ Payment  │ │ Logistics│ │ AfterSale│
    │ Context  │ │ Context  │ │ Context  │
    └──────────┘ └──────────┘ └──────────┘

六、逆向流程深度解析

逆向流程的复杂度通常是正向流程的2-3倍,因为需要处理:

6.1 退款类型矩阵

退款类型触发时机库存处理物流处理资金处理
仅退款(未发货)已支付/待发货释放库存原路退回
仅退款(已发货)已发货/已签收不释放无(商家承担)原路退回
退货退款已签收收货后释放逆向物流验货后退回
换货已签收收旧发新双向物流差价处理
部分退款多商品订单部分释放可能部分退货分摊计算

6.2 优惠分摊退款

当订单使用了优惠券时,部分退款需要重新计算优惠分摊:

def calculate_partial_refund(order, refund_items):
    """
    优惠分摊退款计算
    原则:按商品金额比例分摊优惠
    """
    # 原始总金额(优惠前)
    original_total = sum(item.price * item.qty for item in order.items)

    # 退款商品原始金额
    refund_original = sum(item.price * item.qty for item in refund_items)

    # 优惠分摊比例
    discount_ratio = refund_original / original_total

    # 分摊的优惠金额
    shared_discount = order.total_discount * discount_ratio

    # 实际退款金额 = 退款商品原价 - 分摊的优惠
    refund_amount = refund_original - shared_discount

    # 边界处理:最后一件退款时,退剩余实付金额(避免分摊精度丢失)
    remaining_items = [i for i in order.items if i not in refund_items]
    if not remaining_items:
        refund_amount = order.actual_paid - order.already_refunded

    return round(refund_amount, 2)

对比分析

主流电商平台订单系统对比

维度淘宝/天猫京东拼多多美团
订单模型主子订单模型主子订单+包裹扁平订单主子订单
拆单粒度商家+仓库+配送仓库+配送+温区简化拆单商家+骑手
状态机复杂度极高(20+状态)高(15+状态)中(10+状态)高(含配送状态)
逆向流程极复杂(含争议仲裁)复杂(含京东自营)相对简单中等(即时退款)
订单号位数18-20位12位数字纯数字含业务前缀
分库分表按买家ID按订单ID按买家ID按城市+时间
日订单量级亿级亿级亿级千万级

架构设计实操

设计目标

设计一个"订单中心"系统,满足:

  • 日订单量1000万+
  • 支持多渠道(APP/PC/H5/小程序)
  • 支持多商家入驻
  • 完整正向+逆向流程
  • 高可用(99.99%)

整体架构方案

┌──────────────────────────────────────────────────────┐
│                    接入层(Gateway)                     │
│  APP │ PC │ H5 │ 小程序 │ 开放平台                      │
└──────────────────────┬───────────────────────────────┘
                       │
┌──────────────────────▼───────────────────────────────┐
│                  订单聚合服务(BFF)                      │
│  下单编排 │ 查询聚合 │ 状态机调度 │ 逆向编排              │
└──────────────────────┬───────────────────────────────┘
                       │
┌──────────────────────▼───────────────────────────────┐
│                  核心领域服务层                          │
│  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐        │
│  │订单服务 │ │拆单服务 │ │状态机   │ │逆向服务 │        │
│  │Order   │ │Split   │ │Service │ │Reverse │        │
│  └────────┘ └────────┘ └────────┘ └────────┘        │
│  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐        │
│  │超时服务 │ │快照服务 │ │查询服务 │ │归档服务 │        │
│  │Timeout │ │Snapshot│ │Query   │ │Archive │        │
│  └────────┘ └────────┘ └────────┘ └────────┘        │
└──────────────────────┬───────────────────────────────┘
                       │
┌──────────────────────▼───────────────────────────────┐
│                    基础设施层                           │
│  MySQL(分库分表) │ Redis │ MQ │ ES │ Hbase(归档)       │
└──────────────────────────────────────────────────────┘

ADR: 订单分库分表策略

背景: 日订单量1000万+,单表超5000万行查询性能下降

决策: 采用按买家ID分库分表 + 异构索引(按订单号、商家ID)

理由:

  • 80%查询是买家查自己的订单(C端)
  • 按买家ID分片保证同用户订单在同一分片
  • 商家维度查询通过异构索引(ES/独立表)支持
  • 订单号查询通过订单号中嵌入分片键支持

权衡:

  • 优势:C端查询高效、同用户事务简单
  • 劣势:商家查询需跨库/依赖ES、数据迁移复杂
  • 替代方案:按订单ID分片(查询简单但用户维度需聚合)

AI增强实践

1. AI智能客服与订单异常预判

class AIOrderAssistant:
    """AI驱动的订单智能助手"""

    def predict_order_risk(self, order):
        """
        预测订单风险(欺诈/退款概率)
        输入:订单特征(金额、商品、地址、历史行为)
        输出:风险评分 + 建议操作
        """
        features = self.extract_features(order)
        risk_score = self.risk_model.predict(features)

        if risk_score > 0.8:
            return {"action": "HOLD", "reason": "高风险订单,建议人工审核"}
        elif risk_score > 0.5:
            return {"action": "VERIFY", "reason": "中风险,建议二次验证"}
        return {"action": "PASS", "reason": "正常订单"}

    def auto_resolve_dispute(self, dispute):
        """
        AI自动仲裁退款争议
        基于历史案例 + 规则引擎 + LLM判断
        """
        # RAG: 检索相似历史案例
        similar_cases = self.rag_engine.search(dispute.description)

        # LLM: 综合判断
        prompt = f"""
        争议描述: {dispute.description}
        相似案例: {similar_cases}
        平台规则: {self.platform_rules}
        请给出仲裁建议。
        """
        return self.llm.generate(prompt)

2. AI预测性订单运营

AI能力应用场景技术方案
取消预测预判用户可能取消的订单XGBoost + 用户行为序列
退货预测发货前预判退货概率多模态模型(商品图+评价+历史)
物流异常预测预测延迟/丢包时序模型 + 天气/物流数据
智能催付未支付订单个性化催付LLM生成催付文案 + A/B
库存预分配大促前智能预分仓需求预测 + 仓储优化

与Web3/DeFi的关联

订单系统 × 区块链

场景Web2方案Web3增强
订单存证数据库记录关键节点上链(防篡改)
跨境支付银行清算(T+3)稳定币即时结算
供应链溯源中心化数据库链上全程追溯
争议仲裁平台客服智能合约自动仲裁(Escrow)
跨平台订单无法互通DID + 链上信用

智能合约 Escrow 模式

// 简化的订单托管合约
contract OrderEscrow {
    enum OrderState { Created, Paid, Shipped, Completed, Disputed, Refunded }

    struct Order {
        address buyer;
        address seller;
        uint256 amount;
        OrderState state;
        uint256 deadline;
    }

    mapping(uint256 => Order) public orders;

    function pay(uint256 orderId) external payable {
        // 买家付款,资金锁定在合约中
        orders[orderId] = Order(msg.sender, seller, msg.value, OrderState.Paid, block.timestamp + 7 days);
    }

    function confirmReceived(uint256 orderId) external {
        // 买家确认收货,释放资金给卖家
        require(msg.sender == orders[orderId].buyer);
        orders[orderId].state = OrderState.Completed;
        payable(orders[orderId].seller).transfer(orders[orderId].amount);
    }

    function dispute(uint256 orderId) external {
        // 争议仲裁,引入DAO投票或预言机
        orders[orderId].state = OrderState.Disputed;
    }
}

今日思考

1. 订单系统如何支持"先用后付"(BNPL)模式?

先用后付改变了传统"先支付后履约"的流程。订单状态机需要增加"使用中(IN_USE)"和"待结算(TO_SETTLE)"状态。支付域需要与信用评估域协作,在下单时进行风控评估而非直接收款。结算可以是分期(如花呗三期)或到期一次性扣款。核心挑战在于:如何在用户未付款的情况下保证商家权益?答案是引入担保方(金融机构)和风控模型。

2. 订单分库分表后如何做全局统计分析?

直接跨分片查询性能极差。主流方案有三种:(1) 异构数据同步到OLAP(如ClickHouse/StarRocks)做实时分析;(2) 通过Canal/Flink将变更流同步到ES做查询索引;(3) T+1离线同步到数仓做报表。选哪种取决于时效性要求——实时大屏用方案1,模糊查询用方案2,经营报表用方案3。

3. 多租户(Multi-Tenant)电商平台的订单隔离如何设计?

SaaS电商平台服务多个商家,需要数据隔离。三种模式:(1) 独立数据库(隔离最强,成本高);(2) 共享数据库独立Schema(折中);(3) 共享表加租户ID(成本低,隔离弱)。推荐中小商家用方案3 + 行级安全策略(Row-Level Security),大商家用方案1。订单号中也应嵌入租户标识。


面试题准备

题目1:订单状态机如何处理异常状态?

30秒回答: 订单状态机通过"守卫条件+补偿机制"处理异常。每次状态转换前校验前置条件,转换失败时触发补偿(如定时重试、人工介入)。关键是将异常也纳入状态机设计,而非try-catch了事。

2分钟回答: 异常处理分三层:

  1. 预防层:乐观锁防并发冲突、幂等键防重复提交、守卫条件阻止非法跳转
  2. 检测层:定时任务扫描"卡住"的订单(如PENDING超30分钟)、监控告警
  3. 恢复层:自动补偿(查询支付结果补状态)、降级处理(超时自动关单)、人工介入(争议订单转客服)

实际案例:支付回调丢失是最常见的异常——用户已扣款但订单仍PENDING。解决方案是定时任务每5分钟主动查询支付网关,补偿订单状态。同时订单号要保证幂等,避免重复创建。

追问准备

  • Q: 并发修改同一个订单怎么办?→ 乐观锁(version字段) + 状态机限制合法跳转
  • Q: 状态机太复杂怎么管理?→ 拆分为多个子状态机(订单/支付/物流/售后各一个)

题目2:拆单逻辑如何设计?

30秒回答: 拆单采用"规则链"模式——按商家→按仓库→按配送→按品类依次执行拆分规则,最后通过合并优化器避免过度拆分。核心原则是"拆单结果的每个子订单可以独立履约"。

2分钟回答: 拆单设计要点:

  1. 规则可配置:用规则引擎(如Drools)而非硬编码,运营可动态调整
  2. 链式执行:多条规则依次处理,每条规则在上一条结果上继续拆分
  3. 合并优化:拆完后检查是否可以合并(同仓同配送方式的子组合并)
  4. 优惠分摊:拆单后优惠按比例分摊到子订单(精度问题用"最后一单兜底"策略)
  5. 原子性:拆单是创建子订单前的计算过程,失败则整个订单创建失败

追问准备

  • Q: 拆单后退款怎么算?→ 子订单独立退款,优惠按分摊比例扣减
  • Q: 拆单和合单的优先级?→ 先拆后合,拆单面向履约,合单面向体验

学习资源


明日预告

Day 70: 促销系统设计 —— 促销系统是电商的"增长引擎",也是"复杂度炸弹"。我们将深入优惠券、满减、折扣、秒杀、拼团等促销类型的叠加规则设计,以及价格计算引擎和优惠分摊算法。当AI遇上促销系统,动态定价和智能推荐将如何改变游戏规则?