3. 审批流领域建模:典型审批流状态定义
审批流是业务系统中最高频、最核心的状态机场景之一。一个健壮的审批流状态模型,不仅要覆盖正常流转路径,还必须为异常恢复(如节点超时、审批人离职、系统宕机)预留明确的语义锚点。本节定义企业级审批流的六种标准状态,并给出状态间的合法跃迁规则。
3.1 六种核心状态定义
以下六种状态覆盖了从单据创建到最终归档的完整生命周期,每种状态都对应明确的业务含义和系统行为约束:
| 状态名称 | 状态码 | 业务含义 | 系统行为约束 |
|---|---|---|---|
| 待提交 | DRAFT |
单据已创建,但尚未正式发起审批流程 | 允许编辑、删除;不触发任何通知或超时计时器 |
| 审批中 | PENDING |
单据已提交,正在等待当前审批节点处理 | 只读(除审批人外);启动超时监控;记录操作日志 |
| 已通过 | APPROVED |
所有审批节点均已完成且结果为通过 | 触发后置动作(如数据落库、通知下游);不可撤回 |
| 已驳回 | REJECTED |
当前审批节点或终审节点做出驳回决定 | 根据策略可退回至指定节点或直接终结流程 |
| 已撤回 | WITHDRAWN |
提交人在审批过程中主动撤销申请 | 仅允许在“审批中”且尚未被处理时执行;撤回后回到“待提交” |
| 已终止 | TERMINATED |
因异常(超时、审批人不可达、系统错误)强制结束流程 | 需记录终止原因;支持人工干预恢复或归档 |
领域建模要点: 在状态机设计中,“已终止”与“已驳回”的关键区别在于:驳回是业务决策,终止是系统级异常保护。终止状态必须携带
terminate_reason 和 terminate_timestamp 字段,用于后续的异常恢复审计。
3.2 状态跃迁图
下图展示了六种状态之间的合法跃迁路径。箭头方向表示状态转移方向,虚线表示仅在特定条件下允许的跃迁:
3.3 状态跃迁规则矩阵
为便于状态机编码实现,将跃迁规则整理为决策矩阵。行表示当前状态,列表示目标状态,✓ 表示合法跃迁,✗ 表示非法跃迁:
| 当前状态 \ 目标状态 | 待提交 | 审批中 | 已通过 | 已驳回 | 已撤回 | 已终止 |
|---|---|---|---|---|---|---|
| 待提交 | — | ✓ | ✗ | ✗ | ✗ | ✗ |
| 审批中 | ✗ | — | ✓ | ✓ | ✓ | ✓ |
| 已通过 | ✗ | ✗ | — | ✗ | ✗ | ✗ |
| 已驳回 | ✓ | ✗ | ✗ | — | ✗ | ✓ |
| 已撤回 | ✓ | ✗ | ✗ | ✗ | — | ✗ |
| 已终止 | ✗ | ✗ | ✗ | ✗ | ✗ | — |
实现提示: 在代码中,建议将跃迁矩阵定义为
Map<State, Set<State>> 或二维布尔数组。每次状态变更前先查表,若跃迁非法则抛出 IllegalTransitionException,避免状态机进入未定义分支。
3.4 异常恢复视角的状态分类
从异常恢复策略的角度,六种状态可归为三类,每类对应不同的恢复处理模式:
- 可恢复状态(Recoverable): 待提交、审批中。系统故障后,可通过重试、补偿或人工干预恢复到正常流转。
- 业务终态(Business Final): 已通过、已驳回。流程已产生明确业务结论,通常不需要恢复,但需保证幂等性。
- 异常终态(Abnormal Final): 已撤回、已终止。流程非正常结束,需记录详细原因,并支持管理员手动重置或归档。
3.5 状态机代码骨架(Java 枚举实现)
以下代码片段展示了如何用枚举 + 跃迁矩阵实现审批流状态机。该设计可直接嵌入 Spring StateMachine 或自研状态机引擎:
public enum ApprovalState {
DRAFT,
PENDING,
APPROVED,
REJECTED,
WITHDRAWN,
TERMINATED;
private static final Map<ApprovalState, Set<ApprovalState>> TRANSITIONS = new HashMap<>();
static {
TRANSITIONS.put(DRAFT, Set.of(PENDING));
TRANSITIONS.put(PENDING, Set.of(APPROVED, REJECTED, WITHDRAWN, TERMINATED));
TRANSITIONS.put(APPROVED, Collections.emptySet());
TRANSITIONS.put(REJECTED, Set.of(DRAFT, TERMINATED));
TRANSITIONS.put(WITHDRAWN, Set.of(DRAFT));
TRANSITIONS.put(TERMINATED, Collections.emptySet());
}
public ApprovalState transition(ApprovalState target) {
if (!TRANSITIONS.getOrDefault(this, Collections.emptySet()).contains(target)) {
throw new IllegalStateException(
String.format("非法状态跃迁: %s -> %s", this, target));
}
return target;
}
public boolean isRecoverable() {
return this == DRAFT || this == PENDING;
}
public boolean isBusinessFinal() {
return this == APPROVED || this == REJECTED;
}
public boolean isAbnormalFinal() {
return this == WITHDRAWN || this == TERMINATED;
}
}
避坑指南: 不要将“已驳回”直接视为终态。在实际业务中,驳回后通常允许修改后重新提交(回到待提交),因此跃迁矩阵中必须保留
REJECTED → DRAFT 的路径。如果业务不允许重新提交,则移除该跃迁即可,但不要混淆“驳回”与“终止”的语义。