Files
qhmes/.trae/skills/jeecg-bpmn/references/bpmn-countersign.md

9.9 KiB
Raw Blame History

会签配置详解

会签(多实例任务):一个审批节点同时由多人处理,根据完成比例决定是否通过。

重要前提: 不启动多实例(不加 multiInstanceLoopCharacteristics),则只会创建一个任务,默认不启动。不启动多实例时,会签相关配置都无效。

1. 会签通用结构

<bpmn2:userTask id="task_xxx" name="会签节点"
  flowable:assignee="${assigneeUserId}"
  flowable:countersignRule="${RULE}">
  <bpmn2:extensionElements>
    <flowable:taskExtendJson value="{...}" />
    <flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
    <flowable:taskCountersignExtendJson value="${BASE64_CONFIG}" />
  </bpmn2:extensionElements>
  <bpmn2:multiInstanceLoopCharacteristics isSequential="false"
    flowable:collection="${COLLECTION_EXPR}"
    flowable:elementVariable="assigneeUserId">
    <bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
      ${COMPLETION_CONDITION}
    </bpmn2:completionCondition>
  </bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>

2. 会签规则countersignRule

规则值 说明 completionCondition
countersign_all 全部通过 ${nrOfCompletedInstances/nrOfInstances==1}
countersign_one 一人通过即可 ${nrOfCompletedInstances/nrOfInstances>0}
countersign_half 半数通过 ${nrOfCompletedInstances/nrOfInstances>=0.5}
countersign_proportion 按比例通过(自定义比例) ${nrOfCompletedInstances/nrOfInstances>=0.N}
countersign_custom 自定义选人 自定义

注意: XML 中 >= 需要写成 &gt;=,如 ${nrOfCompletedInstances/nrOfInstances&gt;=0.6}

3. 顺序会签 vs 并行会签

对比项 并行会签isSequential="false" 顺序会签isSequential="true"
任务创建 同时为所有审批人创建任务 按顺序逐个创建,前一个完成才创建下一个
nrOfActiveInstances 等于尚未完成的审批人数 始终为 1
适用场景 多人同时审批,互不影响 按层级逐级审批
完成条件触发 每完成一个就检查条件 每完成一个就检查条件
<!-- 并行会签(默认,所有人同时收到任务) -->
<bpmn2:multiInstanceLoopCharacteristics isSequential="false" ...>
<!-- 顺序会签(按顺序逐个审批) -->
<bpmn2:multiInstanceLoopCharacteristics isSequential="true" ...>

4. 多实例内置流程变量

多实例自动创建以下 3 个流程变量,用于 completionCondition 表达式:

变量名 类型 说明
nrOfInstances int 实例总数(审批人总数)
nrOfActiveInstances int 当前活跃的(未完成的)实例数。顺序会签时此值始终为 1
nrOfCompletedInstances int 已完成的实例个数

完成条件示例:

${nrOfCompletedInstances/nrOfInstances==1}      → 全部完成
${nrOfCompletedInstances/nrOfInstances>0}       → 至少一人完成
${nrOfCompletedInstances/nrOfInstances>=0.5}    → 50%完成
${nrOfCompletedInstances/nrOfInstances>=0.6}    → 60%完成时,删除其他未完成任务,继续下一步

5. 会签工具 Bean — flowUtil

flowUtil 是系统暴露的 Spring Bean提供会签人员集合获取方法

方法 说明 用于
${flowUtil.stringToList('user1,user2')} 将逗号分隔字符串转为 List 集合 固定人员会签candidateUsers
${flowUtil.getAssigneeUsers(execution,'BASE64配置')} 根据 Base64 编码的配置动态获取审批人列表 部门/岗位/表单字段会签

stringToList 的变量写法:

<!-- 固定人员 -->
flowable:collection="${flowUtil.stringToList('admin,jeecg,zhangsan')}"
<!-- 使用流程变量(动态) -->
flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}"

getAssigneeUsers 的 Base64 配置: 将 taskCountersignExtendJson 的 JSON去掉 timestamp 和 countersignProportion进行 Base64 编码后作为参数传入。

4. 会签审批人类型auditorUserType

taskCountersignExtendJson 是 Base64 编码的 JSON解码后结构如下

人员会签candidateUsers

{
  "auditorUserType": "candidateUsers",
  "auditorUserIds": ["jeecg", "admin"],
  "countersignProportion": "0.2",
  "timestamp": 1758257673121
}

对应 XML collection

flowable:collection="${flowUtil.stringToList('jeecg,admin')}"

部门会签candidateDepts

{
  "auditorUserType": "candidateDepts",
  "auditorDeptIds": ["部门ID1", "部门ID2"],
  "timestamp": 1758257664644
}

对应 XML collection

flowable:collection="${flowUtil.getAssigneeUsers(execution,'BASE64编码的配置')}"

职务会签candidatePosts

{
  "auditorUserType": "candidatePosts",
  "auditorPostIds": ["职务ID1", "职务ID2"],
  "timestamp": 1758202368122
}

