flowable工作流中的并行流程回退

目前来看,flowable工作流引擎支持的回退功能仅能实现回退简单的串行流程,对于并行流程则需要我们自己去实现额外的逻辑

像这样一个庞大的串行与并行结合的流程,如何实现处处可回退

一、保证流程图并行流程部分均使用成对的并行网关(fork和join)

因为当流程从并行网关外回退到并行网关内的其中一个任务节点时,此对网关内的其他任务节点将丢失状态,具体表现为ACT_RU_EXECUTION表中数据只有一个任务节点对应的记录。

因此使用成对的并行网关可以在流程运行到网关内时,遍历得到当前应有多少个任务节点记录,再与ACT_RU_EXECUTION表中数据对比,判断是否需要补充任务节点对应的记录以及补充多少

二、在流程走到join网关前补充ACT_RU_EXECUTION表的记录

1.在BpmTaskServiceImpl中引入processEngineConfiguration

2.在BpmTaskServiceImpl中的approveTask方法里调用PgExecutionHandler()方法

因为此方法不适用所有流程,所有用if包裹使其在特定流程模型才能使用,防止对其他流程造成影响

3.定义PgExecutionHandler()方法

    //并行网关任务回退处理,补充任务execution记录
    void PgExecutionHandler(Task task){
        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
        FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
        if (source == null) {
            throw exception(TASK_NOT_EXISTS);
        }
        //获取任务出线
        List<SequenceFlow> outFlow = ((FlowNode) source).getOutgoingFlows();
        if(outFlow != null && outFlow.size() > 0){
            ExecutionEntityManager executionEntityManager = processEngineConfiguration.getExecutionEntityManager();
            for(SequenceFlow sequenceFlow : outFlow){
                //如果下一个节点是并行网关,那么获取并行网关信息
                FlowElement nextElement = sequenceFlow.getTargetFlowElement();
                if(nextElement instanceof ParallelGateway){//判断下一个节点是不是并行网关
                    List<SequenceFlow> inFlows = ((FlowNode) nextElement).getIncomingFlows();
                    if(inFlows != null && inFlows.size() > 1){//以入线数判断是不是join网关
                        //获取execution记录
                        List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(task.getProcessInstanceId()).list();
                        executions = executions.stream().filter(e ->e.getParentId() != null).collect(Collectors.toList());//把父excution过滤掉
                        List<String> flowIds = new ArrayList<>();//存储走过的线路
                        Set<String> activitIds = new HashSet<>();//存储活动的节点
                        List<String> executionActIds = executions.stream().map(e ->e.getActivityId()).collect(Collectors.toList());
                        for(SequenceFlow inSequenceFlow : inFlows){//遍历join网关每一条入线分支寻找活动节点,活动节点数加上join网关execution数等于join网关入线数说明当前join网关不需要补充数据,反之则需要补充
                            traverseBranches(executionActIds, inSequenceFlow,1,flowIds,activitIds);
                        }
                        int joinExecution = executionActIds.stream().filter(e ->e.equals(nextElement.getId())).collect(Collectors.toList()).size();
                        if((activitIds.size() + joinExecution) < inFlows.size()){
                            for(int i = 0 ; i <inFlows.size() - (activitIds.size() + joinExecution);i ++){
                                saveExecution(executionEntityManager,task,nextElement);
                            }
                        }
                    }
                }
            }
        }
    }

4.定义PgExecutionHandler()中所用的traverseBranches()方法

    //从join网关往回遍历直到fork网关,寻找活动节点
    void traverseBranches(List<String> executionActIds,SequenceFlow inFlow,int num,List<String> flowIds,Set<String> activitIds){//这里num初始设1
        if(flowIds.contains(inFlow.getId())){
            return;//走过的线就不走了
        }else {
            flowIds.add(inFlow.getId());
        }
        FlowElement flowElement = inFlow.getSourceFlowElement();
        if(flowElement != null){
            if(flowElement instanceof ParallelGateway){ //如果迭代到并行网关,判断是不是子join并行网关,通过入线条数判断,入线条数大于1则为join网关,num+1,这种通过计数的方法要求fork和join必须成对出现,否则失效
                List<SequenceFlow> nextInFlows = ((FlowNode) flowElement).getIncomingFlows();
                if(nextInFlows.size() > 1){
                    num = num + 1;
                }else {
                    num = num - 1;
                }
                if(num != 0){
                    for(SequenceFlow sequenceFlow : nextInFlows) {
                        traverseBranches(executionActIds, sequenceFlow, num, flowIds,activitIds);
                    }
                }
            }else {
                if(executionActIds.contains(flowElement.getId())){
                    activitIds.add(flowElement.getId());
                }else {//继续迭代直到遇到fork网关
                    List<SequenceFlow> inFlows = ((FlowNode) flowElement).getIncomingFlows();
                    for(SequenceFlow sequenceFlow : inFlows){
                        traverseBranches(executionActIds,sequenceFlow,num,flowIds,activitIds);
                    }
                }
            }
        }
    }

