☰
审批流程上线怎么验收?版本分流、在途待办、结果回写与回退检查
2026/11/4 12:03:50 网站建设 项目流程

测试环境里点通了“提交申请→部门审批→结束”,上线就算完成了吗?实际最容易漏的,是新版定义发布时仍有旧实例在运行:新单可能意外启动旧版,旧单可能被错误迁移;页面上的旧待办虽已隐藏,接口仍允许提交;引擎显示通过,业务单却留在审批中。这几类问题分别发生在定义、实例、任务和业务系统,单看一个页面无法发现。

本文给出一套有输入、有查询、有阻断条件的流程上线验收方法。例子是虚构的甲公司采购审批:V1 为“部门审批后结束”,V2 增加“金额超过 1000 元需合规复核”。发布前已有P601/I601停在部门审批;发布后新建P602/I602(1200 元)和P603/I603(800 元)。另设P604/I604模拟终态回调丢失。示例用 Java 11 和 MySQL 8.x 描述可复核的规则,不依赖任何产品内部流程配置。

目录

  1. 发布前先冻结哪些事实
  2. 发布后先查定义和实例版本
  3. 在途单据与待办怎样验收
  4. 通过、驳回与丢回调怎样验收
  5. 放量和回退的门槛怎样定
  6. 用 Java 固定验收断言
  7. 用 SQL 找出遗漏与重复
  8. 执行顺序与参考资料

一、发布前先冻结哪些事实

上线验收要回答“发布改变了什么”,所以先要保存发布前的基线。只记“版本号 V1”不够,还应记定义 ID、实例 ID、当前活动节点、任务 ID、业务单关联和业务状态。否则发布后看见一张“部门审批”待办,无法判断它来自旧版实例还是新版实例;看见一张已通过业务单,也不知道是本次发布产生的,还是原来就通过了。

样本发布前事实发布后预期要取的证据
P601/I601,1200 元在 V1 启动,部门审批待办未办默认继续绑定 V1,按旧路径完成原定义 ID、实例绑定、活动待办
P602/I602,1200 元发布后才提交使用 V2,部门审批后出现合规复核启动返回的定义 ID、节点轨迹
P603/I603,800 元发布后才提交使用 V2;本次让部门驳回,直接进入驳回终态定义 ID、驳回轨迹、终态
P604/I604故障注入样本引擎通过而业务仍审批中时被发现并恢复引擎终态、业务状态、处理记录

这里的“V1 继续运行”是默认策略。若法规或业务决策要求把部分在途单也纳入合规复核,需要单独制定迁移清单、映射和审批,不应在本次普通发布验收中偷偷改变I601。Flowable 的定义部署文档说明,新定义发布会生成新版本,按定义键启动的新实例使用最新版本;实例迁移文档把在途实例迁移作为单独操作。落地时还要看所用引擎版本和启动 API 的实际行为。

发布基线建议导出为一份只读清单:release_id、tenant_id、definition_key、old_definition_id、new_definition_id、instance_id、business_id、active_task_id、captured_at。验收记录也带release_id,以便后来区分“本次上线造成的差异”和既有存量问题。若生产环境不能创建完整测试单,至少在预发环境执行全链路,再在生产用脱敏、可清理的小流量样本核对入口和指标。

图1:发布前固定定义、在途实例、业务状态和待办事实,后续才能判断变化是否符合预期。

二、发布后先查定义和实例版本

发布接口返回成功,只能证明引擎接受了定义,不能证明业务系统的新单已经路由到它。常见原因包括:启动入口显式传了旧定义 ID、应用缓存仍保存旧版本、某个租户没有切到新发布配置,或多个服务节点使用的启动参数不一致。因此验收第一步是从新建业务单追到实际实例定义 ID,不要从页面显示的流程名称推断版本。

对P602和P603,分别查业务库里的实例关联,再查引擎实例的定义 ID;两单都应是 V2 的定义。对P601,实例定义仍应是 V1。若I601无迁移决策却变成 V2,或I602落到 V1,都应暂停放量。不能用“待办看着能办”掩盖版本错配,因为金额条件和后续节点可能直到下一步才出错。

