代驾系统订单状态机设计:从状态枚举到事件驱动的完整落地

4 阅读4分钟

为什么要单独设计订单状态机

很多同学写订单模块,第一反应是用一个 Integer 字段存状态,然后在业务代码里写一堆 if-else 判断“当前状态能不能执行这个操作”。订单少时没问题,一旦状态变多、角色变多(用户、司机、系统定时任务都能改状态),这种写法很快会失控:非法状态跳转难以排查,新加一个状态要改动十几处。

本文分享一套开源代驾系统的订单状态机设计。

源码地址: gitee.com/zhoujian666…

代驾订单有哪些状态

代驾业务的完整生命周期:

  • 待接单:用户已下单,等待附近司机抢单
  • 已接单:司机抢单成功,正前往用户位置
  • 司机已到达:司机抵达上车点
  • 行程中:用户上车,开始计费
  • 待支付:行程结束,等待用户付款
  • 已支付:支付成功(若有垫付/余额支付可能直接流转)
  • 待评价:等待用户评价
  • 已完成:订单闭环
  • 已取消:下单后未接单可取消;接单后取消涉及违约金判断

核心设计:状态 + 事件 + 转移

状态机三要素:

  1. State(状态):枚举,列出所有合法状态
  2. Event(事件):触发状态变更的动作,如“司机接单”“到达上车点”“支付成功”“用户取消”
  3. Transition(转移):定义“当前状态 + 事件 → 目标状态”的合法映射

关键思想是:把“能不能跳转”的规则集中声明,而不是散落在业务代码里。

落地方式

1. 状态枚举集中管理

用枚举定义状态码、状态描述,避免魔法数字。订单实体里的状态字段只允许取枚举值。

2. 事件与转移表

用一张转移映射表声明规则,例如:

  • 待接单 + 接单事件 → 已接单
  • 已接单 + 到达事件 → 司机已到达
  • 司机已到达 + 开始行程事件 → 行程中
  • 行程中 + 结束行程事件 → 待支付
  • 待支付 + 支付成功事件 → 待评价
  • 任意未完成态 + 取消事件 → 已取消(带条件校验)

3. 统一的状态变更入口

所有状态修改走同一个方法:传入订单、事件,方法内部查转移表,找不到合法映射就抛异常,找到则更新状态并记录变更日志。

这样非法跳转在入口就被拦截,不会污染数据。

事件驱动:状态变更后要做什么

状态变更本身和“变更后的副作用”要解耦。例如订单变为“已接单”后要:

  • 推送通知给用户
  • 通知其他抢单司机该单已被接
  • 给接单侧写一条数据

这些副作用不写在状态机里,而是状态变更成功后发布领域事件,由各自的监听者处理(推送走 Netty,异步任务走 RabbitMQ)。状态机保持纯粹,只负责“状态对不对”。

取消场景的特殊处理

取消是最复杂的事件:

  • 待接单时取消:直接关闭,无费用
  • 已接单后取消:根据司机是否已到达、等待时长判断是否收空驶费/违约金
  • 行程中一般不允许取消,需走异常订单流程

这类带业务条件的判断放在事件处理层,状态机只接收“已经算好的、合法的取消结果”。

状态流转的可观测性

每次状态变更写一条流转日志(订单 ID、原状态、新状态、触发事件、操作人、时间)。线上排查“订单怎么变成这个状态了”时,这张表是最重要的依据,也方便后台给客服展示订单时间轴。

项目其他模块

除订单状态机外,系统还包含:智能派单、Netty WebSocket 实时推送、计价引擎、Redis 在线司机管理、微信/支付宝支付、分润对账。后端 + 管理后台已开源(Java 17 + Spring Boot 2.7 + Vue),可直接部署。

源码:gitee.com/zhoujian666…

欢迎评论区交流状态机设计,觉得有用欢迎 Star。