前言
本文的代碼中沒有對流程做任何特殊處理,用的都是流程本身的數據,因此可以通用,直接複製粘貼即可
方法不支持多對多跳轉
回退不能夠直接回退到子流程上,我這裏按照只能回退到用戶任務節點處理的
駁回可以直接駁回到子流程開始
可根據自己需要對代碼進行調整
支持場景
並行網關,高級網關,包容網關,會籤,子流程
功能描述
駁回
參數:當前任務ID,駁回原因
直接根據歷史數據,獲取上個用戶任務節點,進行跳轉
退回
參數:當前任務ID,駁回的節點Key
回退只能回退到串行路線上
髒數據
什麼是髒數據
如圖,假如我從節點6
回退到節點2
,這時紅框中的數據對於我們來說是一個歷史記錄,但是對於流程來說,這些數據是無意義的廢棄數據
串行樣例
並行樣例
會籤樣例
髒數據清洗效果圖
流程圖
對應數據
清洗效果截圖,由於沒有循環,可以看到除了會籤對應 3 條實例數據,其他節點清洗後都只有一個
sid-4FE193FF-E1E2-4F87-8424-2F00BCA9AFC5 是網關,沒給它命名
完整代碼
FlowableApiController.java
/**
* 流程收回/駁回
* @param taskId 當前任務ID
* @param comment 審覈意見
* @return
*/
@GetMapping(value = "/flowTackback/{taskId}")
public String flowTackback(@PathVariable(value = "taskId") String taskId, @RequestParam(value = "comment", defaultValue = "") String comment) {
if (taskService.createTaskQuery().taskId(taskId).singleResult().isSuspended()) {
return JsonUtil.toJSON(ErrorMsg.ERROR.setNewErrorMsg("任務處於掛起狀態"));
}
// 當前任務 task
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 獲取流程定義信息
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
// 獲取所有節點信息
Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0);
// 獲取全部節點列表,包含子節點
Collection<FlowElement> allElements = FlowableUtils.getAllElements(process.getFlowElements(), null);
// 獲取當前任務節點元素
FlowElement source = null;
if (allElements != null) {
for (FlowElement flowElement : allElements) {
// 類型爲用戶節點
if (flowElement.getId().equals(task.getTaskDefinitionKey())) {
// 獲取節點信息
source = flowElement;
}
}
}
// 目的獲取所有跳轉到的節點 targetIds
// 獲取當前節點的所有父級用戶任務節點
// 深度優先算法思想:延邊迭代深入
List<UserTask> parentUserTaskList = FlowableUtils.iteratorFindParentUserTasks(source, null, null);
if (parentUserTaskList == null || parentUserTaskList.size() == 0) {
return JsonUtil.toJSON(ErrorMsg.ERROR.setNewErrorMsg("當前節點爲初始任務節點,不能駁回"));
}
// 獲取活動 ID 即節點 Key
List<String> parentUserTaskKeyList = new ArrayList<>();
parentUserTaskList.forEach(item -> parentUserTaskKeyList.add(item.getId()));
// 獲取全部歷史節點活動實例,即已經走過的節點歷史,數據採用開始時間升序
List<HistoricTaskInstance> historicTaskInstanceList = historyService.createHistoricTaskInstanceQuery().processInstanceId(task.getProcessInstanceId()).orderByHistoricTaskInstanceStartTime().asc().list();
// 數據清洗,將回滾導致的髒數據清洗掉
List<String> lastHistoricTaskInstanceList = FlowableUtils.historicTaskInstanceClean(allElements, historicTaskInstanceList);
// 此時歷史任務實例爲倒序,獲取最後走的節點
List<String> targetIds = new ArrayList<>();
// 循環結束標識,遇到當前目標節點的次數
int number = 0;
StringBuilder parentHistoricTaskKey = new StringBuilder();
for (String historicTaskInstanceKey : lastHistoricTaskInstanceList) {
// 當會籤時候會出現特殊的,連續都是同一個節點歷史數據的情況,這種時候跳過
if (parentHistoricTaskKey.toString().equals(historicTaskInstanceKey)) {
continue;
}
parentHistoricTaskKey = new StringBuilder(historicTaskInstanceKey);
if (historicTaskInstanceKey.equals(task.getTaskDefinitionKey())) {
number ++;
}
// 在數據清洗後,歷史節點就是唯一一條從起始到當前節點的歷史記錄,理論上每個點只會出現一次
// 在流程中如果出現循環,那麼每次循環中間的點也只會出現一次,再出現就是下次循環
// number == 1,第一次遇到當前節點
// number == 2,第二次遇到,代表最後一次的循環範圍
if (number == 2) {
break;
}
// 如果當前歷史節點,屬於父級的節點,說明最後一次經過了這個點,需要退回這個點
if (parentUserTaskKeyList.contains(historicTaskInstanceKey)) {
targetIds.add(historicTaskInstanceKey);
}
}
// 目的獲取所有需要被跳轉的節點 currentIds
// 取其中一個父級任務,因爲後續要麼存在公共網關,要麼就是串行公共線路
UserTask oneUserTask = parentUserTaskList.get(0);
// 獲取所有正常進行的任務節點 Key,這些任務不能直接使用,需要找出其中需要撤回的任務
List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
List<String> runTaskKeyList = new ArrayList<>();
runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey()));
// 需駁回任務列表
List<String> currentIds = new ArrayList<>();
// 通過父級網關的出口連線,結合 runTaskList 比對,獲取需要撤回的任務
List<UserTask> currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(oneUserTask, runTaskKeyList, null, null);
currentUserTaskList.forEach(item -> currentIds.add(item.getId()));
// 規定:並行網關之前節點必須需存在唯一用戶任務節點,如果出現多個任務節點,則並行網關節點默認爲結束節點,原因爲不考慮多對多情況
if (targetIds.size() > 1 && currentIds.size() > 1) {
return JsonUtil.toJSON(ErrorMsg.ERROR.setNewErrorMsg("任務出現多對多情況,無法撤回"));
}
// 循環獲取那些需要被撤回的節點的ID,用來設置駁回原因
List<String> currentTaskIds = new ArrayList<>();
currentIds.forEach(currentId -> runTaskList.forEach(runTask -> {
if (currentId.equals(runTask.getTaskDefinitionKey())) {
currentTaskIds.add(runTask.getId());
}
}));
// 設置駁回信息
currentTaskIds.forEach(item -> {
taskService.addComment(item, task.getProcessInstanceId(), "taskStatus", "reject");
taskService.addComment(item, task.getProcessInstanceId(), "taskMessage", "已駁回");
taskService.addComment(item, task.getProcessInstanceId(), "taskComment", comment);
});
try {
// 如果父級任務多於 1 個,說明當前節點不是並行節點,原因爲不考慮多對多情況
if (targetIds.size() > 1) {
// 1 對 多任務跳轉,currentIds 當前節點(1),targetIds 跳轉到的節點(多)
runtimeService.createChangeActivityStateBuilder().processInstanceId(task.getProcessInstanceId()).moveSingleActivityIdToActivityIds(currentIds.get(0), targetIds).changeState();
}
// 如果父級任務只有一個,因此當前任務可能爲網關中的任務
if (targetIds.size() == 1) {
// 1 對 1 或 多 對 1 情況,currentIds 當前要跳轉的節點列表(1或多),targetIds.get(0) 跳轉到的節點(1)
runtimeService.createChangeActivityStateBuilder().processInstanceId(task.getProcessInstanceId()).moveActivityIdsToSingleActivityId(currentIds, targetIds.get(0)).changeState();
}
} catch (FlowableObjectNotFoundException e) {
return JsonUtil.toJSON(ErrorMsg.ERROR.setNewErrorMsg("未找到流程實例,流程可能已發生變化"));
} catch (FlowableException e) {
return JsonUtil.toJSON(ErrorMsg.ERROR.setNewErrorMsg("無法取消或開始活動"));
}
return JsonUtil.toJSON(ErrorMsg.SUCCESS);
}
/**
* 流程回退
* @param taskId 當前任務ID
* @param targetKey 要回退的任務 Key
* @return
*/
@GetMapping(value = "/flowReturn/{taskId}/{targetKey}")
public String flowReturn(@PathVariable(value = "taskId") String taskId, @PathVariable(value = "targetKey") String targetKey) {
if (taskService.createTaskQuery().taskId(taskId).singleResult().isSuspended()) {
return JsonUtil.toJSON(ErrorMsg.ERROR.setNewErrorMsg("任務處於掛起狀態"));
}
// 當前任務 task
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 獲取流程定義信息
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
// 獲取所有節點信息
Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0);
// 獲取全部節點列表,包含子節點
Collection<FlowElement> allElements = FlowableUtils.getAllElements(process.getFlowElements(), null);
// 獲取當前任務節點元素
FlowElement source = null;
// 獲取跳轉的節點元素
FlowElement target = null;
if (allElements != null) {
for (FlowElement flowElement : allElements) {
// 當前任務節點元素
if (flowElement.getId().equals(task.getTaskDefinitionKey())) {
source = flowElement;
}
// 跳轉的節點元素
if (flowElement.getId().equals(targetKey)) {
target = flowElement;
}
}
}
// 從當前節點向前掃描
// 如果存在路線上不存在目標節點,說明目標節點是在網關上或非同一路線上,不可跳轉
// 否則目標節點相對於當前節點,屬於串行
Boolean isSequential = FlowableUtils.iteratorCheckSequentialReferTarget(source, targetKey, null, null);
if (!isSequential) {
return JsonUtil.toJSON(ErrorMsg.ERROR.setNewErrorMsg("當前節點相對於目標節點,不屬於串行關係,無法回退"));
}
// 獲取所有正常進行的任務節點 Key,這些任務不能直接使用,需要找出其中需要撤回的任務
List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
List<String> runTaskKeyList = new ArrayList<>();
runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey()));
// 需退回任務列表
List<String> currentIds = new ArrayList<>();
// 通過父級網關的出口連線,結合 runTaskList 比對,獲取需要撤回的任務
List<UserTask> currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(target, runTaskKeyList, null, null);
currentUserTaskList.forEach(item -> currentIds.add(item.getId()));
// 循環獲取那些需要被撤回的節點的ID,用來設置駁回原因
List<String> currentTaskIds = new ArrayList<>();
currentIds.forEach(currentId -> runTaskList.forEach(runTask -> {
if (currentId.equals(runTask.getTaskDefinitionKey())) {
currentTaskIds.add(runTask.getId());
}
}));
// 設置回退信息
for (String currentTaskId: currentTaskIds) {
taskService.addComment(currentTaskId, task.getProcessInstanceId(), "taskStatus", "return");
taskService.addComment(currentTaskId, task.getProcessInstanceId(), "taskMessage", "已退回");
taskService.addComment(currentTaskId, task.getProcessInstanceId(), "taskComment", "流程回退到" + target.getName() + "節點");
}
try {
// 1 對 1 或 多 對 1 情況,currentIds 當前要跳轉的節點列表(1或多),targetKey 跳轉到的節點(1)
runtimeService.createChangeActivityStateBuilder().processInstanceId(task.getProcessInstanceId()).moveActivityIdsToSingleActivityId(currentIds, targetKey).changeState();
} catch (FlowableObjectNotFoundException e) {
return JsonUtil.toJSON(ErrorMsg.ERROR.setNewErrorMsg("未找到流程實例,流程可能已發生變化"));
} catch (FlowableException e) {
return JsonUtil.toJSON(ErrorMsg.ERROR.setNewErrorMsg("無法取消或開始活動"));
}
return JsonUtil.toJSON(ErrorMsg.SUCCESS);
}
/**
* 獲取所有可回退的節點
* @param taskId
* @return
*/
@GetMapping(value = "/findReturnUserTask/{taskId}")
public String findReturnUserTask(@PathVariable(value = "taskId") String taskId) {
// 當前任務 task
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 獲取流程定義信息
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
// 獲取所有節點信息,暫不考慮子流程情況
Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0);
Collection<FlowElement> flowElements = process.getFlowElements();
// 獲取當前任務節點元素
UserTask source = null;
if (flowElements != null) {
for (FlowElement flowElement : flowElements) {
// 類型爲用戶節點
if (flowElement.getId().equals(task.getTaskDefinitionKey())) {
source = (UserTask) flowElement;
}
}
}
// 獲取節點的所有路線
List<List<UserTask>> roads = FlowableUtils.findRoad(source, null, null, null);
// 可回退的節點列表
List<UserTask> userTaskList = new ArrayList<>();
for (List<UserTask> road : roads) {
if (userTaskList.size() == 0) {
// 還沒有可回退節點直接添加
userTaskList = road;
} else {
// 如果已有回退節點,則比對取交集部分
userTaskList.retainAll(road);
}
}
return JsonUtil.toJSON(ErrorMsg.SUCCESS.setNewData(userTaskList));
}
FlowableUtils.java
/**
* 流程引擎工具類封裝
* @author: linjinp
* @create: 2019-12-24 13:51
**/
public class FlowableUtils {
public static final Logger logger = LogManager.getLogger(FlowableUtils.class);
/**
* 根據節點,獲取入口連線
* @param source
* @return
*/
public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
List<SequenceFlow> sequenceFlows = null;
if (source instanceof Task) {
sequenceFlows = ((Task) source).getIncomingFlows();
} else if (source instanceof Gateway) {
sequenceFlows = ((Gateway) source).getIncomingFlows();
} else if (source instanceof SubProcess) {
sequenceFlows = ((SubProcess) source).getIncomingFlows();
} else if (source instanceof StartEvent) {
sequenceFlows = ((StartEvent) source).getIncomingFlows();
} else if (source instanceof EndEvent) {
sequenceFlows = ((EndEvent) source).getIncomingFlows();
}
return sequenceFlows;
}
/**
* 根據節點,獲取出口連線
* @param source
* @return
*/
public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
List<SequenceFlow> sequenceFlows = null;
if (source instanceof Task) {
sequenceFlows = ((Task) source).getOutgoingFlows();
} else if (source instanceof Gateway) {
sequenceFlows = ((Gateway) source).getOutgoingFlows();
} else if (source instanceof SubProcess) {
sequenceFlows = ((SubProcess) source).getOutgoingFlows();
} else if (source instanceof StartEvent) {
sequenceFlows = ((StartEvent) source).getOutgoingFlows();
} else if (source instanceof EndEvent) {
sequenceFlows = ((EndEvent) source).getOutgoingFlows();
}
return sequenceFlows;
}
/**
* 獲取全部節點列表,包含子流程節點
* @param flowElements
* @param allElements
* @return
*/
public static Collection<FlowElement> getAllElements(Collection<FlowElement> flowElements, Collection<FlowElement> allElements) {
allElements = allElements == null ? new ArrayList<>() : allElements;
for (FlowElement flowElement : flowElements) {
allElements.add(flowElement);
if (flowElement instanceof SubProcess) {
// 繼續深入子流程,進一步獲取子流程
allElements = FlowableUtils.getAllElements(((SubProcess) flowElement).getFlowElements(), allElements);
}
}
return allElements;
}
/**
* 迭代獲取父級任務節點列表,向前找
* @param source 起始節點
* @param hasSequenceFlow 已經經過的連線的 ID,用於判斷線路是否重複
* @param userTaskList 已找到的用戶任務節點
* @return
*/
public static List<UserTask> iteratorFindParentUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
// 如果該節點爲開始節點,且存在上級子節點,則順着上級子節點繼續迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = iteratorFindParentUserTasks(source.getSubProcess(), hasSequenceFlow, userTaskList);
}
// 根據類型,獲取入口連線
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows != null) {
// 循環找到目標元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
// 如果發現連線重複,說明循環了,跳過這個循環
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已經走過的連線
hasSequenceFlow.add(sequenceFlow.getId());
// 類型爲用戶節點,則新增父級節點
if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
continue;
}
// 類型爲子流程,則添加子流程開始節點出口處相連的節點
if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
// 獲取子流程用戶任務節點
List<UserTask> childUserTaskList = findChildProcessUserTasks((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
// 如果找到節點,則說明該線路找到節點,不繼續向下找,反之繼續
if (childUserTaskList != null && childUserTaskList.size() > 0) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 繼續迭代
userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
}
}
return userTaskList;
}
/**
* 根據正在運行的任務節點,迭代獲取子級任務節點列表,向後找
* @param source 起始節點
* @param runTaskKeyList 正在運行的任務 Key,用於校驗任務節點是否是正在運行的節點
* @param hasSequenceFlow 已經經過的連線的 ID,用於判斷線路是否重複
* @param userTaskList 需要撤回的用戶任務列表
* @return
*/
public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 如果該節點爲開始節點,且存在上級子節點,則順着上級子節點繼續迭代
if (source instanceof EndEvent && source.getSubProcess() != null) {
userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
// 根據類型,獲取出口連線
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows != null) {
// 循環找到目標元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
// 如果發現連線重複,說明循環了,跳過這個循環
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已經走過的連線
hasSequenceFlow.add(sequenceFlow.getId());
// 如果爲用戶任務類型,且任務節點的 Key 正在運行的任務中存在,添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果節點爲子流程節點情況,則從節點中的第一個節點開始獲取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
// 如果找到節點,則說明該線路找到節點,不繼續向下找,反之繼續
if (childUserTaskList != null && childUserTaskList.size() > 0) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 繼續迭代
userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
}
return userTaskList;
}
/**
* 迭代獲取子流程用戶任務節點
* @param source 起始節點
* @param hasSequenceFlow 已經經過的連線的 ID,用於判斷線路是否重複
* @param userTaskList 需要撤回的用戶任務列表
* @return
*/
public static List<UserTask> findChildProcessUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 根據類型,獲取出口連線
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows != null) {
// 循環找到目標元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
// 如果發現連線重複,說明循環了,跳過這個循環
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已經走過的連線
hasSequenceFlow.add(sequenceFlow.getId());
// 如果爲用戶任務類型,且任務節點的 Key 正在運行的任務中存在,添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果節點爲子流程節點情況,則從節點中的第一個節點開始獲取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = findChildProcessUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
// 如果找到節點,則說明該線路找到節點,不繼續向下找,反之繼續
if (childUserTaskList != null && childUserTaskList.size() > 0) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 繼續迭代
userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
}
}
return userTaskList;
}
/**
* 從後向前尋路,獲取所有髒線路上的點
* @param source 起始節點
* @param passRoads 已經經過的點集合
* @param hasSequenceFlow 已經經過的連線的 ID,用於判斷線路是否重複
* @param targets 目標髒線路終點
* @param dirtyRoads 確定爲髒數據的點,因爲不需要重複,因此使用 set 存儲
* @return
*/
public static Set<String> iteratorFindDirtyRoads(FlowElement source, List<String> passRoads, Set<String> hasSequenceFlow, List<String> targets, Set<String> dirtyRoads) {
passRoads = passRoads == null ? new ArrayList<>() : passRoads;
dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
// 如果該節點爲開始節點,且存在上級子節點,則順着上級子節點繼續迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
dirtyRoads = iteratorFindDirtyRoads(source.getSubProcess(), passRoads, hasSequenceFlow, targets, dirtyRoads);
}
// 根據類型,獲取入口連線
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows != null) {
// 循環找到目標元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
// 如果發現連線重複,說明循環了,跳過這個循環
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已經走過的連線
hasSequenceFlow.add(sequenceFlow.getId());
// 新增經過的路線
passRoads.add(sequenceFlow.getSourceFlowElement().getId());
// 如果此點爲目標點,確定經過的路線爲髒線路,添加點到髒線路中,然後找下個連線
if (targets.contains(sequenceFlow.getSourceFlowElement().getId())) {
dirtyRoads.addAll(passRoads);
continue;
}
// 如果該節點爲開始節點,且存在上級子節點,則順着上級子節點繼續迭代
if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
dirtyRoads = findChildProcessAllDirtyRoad((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, dirtyRoads);
// 是否存在子流程上,true 是,false 否
Boolean isInChildProcess = dirtyTargetInChildProcess((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, targets, null);
if (isInChildProcess) {
// 已在子流程上找到,該路線結束
continue;
}
}
// 繼續迭代
dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, targets, dirtyRoads);
}
}
return dirtyRoads;
}
/**
* 迭代獲取子流程髒路線
* 說明,假如回退的點就是子流程,那麼也肯定會回退到子流程最初的用戶任務節點,因此子流程中的節點全是髒路線
* @param source 起始節點
* @param hasSequenceFlow 已經經過的連線的 ID,用於判斷線路是否重複
* @param dirtyRoads 確定爲髒數據的點,因爲不需要重複,因此使用 set 存儲
* @return
*/
public static Set<String> findChildProcessAllDirtyRoad(FlowElement source, Set<String> hasSequenceFlow, Set<String> dirtyRoads) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads;
// 根據類型,獲取出口連線
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows != null) {
// 循環找到目標元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
// 如果發現連線重複,說明循環了,跳過這個循環
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已經走過的連線
hasSequenceFlow.add(sequenceFlow.getId());
// 添加髒路線
dirtyRoads.add(sequenceFlow.getTargetFlowElement().getId());
// 如果節點爲子流程節點情況,則從節點中的第一個節點開始獲取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
dirtyRoads = findChildProcessAllDirtyRoad((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, dirtyRoads);
}
// 繼續迭代
dirtyRoads = findChildProcessAllDirtyRoad(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, dirtyRoads);
}
}
return dirtyRoads;
}
/**
* 判斷髒路線結束節點是否在子流程上
* @param source 起始節點
* @param hasSequenceFlow 已經經過的連線的 ID,用於判斷線路是否重複
* @param targets 判斷髒路線節點是否存在子流程上,只要存在一個,說明髒路線只到子流程爲止
* @param inChildProcess 是否存在子流程上,true 是,false 否
* @return
*/
public static Boolean dirtyTargetInChildProcess(FlowElement source, Set<String> hasSequenceFlow, List<String> targets, Boolean inChildProcess) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
inChildProcess = inChildProcess == null ? false : inChildProcess;
// 根據類型,獲取出口連線
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows != null && !inChildProcess) {
// 循環找到目標元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
// 如果發現連線重複,說明循環了,跳過這個循環
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已經走過的連線
hasSequenceFlow.add(sequenceFlow.getId());
// 如果發現目標點在子流程上存在,說明只到子流程爲止
if (targets.contains(sequenceFlow.getTargetFlowElement().getId())) {
inChildProcess = true;
break;
}
// 如果節點爲子流程節點情況,則從節點中的第一個節點開始獲取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
inChildProcess = dirtyTargetInChildProcess((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, targets, inChildProcess);
}
// 繼續迭代
inChildProcess = dirtyTargetInChildProcess(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, targets, inChildProcess);
}
}
return inChildProcess;
}
/**
* 迭代從後向前掃描,判斷目標節點相對於當前節點是否是串行
* 不存在直接回退到子流程中的情況,但存在從子流程出去到父流程情況
* @param source 起始節點
* @param isSequential 是否串行
* @param hasSequenceFlow 已經經過的連線的 ID,用於判斷線路是否重複
* @param targetKsy 目標節點
* @return
*/
public static Boolean iteratorCheckSequentialReferTarget(FlowElement source, String targetKsy, Set<String> hasSequenceFlow, Boolean isSequential) {
isSequential = isSequential == null ? true : isSequential;
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
// 如果該節點爲開始節點,且存在上級子節點,則順着上級子節點繼續迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
isSequential = iteratorCheckSequentialReferTarget(source.getSubProcess(), targetKsy, hasSequenceFlow, isSequential);
}
// 根據類型,獲取入口連線
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows != null) {
// 循環找到目標元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
// 如果發現連線重複,說明循環了,跳過這個循環
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已經走過的連線
hasSequenceFlow.add(sequenceFlow.getId());
// 如果目標節點已被判斷爲並行,後面都不需要執行,直接返回
if (isSequential == false) {
break;
}
// 這條線路存在目標節點,這條線路完成,進入下個線路
if (targetKsy.equals(sequenceFlow.getSourceFlowElement().getId())) {
continue;
}
if (sequenceFlow.getSourceFlowElement() instanceof StartEvent) {
isSequential = false;
break;
}
// 否則就繼續迭代
isSequential = iteratorCheckSequentialReferTarget(sequenceFlow.getSourceFlowElement(), targetKsy, hasSequenceFlow, isSequential);
}
}
return isSequential;
}
/**
* 從後向前尋路,獲取到達節點的所有路線
* 不存在直接回退到子流程,但是存在回退到父級流程的情況
* @param source 起始節點
* @param passRoads 已經經過的點集合
* @param roads 路線
* @return
*/
public static List<List<UserTask>> findRoad(FlowElement source, List<UserTask> passRoads, Set<String> hasSequenceFlow, List<List<UserTask>> roads) {
passRoads = passRoads == null ? new ArrayList<>() : passRoads;
roads = roads == null ? new ArrayList<>() : roads;
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
// 如果該節點爲開始節點,且存在上級子節點,則順着上級子節點繼續迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads);
}
// 根據類型,獲取入口連線
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows != null && sequenceFlows.size() != 0) {
for (SequenceFlow sequenceFlow: sequenceFlows) {
// 如果發現連線重複,說明循環了,跳過這個循環
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已經走過的連線
hasSequenceFlow.add(sequenceFlow.getId());
// 添加經過路線
if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
passRoads.add((UserTask) sequenceFlow.getSourceFlowElement());
}
// 繼續迭代
roads = findRoad(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, roads);
}
} else {
// 添加路線
roads.add(passRoads);
}
return roads;
}
/**
* 歷史節點數據清洗,清洗掉又回滾導致的髒數據
* @param allElements 全部節點信息
* @param historicTaskInstanceList 歷史任務實例信息,數據採用開始時間升序
* @return
*/
public static List<String> historicTaskInstanceClean(Collection<FlowElement> allElements, List<HistoricTaskInstance> historicTaskInstanceList) {
// 會籤節點收集
List<String> multiTask = new ArrayList<>();
allElements.forEach(flowElement -> {
if (flowElement instanceof UserTask) {
// 如果該節點的行爲爲會籤行爲,說明該節點爲會籤節點
if (((UserTask) flowElement).getBehavior() instanceof ParallelMultiInstanceBehavior || ((UserTask) flowElement).getBehavior() instanceof SequentialMultiInstanceBehavior) {
multiTask.add(flowElement.getId());
}
}
});
// 循環放入棧,棧 LIFO:後進先出
Stack<HistoricTaskInstance> stack = new Stack<>();
historicTaskInstanceList.forEach(item -> stack.push(item));
// 清洗後的歷史任務實例
List<String> lastHistoricTaskInstanceList = new ArrayList<>();
// 網關存在可能只走了部分分支情況,且還存在跳轉廢棄數據以及其他分支數據的干擾,因此需要對歷史節點數據進行清洗
// 臨時用戶任務 key
StringBuilder userTaskKey = null;
// 臨時被刪掉的任務 key,存在並行情況
List<String> deleteKeyList = new ArrayList<>();
// 臨時髒數據線路
List<Set<String>> dirtyDataLineList = new ArrayList<>();
// 由某個點跳到會籤點,此時出現多個會籤實例對應 1 個跳轉情況,需要把這些連續髒數據都找到
// 會籤特殊處理下標
int multiIndex = -1;
// 會籤特殊處理 key
StringBuilder multiKey = null;
// 會籤特殊處理操作標識
boolean multiOpera = false;
while (!stack.empty()) {
// 從這裏開始 userTaskKey 都還是上個棧的 key
// 是否是髒數據線路上的點
final boolean[] isDirtyData = {false};
for (Set<String> oldDirtyDataLine : dirtyDataLineList) {
if (oldDirtyDataLine.contains(stack.peek().getTaskDefinitionKey())) {
isDirtyData[0] = true;
}
}
// 刪除原因不爲空,說明從這條數據開始回跳或者回退的
// MI_END:會簽完成後,其他未簽到節點的刪除原因,不在處理範圍內
if (stack.peek().getDeleteReason() != null && !stack.peek().getDeleteReason().equals("MI_END")) {
// 可以理解爲髒線路起點
String dirtyPoint = "";
if (stack.peek().getDeleteReason().indexOf("Change activity to ") >= 0) {
dirtyPoint = stack.peek().getDeleteReason().replace("Change activity to ", "");
}
// 會籤回退刪除原因有點不同
if (stack.peek().getDeleteReason().indexOf("Change parent activity to ") >= 0) {
dirtyPoint = stack.peek().getDeleteReason().replace("Change parent activity to ", "");
}
FlowElement dirtyTask = null;
// 獲取變更節點的對應的入口處連線
// 如果是網關並行回退情況,會變成兩條髒數據路線,效果一樣
for (FlowElement flowElement : allElements) {
if (flowElement.getId().equals(stack.peek().getTaskDefinitionKey())) {
dirtyTask = flowElement;
}
}
// 獲取髒數據線路
Set<String> dirtyDataLine = FlowableUtils.iteratorFindDirtyRoads(dirtyTask, null, null, Arrays.asList(dirtyPoint.split(",")), null);
// 自己本身也是髒線路上的點,加進去
dirtyDataLine.add(stack.peek().getTaskDefinitionKey());
logger.info(stack.peek().getTaskDefinitionKey() + "點髒路線集合:" + dirtyDataLine);
// 是全新的需要添加的髒線路
boolean isNewDirtyData = true;
for (int i = 0; i < dirtyDataLineList.size(); i++) {
// 如果發現他的上個節點在髒線路內,說明這個點可能是並行的節點,或者連續駁回
// 這時,都以之前的髒線路節點爲標準,只需合併髒線路即可,也就是路線補全
if (dirtyDataLineList.get(i).contains(userTaskKey.toString())) {
isNewDirtyData = false;
dirtyDataLineList.get(i).addAll(dirtyDataLine);
}
}
// 已確定時全新的髒線路
if (isNewDirtyData) {
// deleteKey 單一路線駁回到並行,這種同時生成多個新實例記錄情況,這時 deleteKey 其實是由多個值組成
// 按照邏輯,回退後立刻生成的實例記錄就是回退的記錄
// 至於駁回所生成的 Key,直接從刪除原因中獲取,因爲存在駁回到並行的情況
deleteKeyList.add(dirtyPoint + ",");
dirtyDataLineList.add(dirtyDataLine);
}
// 添加後,現在這個點變成髒線路上的點了
isDirtyData[0] = true;
}
// 如果不是髒線路上的點,說明是有效數據,添加歷史實例 Key
if (!isDirtyData[0]) {
lastHistoricTaskInstanceList.add(stack.peek().getTaskDefinitionKey());
}
// 校驗髒線路是否結束
for (int i = 0; i < deleteKeyList.size(); i ++) {
// 如果發現髒數據屬於會籤,記錄下下標與對應 Key,以備後續比對,會籤髒數據範疇開始
if (multiKey == null && multiTask.contains(stack.peek().getTaskDefinitionKey())
&& deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) {
multiIndex = i;
multiKey = new StringBuilder(stack.peek().getTaskDefinitionKey());
}
// 會籤髒數據處理,節點退回會籤清空
// 如果在會籤髒數據範疇中發現 Key改變,說明會籤髒數據在上個節點就結束了,可以把會籤髒數據刪掉
if (multiKey != null && !multiKey.toString().equals(stack.peek().getTaskDefinitionKey())) {
deleteKeyList.set(multiIndex , deleteKeyList.get(multiIndex).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
multiKey = null;
// 結束進行下校驗刪除
multiOpera = true;
}
// 其他髒數據處理
// 發現該路線最後一條髒數據,說明這條髒數據線路處理完了,刪除髒數據信息
// 髒數據產生的新實例中是否包含這條數據
if (multiKey == null && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) {
// 刪除匹配到的部分
deleteKeyList.set(i , deleteKeyList.get(i).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
}
// 如果每組中的元素都以匹配過,說明髒數據結束
if ("".equals(deleteKeyList.get(i))) {
// 同時刪除髒數據
deleteKeyList.remove(i);
dirtyDataLineList.remove(i);
break;
}
}
// 會籤數據處理需要在循環外處理,否則可能導致溢出
// 會籤的數據肯定是之前放進去的所以理論上不會溢出,但還是校驗下
if (multiOpera && deleteKeyList.size() > multiIndex && "".equals(deleteKeyList.get(multiIndex))) {
// 同時刪除髒數據
deleteKeyList.remove(multiIndex);
dirtyDataLineList.remove(multiIndex);
multiIndex = -1;
multiOpera = false;
}
// pop() 方法與 peek() 方法不同,在返回值的同時,會把值從棧中移除
// 保存新的 userTaskKey 在下個循環中使用
userTaskKey = new StringBuilder(stack.pop().getTaskDefinitionKey());
}
logger.info("清洗後的歷史節點數據:" + lastHistoricTaskInstanceList);
return lastHistoricTaskInstanceList;
}
}