# scaffold 项目之工作流的选择审批人、发起人自选
# 简介
可以看 scaffold 项目之工作流 文章
# 开始使用
当用户发起流程(审批)时,会根据【流程定义】创建对应的审批任务,审批任务会根据【审批人规则】,自动分配给对应的审批人。
审批人可以是固定的角色(比如上级、HR 等),也可以是发起人自选。
# 选择审批人
我们有 8 种审批人规则类型,它们都是统一的一种实现,如下图所示:

public interface BpmTaskCandidateStrategy { | |
/** | |
* 对应策略 | |
* | |
* @return 策略 | |
*/ | |
BpmTaskCandidateStrategyEnum getStrategy(); | |
/** | |
* 校验参数 | |
* | |
* @param param 参数 | |
*/ | |
void validateParam(String param); | |
/** | |
* 基于执行任务,获得任务的候选用户们 | |
* | |
* @param execution 执行任务 | |
* @return 用户编号集合 | |
*/ | |
Set<Long> calculateUsers(DelegateExecution execution, String param); | |
/** | |
* 是否一定要输入参数 | |
* | |
* @return 是否 | |
*/ | |
default boolean isParamRequired() { | |
return true; | |
} | |
} |
- 关键是
calculateUsers方法,用于计算候选的审批人。
最终,Flowable 在创建审批任务,分配审批人时,会通过 [BpmUserTaskActivityBehavior]=> [BpmTaskCandidateInvoker] => [BpmTaskCandidateStrategy],时序图如下:

# 自定义 BpmTaskCandidateStrategy 策略
① 第一步,在 [BpmTaskCandidateStrategyEnum] 中,自定义一个枚举值。
然后,在 bpm_task_candidate_strategy 数据字典中,配置对应的枚举值。
② 第二步,创建一个 BpmTaskCandidateStrategy 的实现类,实现对应的逻辑,并注册成 Spring Bean 即可。
# 发起人自选
上述的 8 种审批人规则类型中,有一种是【发起人自选】,它是一种特殊的审批人规则类型。在发起流程时,发起人需要选择对应任务的发起人。
下面,我们分别来看看在【流程表单】、【业务表单】下的例子。
# 【流程表单】示例
① 第一步,设置在 [《审批接入(流程表单)》] 的 “部门领导审批” 任务的审批人规则为【发起人自选】。
注意,需要保存流程,并进行发布流程。
② 选择该流程,进行发起流程,则可以看到 “指定审批人” 的表单。
选择 “指定审批人” 为 “源码”,然后进行提交。 如图:

③ 查看发起流程的详情,可以看到审批人为 “源码”。
# 【业务表单】示例
① 第一步,设置在 《审批接入(业务表单)》 的 “领导审批” 任务的审批人规则为【发起人自选】。
注意,需要保存流程,并进行发布流程。
② 选择该流程,进行发起流程,则可以看到 “指定审批人” 的表单。如下图所示:

选择 “指定审批人” 为 “源码”,然后进行提交。
③ 查看发起流程的详情,可以看到审批人为 “源码”。
# 【流程表单】实现原理
① 在【流程表单】的流程发起界面 [ views/bpm/processInstance/create/index.vue ] 中,从后端读取【流程定义】时,发现有任务节点的审批人规则是【发起人自选】,则会增加一个 “指定审批人” 表单项。
② 在提交流程时,会将选择的审批人,存储到 Flowable 的流程的 variables 中。如下所示:
private String createProcessInstance0(Long userId, ProcessDefinition definition, | |
Map<String, Object> variables, String businessKey, | |
Map<String, List<Long>> startUserSelectAssignees) { | |
// 1.1 校验流程定义 | |
if (definition == null) { | |
throw exception(PROCESS_DEFINITION_NOT_EXISTS); | |
} | |
if (definition.isSuspended()) { | |
throw exception(PROCESS_DEFINITION_IS_SUSPENDED); | |
} | |
BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService | |
.getProcessDefinitionInfo(definition.getId()); | |
if (processDefinitionInfo == null) { | |
throw exception(PROCESS_DEFINITION_NOT_EXISTS); | |
} | |
// 1.2 校验是否能够发起 | |
if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { | |
throw exception(PROCESS_INSTANCE_START_USER_CAN_START); | |
} | |
// 1.3 校验发起人自选审批人 | |
validateStartUserSelectAssignees(userId, definition, startUserSelectAssignees, variables); | |
// 2. 创建流程实例 | |
if (variables == null) { | |
variables = new HashMap<>(); | |
} | |
FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 | |
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID | |
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 | |
BpmProcessInstanceStatusEnum.RUNNING.getStatus()); | |
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED, true); // 跳过表达式需要添加此变量为 true,不影响没配置 skipExpression 的节点 | |
if (CollUtil.isNotEmpty(startUserSelectAssignees)) { | |
// 设置流程变量,发起人自选审批人 | |
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, | |
startUserSelectAssignees); | |
} | |
// 3. 创建流程 | |
ProcessInstanceBuilder processInstanceBuilder = runtimeService.createProcessInstanceBuilder() | |
.processDefinitionId(definition.getId()) | |
.businessKey(businessKey) | |
.variables(variables); | |
// 3.1 创建流程 ID | |
BpmModelMetaInfoVO.ProcessIdRule processIdRule = processDefinitionInfo.getProcessIdRule(); | |
if (processIdRule != null && Boolean.TRUE.equals(processIdRule.getEnable())) { | |
processInstanceBuilder.predefineProcessInstanceId(processIdRedisDAO.generate(processIdRule)); | |
} | |
// 3.2 流程名称 | |
processInstanceBuilder.name(generateProcessInstanceName(userId, definition, processDefinitionInfo, variables)); | |
// 3.3 发起流程实例 | |
ProcessInstance instance = processInstanceBuilder.start(); | |
return instance.getId(); | |
} |
③ 最终审批任务在分配审批人时,会读取这个 variables ,然后分配给对应的审批人。如下所示:
package cn.tzzfj.scaffold.module.bpm.framework.flowable.core.candidate.strategy.dept; | |
import cn.hutool.core.collection.CollUtil; | |
import cn.hutool.core.lang.Assert; | |
import cn.tzzfj.scaffold.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; | |
import cn.tzzfj.scaffold.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | |
import cn.tzzfj.scaffold.module.bpm.framework.flowable.core.util.FlowableUtils; | |
import cn.tzzfj.scaffold.module.bpm.service.task.BpmProcessInstanceService; | |
import com.google.common.collect.Sets; | |
import jakarta.annotation.Resource; | |
import org.flowable.bpmn.model.BpmnModel; | |
import org.flowable.engine.delegate.DelegateExecution; | |
import org.flowable.engine.runtime.ProcessInstance; | |
import org.springframework.context.annotation.Lazy; | |
import org.springframework.stereotype.Component; | |
import java.util.LinkedHashSet; | |
import java.util.List; | |
import java.util.Map; | |
/** | |
* <p> Project: scaffold - BpmTaskCandidateStartUserSelectStrategy </p> | |
* | |
* 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类 | |
* | |
* @author Tz | |
* @date 2025/10/25 15:26 | |
* @version 1.0.0 | |
* @since 1.0.0 | |
*/ | |
@Component | |
public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { | |
@Resource | |
@Lazy // 延迟加载,避免循环依赖 | |
private BpmProcessInstanceService processInstanceService; | |
@Override | |
public BpmTaskCandidateStrategyEnum getStrategy() { | |
return BpmTaskCandidateStrategyEnum.START_USER_SELECT; | |
} | |
@Override | |
public void validateParam(String param) {} | |
@Override | |
public boolean isParamRequired() { | |
return false; | |
} | |
@Override | |
public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) { | |
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); | |
Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId()); | |
Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); | |
Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", | |
execution.getProcessInstanceId()); | |
// 获得审批人 | |
List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); | |
return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet(); | |
} | |
@Override | |
public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, | |
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { | |
if (processVariables == null) { | |
return Sets.newLinkedHashSet(); | |
} | |
Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processVariables); | |
if (startUserSelectAssignees == null) { | |
return Sets.newLinkedHashSet(); | |
} | |
// 获得审批人 | |
List<Long> assignees = startUserSelectAssignees.get(activityId); | |
return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet(); | |
} | |
} |
# 【业务表单】实现原理
① 在【业务表单】的流程发起界面 [ views/bpm/oa/leave/create.vue ]
② 在提交流程时,会将选择的审批人,存储到 Flowable 的流程的 variables 中。
后续的流程,和「【流程表单】实现原理」就是一致的!总结来说,就是创建流程指定审批人,创建任务使用指定审批人。
# 流程表达式
除了自定义 BpmTaskCandidateStrategy 策略外,还可以使用流程表达式,实现审批人的动态分配。