PlantUML 状态图实战:订单状态机的 4 种写法

puml.online

状态图是 PlantUML 里被低估的一类图:能精确描绘复杂对象(订单、登录会话、提交流程)的所有可能状态、转换条件和进入动作。

状态图能解决什么问题

业务里的订单生命周期经常这样:

  • 待付款 → 已付款 → 发货中 → 已签收 → 已完成
  • 已付款 → 申请退款 → 已退款
  • 待付款 → 取消
  • 已签收 → 申请售后 → 售后处理中 → 售后完成

代码实现时这些都是 if/else/switch 的海洋。状态图先把业务对象的生命周期画清楚,代码照着搬实现就行。

1. 最基本的状态图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@startuml
title 订单基本生命周期

[*] --> 待付款 : 下单
待付款 --> 已取消 : 用户取消
待付款 --> 已超时 : 24h 未支付
待付款 --> 已付款 : 支付成功

已付款 --> 待发货 : 系统确认
待发货 --> 已发货 : 仓库出库
已发货 --> 已签收 : 用户确认收货

已签收 --> 已完成 : 7 天后
已签收 --> 售后中 : 申请售后
已完成 --> [*]
已取消 --> [*]
已超时 --> [*]
已退款 --> [*]

售后中 --> 已退款 : 同意退款
售后中 --> 售后完成 : 仅退款 / 仅换货
售后完成 --> [*]
已退款 --> [*]
@enduml

要点:

  • [*] 表示起点和终点
  • 状态A --> 状态B : 触发事件 是从 A 到 B 的转换,标签是触发事件
  • 渲染出来圆角矩形 + 黑色箭头 + 灰底

这种基本图覆盖了 80% 的业务对象状态。

2. 加守卫条件

不是所有转换都能发生。在 --> : 后面可以接 [] 标记守卫:

1
2
3
4
5
6
7
8
9
10
@startuml
title 订单加守卫

[*] --> 待付款
待付款 --> 已付款 : 用户付款 [支付成功]
待付款 --> 已超时 : 24h 未支付 [余额 / 验签通过]
待付款 --> 已取消 : 用户取消 [订单未超 24h]

note right of 待付款 : 超过 24h 的订单只能由定时任务手动变更
@enduml

[条件] 是 UML 标准的守卫语法。PlantUML 把多个守卫放一行写也可:

1
待付款 --> 已取消 : 用户取消 [库存未扣减]

3. 加进入动作和活动

进入某个状态时要做什么(比如发邮件、扣库存):

1
2
3
4
5
6
7
8
9
10
11
@startuml
title 订单进入动作

[*] --> 待付款
待付款 --> 已付款 : 支付成功 / 扣减库存
已付款 --> 待发货 : 队列入仓 / 等待扫描
已发货 --> 已签收 : 用户签收 / 触发积分发放

note right of 已付款 : 内部要写日志 + 通知
note left of 待发货 : 仓库流水线消费拣货单
@enduml

语法:源状态 --> 目标状态 : 触发事件 / 进入目标时执行

注意斜杠前后空格。动作可以是「给用户发短信」「调库存接口」之类的业务动作描述,不需要真正写代码。

4. 复合状态 / 子状态

「发货」是个流程,不只是一个动作。你需要嵌套 state:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@startuml
title 订单 - 复合状态

[*] --> 待付款
待付款 --> 已付款

已付款 --> 发货流程中

state 发货流程中 {
[*] --> 待拣货
待拣货 --> 拣货中 : 仓库任务开始
拣货中 --> 已打包 : 装箱完成
已打包 --> 待出库 : 等待物流揽收
待出库 --> 已发货 : 揽件成功
}

已发货 --> 已签收
已签收 --> [*]
@enduml

state X { ... } 表示 X 这个状态内部还有子状态。PlantUML 会画成大圆角框里嵌小圆角框。

5. 历史标记(H)和深历史(H*)

子状态机返回时是回到入口,还是回到「上次停留的状态」?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@startuml

state 发货流程中 {
[*] --> 待拣货
待拣货 --> 拣货中
拣货中 --> 已打包
已打包 --> 待出库
待出库 --> 已发货

待出库 --> 待拣货 : 退回 [库存不足]
}

note right of 发货流程中 : 使用 H 表示历史入口
@enduml

不过 PlantUML 对 H / H* 支持有限,复杂回退要靠自己实现:退出子状态时记录当前状态、再次进入时查记录恢复到原状态。

6. 并发区域(fork / join)

同一状态下多件事并行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@startuml
title 订单 - 并发处理

[*] --> 订单创建
订单创建 --> 待处理
待处理 --> 支付确认

