前言
Camunda驳回至上一节点,网上大多都是回退至开始节点,这样逻辑比较简单清晰。但实际使用中,往往需要驳回至上一节点,甚至需要连续驳回多次。
流程驳回关键的一步就是获取到要回退到的节点。其中,需要理解的是(假设流程节点是1,2,3,4,5这样顺序的流程),如果当前已办历史节点是{1,2,3},如果4节点执行了驳回操作,那么历史节点会变成{1,2,3,4},而当前待办节点是3, 节点4执行了驳回操作,4同样会出现在历史执行节点中。
也就是说,当历史节点是{1,2,3,4}时,有两种可能性:一种是常规的顺序执行,节点3执行通过到达4节点,当前待办节点是4;另外一种是节点4执行了驳回操作,当前待办节点是3。
- 思路一(实现代码见getLastNode):
分两种情况:
1、当前节点不在历史节点里
2、当前节点在历史节点里
假设,已办历史节点 resultList={1,2,3}
(1)当前节点是4,表示3是完成节点,4驳回需要回退到3
(2)当前节点是2,表示3是驳回节点,3驳回到当前2节点,2驳回需要回退到1
其他驳回过的情况也都包含在情况2中。
- 思路二:
假设,已办历史节点 resultList={1,2,3}
无论当前节点在不在历史节点里,一律将当前节点追加到已办历史节点列表中,再调用currentNodeInHis方法获取上一节点。
(1)当前节点是4,追加后 resultList={1,2,3,4}
(2)当前节点是2,追加后 resultList={1,2,3,2}
一、版本
camunda : 7.15.0
spring-boot : 2.4.3
spring-cloud :2020.0.1
二、实现
1、回退至上一节点
代码如下:
引入的jar包
import io.swagger.annotations.ApiOperation;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.history.HistoricActivityInstance;
import org.camunda.bpm.engine.runtime.ActivityInstance;
import org.camunda.bpm.engine.task.Task;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.*;
实现方法
其中RejectBean 实体类有三个属性:processInstanceId, currentUserId, rejectComment
public Result rejectToLastNode(@RequestBody RejectBean rejectBean) {
//获取当前task
Task task = taskService.createTaskQuery()
.taskAssignee(rejectBean.getCurrentUserId()) //当前登录用户的id
.processInstanceId(rejectBean.getProcessInstanceId())
.singleResult();
ActivityInstance tree = runtimeService.getActivityInstance(rejectBean.getProcessInstanceId());
//获取所有已办用户任务节点
List<HistoricActivityInstance> resultList = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(rejectBean.getProcessInstanceId())
.activityType("userTask")
.finished()
.orderByHistoricActivityInstanceEndTime()
.asc()
.list();
if(null == resultList || resultList.size() == 0){
return ResultFactory.buildFailResult("当前任务无法驳回!");
}
//得到第一个任务节点的id
HistoricActivityInstance historicActivityInstance = resultList.get(0);
String startActId = historicActivityInstance.getActivityId();
if(startActId.equals(task.getTaskDefinitionKey())){
return ResultFactory.buildFailResult("开始节点无法驳回!");
}
//得到上一个任务节点的ActivityId和待办人
Map<String,String> lastNode = getLastNode(resultList,task.getTaskDefinitionKey());
if(null == lastNode){
return ResultFactory.buildFailResult("回退节点异常!");
}
String toActId = lastNode.get("toActId");
String assignee = lastNode.get("assignee");
//设置流程中的可变参数
Map<String, Object> taskVariable = new HashMap<>(2);
taskVariable.put("user", assignee);
//taskVariable.put("formName", "流程驳回");
taskService.createComment(task.getId(), rejectBean.getProcessInstanceId(), "驳回原因:" + rejectBean.getRejectComment());
runtimeService.createProcessInstanceModification(rejectBean.getProcessInstanceId())
.cancelActivityInstance(getInstanceIdForActivity(tree, task.getTaskDefinitionKey()))//关闭相关任务
.setAnnotation("进行了驳回到上一个任务节点操作")
.startBeforeActivity(toActId)//启动目标活动节点
.setVariables(taskVariable)//流程的可变参数赋值
.execute();
return ResultFactory.buildSuccessResult(null);
}
private String getInstanceIdForActivity(ActivityInstance activityInstance, String activityId) {
ActivityInstance instance = getChildInstanceForActivity(activityInstance, activityId);
if (instance != null) {
return instance.getId();
}
return null;
}
private ActivityInstance getChildInstanceForActivity(ActivityInstance activityInstance, String activityId) {
if (activityId.equals(activityInstance.getActivityId())) {
return activityInstance;
}
for (ActivityInstance childInstance : activityInstance.getChildActivityInstances()) {
ActivityInstance instance = getChildInstanceForActivity(childInstance, activityId);
if (instance != null) {
return instance;
}
}
return null;
}
/**
* 获取上一节点信息
* 分两种情况:
* 1、当前节点不在历史节点里
* 2、当前节点在历史节点里
* 比如,resultList={1,2,3}
* (1)当前节点是4,表示3是完成节点,4驳回需要回退到3
* (2)当前节点是2,表示3是驳回节点,3驳回到当前2节点,2驳回需要回退到1
* 其他驳回过的情况也都包含在情况2中。
*
* @param resultList 历史节点列表
* @param currentActivityId 当前待办节点ActivityId
* @return 返回值:上一节点的ActivityId和待办人(toActId, assignee)
*/
private static Map<String,String> getLastNode(List<HistoricActivityInstance> resultList, String currentActivityId){
Map<String,String> backNode = new HashMap<>();
//新建一个有序不重复集合
LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap();
for(HistoricActivityInstance hai : resultList){
linkedHashMap.put(hai.getActivityId(),hai.getAssignee());
}
//分两种情况:当前节点在不在历史节点里面,当前节点在历史节点里
//情况1、当前节点不在历史节点里
int originSize = resultList.size();
int duplicateRemovalSize = linkedHashMap.size();
//判断历史节点中是否有重复节点
//if(originSize == duplicateRemovalSize){
boolean flag = false;
for(Map.Entry entry: linkedHashMap.entrySet()){
if(currentActivityId.equals(entry.getKey())){
flag = true;
break;
}
}
// if(flag){
// //当前节点在历史节点里:最后一个节点是回退节点
// return currentNodeInHis(linkedHashMap, currentActivityId);
// }
if(!flag) {
//当前节点不在历史节点里:最后一个节点是完成节点
HistoricActivityInstance historicActivityInstance = resultList.get(originSize - 1);
backNode.put("toActId", historicActivityInstance.getActivityId());
backNode.put("assignee", historicActivityInstance.getAssignee());
return backNode;
}
//}
//情况2、当前节点在历史节点里(已回退过的)
return currentNodeInHis(linkedHashMap, currentActivityId);
}
private static Map<String,String> currentNodeInHis(LinkedHashMap<String,String> linkedHashMap,String currentActivityId){
//情况2、当前节点在历史节点里(已回退过的)
Map<String,String> backNode = new HashMap<>();
ListIterator<Map.Entry<String,String>> li = new ArrayList<>(linkedHashMap.entrySet()).listIterator();
//System.out.println("已回退过的");
while (li.hasNext()){
Map.Entry<String,String> entry = li.next();
if(currentActivityId.equals(entry.getKey())){
li.previous();
Map.Entry<String,String> previousEntry = li.previous();
backNode.put("toActId",previousEntry.getKey());
backNode.put("assignee",previousEntry.getValue());
return backNode;
}
}
return null;
}
2、回退至开始节点
public Result rejectToFirstNode(@RequestBody RejectBean rejectBean) {
//String rejectMessage="项目的金额款项结算不正确";
Task task = taskService.createTaskQuery()
.taskAssignee(rejectBean.getCurrentUserId()) //当前登录用户的id
.processInstanceId(rejectBean.getProcessInstanceId())
.singleResult();
ActivityInstance tree = runtimeService.getActivityInstance(rejectBean.getProcessInstanceId());
List<HistoricActivityInstance> resultList = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(rejectBean.getProcessInstanceId())
.activityType("userTask")
.finished()
.orderByHistoricActivityInstanceEndTime()
.asc()
.list();
if(null == resultList || resultList.size()<2){
return ResultFactory.buildFailResult("第一个用户节点无法驳回!");
}
//得到第一个任务节点的id
HistoricActivityInstance historicActivityInstance = resultList.get(0);
String toActId = historicActivityInstance.getActivityId();
String assignee = historicActivityInstance.getAssignee();
//设置流程中的可变参数
Map<String, Object> taskVariable = new HashMap<>(2);
taskVariable.put("user", assignee);
//taskVariable.put("formName", "流程驳回");
taskService.createComment(task.getId(), rejectBean.getProcessInstanceId(), "驳回原因:" + rejectBean.getRejectComment());
runtimeService.createProcessInstanceModification(rejectBean.getProcessInstanceId())
.cancelActivityInstance(getInstanceIdForActivity(tree, task.getTaskDefinitionKey()))//关闭相关任务
.setAnnotation("进行了驳回到第一个任务节点操作")
.startBeforeActivity(toActId)//启动目标活动节点
.setVariables(taskVariable)//流程的可变参数赋值
.execute();
return ResultFactory.buildSuccessResult(null);
}
3、测试方法
测试时只需给getAcitvityId和getAssingee返回值赋值即可,本身也就只需要这两个属性
public static void main(String[] args) {
HistoricActivityInstance hai1 = new HistoricActivityInstance() {
@Override
public String getId() {
return "act-1";
}
@Override
public String getParentActivityInstanceId() {
return null;
}
@Override
public String getActivityId() {
return "act-1";
}
@Override
public String getActivityName() {
return null;
}
@Override
public String getActivityType() {
return null;
}
@Override
public String getProcessDefinitionKey() {
return null;
}
@Override
public String getProcessDefinitionId() {
return null;
}
@Override
public String getRootProcessInstanceId() {
return null;
}
@Override
public String getProcessInstanceId() {
return null;
}
@Override
public String getExecutionId() {
return null;
}
@Override
public String getTaskId() {
return null;
}
@Override
public String getCalledProcessInstanceId() {
return null;
}
@Override
public String getCalledCaseInstanceId() {
return null;
}
@Override
public String getAssignee() {
return "user-1";
}
@Override
public Date getStartTime() {
return null;
}
@Override
public Date getEndTime() {
return null;
}
@Override
public Long getDurationInMillis() {
return null;
}
@Override
public boolean isCompleteScope() {
return false;
}
@Override
public boolean isCanceled() {
return false;
}
@Override
public String getTenantId() {
return null;
}
@Override
public Date getRemovalTime() {
return null;
}
};
HistoricActivityInstance hai2 = new HistoricActivityInstance() {
@Override
public String getId() {
return "act-2";
}
@Override
public String getParentActivityInstanceId() {
return null;
}
@Override
public String getActivityId() {
return "act-2";
}
@Override
public String getActivityName() {
return null;
}
@Override
public String getActivityType() {
return null;
}
@Override
public String getProcessDefinitionKey() {
return null;
}
@Override
public String getProcessDefinitionId() {
return null;
}
@Override
public String getRootProcessInstanceId() {
return null;
}
@Override
public String getProcessInstanceId() {
return null;
}
@Override
public String getExecutionId() {
return null;
}
@Override
public String getTaskId() {
return null;
}
@Override
public String getCalledProcessInstanceId() {
return null;
}
@Override
public String getCalledCaseInstanceId() {
return null;
}
@Override
public String getAssignee() {
return "user-2";
}
@Override
public Date getStartTime() {
return null;
}
@Override
public Date getEndTime() {
return null;
}
@Override
public Long getDurationInMillis() {
return null;
}
@Override
public boolean isCompleteScope() {
return false;
}
@Override
public boolean isCanceled() {
return false;
}
@Override
public String getTenantId() {
return null;
}
@Override
public Date getRemovalTime() {
return null;
}
};
HistoricActivityInstance hai3 = new HistoricActivityInstance() {
@Override
public String getId() {
return "act-3";
}
@Override
public String getParentActivityInstanceId() {
return null;
}
@Override
public String getActivityId() {
return "act-3";
}
@Override
public String getActivityName() {
return null;
}
@Override
public String getActivityType() {
return null;
}
@Override
public String getProcessDefinitionKey() {
return null;
}
@Override
public String getProcessDefinitionId() {
return null;
}
@Override
public String getRootProcessInstanceId() {
return null;
}
@Override
public String getProcessInstanceId() {
return null;
}
@Override
public String getExecutionId() {
return null;
}
@Override
public String getTaskId() {
return null;
}
@Override
public String getCalledProcessInstanceId() {
return null;
}
@Override
public String getCalledCaseInstanceId() {
return null;
}
@Override
public String getAssignee() {
return "user-3";
}
@Override
public Date getStartTime() {
return null;
}
@Override
public Date getEndTime() {
return null;
}
@Override
public Long getDurationInMillis() {
return null;
}
@Override
public boolean isCompleteScope() {
return false;
}
@Override
public boolean isCanceled() {
return false;
}
@Override
public String getTenantId() {
return null;
}
@Override
public Date getRemovalTime() {
return null;
}
};
HistoricActivityInstance hai4 = new HistoricActivityInstance() {
@Override
public String getId() {
return "act-4";
}
@Override
public String getParentActivityInstanceId() {
return null;
}
@Override
public String getActivityId() {
return "act-4";
}
@Override
public String getActivityName() {
return null;
}
@Override
public String getActivityType() {
return null;
}
@Override
public String getProcessDefinitionKey() {
return null;
}
@Override
public String getProcessDefinitionId() {
return null;
}
@Override
public String getRootProcessInstanceId() {
return null;
}
@Override
public String getProcessInstanceId() {
return null;
}
@Override
public String getExecutionId() {
return null;
}
@Override
public String getTaskId() {
return null;
}
@Override
public String getCalledProcessInstanceId() {
return null;
}
@Override
public String getCalledCaseInstanceId() {
return null;
}
@Override
public String getAssignee() {
return "user-4";
}
@Override
public Date getStartTime() {
return null;
}
@Override
public Date getEndTime() {
return null;
}
@Override
public Long getDurationInMillis() {
return null;
}
@Override
public boolean isCompleteScope() {
return false;
}
@Override
public boolean isCanceled() {
return false;
}
@Override
public String getTenantId() {
return null;
}
@Override
public Date getRemovalTime() {
return null;
}
};
List<HistoricActivityInstance> resultList = new ArrayList<>();
resultList.add(hai1);
resultList.add(hai2);
resultList.add(hai3);
// resultList.add(hai4);
// resultList.add(hai3);
Map<String,String> map = getLastNode(resultList,"act-2");
System.out.println("toActId ==> "+map.get("toActId"));
System.out.println("assignee ==> "+map.get("assignee"));
}