同一个节点接力办几轮,流程才往下走:jeeflow 的后加签

0 阅读1分钟

一、一个反直觉的现场:审批办完了,节点还在原地

合同审批走到「合同复核」这一格,李娜看完了,也点了同意。

按直觉,这一格应该结束了,单子该去终审。但在后加签里,李娜同意之后,单子还在这一格——她只是把自己这一手办结了,然后又指定吴昊在同一格接手;吴昊办完再指定赵敏;赵敏确认不再加人,这一格才真正流出。

这不是转办,也不是往同一轮里加一个参与人,更不是改流程图。

它叫后加签(可重复)。这篇只回答三件事:后加签改的是哪一行;节点什么时候才动;配的时候哪些边界不能踩。

二、先把「加人」分成四档

在同一个审批节点上,往里面加人其实有四个动作。它们长得很像,改的东西完全不同:

档位它改了什么,什么时候用
并加签(surrogate / addCandidate)不办结,追加参与人;同一轮、计票共享、原人保留。适合同一轮多人共享一票,谁办都算这一手办完。
后加签(allowAddSign)办结我这一手,原人退出;同节点新一轮、计票不共享。适合我办完,指定下一位接力。
转办(transfer)我不办,换人办;同一任务行摘原人换新人。
委托(SurrogateInterceptor)建单时把代理人并进名册,不摘原人、不新建。

只有第二档同时满足三个条件:我办结、原人退出、同一节点新一轮。它的特殊之处就在这里。

先解释几个词:DOING 是任务状态 10(待办);token 是流程令牌,代表流程当前停在哪;merged 是本次提交要不要把节点汇合流出。这里说的「计票」指同节点多行任务要不要合并成一次结果——并加签合并,后加签不合并。

后加签不新增节点类型、不画自环边、不新增 submitType。提交动作仍然是同意(1)或不同意(2),加签只是随这次提交带的一个参数 tf_addSignOperators;账本 tf_addSignHistory 也是任务变量上的一棵追加式列表,不新增表。

加人四档:只有后加签是「办结 + 原人退出 + 同节点新一轮」

三、办结的是我这一手,不是这个节点

李娜在同意时带上吴昊的用户 id:

POST /wf/processTask/execute
{
  "operator": "<当前用户 id>",
  "processTaskId": "2118334755562127362",
  "submitType": 1,
  "tf_approvalComment": "技术复核通过,请财务接力",
  "tf_addSignOperators": ["2086772723853299712"]
}

通过集成壳调用时,operator 由壳从登录态注入;直连门面时要显式带。

引擎做完这件事,数据上是三件:

  1. 李娜那一行置为 FINISHED(20),不改回 DOING;
  2. 同一节点新建一行,task_parent_id 指向李娜刚办结的那一行,参与者只有吴昊,performType=NORMAL,expire_time 按本节点表达式同尺重算;
  3. 李娜那一行追加一条账本 tf_addSignHistory:{round:1, fromActor:<李娜 id>, toActors:[<吴昊 id>], time, operator}。

吴昊办完再加赵敏时,同样的三件事再来一次:吴昊那一行置 FINISHED,同节点新建赵敏那一行,账本 round:2 落在吴昊那一行上。赵敏办完不再加人,这一轮就没有新行了。

所以「节点有三行」不是某个任务被改了三次状态,而是三个独立的任务行:

  • 第 1 行:李娜,FINISHED,账本 round 1 → 吴昊;
  • 第 2 行:吴昊,FINISHED,账本 round 2 → 赵敏;
  • 第 3 行:赵敏,FINISHED,无加签账本。

同一节点三行:原行状态不改写,账本追加在原行上,新行是新任务 id

两个数据细节值得单独说:

  • 原行状态与办理结果不改写:jeeflow 没有一条独立的历史表,任务行本身即审批历史。把已办结行翻回 DOING 等于改写已经出证的记录;所以撤回、退回这些动作也都是「旧行留痕、另起一行」,后加签沿用同一个原则。唯一追加在原行上的,是它的账本 tf_addSignHistory。
  • 上一行的账本不会复制到新行:tf_addSignHistory 留在已经完成的那一行上;新行只带自己的轮次键 addSignRound_{节点 id}(例子里的节点 id 是 review)。注意这个键记的是创建它的那一轮:吴昊那一行是 1,但他再点赵敏时,账本写的是 round 2。tf_ 控制键也不会并进实例变量,避免上一轮的控制参数在后续请求里复活。

多选时,同一圈会建多行任务,每行一个参与者;它们共享这一圈的轮次,但计票不共享。加签人如果是通过委托进来的,新行照普通建单的规则把当时生效的受托人并进参与者,不摘原人。

