花拾录
← 返回知识库

用状态图梳理一个真实的审批流:从建模到代码落地的完整推演

软件工程 / 工具AI2026/09/280 阅读0 评论

为什么审批流适合用状态图来梳理

审批流是典型的"状态驱动"业务:一份申请从提交到最终归档,中间要经历多个环节,每个环节允许的操作不同,且存在驳回、撤回、加签等分支。如果只用 if/else 堆逻辑,代码很快会变成难以维护的面条。

状态图(State Diagram)能把"有哪些状态、什么事件触发迁移、迁移后做什么"一次说清,是审批流建模最自然的工具。

第一步:先定义状态,而不是先写代码

以一个常见的报销审批为例,先罗列状态:

  • DRAFT:草稿,仅创建人可见
  • SUBMITTED:已提交,等待直属主管审批
  • MANAGER_APPROVED:主管通过,等待财务审批
  • FINANCE_APPROVED:财务通过,等待打款
  • PAID:已打款(终态)
  • REJECTED:被驳回(终态)
  • CANCELLED:申请人撤回(终态)

要点:终态一旦到达就不再迁出,这能在代码层面直接杜绝"已打款的单子又被驳回"这类脏数据。

第二步:定义事件与迁移规则

状态图的核心是迁移表,用"当前状态 + 事件 → 目标状态"表示:

当前状态事件目标状态
DRAFTsubmitSUBMITTED
SUBMITTEDmanagerApproveMANAGER_APPROVED
SUBMITTEDmanagerRejectREJECTED
SUBMITTEDcancelCANCELLED
MANAGER_APPROVEDfinanceApproveFINANCE_APPROVED
MANAGER_APPROVEDfinanceRejectREJECTED
FINANCE_APPROVEDpayPAID

把这张表画成状态图,你会立刻发现几个问题:谁有权触发 managerApprove?如果主管和财务是同一个人怎么办?驳回后能不能重新提交?这些在写代码前就暴露出来,比上线后返工便宜得多。

第三步:为迁移补充守卫与动作

状态图的迁移边上可以挂两类东西:

  • 守卫(Guard):迁移的前置条件,如"只有当前审批人有权限"、"金额超过 5000 需财务总监二次确认"。
  • 动作(Action):迁移成功后执行,如发通知、写审计日志、调用打款接口。

建议把守卫和动作都写成独立函数,与状态机本身解耦,方便单测。

第四步:落地到代码

主流语言都有成熟的状态机库,不必手写。以 Java 的 Spring Statemachine、Python 的 transitions、JavaScript 的 xstate 为例,思路一致:

  1. 声明状态集合与初始状态
  2. 声明迁移(事件、源状态、目标状态、守卫、动作)
  3. 由状态机实例承载单条业务记录
  4. 每次操作前先 can(event) 判断,再 send(event) 执行

伪代码示意:

machine = StateMachine(
  states = [DRAFT, SUBMITTED, MANAGER_APPROVED, ...],
  initial = DRAFT,
  transitions = [
    (DRAFT, 'submit', SUBMITTED, guard=isOwner),
    (SUBMITTED, 'managerApprove', MANAGER_APPROVED, guard=isManager),
    ...
  ]
)

关键工程实践:把状态机的当前状态持久化到数据库字段(如 status 列),每次操作时先加载状态、再触发事件、最后在同一事务里写回状态并记录流转日志。这样既保证了数据一致性,也留下了可追溯的审计轨迹。

第五步:常见坑与应对

  • 状态爆炸:不要为每个审批人建一个状态,用"审批节点"字段区分,状态只表达阶段。
  • 并发操作:两人同时审批同一条记录,用乐观锁(版本号)或数据库行锁解决。
  • 历史回溯:单独建一张 approval_log 表,记录每次迁移的事件、操作人、时间、备注。
  • 规则变更:审批层级经常变,尽量把"谁审批"配置化,状态机只负责"能不能迁"。

小结

用状态图梳理审批流的价值在于:先把"有哪些状态、谁能触发什么、触发后发生什么"想清楚,再写代码。状态图是沟通工具,也是设计文档,最终还能直接映射到状态机库的配置上。下次遇到多环节、多分支的流程,不妨先画一张状态图,往往能省下大量调试时间。

评论(0)

  • 还没有评论,来抢沙发~

相关文章