对应 XML collection

flowable:collection="${flowUtil.getAssigneeUsers(execution,'BASE64编码的配置')}"

表单字段会签formData

从表单中的用户选择控件动态获取会签人:

{
  "auditorUserType": "formData",
  "auditorCountersignFormField": "select_user_xxx",
  "auditorCountersignFormFieldType": "select-user",
  "timestamp": 1758257668105
}

对应 XML collection

flowable:collection="${flowUtil.getAssigneeUsers(execution,'BASE64编码的配置')}"

7. 完整会签示例 — 人员会签(并行,全部通过)

<bpmn2:userTask id="task_countersign" name="部门会签"
  flowable:assignee="${assigneeUserId}"
  flowable:countersignRule="countersign_all">
  <bpmn2:extensionElements>
    <flowable:taskCountersignExtendJson value="eyJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVVc2VycyIsImF1ZGl0b3JVc2VySWRzIjpbImFkbWluIiwiamVlY2ciXSwidGltZXN0YW1wIjoxNzU4MjU3NjczMTIxfQ==" />
  </bpmn2:extensionElements>
  <bpmn2:multiInstanceLoopCharacteristics
    flowable:collection="${flowUtil.stringToList('admin,jeecg')}"
    flowable:elementVariable="assigneeUserId">
    <bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
      ${nrOfCompletedInstances/nrOfInstances==1}
    </bpmn2:completionCondition>
  </bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>

8. 完整会签示例 — 人员比例通过并行20%

<bpmn2:userTask id="task_vote" name="投票表决"
  flowable:assignee="${assigneeUserId}"
  flowable:countersignRule="countersign_proportion"
  flowable:countersignProportion="0.2">
  <bpmn2:extensionElements>
    <flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:false,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:false,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
    <flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
    <flowable:taskCountersignExtendJson value="eyJjb3VudGVyc2lnblByb3BvcnRpb24iOiIwLjIiLCJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVVc2VycyIsImF1ZGl0b3JVc2VySWRzIjpbImplZWNnIl0sInRpbWVzdGFtcCI6MTc1ODI1NzY3MzEyMX0=" />
  </bpmn2:extensionElements>
  <bpmn2:multiInstanceLoopCharacteristics
    flowable:collection="${flowUtil.stringToList('jeecg')}"
    flowable:elementVariable="assigneeUserId">
    <bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
      ${nrOfCompletedInstances/nrOfInstances&gt;=0.2}
    </bpmn2:completionCondition>
  </bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>

9. 完整会签示例 — 岗位会签顺序比例20%

来自生产环境的岗位candidatePosts顺序会签示例使用 flowUtil.getAssigneeUsers 动态获取审批人:

taskCountersignExtendJson Base64 解码后:

{
  "countersignProportion": "0.2",
  "auditorUserType": "candidatePosts",
  "auditorPostIds": ["2032387176954642433", "1958471111989067778"],
  "timestamp": 1773418938149
}

getAssigneeUsers 参数 Base64 解码后(不含 timestamp 和 countersignProportion

{
  "auditorUserType": "candidatePosts",
  "auditorPostIds": ["2032387176954642433", "1958471111989067778"]
}
<bpmn2:userTask id="task_dept_mgr" name="部门经理审批"
  flowable:assignee="${assigneeUserId}"
  flowable:countersignRule="countersign_proportion">
  <bpmn2:extensionElements>
    <flowable:taskExtendJson value="{&quot;sameMode&quot;:0,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:false,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:false,&quot;isSkipApprovedOnCountersignReturn&quot;:false}" />
    <flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
    <flowable:taskCountersignExtendJson value="eyJjb3VudGVyc2lnblByb3BvcnRpb24iOiIwLjIiLCJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVQb3N0cyIsImF1ZGl0b3JQb3N0SWRzIjpbIjIwMzIzODcxNzY5NTQ2NDI0MzMiLCIxOTU4NDcxMTExOTg5MDY3Nzc4Il0sInRpbWVzdGFtcCI6MTc3MzQxODkzODE0OX0=" />
  </bpmn2:extensionElements>
  <bpmn2:multiInstanceLoopCharacteristics isSequential="true"
    flowable:collection="${flowUtil.getAssigneeUsers(execution,'eyJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVQb3N0cyIsImF1ZGl0b3JQb3N0SWRzIjpbIjIwMzIzODcxNzY5NTQ2NDI0MzMiLCIxOTU4NDcxMTExOTg5MDY3Nzc4Il19')}"
    flowable:elementVariable="assigneeUserId" />
</bpmn2:userTask>

关键差异:

  • isSequential="true" — 顺序逐个审批
  • 没有 completionCondition — 当 countersignRule 为 proportion 但未写 completionCondition 时,系统通过 countersignProportion 属性和 taskCountersignExtendJson 中的 countersignProportion 值自动处理
  • flowUtil.getAssigneeUsers(execution, 'BASE64') — 运行时根据岗位ID动态获取用户列表