选择审批人
这篇解决什么
改用户任务上的审批人规则:设计器里选哪种人、参数怎么落到 XML / SIMPLE JSON,运行时怎么算出办理人。读完能对上 candidateStrategy / candidateParam、各策略实现,以及发起人自选为什么必须带 startUserSelectAssignees。
默认端口 48080,管理端前缀 /admin-api。PC 开发页 http://127.0.0.1:5800。规则配在流程中心 → 流程设置 → 流程模型,第三步点开审批节点。画布本身见 流程设计器(BPMN)。
示意图:设计器只写规则。创建任务时由 Invoker 调策略,再交给 Flowable 落 assignee。
从哪改
| 名称 | 说明 | 仓库路径 |
|---|---|---|
| 策略枚举 | 整数码,前后端同一套 | ruoyi-office/yudao-module-bpm/.../enums/BpmTaskCandidateStrategyEnum.java |
| 策略接口 | calculateUsers / ByTask / ByActivity | .../candidate/BpmTaskCandidateStrategy.java |
| 调用入口 | 发布校验、算人、空人兜底 | .../candidate/BpmTaskCandidateInvoker.java |
| 单人任务 | 候选人里随机一人当办理人 | .../behavior/BpmUserTaskActivityBehavior.java |
| 行为工厂 | 把 Invoker 注入 UserTask / 会签 | .../behavior/BpmActivityBehaviorFactory.java |
| 扩展属性名 | candidateStrategy / candidateParam | yudao-module-bpm-api/.../BpmnModelConstants.java |
| 自选变量 | PROCESS_START_USER_SELECT_ASSIGNEES | .../enums/BpmnVariableConstants.java |
| PC 枚举 | 与后端码一致 | ruoyi-office-vben/packages/constants/src/biz-bpm-enum.ts |
| SIMPLE 下拉 | 界面文案顺序,不读字典 | .../simple-process-design/consts.ts 的 CANDIDATE_STRATEGY |
| BPMN 面板 | 右侧「用户任务」里的规则类型 | .../bpmn-process-designer/.../UserTask.vue |
| SIMPLE 抽屉 | 「审批人设置」单选 | .../nodes-config/user-task-node-config.vue |
官方菜单:1185 流程中心,1186 流程设置,1193 流程模型。改图、发布要 bpm:model:update、bpm:model:deploy。字典 bpm_task_candidate_strategy 给列表翻译用,设计器选项写死在 CANDIDATE_STRATEGY。
新增策略做成 Spring Bean 即可,BpmFlowableConfiguration 会把全部 BpmTaskCandidateStrategy 收进 Invoker,不要重复注册同一个枚举。
规则写到节点上
标准 UserTask 没有「按角色 / 按部门」这类属性。设计器把规则写进 Flowable 命名空间:candidateStrategy 是类型,candidateParam 是参数。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
| 写入 | addCandidateElements | .../util/BpmnModelUtils.java |
| 读取 | 先看节点属性,没有再读 extensionElements | #parseCandidateStrategy / #parseCandidateParam |
| SIMPLE 节点 | BpmSimpleModelNodeVO.candidateStrategy | .../vo/model/simple/BpmSimpleModelNodeVO.java |
| 发布前校验 | 人工审批且策略为空 → 发布失败 | BpmTaskCandidateInvoker#validateBpmnConfig |
BPMN 面板改规则时,走 flowable:CandidateStrategy / flowable:CandidateParam 扩展元素,不再写到元素根属性上。读的时候两套都认,老图还能开。
isParamRequired() 默认 true。发起人自选、审批人自选、发起人本人不需要参数。自动通过 / 自动拒绝的节点不算人工审批,发布时不查候选人。

本系统截图:用车申请单 → 流程设计,点开「部门负责人」。抽屉里是审批人设置;当前是指定角色,并按单据公司过滤。

