引言:为什么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与其他系统的边界
| 功能 | OMS | WMS | TMS | ERP |
|---|---|---|---|---|
| 订单创建 | ✓ | |||
| 库存预占 | ✓ | |||
| 实物库存 | ✓ | |||
| 拣货发货 | ✓ | |||
| 物流跟踪 | ✓ | |||
| 财务核算 | ✓ |
二、架构设计
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 17 | LTS版本,稳定 |
| 框架 | 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核心要点
- 定位清晰:OMS是订单中台,连接前端渠道和后端履约
- 架构合理:接入层、服务层、数据层分离
- 核心模块:订单接入、订单处理、库存管理、履约调度
- 防止超卖:库存预占使用Redis+Lua保证原子性
- 状态机:订单状态流转要有严格的状态机控制
7.2 下一步
- 完成OMS核心模块开发
- 对接第一个销售渠道
- 与WMS系统集成
- 上线试运行
下一篇,我们将详细讲解《多渠道订单接入——Amazon、eBay、独立站统一处理》。
系列文章导航
本文是《跨境电商数字化转型指南》系列的第13篇
- 01-09. 战略篇+架构篇+决策篇(9篇)
- 10-12. ERP自研篇(3篇)
- 13. OMS订单系统自研实战(本文)
- 14. 多渠道订单接入
- 15. 订单拆分与合并策略