5.定义保存exection记录的方法saveExecution()

    //保存exection记录
    void saveExecution(ExecutionEntityManager executionEntityManager,Task task,FlowElement joinElement){
        IdGenerator idGenerator = processEngineConfiguration.getIdGenerator();
        ExecutionEntity execution = executionEntityManager.create();
        execution.setId(idGenerator.getNextId());
        execution.setProcessDefinitionId(task.getProcessDefinitionId());
        execution.setParentId(task.getProcessInstanceId());
        execution.setProcessInstanceId(task.getProcessInstanceId());
        execution.setRootProcessInstanceId(task.getProcessInstanceId());
        execution.setActive(false);
        ((ExecutionEntityImpl) execution).setActivityId(joinElement.getId());
        execution.setSuspensionState(1);
        execution.setRevision(0);
        execution.setStartTime(new Date());
        execution.setTenantId(null);
        ((ExecutionEntityImpl) execution).setIsScope(false);
        ((ExecutionEntityImpl) execution).setCountEnabled(true);
        managementService.executeCommand(new SaveExecutionCmd(execution,idGenerator));
    }

6.修改BpmTaskServiceImpl中的returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO)方法

因为普通任务在ACT_RU_EXECUTION表中对应一条记录,而会签任务对应多条记录,所有在此处需要做额外处理

将方法中最后的

runtimeService.createChangeActivityStateBuilder()
                    .processInstanceId(currentTask.getProcessInstanceId())
                    .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多)
                            reqVO.getTargetTaskDefinitionKey()) // targetKey 跳转到的节点(1)
                    .changeState();

改为

if(currentTask.getProcessDefinitionId().startsWith("npi:")){
            List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(currentTask.getProcessInstanceId()).list();
            List<String> executionIds = executions.stream().filter(e ->returnTaskKeyList.contains(e.getActivityId()) && e.getParentId().equals(currentTask.getProcessInstanceId())).map(e ->e.getId()).collect(Collectors.toList());
            // 3. 执行驳回
            runtimeService.createChangeActivityStateBuilder()
                    .processInstanceId(currentTask.getProcessInstanceId())
                    .moveExecutionsToSingleActivityId(executionIds, // 当前要跳转的任务对应的execution( 1 或多)
                            reqVO.getTargetTaskDefinitionKey()) // targetKey 跳转到的节点(1)
                    .changeState();
            mesNpiProjectMapper.deleteGatewayExecutesByProcessInstanceId(currentTask.getProcessInstanceId());
        }else{
            runtimeService.createChangeActivityStateBuilder()
                    .processInstanceId(currentTask.getProcessInstanceId())
                    .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多)
                            reqVO.getTargetTaskDefinitionKey()) // targetKey 跳转到的节点(1)
                    .changeState();
        }

if中的条件根据自己的情况修改,目的也是使其在特定流程下使用

7.新增类SaveExecutionCmd,可放在BpmTaskServiceImpl相同目录下

package cn.iocoder.yudao.module.bpm.service.task;

import org.flowable.common.engine.impl.cfg.IdGenerator;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.idm.engine.impl.util.CommandContextUtil;
import org.jeecg.common.exception.JeecgBootException;

import java.io.Serializable;

public class SaveExecutionCmd implements Command<Void>, Serializable {
    private static final long serialVersionUID = 1L;
    protected ExecutionEntity entity;
    protected IdGenerator idGenerator;

    public SaveExecutionCmd(ExecutionEntity entity, IdGenerator idGenerator) {
        this.entity = entity;
        this.idGenerator = idGenerator;
    }

    @Override
    public Void execute(CommandContext commandContext) {
        if (this.entity == null) {
            throw new JeecgBootException("executionEntity is null");
        } else {
            CommandContextUtil.getDbSqlSession(commandContext).insert(this.entity,idGenerator);
        }
        return null;
    }
}

这样总体就完成了

参考:Flowable回退到并行任务的bug解决思路_flowable并行网关回退-优快云博客

