首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >业务表单与审批工作流引擎设计:表单Schema、条件分支与状态流转

业务表单与审批工作流引擎设计:表单Schema、条件分支与状态流转

原创
作者头像
用户5598620
修改2026-09-10 11:15:34
修改2026-09-10 11:15:34
220
举报

业务表单与审批工作流引擎设计:表单Schema、条件分支与状态流转

业务系统里最容易被低估的复杂度,不是表单本身,而是表单背后那条"谁来填、填完给谁、什么条件走哪条路"的流程。请假、报销、采购申请、合同用印,表面都是一张表,硬编码写起来很快,可一旦业务方提出"金额超过一万要多加一级审批""A 部门走这个流程、B 部门走另一个",代码就会迅速膨胀成一堆 if-else 嵌套,改一处怕动全身。本文拆解一套可配置的表单与审批工作流引擎的核心设计:表单 Schema 化、流程的条件分支、审批实例的状态机,并给出落地时最容易踩的坑。

一、先分清三件事:表单、流程定义、流程实例

很多系统把这三者揉在一张业务表里,结果就是改不动。清晰的引擎会把它们拆开:

  • 表单(Form):定义"填什么",即字段集合、类型、校验规则、字段间联动;
  • 流程定义(Process Definition):定义"怎么走",即有哪些节点、节点之间按什么条件跳转,是一份可以版本化的配置;
  • 流程实例(Process Instance):一次具体的申请,它引用某个版本的流程定义,并持有自己当前走到哪个节点、每个节点的审批结果。

拆开之后,"改流程"只是新增一个流程定义版本,历史实例仍然引用它发起时的旧版本,不会因为流程改了而错乱——这是工作流引擎能长期维护的前提。

二、表单 Schema 化:把字段描述变成数据

不要为每种申请表单写一套页面和校验。用一份 JSON 描述字段,前端用渲染器统一生成,后端用同一份 Schema 做服务端校验(前端校验永远不可信)。

代码语言:json
复制
{
  "formId": "purchase_apply",
  "version": 3,
  "fields": [
    {"key": "title", "label": "事由", "type": "text", "required": true, "maxLength": 50},
    {"key": "amount", "label": "金额(元)", "type": "number", "required": true, "min": 0.01},
    {"key": "department", "label": "申请部门", "type": "select", "options": ["研发", "市场", "行政"]},
    {"key": "supplier", "label": "供应商", "type": "text", "visibleWhen": "amount > 5000"}
  ]
}

几个关键设计点:

  1. 字段联动用声明式表达式,如上例 visibleWhen,而不是在页面里写监听函数,否则联动关系散落各处、无法回溯;
  2. 校验规则前后端共用同一份 Schema,服务端必须独立再校验一遍;
  3. Schema 要带版本号,已经提交的实例按当时版本回显,避免字段增删后老单据打开报错。

三、流程定义:节点、边与条件分支

流程的本质是一张有向图:节点(Node)表示一个处理环节,边(Edge)表示流转方向,边上可以挂条件。常见节点类型有发起节点、审批节点、抄送节点、条件网关、结束节点。

代码语言:json
复制
{
  "nodes": [
    {"id": "start", "type": "start"},
    {"id": "manager", "type": "approval", "assignee": "applicant.leader"},
    {"id": "gateway", "type": "condition"},
    {"id": "director", "type": "approval", "assignee": "role:director"},
    {"id": "finance", "type": "approval", "assignee": "role:finance"},
    {"id": "end", "type": "end"}
  ],
  "edges": [
    {"from": "start", "to": "manager"},
    {"from": "manager", "to": "gateway"},
    {"from": "gateway", "to": "director", "when": "form.amount > 10000"},
    {"from": "gateway", "to": "finance", "when": "form.amount <= 10000"},
    {"from": "director", "to": "finance"},
    {"from": "finance", "to": "end"}
  ]
}

