Flowable 快速入門教程:任務駁回與回退

前言

本文的代碼中沒有對流程做任何特殊處理,用的都是流程本身的數據,因此可以通用,直接複製粘貼即可

方法不支持多對多跳轉

回退不能夠直接回退到子流程上,我這裏按照只能回退到用戶任務節點處理的

駁回可以直接駁回到子流程開始

可根據自己需要對代碼進行調整

支持場景

並行網關,高級網關,包容網關,會籤,子流程

功能描述

駁回

參數:當前任務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;
    }
}

效果圖

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章