<think>好的,我需要帮助用户了解Flowable工作流中多实例并行的实现方式和配置方法。首先,我应该回忆一下Flowable的相关知识,特别是多实例任务的部分。根据用户提供的引用内容,特别是引用[1]、[3]和[4],这些提到了多实例的配置、会签以及并行临时任务的添加。 首先,多实例在Flowable中通常用于处理需要多个参与者的情况,比如会签。用户提到的多实例并行流程设计,应该涉及到在用户任务节点上设置多实例属性。引用[3]指出,多实例任务会签是通过在userTask节点配置多实例属性实现的,这可能包括设置并行类型、集合变量、实例数量等。 接下来,我需要考虑配置的具体步骤。根据引用[1],多实例类型有串行和并行两种,这里用户关注的是并行。因此,配置时需要在流程设计中将multiInstanceType设置为parallel。此外,集合变量如userList用来指定参与者,元素变量如userItem用于迭代,这可能需要在XML中定义。 然后,流程设计器的操作部分也很重要。用户可能使用Flowable UI设计器,所以需要指导如何在图形界面中设置多实例属性。例如,在任务属性面板中勾选多实例,选择并行,并填写相关变量。 另外,引用[4]提到添加并行临时任务的情况,虽然用户的问题主要在于配置已有的多实例并行任务,但这也可能涉及动态添加任务的方法,不过可能超出当前问题范围,但可以作为补充信息提及。 还需要注意,多实例并行的执行逻辑,比如所有分支同时创建,需要全部完成才能继续流程。这可能涉及到会签的完成条件,如使用nrOfInstances和nrOfActiveInstances变量来判断是否所有实例都完成。 最后,用户可能遇到的问题,比如回退并行流程的困难,引用[2]指出Flowable默认不支持并行流程回退,需要自定义逻辑,这可能是一个注意事项。 总结一下,回答的结构应该包括基本概念、XML配置示例、设计器操作步骤、执行逻辑和注意事项,并引用提供的参考文献。需要确保使用正确的中文术语,并按照用户的要求分点说明,保持清晰的结构。</think>### Flowable工作流多实例并行实现与配置方法 #### 一、基本概念 多实例并行(Multi-instance Parallel)是Flowable中用于**同时处理多个相同任务分支**的机制,常用于会签、并行审批等场景。其核心逻辑是通过定义任务节点的`multiInstanceLoopCharacteristics`属性实现[^3]。 #### 二、XML配置方式 在流程定义文件(BPMN 2.0 XML)中,需为任务节点添加以下属性: ```xml <userTask id="multiInstanceTask" name="并行审批"> <multiInstanceLoopCharacteristics isSequential="false" flowable:collection="userList" flowable:elementVariable="userItem"> <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 1}</completionCondition> </multiInstanceLoopCharacteristics> </userTask> ``` - `isSequential="false"`:声明并行模式[^1] - `collection`:指定参与者集合变量,如`${userList}` - `elementVariable`:迭代元素变量,用于任务分配 - `completionCondition`:完成条件,此处表示任意一个实例完成即可推进流程 #### 三、流程设计器操作步骤(Flowable Modeler) 1. **设置多实例属性** - 在用户任务属性面板勾选`Multi instance` - 选择`Parallel`类型 - 配置集合变量:`${userList}` 2. **定义任务分配** ```javascript ${userItem} //直接使用元素变量 //或复杂表达式 ${taskService.createTaskQuery().taskCandidateUser(userItem).list()} ``` 3. **配置完成条件** - 使用预定义变量: $$nrOfInstances = \text{总实例数}, \quad nrOfCompletedInstances = \text{已完成实例数}$$ - 示例条件表达式:`${nrOfCompletedInstances == nrOfInstances}`(全部完成) #### 四、执行逻辑特点 1. **实例创建**:根据集合变量`userList`长度自动生成并行任务 2. **变量隔离**:每个实例拥有独立的`execution`上下文[^3] 3. **流程推进**:需满足`completionCondition`才继续后续节点 #### 五、特殊场景实现 1. **动态添加并行任务**(运行时修改): ```java runtimeService.addMultiInstanceExecution( "taskId", "流程实例ID", Collections.singletonMap("新增用户变量", newUser) ); ``` 此方法可在不修改流程定义的情况下追加并行分支[^4] 2. **多实例会签**: - 需配合`Candidate users`配置 - 每个实例对应不同处理人 - 支持`nrOfActiveInstances`监控进行中任务数 #### 六、注意事项 1. **变量作用域**:集合变量需在流程实例级别定义 2. **历史数据**:并行任务会产生多条历史记录 3. **回退限制**:原生引擎不支持并行流程回滚,需自行实现补偿逻辑[^2] ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值