还要区分发布定义和切换启动入口。前者创建一个可用版本,后者决定新请求何时开始用它。大型系统可以先部署并验证定义元数据,再小范围启用启动入口。回退时先关闭新版入口或恢复旧入口配置,控制新的启动;已经生成的 V2 实例不会因为入口回退自动变回 V1,需要单列受影响实例继续处理。

图2:旧单P601保持 V1;新单P602、P603必须由实例定义 ID 证实落到 V2。

三、在途单据与待办怎样验收

版本正确以后,验收要从“看得到待办”推进到“谁能办理、旧任务能否继续办理”。对仍在运行的I601和I602,正常串行节点通常应各有一条活动待办;若流程设计有并行会签,预期数量应来自模型而不是固定写成 1。本文的教学样本是串行审批,故把“1 条活动待办”作为明确断言。

对每条活动待办做四种操作:目标办理人查询列表和详情、目标办理人实际提交、无权限用户尝试提交、旧任务链接在完成或转办后再次提交。最后一种必须通过服务端接口验证;页面隐藏按钮不能证明旧任务不可执行。任务完成后再查引擎活动任务与业务侧待办投影,允许短暂同步延迟,但必须有约定的上限和最终一致结果。

若P602金额超过 1000,部门审批通过后应出现合规复核待办;P603在部门环节驳回时,应直接结束,不应出现合规待办。要验证“800 元且部门通过时跳过合规”这条条件分支,应另建一张独立的小额通过样本,不能让同一实例既通过又驳回。验收记录至少保留:提交前后的task_id、办理人、实例 ID、定义 ID、处理时间、接口结果和当前活动任务列表。否则“审批成功”可能只表示接口响应成功,实际待办仍重复留在列表中。

注意不要用生产真实待办做破坏性试验。对“无权限用户”和“旧链接重放”,使用隔离的验收单;若权限系统与待办投影分开存储,应同时验证引擎能否办理与业务页面是否可见,两者任一侧放行都不算通过。

图3:检查活动待办、目标办理人、无权限请求及失效旧链接,避免只看页面列表。

四、通过、驳回与丢回调怎样验收

流程结束还没有走完整条链。以P602通过为例,至少要同时看到:I602=APPROVED、P602=APPROVED、活动待办为 0、业务侧有结果处理记录、通过后的业务动作按业务键只发生一次。P603则用驳回结果验证反向分支:I603=REJECTED时不能把业务单写成通过,也不能触发通过后的采购动作。

故障样本P604/I604故意让终态通知丢失:引擎已通过,业务单仍审批中。上线验收不是要求“这类窗口永远不存在”,而是要求能监测差异、在规定时间内补偿、迟到消息不产生第二次效果。若定时对账发现I604的已提交终态,核实组织和原实例绑定后,可补写业务状态并记录对账来源;若业务单已撤销或出现相反终态,应阻断自动补写并人工核对。这部分与上一节的普通通过样本一起验,才能证明系统不仅能处理顺畅路径,也能从中断处恢复。

不能把单个 HTTP 200 当成终态回写完成。流程中心、业务库和后续动作可能各自提交;验收要从三处读事实,并按instance_id与业务编号关联。如果后续通知是异步的,可单独设通知完成时限,不应把“审批状态已回写”写成“所有外部动作均已成功”。

图4:通过、驳回和丢回调三种样本分别核对引擎事实、业务状态、处理记录和后续效果。

五、放量和回退的门槛怎样定

上线可分为三段:发布定义但不切换入口;选定组织或小比例新单启动 V2;通过验收后扩大到全部目标范围。每段都有可观察窗口和退出条件。发布前就要写清“谁看指标、看多久、出现什么立即停”,不能等到故障发生再讨论是否回退。下面是一套示例门槛,具体阈值应依据实际流量和业务延迟设定:

