大型后台管理系统如何把业务操作做可靠(13):低代码表单与审批流程的执行边界
运营在低代码平台给退款表单增加了「退货凭证」,还把金额超过 500 元的审批改成两级。发布后,昨天已经进入审批的一笔退款仍然需要展示当时的输入、找到正确审批人,并按约定继续处理。
表单配置和流程定义分别版本化
表单描述字段、校验和展示。审批定义描述状态、节点、分支、候选人和允许的动作。二者可以引用彼此的版本,各自保留发布历史。实例创建时固定表单与流程版本,并保存关键输入及用于审批的依据。下方是示例实例的最小关联。
{
"caseId": "A42",
"formVersion": "refund-form-v3",
"workflowVersion": "refund-approval-v5",
"resourceId": "R17",
"taskId": "T2",
"taskVersion": 4,
"state": "awaiting_finance"
}
低代码表单的条件可用受限的声明式表达式。把字段、函数、类型和执行成本限制在约定范围,发布前检查缺失字段、循环依赖、无效跳转及越权连接器。浏览器负责即时提示,服务端依据已发布定义验证提交。客户扩展代码要有独立授权和隔离,配置提供者不自动获得执行任意代码或访问全库的能力。Formily 对复杂表单的分析
每次审批针对一个有版本的待办
接收审批时,服务端读取任务并检查当前候选人或受托人、任务版本和流程状态。完成任务、写入决定和创建下一节点应位于同一可靠边界。重复点击只得到已有决定。两个审批人并行操作时,流程应区分任一人同意、所有人同意或按比例同意,并记录拒绝、转交和超时如何改变后续节点。
退款的资金写入由交易服务完成。审批成功可以可靠投递执行命令,退款本身还有独立状态。如果支付渠道超时,审批页可以显示「已批准,退款结果待确认」,并链接到退款结果查询。
用一笔退款推演流程节点
假设 620 元退款需要门店主管和财务先后审批。提交时保存退款金额、订单和证据引用,创建主管待办。主管同意后,记录决定并完成当前待办,再创建财务待办。财务同意后,流程可以进入已批准,交易任务进入已受理。两者通过关联 ID 串起来。页面在退款待确认时仍展示审批已经完成。
| 当前任务 | 收到的动作 | 状态变化 |
|---|---|---|
| 主管审批 | 同意 | 完成主管待办,生成财务待办 |
| 主管审批 | 驳回 | 保存意见,结束本次审批或退回草稿 |
| 财务审批 | 同意 | 保存最终决定,产生退款执行任务 |
| 已完成任务 | 重复提交同一决定 | 返回已有决定 |
| 已转交任务 | 旧处理人提交 | 重新检查候选人和任务版本 |
在同一事务边界验证待办的主体与版本,然后写入决定、任务结果和下一节点或待投递记录。如果两个财务打开的是同一任务,只有符合版本的决定能完成任务。审批意见和关联证据也属于这次决定,后续有变更要保留变更记录。
让低代码规则在发布前可以解释
以「超过 500 元需要财务审批」为例,规则表达式应声明依赖的金额字段和单位。发布前用 499.99、500 和 500.01 元验证三种结果。字段在新表单中改名时,流程编译或校验应能指出缺少输入。条件不可计算时按事先约定拒绝提交或进入人工复核,不应默认跳过审批节点。
迁移在途实例时先挑选少量样例。已经完成主管审批、尚未生成财务待办的实例,和财务待办已有人领取的实例需要分别判断。若新定义改变了审批依据,要让业务方明确是否接受原决定。迁移清单保存原节点、目标节点、数据转换、失败原因和恢复方式。验证完成后再扩大范围。
发布前演练在途实例
新版本通常先用于新实例。在途迁移需要说明旧节点如何映射到新节点、已做的决定是否仍然有效、字段能否无损转换。遇到无法映射的等待节点,可继续运行旧版本,或按业务规则撤回后重走审批。发布前用一组实例预演影响,检查旧任务如何完成和恢复。流程引擎的迁移文档也把活动节点映射作为需要明确处理的步骤。Camunda 流程实例迁移
设计编辑器、发布器和执行器的职责
配置编辑器允许负责人保存未完成草稿。发布器接收一组明确的表单与流程版本,校验字段、节点、权限和外部引用,产生不可变的发布版本。执行器只读取已发布定义和当前实例状态。这样编辑器中临时改掉一个字段名时,运行中的审批不受草稿影响。
审批操作也需要区分「页面可以看到按钮」和「任务此刻可以完成」。下面是简化伪代码。读取待办时得到任务版本,提交时按当前候选人和任务状态再次判断。
completeTask(actor, taskId, expectedVersion, decisionId, decision):
transaction:
task = loadTaskForUpdate(actor.tenant, taskId)
requireActorCanReadTask(actor, task)
if sameRecordedDecision(task, decisionId, decision):
return recordedResult(task)
requireCurrentAssigneeAndVersion(actor, task, expectedVersion)
appendDecision(task, decision)
advanceWorkflow(task, decision)
writeRequiredOutboxEntries(task)
如果流程已经拒绝,另一个候选人晚到的同意应得到明确冲突。若提交成功但响应丢失,重放原决定要得到原结果。审批同意和资金实际到账之间应保留独立状态,回访旧单据时可以按各自的 ID 查到历史和当前结果。
总结
配置化后台需要分别管理表单定义、流程定义和执行中的实例。版本记录能还原当时的审批依据,当前权限和交易约束则继续控制每一次实际执行。