# scaffold 项目之工作流的流程发起、取消、重新发起

# 简介

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

# 开始使用

支持多人审批,包括:

  • 会签(并行会签):同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点
  • 或签(并行或签):同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点
  • 依次审批(顺序会签):同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点

# 多人审批

# 会签(并行会签)

在流程设计中,选择【任务节点】 然后在选择【多人审批方式】

会签(可同时审批,至少 % 人必须审批通过)

# 或签(并行或签)

在流程设计中,选择【任务节点】 然后在选择【多人审批方式】

或签(可同时审批,有一人通过即可)

因此,会签和或签的差异,就在于完成条件的不同。

# 随机挑选一人审批

在流程设计中,选择【任务节点】 然后在选择【多人审批方式】

就是随机选中指定一名人员审批

# 依次审批

在流程设计中,选择【任务节点】 然后在选择【多人审批方式】

依次审批配置:顺序多重事件,实现多个人按顺序审批

因此,依次审批和会签的差异,就在于是否并行审批。


按照这个思路,实现 “票签”,是不是很简单?!

友情提示:什么是 “票签”?

指同一个审批节点设置多个人,如 A、B、C 三人,当通过比例大于 50% 就能进入下一个节点。

# 实现原理

在 [《选择审批人、发起人自选》] 小节中,我们看到使用 BpmUserTaskActivityBehavior 实现了审批任务的审批人分配。实际上,还有两个 Behavior 类,如下所示:

package cn.tzzfj.scaffold.module.bpm.framework.flowable.core.behavior;
import cn.tzzfj.scaffold.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.flowable.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
/**
 * <p> Project: scaffold - BpmActivityBehaviorFactory  </p>
 *
 * 自定义的 ActivityBehaviorFactory 实现类,目的如下:
 *
 * 1. 自定义 {@link #createUserTaskActivityBehavior (UserTask)}:实现自定义的流程任务的 assignee 负责人的分配
 * 
 * @author Tz
 * @date 2025/10/25 15:26
 * @version 1.0.0
 * @since 1.0.0
 */
@Setter
public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
    private BpmTaskCandidateInvoker taskCandidateInvoker;
    @Override
    public UserTaskActivityBehavior createUserTaskActivityBehavior(UserTask userTask) {
        return new BpmUserTaskActivityBehavior(userTask)
                .setTaskCandidateInvoker(taskCandidateInvoker);
    }
    @Override
    public ParallelMultiInstanceBehavior createParallelMultiInstanceBehavior(Activity activity,
                                                                             AbstractBpmnActivityBehavior behavior) {
        return new BpmParallelMultiInstanceBehavior(activity, behavior)
                .setTaskCandidateInvoker(taskCandidateInvoker);
    }
    @Override
    public SequentialMultiInstanceBehavior createSequentialMultiInstanceBehavior(Activity activity,
                                                                                 AbstractBpmnActivityBehavior behavior) {
        return new BpmSequentialMultiInstanceBehavior(activity, behavior)
                .setTaskCandidateInvoker(taskCandidateInvoker);
    }
}
  • [BpmParallelMultiInstanceBehavior] 并行 + 多实例(单节点多任务)的 Behavior 类
  • [BpmSequentialMultiInstanceBehavior] 顺序 + 多实例(单节点多任务)的 Behavior 类

# 并行 BpmParallelMultiInstanceBehavior

① BpmParallelMultiInstanceBehavior 实现 Flowable ParallelMultiInstanceBehavior 类,实现单节点多任务的审批人 “计算”。如下所示:

package cn.tzzfj.scaffold.module.bpm.framework.flowable.core.behavior;
/**
 * <p> Project: scaffold - BpmParallelMultiInstanceBehavior  </p>
 *
 * 自定义的【并行】的【多个】流程任务的 assignee 负责人的分配
 *
 * 第一步,基于分配规则,计算出分配任务的【多个】候选人们。
 *
 * 第二步,将【多个】任务候选人们,设置到 DelegateExecution 的 collectionVariable 变量中,以便 BpmUserTaskActivityBehavior 使用它
 * 
 * @author Tz
 * @date 2025/10/25 15:26
 * @version 1.0.0
 * @since 1.0.0
 */