条件网关的求值要在服务端完成,表达式只允许访问白名单上下文(表单数据、申请人属性),不要直接 eval 用户输入,避免注入。审批人不要写死成某个人,而是用"申请人直属上级""某角色"这类解析规则,在节点流转时动态解析,这样人员调岗后流程不用改。

四、流程实例:用状态机约束流转

一个审批实例在任意时刻只能处于确定的状态,且状态之间的迁移必须合法。用状态机显式约束,比在每个接口里判断要可靠得多。

代码语言:javascript
复制
const TRANSITIONS = {
  draft:     { submit: 'pending' },
  pending:   { approve: 'approving', reject: 'rejected', withdraw: 'withdrawn' },
  approving:{ pass: 'approved', reject: 'rejected', returnBack: 'pending' },
  approved:  {},
  rejected:  { resubmit: 'pending' },
  withdrawn: { resubmit: 'pending' }
};

function move(current, action) {
  const next = TRANSITIONS[current]?.[action];
  if (!next) throw new Error(`非法流转:${current} 不允许 ${action}`);
  return next;
}

状态机带来三个好处:一是杜绝并发覆盖,两个人同时点同意,第二个请求会因为状态已经变化而命中"非法流转";二是每个动作幂等,重复提交不会产生两条审批记录;三是全程可追溯,状态迁移和审批意见一起落审计表,任何一单都能还原完整路径。

五、踩坑清单

  • 只存当前状态、不存流转记录:后期排查"这单到底卡在哪一步"时无从下手,务必单独建审批流水表;
  • 审批人在发起时固化:直属上级发起后调岗,后续节点仍应按规则动态解析,还是锁定发起人?这要在需求阶段定清楚并写进文档;
  • 条件分支只考虑两条:现实里常有"金额分三档",网关要支持多出口加一条默认分支,避免条件都不满足时流程悬空;
  • 退回逻辑含糊:退回到发起人修改后重新提交,是回到第一个节点还是退回前的节点?两种语义都常见,必须明确;
  • 流程定义改了影响在途单:一定要版本化,在途实例绑定旧版本;
  • 抄送和审批混为一谈:抄送不产生待办、不阻塞流转,别让它卡住流程。

六、工程落地建议

自研一套工作流引擎的成本主要在边界情况:并签会签、加签转办、超时催办、委托代理,这些往往做到第二三期才暴露。如果团队的核心诉求是"把高频审批和表单快速线上化、流程可配置",可以引入开源工作流引擎做二次开发,也可以在已有内部框架上抽象出流程配置层,把精力留给真正差异化的核心业务,而不是在审批流的边界情况上重复投入。无论哪条路径,上面的"表单/定义/实例三层分离 + 状态机约束 + 全程留痕"都是通用骨架。

七、上线前复盘清单

  1. 表单 Schema 是否前后端共用、服务端是否独立校验;
  2. 流程定义是否带版本、在途单是否锁定旧版本;
  3. 审批人是否用规则动态解析而非写死;
  4. 状态机是否覆盖同意/驳回/退回/撤回/重新提交全部分支;
  5. 并发点击、重复提交是否被状态机拦截;
  6. 每个节点的进入、转出、审批意见是否都落了审计流水;
  7. 条件网关是否有默认分支,杜绝流程悬空。

结语

表单和审批看起来是业务系统里最"没技术含量"的部分,但真正决定它好不好维护的,是有没有在一开始就把表单、流程定义、流程实例分清楚,并用状态机把流转约束住。把易变的业务规则变成可版本化的配置,把不变的流转约束沉淀成引擎骨架,后续业务方再怎么调整审批路径,系统都能从容应对。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 业务表单与审批工作流引擎设计:表单Schema、条件分支与状态流转
    • 一、先分清三件事:表单、流程定义、流程实例
    • 二、表单 Schema 化:把字段描述变成数据
    • 三、流程定义:节点、边与条件分支
    • 四、流程实例:用状态机约束流转
    • 五、踩坑清单
    • 六、工程落地建议
    • 七、上线前复盘清单
    • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档