引言:为什么OMS是核心

在跨境电商的系统矩阵中,OMS(Order Management System)是绝对的核心

  • 连接前端:对接Amazon、eBay、独立站等多个销售渠道
  • 连接后端:驱动WMS仓储、TMS物流、ERP财务
  • 数据中枢:订单数据是最有价值的业务数据

OMS做得好不好,直接决定了整个数字化转型的成败。

本文将详细讲解如何从0到1自研一套OMS订单系统。


一、OMS系统定位

1.1 OMS在系统矩阵中的位置

┌─────────────────────────────────────────────────────┐
│                    销售渠道                          │
│   Amazon │ eBay │ Shopify │ 独立站 │ ...           │
└─────────────────────┬───────────────────────────────┘
                      │ 订单
                      ▼
┌─────────────────────────────────────────────────────┐
│                     OMS                              │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐   │
│  │订单接入 │ │订单处理 │ │库存管理 │ │履约调度 │   │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘   │
└───────┬─────────────┬─────────────┬─────────────────┘
        │             │             │
        ▼             ▼             ▼
    ┌───────┐    ┌───────┐    ┌───────┐
    │  WMS  │    │  TMS  │    │  ERP  │
    │ 仓储  │    │ 物流  │    │ 财务  │
    └───────┘    └───────┘    └───────┘

1.2 OMS的核心职责

职责说明
订单接入从各渠道获取订单,统一格式
订单处理审核、拆分、合并、取消
库存管理可售库存计算、库存预占
履约调度选仓、选物流、下发执行
状态管理订单全生命周期状态跟踪
异常处理缺货、超时、取消等异常

1.3 OMS与其他系统的边界

功能OMSWMSTMSERP
订单创建
库存预占
实物库存
拣货发货
物流跟踪
财务核算

二、架构设计

2.1 整体架构

┌─────────────────────────────────────────────────────┐
│                    接入层                            │
│  Amazon适配器 │ eBay适配器 │ Shopify适配器 │ ...    │
└─────────────────────┬───────────────────────────────┘
                      │
┌─────────────────────▼───────────────────────────────┐
│                   服务层                             │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐            │
│  │ 订单服务 │ │ 库存服务 │ │ 履约服务 │            │
│  └──────────┘ └──────────┘ └──────────┘            │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐            │
│  │ 规则引擎 │ │ 消息服务 │ │ 调度服务 │            │
│  └──────────┘ └──────────┘ └──────────┘            │
└─────────────────────┬───────────────────────────────┘
                      │
┌─────────────────────▼───────────────────────────────┐
│                   数据层                             │
│     MySQL │ Redis │ RocketMQ │ Elasticsearch        │
└─────────────────────────────────────────────────────┘

2.2 核心服务拆分

订单服务(Order Service)

  • 订单CRUD
  • 订单状态机
  • 订单查询

库存服务(Inventory Service)

  • 可售库存计算
  • 库存预占/释放
  • 库存同步

履约服务(Fulfillment Service)

  • 订单拆分
  • 选仓选物流
  • 下发WMS/TMS

规则引擎(Rule Engine)

  • 审核规则
  • 拆单规则
  • 路由规则

2.3 技术选型

组件选型说明
开发语言Java 17LTS版本,稳定
框架Spring Boot 3.x主流框架
数据库MySQL 8.0主数据存储
缓存Redis 7.x库存缓存、分布式锁
消息队列RocketMQ异步解耦
搜索Elasticsearch订单搜索
任务调度XXL-Job定时任务

三、核心模块设计

3.1 订单接入模块

功能:从各渠道获取订单,转换为统一格式

架构

┌─────────────────────────────────────────────────────┐
│                  渠道适配器                          │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐            │
│  │ Amazon   │ │  eBay    │ │ Shopify  │            │
│  │ Adapter  │ │ Adapter  │ │ Adapter  │            │
│  └────┬─────┘ └────┬─────┘ └────┬─────┘            │
│       │            │            │                   │
│       └────────────┼────────────┘                   │
│                    ▼                                │
│            ┌──────────────┐                         │
│            │ 订单转换器   │                         │
│            │ (统一格式)   │                         │
│            └──────┬───────┘                         │
│                   ▼                                 │
│            ┌──────────────┐                         │
│            │  订单入库    │                         │
│            └──────────────┘                         │
└─────────────────────────────────────────────────────┘

统一订单模型