@Setter
public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior {
    private BpmTaskCandidateInvoker taskCandidateInvoker;
    public BpmParallelMultiInstanceBehavior(Activity activity,
                                            AbstractBpmnActivityBehavior innerActivityBehavior) {
        super(activity, innerActivityBehavior);
    }
    /**
     * 重写该方法,主要实现两个功能:
     * 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的
     * 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人
     *
     * 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量
     *
     * @param execution 执行任务
     * @return 数量
     */
    @Override
    protected int resolveNrOfInstances(DelegateExecution execution) {
        // 情况一:UserTask 节点
        if (execution.getCurrentFlowElement() instanceof UserTask) {
            // 第一步,设置 collectionVariable 和 CollectionVariable
            // 从  execution.getVariable () 读取所有任务处理人的 key
            super.collectionExpression = null; //collectionExpression 和 collectionVariable 是互斥的
            super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
            // 从 execution.getVariable () 读取当前所有任务处理的人的 key
            super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
            // 第二步,获取任务的所有处理人
            @SuppressWarnings("unchecked")
            Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
            if (assigneeUserIds == null) {
                assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
                if (CollUtil.isEmpty(assigneeUserIds)) {
                    // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
                    // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
                    // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
                    assigneeUserIds = SetUtils.asSet((Long) null);
                }
                execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
            }
            return assigneeUserIds.size();
        }
        // 情况二:CallActivity 节点
        if (execution.getCurrentFlowElement() instanceof CallActivity) {
            FlowElement flowElement = execution.getCurrentFlowElement();
            Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement);
            if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType())) {
                return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class);
            }
            if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) {
                return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size();
            }
        }
        return super.resolveNrOfInstances(execution);
    }
}

② BpmUserTaskActivityBehavior,判断是多实例的情况,则复用 BpmParallelMultiInstanceBehavior “计算” 结果,直接设置审批人。如下所示:

package cn.tzzfj.scaffold.module.bpm.framework.flowable.core.behavior;
/**
 * <p> Project: scaffold - BpmUserTaskActivityBehavior  </p>
 *
 * 自定义的【单个】流程任务的 assignee 负责人的分配
 *
 * 第一步,基于分配规则,计算出分配任务的【单个】候选人。如果找不到,则直接报业务异常,不继续执行后续的流程;
 *
 * 第二步,随机选择一个候选人,则选择作为 assignee 负责人。
 * 
 * @author Tz
 * @date 2025/10/25 15:26
 * @version 1.0.0
 * @since 1.0.0
 */
@Slf4j
public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
    @Setter
    private BpmTaskCandidateInvoker taskCandidateInvoker;
    public BpmUserTaskActivityBehavior(UserTask userTask) {
        super(userTask);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    protected void handleAssignments(TaskService taskService, String assignee, String owner,
        List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager,
        DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
        // 第一步,获得任务的候选用户
        Long assigneeUserId = calculateTaskCandidateUsers(execution);
        // 第二步,设置作为负责人
        if (assigneeUserId != null) {
            TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
        }
    }
    private Long calculateTaskCandidateUsers(DelegateExecution execution) {
        // 情况一,如果是多实例的任务,例如说会签、或签等情况,则从 Variable 中获取。
        // 顺序审批可见 BpmSequentialMultiInstanceBehavior,并发审批可见 BpmSequentialMultiInstanceBehavior
        if (super.multiInstanceActivityBehavior != null) {
            return execution.getVariable(super.multiInstanceActivityBehavior.getCollectionElementVariable(), Long.class);
        }
        // 情况二,如果非多实例的任务,则计算任务处理人
        // 第一步,先计算可处理该任务的处理人们
        Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
        if (CollUtil.isEmpty(candidateUserIds)) {
            return null;
        }
        // 第二步,后随机选择一个任务的处理人
        // 疑问:为什么一定要选择一个任务处理人?
        // 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。
        //      如果希望一个任务可以同时被多个人处理,可以考虑使用 BpmParallelMultiInstanceBehavior 实现的会签 or 或签。
        int index = RandomUtil.randomInt(candidateUserIds.size());
        return CollUtil.get(candidateUserIds, index);
    }
    @Override
    protected void handleCategory(CreateUserTaskBeforeContext beforeContext, ExpressionManager expressionManager,
                                  TaskEntity task, DelegateExecution execution) {
        ProcessDefinitionEntity processDefinitionEntity = CommandContextUtil.getProcessDefinitionEntityManager().findById(execution.getProcessDefinitionId());
        if (processDefinitionEntity == null) {
            log.warn("[handleCategory][任务编号({}) 找不到流程定义({})]", task.getId(), execution.getProcessDefinitionId());
            return;
        }
        task.setCategory(processDefinitionEntity.getCategory());
    }
}

