用户需求
有客户反映一个子流程审批时将审批通过看成了退回修改,想让其退回到上一步。
局部流程图如下图
这是并行网关下的一个分支流程,该订单在另一个分支中已经走了多步,当前分支的情况是:在审批这个节点本来要选择退回待修改结果误选了审批通过以致该该子流程直接结束。我在网上搜了体面的流程退回,但有流程不能已结束的要求,我也搜到一篇手动退回一个结束的流程的文章(文末链接),就按照该文章的思路尝试将已结束的流程退回到子流程审批的节点。
先查看流程相关表的执行情况(具体流程可能会有不同情况),在备份库中,手动在子流程审批节点推进到结束节点,查看sql执行。
insert into ACT_HI_VARINST (ID_, PROC_INST_ID_, EXECUTION_ID_, TASK_ID_, NAME_, REV_, VAR_TYPE_, BYTEARRAY_ID_, DOUBLE_, LONG_ , TEXT_, TEXT2_, CREATE_TIME_, LAST_UPDATED_TIME_) values ('1895008', '1892897', '1892903', '1892991',... update ACT_HI_TASKINST set PROC_DEF_ID_ = 'Process_1:62:572507', EXECUTION_ID_ = '1892903',... WHERE ID_ = '1892991'; update ACT_RU_EXECUTION set REV_ = 5, BUSINESS_KEY_ = null, PROC_DEF_ID_ = 'Process_1:62:572507', ACT_ID_ = 'EndEvent_1vt7vcc', IS_ACTIVE_ = false, IS_CONCURRENT_ = false, IS_SCOPE_ = false, IS_EVENT_SCOPE_ = false, IS_MI_ROOT_ = false, PARENT_ID_ = '1892897', ... WHERE ID_ = '1892903' and REV_ = 4; select * FROM ACT_RU_VARIABLE WHERE TASK_ID_ = '1892991' and NAME_= 'result'; select * FROM ACT_RU_VARIABLE WHERE EXECUTION_ID_ = '1892903' and NAME_= 'result' and TASK_ID_ is null; delete FROM ACT_RU_IDENTITYLINK WHERE ID_ = '1892992'; delete FROM ACT_RU_TASK WHERE ID_ = '1892991' and REV_ = 4; delete FROM ACT_RU_EXECUTION WHERE ID_ = '1892903' and REV_ = 5;
#等等
语句非常多,一部分任务表,节点表和详情表的修改操作忽略了,值得关注的是插入了一条历史变量表,即判断审批通过的变量;更新了历史任务表;更新了当前execution,当前节点变成EndEvent,即结束节点。后面又删除了运行时的人员表,任务表和execution。根据参考文章,手动恢复ACT_RU_TASK,ACT_RU_EXECUTION,ACT_RU_IDENTITYLINK和ACT_RU_VARIABLE。涉及到审批相关变量result的ACT_RU_VARIABLE表的两个查询语句,第一据查询 WHERE TASK_ID_ = '1892991' and NAME_= 'result';这个task_id是指子流程审批任务的id,第二句execution_id是该分支的execution,以及task_id为空,这里需要注意,如果手动退回后报无法解析expression表达式的错,可能是查找变量时没有找到,这里如果不恢复ACT_RU_VARIABLE表查找的也可能为全局变量result。
后面回退遇到问题时也可以结合执行的sql找出问题,这里不必赘述。
代码实现回退逻辑
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricVariableInstance;
import org.activiti.engine.impl.persistence.entity.*;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.*;
/** * phone为审批人的assignee值,nodeName是子流程审批节点名 */ @Transactional public void revoke(String businessId, String phone, String nodeName) { // 1.找到taskId,下面选取符合的assignee可以直接在query中筛选同businessId List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery() .processInstanceBusinessKey(businessId) .orderByTaskCreateTime() .desc() .list(); String myTaskId = null; HistoricTaskInstance myTask = null; for(HistoricTaskInstance hti : htiList) { if(phone.equals(hti.getAssignee()) && nodeName.equals(hti.getName())) { myTaskId = hti.getId(); // 选取最后一个符合条件的task,即为最近的task myTask = hti; break; } } if(null==myTaskId) { throw new MyException(101,"该任务非当前用户提交,无法撤回"); } /* 关于实体类,自己创建太麻烦,我直接使用activiti自带的类,格式一般是“xxxEntityImpl”比如execution的实体用 ExecutionEntityImpl,具体的字段信息可以跟正规流程的一一对照。这里有个坑,ExecutionEntityImpl对象的isScope属性初始化为true,必须修改为false,因为一个流程*实例只有一个主execution的isScope字段为true,其他execution的必须为false,如果设置错了,手动退回后只能正常推进一次节点,然后该execution会被异常地删除,导致流转异常。 execution的acti_id字段指当前正在执行的节点id,ExecutionEntityImpl对象通过setcurrentFlowElement的方式赋值,需要从流程定义中获取。 推进时流程的exection对象会根据这条记录获取,对象包含自身execution,主execution和活动节点activity对象 */ // 2.创建execution // 获取当前节点对象,塞到executionEntity中 FlowElement flowElement = repositoryService.getBpmnModel(myTask.getProcessDefinitionId()).getMainProcess().getFlowElement(myTask.getTaskDefinitionKey()); ExecutionEntityImpl executionEntity = new ExecutionEntityImpl(); executionEntity.setId(myTask.getExecutionId()); executionEntity.setProcessInstanceId(myTask.getProcessInstanceId()); executionEntity.setProcessDefinitionId(myTask.getProcessDefinitionId()); executionEntity.setParentId(myTask.getProcessInstanceId()); executionEntity.setRootProcessInstanceId(myTask.getProcessInstanceId()); executionEntity.setCurrentFlowElement(flowElement); executionEntity.setStartTime(new Date()); // 子execution分支的is_scope的值为0,主分支的为1。这里一定要设置,否则只能暂时退回,再走一个节点该execution就会被删除 executionEntity.setScope(false); executionEntity.setActive(true); /* mapper对象的sql可以写对应表的增删改查,我拿的是activiti中自身的mapperxml文件的对应语句(源码activiti-engine-6.0.0的db.mapping包下),稍作修改 */ int ie = activitiMapper.insertExecution(executionEntity); if ( ie == 1) { logger.error("新添execution成功,id={}",myTask.getExecutionId()); } /* task的assignee字段其他的ru_task没有,我也没有加 */ // 3.insert task into ACT_RU_TASK TaskEntityImpl runTask = new TaskEntityImpl(); runTask.setId(myTask.getId()); runTask.setName(myTask.getName()); runTask.setPriority(myTask.getPriority()); //runTask.setAssignee(myTask.getAssignee()); runTask.setCreateTime(new Date()); runTask.setExecutionId(myTask.getExecutionId()); runTask.setProcessInstanceId(myTask.getProcessInstanceId()); runTask.setProcessDefinitionId(myTask.getProcessDefinitionId()); runTask.setTaskDefinitionKey(myTask.getTaskDefinitionKey()); int it = activitiMapper.insertTask(runTask); if( it == 1) { logger.error("新添task成功,id={}",myTask.getId()); } /* identityLink只需要添加该任务的一条记录,用taskId筛选即可 */ // 4.insert task identitylink into ACT_RUN_IDENTITYLINK List<HistoricIdentityLinkEntityImpl> historicIdentityLinkEntities = activitiMapper.selectHistoricIdentityLinksByProcessInstanceAndTaskId(myTask.getProcessInstanceId(),myTask.getId()); for ( HistoricIdentityLinkEntity e: historicIdentityLinkEntities) { if (e.getTaskId() != null && e.getTaskId().equals(myTaskId)) { IdentityLinkEntityImpl identityLink = new IdentityLinkEntityImpl(); identityLink.setId(e.getId()); identityLink.setType(e.getType()); identityLink.setUserId(e.getUserId()); identityLink.setGroupId(e.getGroupId()); identityLink.setTaskId(e.getTaskId()); identityLink.setProcessInstanceId(e.getProcessInstanceId()); activitiMapper.insertIdentityLink(identityLink); } } /* 这里的variable根据情况加一条审批相关的变量,可能不用加;加了话需要注意子流程审批用到的变量是什么语句查询的,有executionId和taskId的条件话就按条件给字段赋值。推节点时如果查不到会报无法解析表达式异常 */ // 5.insert variables into ACT_RU_VARIABLE List<HistoricVariableInstance> historicVariableInstances = processEngine.getHistoryService().createHistoricVariableInstanceQuery().executionId(myTask.getExecutionId()).list(); List<VariableInstanceEntity> variables = new ArrayList<>(); for (HistoricVariableInstance e : historicVariableInstances) { if ("result".equals(e.getVariableName())) { VariableInstanceEntityImpl v = new VariableInstanceEntityImpl(); v.setName(e.getVariableName()); v.setId(e.getId()); v.setTypeName(e.getVariableTypeName()); v.setExecutionId(myTask.getExecutionId()); v.setProcessInstanceId(e.getProcessInstanceId()); v.setTextValue(String.valueOf(e.getValue())); variables.add(v); break; } } int ih = activitiMapper.bulkInsertVariableInstance(variables); // 6.更改业务表,修改业务相关的逻辑 }
因为理解不了源码,对流程的实际流转过程认识有限,就只能通过多次尝试,观察相关表的记录变更查看执行情况:查看act_ru_exection表,business_key筛选该订单,然后根据该实例proc_inst_id查看task/variable/identityLink表,根据taskid查看variable/identitylink的该任务的记录。在修改了不少细节,检查了是否对后续子流程和其他子流程有影响后,成功回退了流程。
相关链接
https://blog.csdn.net/jiajane/article/details/103901187 -【Activiti】activiti 手动回退一个结束的流程
感谢这位博主的思路和逻辑