门槛放行证据阻断条件首要动作
定义与入口新单实例全部落 V2,旧实例保持 V1新单落 V1,或旧单无授权迁移暂停 V2 新单入口,查启动参数与租户配置
待办与权限活动任务数量符合模型,越权和旧链接被拒绝重复待办、失效任务可提交停止放量,保留任务与权限快照
业务回写引擎终态与业务状态一致,处理记录可追溯终态相反或重复业务效果关闭新入口,隔离受影响实例与后续动作
故障恢复丢回调可被发现并补偿,迟到消息幂等差异积压持续增长或补偿覆盖冲突暂停自动补偿,按实例排查

回退不是“删除 V2 定义”或“把所有 V2 实例的版本字段改成 V1”。安全的第一步是停止产生更多受影响实例,保存已经启动的 V2 实例清单,再决定它们继续、补救还是经预检迁移。即便启动入口切回 V1,已经完成的 V2 实例和业务回写也不能靠发布回退倒转。若已产生错误的业务效果,还要按业务补偿流程处理,不能仅恢复流程配置。

发布记录应写出负责人、开始/结束时间、定义 ID、样本编号、失败项、受影响实例数、补偿状态和最终放行人。这样下一次升级时可以复用判定方式,而不是只留下“上线正常”四个字。

图5:版本、待办、回写和恢复四类证据共同决定是否放量;入口回退后仍需核对已启动实例。

六、用 Java 固定验收断言

下面的 Java 11 程序把每张验收单的观察结果归约成失败原因。它不是流程引擎实现,也不访问真实接口;真正上线时应把读取引擎、业务库和待办接口的适配器接到这些断言前。程序覆盖旧版在途、新版启动、通过、驳回,以及五类必须阻断的异常,运行方式为javac -encoding UTF-8 Stage10WorkflowReleaseGateExample.java、java Stage10WorkflowReleaseGateExample,预期TOTAL 9/9 PASS。