本系统截图:BPMN 画布选中用户任务,右侧「用户任务」折叠项里是规则类型和跳过表达式。
策略清单
BpmTaskCandidateStrategyEnum 的整数码。界面文案以 CANDIDATE_STRATEGY 为准。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
USER 30 | 指定用户,逗号分隔用户编号 | .../strategy/user/BpmTaskCandidateUserStrategy.java |
ROLE 10 | 指定角色。参数 roleIds 或 roleIds|scope | .../strategy/user/BpmTaskCandidateRoleStrategy.java |
POST 22 | 指定岗位,编码方式同角色 | .../strategy/user/BpmTaskCandidatePostStrategy.java |
DEPT_MEMBER 20 | 指定部门的成员,含负责人 | .../strategy/dept/BpmTaskCandidateDeptMemberStrategy.java |
DEPT_LEADER 21 | 指定部门的负责人 | .../strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java |
MULTI_DEPT_LEADER_MULTI 23 | 连续多级部门负责人,参数带 |层级 | .../strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java |
START_USER_SELECT 35 | 发起人自选 | .../strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java |
APPROVE_USER_SELECT 34 | 当前审批人指定下一节点 | .../strategy/dept/BpmTaskCandidateApproveUserSelectStrategy.java |
START_USER 36 | 发起人本人 | .../strategy/user/BpmTaskCandidateStartUserStrategy.java |
START_USER_DEPT_LEADER 37 | 发起人部门负责人,参数是层级 | .../strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java |
START_USER_DEPT_LEADER_MULTI 38 | 发起人连续多级部门负责人 | .../strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java |
USER_GROUP 40 | 用户组,表 bpm_user_group | .../strategy/user/BpmTaskCandidateGroupStrategy.java |
FORM_USER 50 | 表单里某个用户字段 | .../strategy/form/BpmTaskCandidateFormUserStrategy.java |
FORM_DEPT_LEADER 51 | 表单里部门字段 + 层级,字段|层级 | .../strategy/form/BpmTaskCandidateFormDeptLeaderStrategy.java |
EXPRESSION 60 | 流程表达式,结果转成用户编号集合 | .../strategy/other/BpmTaskCandidateExpressionStrategy.java |
ASSIGN_EMPTY 1 | 候选人为空时的兜底,不是设计器选项 | .../strategy/other/BpmTaskCandidateAssignEmptyStrategy.java |
角色、岗位先收组织范围再匹配
ROLE / POST 的 candidateParam 可带后缀 company / dept / none。先算出单据公司或部门上的任职,再在这些人里找角色或岗位。不要先取全租户并集再按部门滤:总部管理员兼职子公司时,会被子公司单据捞成审批人。
组织范围编码见 BpmCandidateOrgScopeUtils,枚举 BpmCandidateOrgScopeEnum。老模型没有后缀,按 none(不过滤)。
角色、部门、用户
日常单据这三种最多。
指定用户:参数就是用户编号列表。validateParam 调 AdminUserApi.validateUserList。算出的集合不再按组织过滤。
指定部门成员 / 负责人:参数是部门编号列表。成员走 getUserListByDeptIds;负责人取部门的 leaderUserId。部门不存在,发布阶段就会被拦住。
指定角色:PermissionApi.getUserRoleIdListByRoleIds。带组织范围时改走 getUserRoleIdListByRoleIdsAndDeptIds,结果进 BpmCandidateResolveCache,同一组角色不要反复打权限服务。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
| 组织过滤 | 按流程变量里的公司 / 部门收窄 | .../candidate/BpmCandidateOrgScopeFilter.java |
| 解析缓存 | 角色、岗位按范围缓存候选人 | .../candidate/BpmCandidateResolveCache.java |
| 前端后缀 | formatCandidateIdsWithScope | .../simple-process-design/helpers.ts |
| 角色多选 | BpmCandidateMultiPicker type=role | BPMN UserTask.vue、SIMPLE user-task-node-config.vue |
部门负责人、发起人部门负责人还要填层级。发起人部门从流程变量的部门编号取,没有再回落到发起人档案部门。
发起人自选
节点策略是 35 时,发起当时就要把这个节点的人带上。后端只认流程变量里、按节点 id 分组的用户编号。
示意图:人选是实例变量,不是节点上的固定名单。策略只按当前节点 id 取值。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
| 流程表单发起 | POST /bpm/process-instance/create | BpmProcessInstanceController#createProcessInstance |
| 入参 Map | key 是节点 id,value 是用户编号数组 | .../vo/instance/BpmProcessInstanceCreateReqVO.java |
| 业务 RPC | BpmProcessInstanceCreateReqDTO.startUserSelectAssignees | yudao-module-bpm-api/.../BpmProcessInstanceCreateReqDTO.java |
| 写入变量 | createProcessInstance0 | BpmProcessInstanceServiceImpl |
| 发起校验 | 只校验预测会走到的自选节点 | #validateStartUserSelectAssignees |
| 未选人 | PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG | ErrorCodeConstants |
| 流程表单页 | 右侧时间线点选 | .../processInstance/create/modules/form.vue |
| 业务表单底栏 | 提交前弹自选 | .../components/basic-form/basic-form.vue |
| 示例请假单 | key oa_leave,提交时带上 Map | .../views/bpm/oa/leave/create.vue |
预测用 getApprovalDetail,入参带当前表单变量。分支还没走到的自选节点,这次可以不选。发起后变量里已经有该节点的人,审批通过时 validateAndSetNextAssignees 不会覆盖。
业务单据按用印、用车那样:submit 接收 { startUserSelectAssignees },再传给 submitProcessInstance。不要自己造一套字段名。
审批人自选(34)是下一节点的人,存在 PROCESS_APPROVE_USER_SELECT_ASSIGNEES。当前人点通过时用 nextAssignees 补上。第一个用户任务不能配这个策略,发布会失败。
自选节点空着发不出去
validateStartUserSelectAssignees 按节点名称报「任务(xx)的候选人未配置」。流程表单在提交前就会提示「请选择xx的候选人」;业务表单走 basic-form 的提交气泡。不要只在后端兜。
运行时怎么落到办理人
创建 UserTask 时,Flowable 调自定义 Behavior,不再用 BPMN 里写死的 assignee。
示意图:单人任务责任到人,集合里只留一个办理人。多人同时批,走会签行为,见 会签、或签、依次审批。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
| 算人 | @DataPermission(enable = false),避免数据权限把人滤空 | BpmTaskCandidateInvoker#calculateUsersByTask |
| 预览未走节点 | calculateUsersByActivity,给时间线、发起预览 | 同上 |
| 空人 | 指定人 / 转流程管理员;都不配就仍为空 | BpmUserTaskAssignEmptyHandlerTypeEnum |
| 与发起人相同 | 配置了跳过且候选多于 1 人,才去掉发起人 | #removeStartUserIfSkip |
Invoker 包在 FlowableUtils.execute(tenantId) 里,避免异步触发时租户丢了。禁用用户先删掉;空人策略不再删,免得兜底也被掏空。
表达式策略用 FlowableUtils.getExpressionValue。预览未运行节点时,变量不齐会 PropertyNotFoundException,这时返回空集合,不要把整单预测打挂。
单人任务会随机挑人
ROLE、DEPT_MEMBER 常算出一堆人。没开会签时,Behavior 用 RandomUtil.randomInt 只留一个。要部门里人人都能批,配或签;要按顺序批,配依次审批。
加一种策略
- 在
BpmTaskCandidateStrategyEnum加码和文案。 - 写一个
@Component实现BpmTaskCandidateStrategy:getStrategy、validateParam、至少一种calculateUsers*。 - SIMPLE / BPMN 的
CANDIDATE_STRATEGY和CandidateStrategy补同一数字。 - 字典
bpm_task_candidate_strategy补一项,给已办、详情翻译。 - 需要参数就保持
isParamRequired()默认值;像自选那样没有参数就重写成false。
发布时 Invoker 会调 validateParam。角色、用户、部门这类要在这里调 API 校验,不要拖到运行时才发现编号是空的。
配置与操作
管理员在模型第三步点开审批节点,选 candidateStrategy 和 candidateParam(角色可带 company / dept 后缀),保存后发布。只改未发布草稿不影响在途单。
发起人自选(35)要在提交时带 startUserSelectAssignees,员工在发起页或业务表单底栏选人。空着发不出去。
开启见 流程功能设计。截图见上文 SIMPLE 抽屉和 BPMN 面板。
