# scaffold 项目之工作流的选择审批人、发起人自选

# 简介

可以看 scaffold 项目之工作流 文章

# 开始使用

当用户发起流程(审批)时,会根据【流程定义】创建对应的审批任务,审批任务会根据【审批人规则】,自动分配给对应的审批人。

审批人可以是固定的角色(比如上级、HR 等),也可以是发起人自选。

# 选择审批人

我们有 8 种审批人规则类型,它们都是统一的一种实现,如下图所示:

BpmTaskCandidateStrategy 类图

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 策略外,还可以使用流程表达式,实现审批人的动态分配。