四、节点什么时候才往下走:三个判据,点人排第一

任务节点执行闸分档:会签节点走会签档;开了 allowAddSign 的节点走加签闸;开了 dynamicCountersign 的节点走征询档(下一篇);其余走普通档。后加签闸的 merged 判据只有三步,点人排在最前面:

本次给了 tf_addSignOperators → 在本节点建单,不流转
否则,本节点还有 DOING 任务  → 不流转
否则                         → 流出本节点

为什么点人排第一:这次点了人,就是要新建一行;先把新行建出来,再等所有行办完。哪怕同节点还有别的 DOING 任务,也先建单——否则这次加签请求会被静默吞掉。

对着真机记录看就很清楚:

  • 李娜办结后,吴昊那一行还是 DOING,所以节点不流出;
  • 吴昊办结后,赵敏那一行还是 DOING,所以节点不流出;
  • 赵敏办结、且没有点人,本节点没有 DOING 行,也没有新的加签人,节点才流出,终审任务才出现。

多选时,同一圈建了多行;某一行既办结又点人,仍然先建新行、节点继续等。只有最后一行办结且没再点人,merged 才为真。停止条件就是这个判据本身,不设第二判据源——否则就会出现「刚建完接力任务,节点已经走了」的假完成。

三判据闸门:先看本次有没有点人,再看还有没有 DOING,最后才放行

同一节点三行,在流程图上不会画成三个节点。它仍然是一个「合同复核」节点,节点里竖排三个人名:

  • 停在中间时,review 是橙色边框,李娜、吴昊已经绿勾,赵敏还是蓝点,终审还没动;
  • 三个人都办完后,review 变绿,三个人名全绿,终审才变成当前节点。

这张图不是示意图,是同一套定义在两个时间点的任务详情「流程图」。

同一张流程图的两种状态:进行时赵敏未办,办结后 review 变绿、终审变当前

下面是真跑的审批记录:

审批记录:同一节点三行,前两行各带一条加签账本

图里账本显示的是用户 id;三行办理人姓名在每行上方,第 1 轮对应第二行的吴昊,第 2 轮对应第三行的赵敏。终审那一行在赵敏办结之后才出现;它还是待办,所以办理人一栏是空的——王强还没办。

五、怎么配:两个键、四条解析期拦截、四个写入口

和后加签有关的键只有两个:

键说明
allowAddSignbool,不写键=false;开启后该节点办理人可在办结时指定后继,在同一节点续办。
addSignMaxRoundsint ≥ 0,不写键=0=不限;后加签和动态会签共用。

落在节点上就是两个属性:

"properties": {
  "allowAddSign": true,
  "addSignMaxRounds": 0
}

把它加在普通审批节点的 properties 上;其余节点字段按你项目的流程定义保留。

解析期会拦下四类:

  • 非法值:解析期报错,不静默缺省;别指望它悄悄当成 false 或 0;
  • 会签节点配 allowAddSign:报错。会签池内加人和会签之后加人是两种语义,推进闸会打架;
  • 首任务节点(申请节点)配 allowAddSign:报错。申请节点的办理是发起/重提语义,和加签路由打架;
  • 后加签和动态会签双开:报错。两者共用 tf_addSignOperators,双开等于行为歧义。

设计器把这个互斥做成了一个三选一的「办理协同」:无、后加签(接力)、动态会签(征询)。选后加签写 allowAddSign、删 dynamicCountersign;选无则把 allowAddSign、dynamicCountersign、addSignMaxRounds 一起清掉;已有 allowAddSign:true 的存量定义回显为「后加签」。

写入口还有四个判据,全部在状态变更之前,报错即回滚整次提交:

  1. 节点没开 allowAddSign 却传了 tf_addSignOperators:报错;
  2. 把加签加给自己:报错;
  3. 超过 addSignMaxRounds:报错,不强制放行——放行等于伪造一次审批完成;
  4. tf_addSignOperators 和 tf_nextNodeOperator 同传:报错。一个管本节点再来一轮,一个管流出后下一节点交给谁;加签让 token 停在本节点,流出发生在后续请求,届时先传的下一节点处理人已经读不到了,同传时它是死参数,所以宁可报错,也不静默择一。

空串、纯空白、null 会被归一成「没传」,按普通流转处理;别拿空值当加签参数。

配置与执行:设计器选「后加签(接力)」,执行弹窗勾「加签给下一位」再选人

图里还能看到「允许已办撤回」,那是另一项能力,不是后加签的开关。