所以,先是 BpmParallelMultiInstanceBehavior 计算审批任务数量 + 审批人列表,然后 BpmUserTaskActivityBehavior 直接设置审批人。

# 顺序 BpmSequentialMultiInstanceBehavior

① BpmSequentialMultiInstanceBehavior 实现 Flowable SequentialMultiInstanceBehavior 类,实现单节点多任务的审批人 “计算”。如下所示:

package cn.tzzfj.scaffold.module.bpm.framework.flowable.core.behavior;
/**
 * <p> Project: scaffold - BpmSequentialMultiInstanceBehavior  </p>
 *
 * 自定义的【串行】的【多个】流程任务的 assignee 负责人的分配
 *
 * 本质上,实现和 {@link BpmParallelMultiInstanceBehavior} 一样,只是继承的类不一样
 * 
 * @author Tz
 * @date 2025/10/25 15:26
 * @version 1.0.0
 * @since 1.0.0
 */
@Setter
public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceBehavior {
    private BpmTaskCandidateInvoker taskCandidateInvoker;
    public BpmSequentialMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) {
        super(activity, innerActivityBehavior);
    }
    /**
     * 逻辑和 {@link BpmParallelMultiInstanceBehavior#resolveNrOfInstances (DelegateExecution)} 类似
     *
     * 差异的点:是在【第二步】的时候,需要返回 LinkedHashSet 集合!因为它需要有序!
     */
    @Override
    protected int resolveNrOfInstances(DelegateExecution execution) {
        // 情况一:UserTask 节点
        if (execution.getCurrentFlowElement() instanceof UserTask) {
            // 第一步,设置 collectionVariable 和 CollectionVariable
            // 从  execution.getVariable () 读取所有任务处理人的 key
            super.collectionExpression = null; //collectionExpression 和 collectionVariable 是互斥的
            super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
            // 从 execution.getVariable () 读取当前所有任务处理的人的 key
            super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
            // 第二步,获取任务的所有处理人
            // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
            @SuppressWarnings("unchecked")
            Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
            if (assigneeUserIds == null) {
                assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
                if (CollUtil.isEmpty(assigneeUserIds)) {
                    // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
                    // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
                    // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
                    assigneeUserIds = SetUtils.asSet((Long) null);
                }
                execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
            }
            return assigneeUserIds.size();
        }
        // 情况二:CallActivity 节点
        if (execution.getCurrentFlowElement() instanceof CallActivity) {
            FlowElement flowElement = execution.getCurrentFlowElement();
            Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement);
            if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType())) {
                return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class);
            }
            if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) {
                return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size();
            }
        }
        return super.resolveNrOfInstances(execution);
    }
    @Override
    protected void executeOriginalBehavior(DelegateExecution execution, ExecutionEntity multiInstanceRootExecution, int loopCounter) {
        // 参见 https://t.zsxq.com/53Meo 情况
        if (execution.getCurrentFlowElement() instanceof CallActivity
            || execution.getCurrentFlowElement() instanceof SubProcess) {
            super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter);
            return;
        }
        // 参见 https://gitee.com/zhijiantianya/scaffold-cloud/issues/IC239F
        super.collectionExpression = null;
        super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
        super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
        super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter);
    }
}

② 还是使用 BpmUserTaskActivityBehavior,逻辑是一模一样的。

所以,BpmSequentialMultiInstanceBehavior 和 BpmParallelMultiInstanceBehavior 基本是一致的,差异只是前者返回的是 LinkedHashSet 有序集合。最终,还是交给 Flowable 到底是一次性创建多个审批任务,还是按照顺序创建多个审批任务。