importjava.util.ArrayList;importjava.util.List;/** 教学程序:将流程上线验收事实归约为可复核的放行结果。Java 11。 */publicclassStage10WorkflowReleaseGateExample{enumVersion{V1,V2}enumState{IN_APPROVAL,APPROVED,REJECTED}enumEngineState{RUNNING,APPROVED,REJECTED}staticfinalclassCaseFact{finalStringorderId,instanceId;finalVersionexpectedVersion,actualVersion;finalStatebusinessState;finalEngineStateengineState;finalintactiveTasks,authorizedUsers,effectCount;finalbooleanoldTaskCanSubmit,resultInboxRecorded;CaseFact(StringorderId,StringinstanceId,VersionexpectedVersion,VersionactualVersion,StatebusinessState,EngineStateengineState,intactiveTasks,intauthorizedUsers,inteffectCount,booleanoldTaskCanSubmit,booleanresultInboxRecorded){this.orderId=orderId;this.instanceId=instanceId;this.expectedVersion=expectedVersion;this.actualVersion=actualVersion;this.businessState=businessState;this.engineState=engineState;this.activeTasks=activeTasks;this.authorizedUsers=authorizedUsers;this.effectCount=effectCount;this.oldTaskCanSubmit=oldTaskCanSubmit;this.resultInboxRecorded=resultInboxRecorded;}}staticList<String>check(CaseFactf){List<String>failures=newArrayList<>();if(f.orderId==null||f.instanceId==null)failures.add("缺少业务单或实例编号");if(f.actualVersion!=f.expectedVersion)failures.add("实例定义版本不符");if(f.oldTaskCanSubmit)failures.add("失效待办仍可办理");if(f.activeTasks<0||f.activeTasks>1)failures.add("活动待办数量异常");if(f.authorizedUsers<0||f.authorizedUsers>1)failures.add("待办授权人数异常");if(f.engineState==EngineState.RUNNING){if(f.businessState!=State.IN_APPROVAL)failures.add("运行中实例与业务状态不一致");if(f.activeTasks!=1||f.authorizedUsers!=1)failures.add("运行中待办不可唯一办理");if(f.effectCount!=0)failures.add("未结束就产生终态业务效果");}else{if(f.activeTasks!=0)failures.add("终态实例仍有活动待办");Stateexpected=f.engineState==EngineState.APPROVED?State.APPROVED:State.REJECTED;if(f.businessState!=expected)failures.add("终态未正确回写业务单");if(!f.resultInboxRecorded)failures.add("终态缺少处理记录");if(f.effectCount!=1)failures.add("终态业务效果不是一次");}returnfailures;}staticvoidrequire(Stringlabel,booleanok){if(!ok)thrownewAssertionError(label);System.out.println("PASS "+label);}publicstaticvoidmain(String[]args){CaseFactoldRunning=newCaseFact("P601","I601",Version.V1,Version.V1,State.IN_APPROVAL,EngineState.RUNNING,1,1,0,false,false);CaseFactnewRunning=newCaseFact("P602","I602",Version.V2,Version.V2,State.IN_APPROVAL,EngineState.RUNNING,1,1,0,false,false);CaseFactnewApproved=newCaseFact("P602","I602",Version.V2,Version.V2,State.APPROVED,EngineState.APPROVED,0,0,1,false,true);CaseFactnewRejected=newCaseFact("P603","I603",Version.V2,Version.V2,State.REJECTED,EngineState.REJECTED,0,0,1,false,true);CaseFactlostCallback=newCaseFact("P604","I604",Version.V2,Version.V2,State.IN_APPROVAL,EngineState.APPROVED,0,0,0,false,false);CaseFactduplicateTask=newCaseFact("P605","I605",Version.V2,Version.V2,State.IN_APPROVAL,EngineState.RUNNING,2,1,0,false,false);CaseFactstaleTask=newCaseFact("P606","I606",Version.V2,Version.V2,State.IN_APPROVAL,EngineState.RUNNING,1,1,0,true,false);CaseFactwrongVersion=newCaseFact("P607","I607",Version.V2,Version.V1,State.IN_APPROVAL,EngineState.RUNNING,1,1,0,false,false);CaseFactduplicateEffect=newCaseFact("P602","I602",Version.V2,Version.V2,State.APPROVED,EngineState.APPROVED,0,0,2,false,true);require("旧版在途不被覆盖",check(oldRunning).isEmpty());require("新版实例正常启动",check(newRunning).isEmpty());require("新版通过完整回写",check(newApproved).isEmpty());require("新版驳回完整回写",check(newRejected).isEmpty());require("丢失回调必须阻断放行",check(lostCallback).contains("终态未正确回写业务单"));require("重复待办必须阻断放行",check(duplicateTask).contains("活动待办数量异常"));require("旧待办可提交必须阻断",check(staleTask).contains("失效待办仍可办理"));require("错误定义版本必须阻断",check(wrongVersion).contains("实例定义版本不符"));require("重复业务效果必须阻断",check(duplicateEffect).contains("终态业务效果不是一次"));System.out.println("TOTAL 9/9 PASS");}}

代码中的effectCount代表终态业务动作的实际生成次数,包括通过后的采购动作或驳回后的结案动作,不是事件到达次数;事件可以重投,同类业务动作必须按业务键防重。oldTaskCanSubmit也要求真的请求办理接口,不能从前端按钮状态推导。若流程含并行任务,应替换“活动任务恰好一条”的教学断言为按模型节点计算的期望集合,而不是把代码硬套到所有流程。

七、用 SQL 找出遗漏与重复

下面给出业务侧最小取证表。流程定义、实例和活动任务仍应以引擎 API 或引擎公开查询为准,不建议直接修改引擎运行表。业务侧保存原实例与定义 ID,才能把新旧版本、结果回写和后续动作串在一起。字段和索引是教学示例,可按既有业务表调整。