state 订单履行中 {
支付确认 --> fork点
fork点 --> 库存扣减
fork点 --> 风控审核

库存扣减 --> join点
风控审核 --> join点
join点 --> 已成交
}

已成交 --> [*]
@enduml

--> 默认箭头本身就能表达顺序,要画并发用 fork / join 这两个关键字:

1
2
state fork点 <<fork>>
state join点 <<join>>

但 PlantUML 里更简单的方式是用同一个状态进入两条转换:

1
2
待支付 --> 风控审核 : 并行 -1
待支付 --> 库存扣减 : 并行 -2

不标 fork/join 也能让评审理解。

7. 时间触发的状态机

PlantUML 状态图对时间触发支持有限(不像 UML 标准里能写 after 24h)。常见折中:

1
2
3
4
5
6
@startuml
title 订单 + 时间触发

[*] --> 待付款
待付款 --> 已超时 : <&hourglass> 24h [定时任务]
@enduml

<&hourglass> 这种图标表示「由外部定时器触发」。

实践中代码端:

1
2
// 状态图描述的是「应该发生什么」
// 代码端定时任务负责实现「时间到触发」

实战例子:登录会话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@startuml
title 登录会话状态机

[*] --> 匿名
匿名 --> 已登录 : 登录成功 / 颁发 token
已登录 --> 匿名 : 退出 / 销毁 token

匿名 --> 待刷新 : 携带过期 token [请求]
待刷新 --> 已登录 : 刷新成功
待刷新 --> 匿名 : 刷新失败 / 要求重新登录

已登录 --> 锁定 : 失败 5 次 / 重置计数
锁定 --> 匿名 : <&hourglass> 30 分钟 / 解锁

note right of 锁定 : 此状态的请求一律 423
@enduml

这套状态图让代码一眼看懂:

1
2
3
4
5
6
7
// 状态机驱动实现
switch (session.state) {
case '匿名': /* 任意操作前需要登录 */ break;
case '已登录': /* 检查 token 有效期 */ break;
case '待刷新': /* 尝试刷新 token */ break;
case '锁定': /* 一律拒绝 */ break;
}

评审 checklist

  • 所有状态都用 [*] 起点和终点表示吗?
  • 转换条件(条件 方括号)写在 : 后面?
  • 业务动作(/ 动作)放在哪里,需要被实现吗?
  • 复合状态的子状态图能独立读懂吗?
  • 时间触发的转换有明确 cron / 定时器文档吗?
  • 状态图能覆盖所有合法路径和异常路径吗?

实战组合

把状态图配合活动图一起用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@startuml
title 订单 - 状态图 + 转换活动

[*] --> 待付款
待付款 --> 已付款 : 用户付款
待付款 --> 已取消 : 取消订单

state 支付中 {
[*] --> 提交支付
提交支付 --> 风控
风控 --> 银行
银行 --> 风控 : 同步扣款
风控 --> [*]
}

note right of 已付款 : 转换时调 notification 中心和库存接口
@enduml

踩坑清单

  • --> : 后面的标签不能太长待付款 --> 已超时 : 用户点击取消且支付网关回调失败且订单未确认 —— 这种长描述截断,建议 note 拆开写。
  • [*] 不能作转换的源:要从 [*] 进入必须显式写 [*] --> X : event
  • 复合状态里再写 [*] -->:嵌套子状态机的入口是父状态的入口,不要到处都 [*] -->
  • 守卫条件不写 []:PlantUML 没方括号就把 条件 当成转换标签了,整体错乱。
  • 时间触发不灵after 24h 之类 PlantUML 不识别,要么用图标表示定时任务触发,要么转移到代码端定时任务里。

对比 UML 标准

PlantUML 状态图支持 UML State Machine 大部分核心功能:

特性 UML 标准 PlantUML
简单状态
起始 / 终止
转换(trigger)
守卫(guard)
进入 / 退出动作 ✅ (entry / exit)
复合状态
历史(H / H*) ⚠️ 部分支持
深历史 ⚠️ 部分支持
同步(fork / join)
时间触发 after ❌(用图标或外部定时器)

PlantUML 写的状态图能直接给 UML 工具(EA、StarUML)解析。

一句话总结

状态图是 PlantUML 里被严重低估的一类图。在写代码前用状态图先过一遍业务流程,状态机的逻辑就自然落地到了代码里 —— 不再写一堆散乱的 if/else。

  • 标题: PlantUML 状态图实战:订单状态机的 4 种写法
  • 作者: puml.online
  • 创建于 : 2026-07-29 15:00:00
  • 更新于 : 2026-08-14 21:34:29
  • 链接: https://puml.online/blog/plantuml-state-diagram/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。