返回文章列表

企业审批流引擎:从节点设计到回退机制

企业 OA 的审批流程表面上看只是“下一步”和“退回”,但真正落到系统里,还需要把流程定义、实例、任务和历史记录拆开建模。本文记录审批流引擎里几个容易返工的设计点。

流程建模

流程不能只存一张“当前走到哪”的表。实际开发中建议至少拆分流程定义、流程实例、节点实例、任务和审批记录,分别保存“模板”和“运行状态”。

节点类型需要覆盖开始、审批、抄送、条件、结束。节点之间用边连接,边负责描述下一步是谁。

{
  "definition": {
    "nodes": [
      {"id": "start", "type": "start"},
      {"id": "manager_approve", "type": "approve"},
      {"id": "finance_approve", "type": "approve"},
      {"id": "end", "type": "end"}
    ],
    "edges": [
      {"from": "start", "to": "manager_approve"},
      {"from": "manager_approve", "to": "finance_approve"},
      {"from": "finance_approve", "to": "end"}
    ]
  }
}

审批任务

审批人的来源不只有“指定人”。实际项目里常见的是指定人、角色、部门负责人、表单字段指定的用户,甚至需要根据提交人自动推导。最好把审批人来源做成一个可解析的表达式,而不是在代码里写死。

任务本身需要区分会签、或签和依次审批:

  • 会签:所有审批人都同意,节点才通过。
  • 或签:任意一个审批人同意,节点即通过。
  • 依次审批:按顺序处理,前一个完成才生成后一个任务。

条件分支

条件分支的难点不在画图,而在条件表达式的校验。给用户配置条件时,不要直接执行拼接出来的代码,应该使用字段白名单和固定操作符,避免配置错误影响整个流程实例。

{
  "condition": {
    "field": "amount",
    "operator": ">",
    "value": 10000
  }
}

回退机制

回退是审批流里最容易出问题的地方。退回不是简单地把当前节点改回“待审批”,而是要生成新的任务,同时保留上一轮任务的处理记录。

需要注意三个边界:

  • 退回到指定节点时,目标节点必须位于当前实例已走过的路径内,不能退回一个不存在的未来节点。
  • 重新提交后,历史记录不能被覆盖,新的提交要生成新版本任务。
  • 连续退回需要限制范围,避免流程实例在几个节点之间反复循环。

幂等与留痕

审批动作通常由前端按钮触发,网络重试可能导致同一操作执行多次。每个审批动作都应该有唯一的业务幂等键,重复请求只生效一次。

每次操作都要写入审批记录,包含操作人、操作时间、处理结果和意见。这些记录既用于审计,也用于回退时还原上下文。

审批流真正复杂的地方不是拖拽画布,而是状态迁移、回退边界和异常恢复。先把这些边界定清楚,后面的功能才不会越加越乱。