目前来看,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;
}
}
这样总体就完成了