做外卖配送开发系统,真正拉开差距的不是页面,而是订单状态流转是否严谨、结算逻辑是否清晰。
很多平台前端看着差不多,但一旦订单量上来,状态错乱、重复结算、骑手账目对不上,问题马上爆发。
今天我们从系统设计角度,把核心逻辑拆清楚,并附带关键代码示例。
一、订单状态设计:不要只写“已完成”
一个成熟的外卖配送开发系统,订单至少包含以下状态:
1. CREATED 已创建(待支付)
2. PAID 已支付(待接单)
3. ACCEPTED 商户已接单
4. PREPARING 制作中
5. WAITING_DELIVERY 待骑手接单
6. DELIVERING 配送中
7. COMPLETED 已完成
8. CANCELLED 已取消
9. REFUNDING 退款中
10. REFUNDED 已退款
状态流转图核心原则
- 状态必须单向推进(避免回滚)
- 每次变更必须记录日志
- 状态变更必须做幂等控制
- 所有变更必须在事务中完成
二、数据库结构设计
1️⃣ 订单主表
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
merchant_id BIGINT NOT NULL,
rider_id BIGINT,
total_amount DECIMAL(10,2),
pay_amount DECIMAL(10,2),
status VARCHAR(32),
created_at DATETIME,
updated_at DATETIME,
version INT DEFAULT 0
);
2️⃣ 订单状态日志表
CREATE TABLE order_status_logs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT,
from_status VARCHAR(32),
to_status VARCHAR(32),
operator_type VARCHAR(32),
created_at DATETIME
);
三、订单状态流转核心代码(Java 示例)
状态枚举
public enum OrderStatus {
CREATED,
PAID,
ACCEPTED,
PREPARING,
WAITING_DELIVERY,
DELIVERING,
COMPLETED,
CANCELLED,
REFUNDING,
REFUNDED
}
状态变更服务(核心逻辑)
@Transactional
public void changeStatus(Long orderId, OrderStatus newStatus) {
Order order = orderRepository.findById(orderId);
OrderStatus oldStatus = order.getStatus();
if (!canTransfer(oldStatus, newStatus)) {
throw new RuntimeException("非法状态流转");
}
order.setStatus(newStatus);
order.setVersion(order.getVersion() + 1);
orderRepository.update(order);
orderStatusLogRepository.save(
new OrderStatusLog(orderId, oldStatus, newStatus)
);
}
状态校验规则
private boolean canTransfer(OrderStatus from, OrderStatus to) {
Map<OrderStatus, List<OrderStatus>> flowMap = new HashMap<>();
flowMap.put(OrderStatus.CREATED, Arrays.asList(OrderStatus.PAID, OrderStatus.CANCELLED));
flowMap.put(OrderStatus.PAID, Arrays.asList(OrderStatus.ACCEPTED, OrderStatus.CANCELLED));
flowMap.put(OrderStatus.ACCEPTED, Arrays.asList(OrderStatus.PREPARING));
flowMap.put(OrderStatus.PREPARING, Arrays.asList(OrderStatus.WAITING_DELIVERY));
flowMap.put(OrderStatus.WAITING_DELIVERY, Arrays.asList(OrderStatus.DELIVERING));
flowMap.put(OrderStatus.DELIVERING, Arrays.asList(OrderStatus.COMPLETED));
return flowMap.getOrDefault(from, Collections.emptyList()).contains(to);
}
这一层是系统稳定的关键。
没有这层控制,订单一定乱。

四、结算逻辑设计:平台、商户、骑手如何分钱?
一个标准分账模型:
用户支付金额 = 商品金额 + 配送费
平台抽佣 = 商品金额 × 抽佣比例
骑手收入 = 配送费
商户收入 = 商品金额 - 平台抽佣
五、结算表结构设计
CREATE TABLE order_settlement (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT,
merchant_income DECIMAL(10,2),
rider_income DECIMAL(10,2),
platform_income DECIMAL(10,2),
settlement_status VARCHAR(32),
created_at DATETIME
);
六、订单完成后自动触发结算
监听订单完成事件
@EventListener
public void handleOrderCompleted(OrderCompletedEvent event) {
settle(event.getOrderId());
}
核心结算代码
@Transactional
public void settle(Long orderId) {
Order order = orderRepository.findById(orderId);
BigDecimal commissionRate = new BigDecimal("0.15");
BigDecimal platformIncome =
order.getTotalAmount().multiply(commissionRate);
BigDecimal merchantIncome =
order.getTotalAmount().subtract(platformIncome);
BigDecimal riderIncome =
order.getDeliveryFee();
OrderSettlement settlement = new OrderSettlement();
settlement.setOrderId(orderId);
settlement.setMerchantIncome(merchantIncome);
settlement.setPlatformIncome(platformIncome);
settlement.setRiderIncome(riderIncome);
settlement.setSettlementStatus("PENDING");
settlementRepository.save(settlement);
}
七、高并发场景必须注意的3个坑
1️⃣ 重复结算问题
解决方案:
- settlement表增加唯一索引
UNIQUE KEY uk_order_id (order_id)
- 代码中增加幂等校验
2️⃣ 并发更新问题
使用:
- 乐观锁 version 字段
- 或 select for update
3️⃣ 分布式事务问题
建议:
- 订单系统
- 支付系统
- 结算系统
通过消息队列解耦,避免强依赖。
八、为什么订单流转和结算逻辑决定平台生死?
外卖配送开发系统真正的核心不是:
- 页面好不好看
- 功能多不多
而是:
- 状态是否严谨
- 数据是否可追溯
- 账目是否清晰
- 分账是否可扩展
如果你的系统不能清楚回答:
“这笔钱从哪来,分给谁,什么时候分,为什么这么分?”
那平台一定走不远。
结语
外卖配送开发系统本质是:
订单驱动型 + 资金驱动型平台。
前端只是表象,
真正的壁垒在于:
- 状态机设计能力
- 事务控制能力
- 分账与风控能力
如果你在做本地生活或同城配送项目,建议优先把这两块打牢。
系统稳定,商业模式才有意义。