CREATETABLEpurchase_apply(tenant_idVARCHAR(32)NOTNULL,apply_idVARCHAR(32)NOTNULL,amountDECIMAL(12,2)NOTNULL,instance_idVARCHAR(64)NOTNULL,definition_idVARCHAR(128)NOTNULL,state_codeVARCHAR(24)NOTNULL,result_atDATETIME(3)NULL,PRIMARYKEY(tenant_id,apply_id),UNIQUEKEYuk_instance(tenant_id,instance_id),KEYidx_state(tenant_id,state_code,apply_id));CREATETABLEworkflow_result_receipt(tenant_idVARCHAR(32)NOTNULL,event_idVARCHAR(64)NOTNULL,instance_idVARCHAR(64)NOTNULL,apply_idVARCHAR(32)NOTNULL,result_codeVARCHAR(24)NOTNULL,source_codeVARCHAR(24)NOTNULL,handled_atDATETIME(3)NOTNULL,PRIMARYKEY(tenant_id,event_id),KEYidx_apply(tenant_id,apply_id,instance_id));CREATETABLEpurchase_effect(tenant_idVARCHAR(32)NOTNULL,effect_keyVARCHAR(96)NOTNULL,apply_idVARCHAR(32)NOTNULL,effect_typeVARCHAR(32)NOTNULL,created_atDATETIME(3)NOTNULL,PRIMARYKEY(tenant_id,effect_key),KEYidx_apply(tenant_id,apply_id));

验收查询按release_id或验收单编号缩小范围;以下用四张样本单说明预期。definition_id的具体值应来自发布记录,而不是在 SQL 中猜一个“V2”。

SELECTapply_id,instance_id,definition_id,state_code,amountFROMpurchase_applyWHEREtenant_id='orgA'ANDapply_idIN('P601','P602','P603','P604')ORDERBYapply_id;-- P601 应绑定旧定义;P602/P603/P604 应绑定新定义。-- P602=APPROVED,P603=REJECTED;P604 补偿后才应为 APPROVED。SELECTapply_id,effect_type,COUNT(*)ASeffectsFROMpurchase_effectWHEREtenant_id='orgA'ANDapply_idIN('P602','P603','P604')GROUPBYapply_id,effect_typeHAVINGCOUNT(*)>1;-- 预期 0 行;重复回调不应生成第二份同类业务效果。SELECTa.apply_id,a.instance_id,a.state_codeFROMpurchase_apply aLEFTJOINworkflow_result_receipt rONr.tenant_id=a.tenant_idANDr.apply_id=a.apply_idANDr.instance_id=a.instance_idWHEREa.tenant_id='orgA'ANDa.apply_idIN('P602','P603','P604')ANDa.state_codeIN('APPROVED','REJECTED')ANDr.event_idISNULL;-- 预期 0 行;终态业务单应有回写或对账处理记录。

上述查询只能证明业务库内部的版本记录与处理记录,不足以证明引擎事实。最终仍需按instance_id向引擎查定义 ID、终态和活动待办,形成“引擎事实↔业务事实”的对照表。还要特别核查P604:对账前故意出现差异,报警应触发;补偿后差异归零、迟到回调再来,purchase_effect仍只有一条。MySQL 的SELECT 文档给出GROUP BY和HAVING的语义;生产查询应结合实际数据量和执行计划校验。

八、执行顺序与参考资料

一次可复核的上线演练可以按以下顺序执行:发布前保存P601/I601基线;发布 V2 并核对定义 ID;用小流量创建P602/P603,确认都落 V2;验证金额分支、授权办理与旧链接拒绝;分别完成通过和驳回;注入P604丢回调并验证监测、补偿和迟到消息;最后汇总 SQL 与引擎查询证据,决定放量或暂停。任何一环失败,都记录失败样本与受影响范围,修复后从失败点重测并复核其前后关联状态。

这套验收的核心不是多做几张截图,而是让每个结论都有可读取的事实:实例用了哪个定义、当前谁有活动任务、终态是否已回写、重复消息是否只产生一次业务效果。发布入口可以回退,已启动实例和已发生业务动作仍要单独处理。

参考资料:

  1. Flowable:流程定义部署与版本
  2. Flowable:运行实例迁移
  3. MySQL:SELECT、GROUP BY 与 HAVING

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询