public class Order {
    private String orderId;           // 内部订单号
    private String channelOrderId;    // 渠道订单号
    private String channel;           // 渠道:AMAZON/EBAY/SHOPIFY
    private String shopId;            // 店铺ID

    private String buyerId;           // 买家ID
    private String buyerName;         // 买家姓名
    private Address shippingAddress;  // 收货地址

    private List<OrderItem> items;    // 订单明细
    private BigDecimal totalAmount;   // 订单金额
    private String currency;          // 币种

    private OrderStatus status;       // 订单状态
    private Date orderTime;           // 下单时间
    private Date payTime;             // 支付时间
}

适配器模式实现

public interface ChannelAdapter {
    // 拉取订单
    List<ChannelOrder> pullOrders(Date startTime, Date endTime);

    // 转换为统一格式
    Order convert(ChannelOrder channelOrder);

    // 回传状态
    void pushStatus(String channelOrderId, String status);
}

@Component
public class AmazonAdapter implements ChannelAdapter {
    @Override
    public List<ChannelOrder> pullOrders(Date startTime, Date endTime) {
        // 调用Amazon API拉取订单
    }

    @Override
    public Order convert(ChannelOrder channelOrder) {
        // Amazon订单格式转换为统一格式
    }
}

3.2 订单处理模块

订单状态机

┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│  待审核 │───>│  待拆分 │───>│  待下发 │───>│  已下发 │
│ PENDING │    │ APPROVED│    │ SPLIT   │    │ PUSHED  │
└────┬────┘    └─────────┘    └─────────┘    └────┬────┘
     │                                            │
     │ 审核不通过                                  │
     ▼                                            ▼
┌─────────┐                                  ┌─────────┐
│  已取消 │                                  │  已完成 │
│ CANCELED│                                  │ COMPLETED│
└─────────┘                                  └─────────┘

状态机实现

public enum OrderStatus {
    PENDING,    // 待审核
    APPROVED,   // 已审核
    SPLIT,      // 已拆分
    PUSHED,     // 已下发
    SHIPPED,    // 已发货
    DELIVERED,  // 已签收
    COMPLETED,  // 已完成
    CANCELED    // 已取消
}

@Service
public class OrderStateMachine {

    private static final Map<OrderStatus, Set<OrderStatus>> TRANSITIONS = Map.of(
        OrderStatus.PENDING, Set.of(OrderStatus.APPROVED, OrderStatus.CANCELED),
        OrderStatus.APPROVED, Set.of(OrderStatus.SPLIT, OrderStatus.CANCELED),
        OrderStatus.SPLIT, Set.of(OrderStatus.PUSHED, OrderStatus.CANCELED),
        OrderStatus.PUSHED, Set.of(OrderStatus.SHIPPED, OrderStatus.CANCELED),
        OrderStatus.SHIPPED, Set.of(OrderStatus.DELIVERED),
        OrderStatus.DELIVERED, Set.of(OrderStatus.COMPLETED)
    );

    public void transition(Order order, OrderStatus targetStatus) {
        OrderStatus currentStatus = order.getStatus();
        if (!TRANSITIONS.get(currentStatus).contains(targetStatus)) {
            throw new IllegalStateException(
                "Cannot transition from " + currentStatus + " to " + targetStatus);
        }
        order.setStatus(targetStatus);
        // 发布状态变更事件
        eventPublisher.publish(new OrderStatusChangedEvent(order));
    }
}

3.3 库存管理模块

库存模型

┌─────────────────────────────────────────────────────┐
│                    库存类型                          │
├─────────────────────────────────────────────────────┤
│  实物库存 = WMS系统中的实际库存                      │
│  可售库存 = 实物库存 - 预占库存 - 锁定库存           │
│  预占库存 = 订单已预占但未发货的库存                 │
│  锁定库存 = 因其他原因锁定的库存(如盘点)           │
│  在途库存 = 采购已下单但未入库的库存                 │
└─────────────────────────────────────────────────────┘

库存预占流程

下单 ──> 检查可售库存 ──> 预占库存 ──> 创建订单
              │
              │ 库存不足
              ▼
         订单挂起/取消

库存预占实现(防止超卖)

@Service
public class InventoryService {

    @Autowired
    private RedisTemplate<String, Integer> redisTemplate;