前端提交时,两组互斥的控件会做第二步清理:勾了加签就删掉「指定下一节点处理人」那组键,没勾就删掉加签那组,避免残留值把服务端判成同传。这一步不是可选的——只把控件隐藏、不删值,隐藏的那一半仍会进 payload。同意以外的动作(不同意、退回、跳转)会把两组键都删掉;别在这些动作里带加签参数。

账本在任务行的 ext.tf_addSignHistory 里,processTask/detail 或流程详情的审批记录都能看到。

这里最容易混的是动态会签:它复用的也是 tf_addSignOperators,但主办行不办结、挂起收意见,收齐后主办再拍板。后加签是主办已经表过态、把同一手交给下一位;动态会签是主办还没拍板、先收集意见。两件事,下一篇单独讲。

六、边界:哪些事后加签不做

  • 加签链上不能退回上一步:当前行和它的血缘 parent 在同一节点,既有的 canRejected 守卫会拦下。要往回走,用「退回发起人(6)」或「跳转(4)」。
  • 不同意(2):按拒绝档结束实例,并废弃本节点未完成的加签任务;**退回发起人(6)**同样废弃,不留孤儿待办。
  • 加给自己报错;加给已经办过的人允许:A→B→A 是合法业务,靠 addSignMaxRounds 兜上限。
  • 加签任务自身的能力:可以再后加签、可以不同意、可以被转办/委托/抄送。
  • 并发承诺边界:同一实例的办理在单进程部署下串行化;多进程部署的跨进程锁不在本特性的承诺内。这一点如实写在这里,别把它当成已经支持。
  • 不改流程图:不新增节点类型、不画自环边;任务行在同一节点续建,图上仍然是一个节点。

七、什么时候选它

  • 选后加签:主办已经表过态,只需要把同一手交给下一位专业复核;需要一轮一轮接力;不想改流程图。
  • 选动态会签:主办不能独自拍板,需要先收意见、自己再决定;人员运行期才定。
  • 选转办:我不办,换人办。
  • 选并加签:同一轮多人共享一票,谁办都算这一手办完。
  • 选委托:建单时就把代理人并进名册,不摘原人。

一句话:后加签不是会签的替代品。如果要求所有人在流出前都表态,应该用会签节点;后加签解决的是「我这一手已经办结,但这一手还没完」的接力问题。

八、八种语言同一套契约

后加签的参考实现是 Java;八种语言的引擎按同一份契约实现,八仓里放着同一份测试流程 19-add-sign-after.json。这一批代次发版后,各集成壳和演示镜像都已跟上。

我这次真跑的是 boot3 演示栈(Java 参考实现 + vben5 前端),实例 2118334755528572930:review 节点三行分别是李娜、吴昊、赵敏,前两行各带一条加签账本,第三行不再加签,终审任务在第三行办结后才创建。其余七种语言我这次只对照了规范和共享测试流程,没有逐个做 UI 走查——跨栈断言按这个口径看。

版本怎么确认:在装好的引擎里搜 allowAddSign,或看文档站更新日志里你那一语言从哪版起带上它。八种语言版本线独立,正文不写死版本号。

落地清单

  1. 确认你的引擎代次带 allowAddSign 和 addSignMaxRounds,前端设计器有「办理协同」三选一。
  2. 只在普通审批节点上开;会签节点、申请节点解析期就会拒。
  3. 想限制 A→B→A 的来回加签,就把 addSignMaxRounds 设成非 0;0 是不限。
  4. 前端两组控件必须互斥清理:勾加签删「指定下一节点处理人」,不勾删加签那组键。
  5. 上线前跑一遍自己的样本:主办办结时点人 → 下一位再点人 → 最后一位不点人,看节点是否在最后一位办结后才流出,审批记录里是否三行都在。

参考资料

jeeflow 文档站 规范 04「引擎操作」——后加签(可重复) jeeflow-doc.mldong.com/spec/04-eng…

jeeflow 文档站 规范 02「流程定义」——后加签开关(allowAddSign / addSignMaxRounds) jeeflow-doc.mldong.com/spec/02-flo…

jeeflow 文档站 规范 06「统一门面」——tf_addSignOperators / tf_addSignHistory jeeflow-doc.mldong.com/spec/06-fac…

jeeflow 文档站 更新日志(八引擎各自代次) jeeflow-doc.mldong.com/changelog/

jeeflow Java 参考实现 github.com/mldong/jeef…

八语言演示站(在线可试;后加签夹具见各引擎仓的 19-add-sign-after.json) jeeflow-demo.mldong.com/