    /**
     * 预占库存(使用Redis + Lua脚本保证原子性)
     */
    public boolean reserveInventory(String skuId, String warehouseId, int quantity) {
        String key = "inventory:" + warehouseId + ":" + skuId;

        // Lua脚本:检查并扣减库存
        String script = """
            local current = tonumber(redis.call('get', KEYS[1]) or 0)
            if current >= tonumber(ARGV[1]) then
                redis.call('decrby', KEYS[1], ARGV[1])
                return 1
            else
                return 0
            end
            """;

        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            List.of(key),
            String.valueOf(quantity)
        );

        return result != null && result == 1;
    }

    /**
     * 释放库存(订单取消时)
     */
    public void releaseInventory(String skuId, String warehouseId, int quantity) {
        String key = "inventory:" + warehouseId + ":" + skuId;
        redisTemplate.opsForValue().increment(key, quantity);
    }
}

3.4 履约调度模块

选仓策略

public interface WarehouseSelector {
    String selectWarehouse(Order order, List<String> candidateWarehouses);
}

// 就近发货策略
@Component
public class NearestWarehouseSelector implements WarehouseSelector {
    @Override
    public String selectWarehouse(Order order, List<String> candidateWarehouses) {
        Address shippingAddress = order.getShippingAddress();
        return candidateWarehouses.stream()
            .min(Comparator.comparingDouble(wh ->
                calculateDistance(wh, shippingAddress)))
            .orElseThrow();
    }
}

// 成本最优策略
@Component
public class CostOptimalWarehouseSelector implements WarehouseSelector {
    @Override
    public String selectWarehouse(Order order, List<String> candidateWarehouses) {
        return candidateWarehouses.stream()
            .min(Comparator.comparingDouble(wh ->
                calculateShippingCost(wh, order)))
            .orElseThrow();
    }
}

订单拆分逻辑

@Service
public class OrderSplitService {

    /**
     * 订单拆分
     * 拆分场景:多仓拆单、预售拆单、超重拆单
     */
    public List<FulfillmentOrder> splitOrder(Order order) {
        List<FulfillmentOrder> result = new ArrayList<>();

        // 1. 按仓库分组
        Map<String, List<OrderItem>> warehouseItems =
            groupByWarehouse(order.getItems());

        // 2. 为每个仓库创建履约单
        for (Map.Entry<String, List<OrderItem>> entry : warehouseItems.entrySet()) {
            FulfillmentOrder fo = new FulfillmentOrder();
            fo.setOrderId(order.getOrderId());
            fo.setWarehouseId(entry.getKey());
            fo.setItems(entry.getValue());
            fo.setShippingAddress(order.getShippingAddress());

            // 3. 选择物流
            fo.setCarrier(selectCarrier(fo));

            result.add(fo);
        }

        return result;
    }
}

四、数据库设计

4.1 核心表结构

订单主表(t_order)

CREATE TABLE t_order (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id VARCHAR(32) NOT NULL COMMENT '订单号',
    channel_order_id VARCHAR(64) COMMENT '渠道订单号',
    channel VARCHAR(32) NOT NULL COMMENT '渠道',
    shop_id VARCHAR(32) NOT NULL COMMENT '店铺ID',

    buyer_id VARCHAR(64) COMMENT '买家ID',
    buyer_name VARCHAR(128) COMMENT '买家姓名',

    total_amount DECIMAL(12,2) NOT NULL COMMENT '订单金额',
    currency VARCHAR(8) NOT NULL COMMENT '币种',

    status VARCHAR(32) NOT NULL COMMENT '订单状态',
    order_time DATETIME NOT NULL COMMENT '下单时间',
    pay_time DATETIME COMMENT '支付时间',

    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    UNIQUE KEY uk_order_id (order_id),
    KEY idx_channel_order (channel, channel_order_id),
    KEY idx_status (status),
    KEY idx_order_time (order_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';

订单明细表(t_order_item)

CREATE TABLE t_order_item (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id VARCHAR(32) NOT NULL COMMENT '订单号',
    item_id VARCHAR(32) NOT NULL COMMENT '明细ID',

    sku_id VARCHAR(32) NOT NULL COMMENT 'SKU编码',
    sku_name VARCHAR(256) COMMENT 'SKU名称',
    quantity INT NOT NULL COMMENT '数量',
    unit_price DECIMAL(12,2) NOT NULL COMMENT '单价',

    warehouse_id VARCHAR(32) COMMENT '分配仓库',

    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,

    KEY idx_order_id (order_id),
    KEY idx_sku_id (sku_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';

收货地址表(t_order_address)

CREATE TABLE t_order_address (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id VARCHAR(32) NOT NULL COMMENT '订单号',

    receiver_name VARCHAR(128) NOT NULL COMMENT '收件人',
    phone VARCHAR(32) COMMENT '电话',
    country VARCHAR(64) COMMENT '国家',
    province VARCHAR(64) COMMENT '省/州',
    city VARCHAR(64) COMMENT '城市',
    district VARCHAR(64) COMMENT '区县',
    address VARCHAR(512) NOT NULL COMMENT '详细地址',
    postal_code VARCHAR(16) COMMENT '邮编',

    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,

    UNIQUE KEY uk_order_id (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收货地址表';

库存表(t_inventory)

CREATE TABLE t_inventory (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    sku_id VARCHAR(32) NOT NULL COMMENT 'SKU编码',
    warehouse_id VARCHAR(32) NOT NULL COMMENT '仓库ID',

    total_qty INT NOT NULL DEFAULT 0 COMMENT '总库存',
    available_qty INT NOT NULL DEFAULT 0 COMMENT '可售库存',
    reserved_qty INT NOT NULL DEFAULT 0 COMMENT '预占库存',
    locked_qty INT NOT NULL DEFAULT 0 COMMENT '锁定库存',

    version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本',
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    UNIQUE KEY uk_sku_warehouse (sku_id, warehouse_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存表';

4.2 索引设计原则

原则说明
查询优先根据查询场景设计索引
覆盖索引尽量让查询只走索引
最左前缀联合索引注意字段顺序
避免冗余不要创建重复索引

五、关键流程实现

5.1 订单创建流程

@Service
@Transactional
public class OrderCreateService {

    public Order createOrder(CreateOrderRequest request) {
        // 1. 参数校验
        validateRequest(request);

        // 2. 生成订单号
        String orderId = orderIdGenerator.generate();

        // 3. 库存预占
        for (OrderItemRequest item : request.getItems()) {
            boolean success = inventoryService.reserveInventory(
                item.getSkuId(),
                item.getWarehouseId(),
                item.getQuantity()
            );
            if (!success) {
                throw new InsufficientInventoryException(item.getSkuId());
            }
        }

        // 4. 创建订单
        Order order = buildOrder(orderId, request);
        orderRepository.save(order);

        // 5. 发布订单创建事件
        eventPublisher.publish(new OrderCreatedEvent(order));

        return order;
    }
}

5.2 订单履约流程

@Service
public class OrderFulfillmentService {

    public void fulfillOrder(String orderId) {
        Order order = orderRepository.findByOrderId(orderId);

        // 1. 订单拆分
        List<FulfillmentOrder> fulfillmentOrders = orderSplitService.splitOrder(order);

        // 2. 下发WMS
        for (FulfillmentOrder fo : fulfillmentOrders) {
            wmsClient.pushOutboundOrder(fo);
        }

        // 3. 更新订单状态
        orderStateMachine.transition(order, OrderStatus.PUSHED);
    }
}

六、性能优化

6.1 高并发场景优化

库存预占优化

  • 使用Redis缓存库存,减少数据库压力
  • 使用Lua脚本保证原子性
  • 库存分桶,减少热点

订单查询优化

  • 使用Elasticsearch做订单搜索
  • 读写分离,查询走从库
  • 合理使用缓存

6.2 数据库优化

分库分表策略

  • 按订单号hash分表
  • 按时间范围分库
  • 历史数据归档

七、总结

7.1 OMS核心要点

  1. 定位清晰:OMS是订单中台,连接前端渠道和后端履约
  2. 架构合理:接入层、服务层、数据层分离
  3. 核心模块:订单接入、订单处理、库存管理、履约调度
  4. 防止超卖:库存预占使用Redis+Lua保证原子性
  5. 状态机:订单状态流转要有严格的状态机控制

7.2 下一步

  • 完成OMS核心模块开发
  • 对接第一个销售渠道
  • 与WMS系统集成
  • 上线试运行

下一篇,我们将详细讲解《多渠道订单接入——Amazon、eBay、独立站统一处理》。


系列文章导航

本文是《跨境电商数字化转型指南》系列的第13篇

  • 01-09. 战略篇+架构篇+决策篇(9篇)
  • 10-12. ERP自研篇(3篇)
  • 13. OMS订单系统自研实战(本文)
  • 14. 多渠道订单接入
  • 15. 订单拆分与合并策略