Flowable退回任意歷史用戶節(jié)點

使用Flowable助隧,退回到歷史某一個節(jié)點违孝,退回場景有:

1.目標節(jié)點和當前節(jié)點都不在并行網(wǎng)關上个从,或者目標節(jié)點和當前節(jié)點在同一個并行網(wǎng)關的同一個分支
2.退回到并行網(wǎng)關分支中的某一個節(jié)點上
3.并行網(wǎng)關中的某一個分支節(jié)點上發(fā)起退回搔扁,退回到并行網(wǎng)關前面的某一個節(jié)點上
4.流程內(nèi)部子流程(SubProcess)践啄,子流程中退回到主干流程中某一個節(jié)點
5.流程內(nèi)部子流程(SubProcess)赚哗,主干流程退回到子流程中某一個節(jié)點
6.流程外部子流程(CallActivity)她紫,子流程中退回到主干流程中某一個節(jié)點、主干流程退回到子流程中某一個節(jié)點

我的思路屿储,目前已實現(xiàn) 1~4 贿讹,其中 5 可以退回到讓子流程重新開始,但不是子流程中的某一個節(jié)點够掠,完整源碼詳見:
springboot:https://gitee.com/zjm16/zjmzxfzhl
springcloud:https://gitee.com/zjm16/zjmzxfzhl-cloud
詳細流程圖可參見完整源碼路徑 zjmzxfzhl/src/main/resources/processes_test 下的流程文件
具體實現(xiàn)的java文件BackUserTaskCmd.java

package com.zjmzxfzhl.modules.flowable.common.cmd;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.delegate.ActivityBehavior;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.task.api.Task;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;

import com.google.common.collect.Sets;
import com.zjmzxfzhl.modules.flowable.constant.FlowableConstant;
import com.zjmzxfzhl.modules.flowable.util.FlowableUtils;

/**
 * @author 莊金明
 * @date 2020年3月23日
 */
public class BackUserTaskCmd implements Command<String>, Serializable {

    public static final long serialVersionUID = 1L;

    protected RuntimeService runtimeService;
    protected String taskId;
    protected String targetActivityId;

    public BackUserTaskCmd(RuntimeService runtimeService, String taskId, String targetActivityId) {
        this.runtimeService = runtimeService;
        this.taskId = taskId;
        this.targetActivityId = targetActivityId;
    }

    @Override
    public String execute(CommandContext commandContext) {
        if (targetActivityId == null || targetActivityId.length() == 0) {
            throw new FlowableException("TargetActivityId cannot be empty");
        }
        TaskEntity task = CommandContextUtil.getTaskService().getTask(taskId);
        if (task == null) {
            throw new FlowableObjectNotFoundException("task " + taskId + " doesn't exist", Task.class);
        }
        String sourceActivityId = task.getTaskDefinitionKey();
        String processInstanceId = task.getProcessInstanceId();
        String processDefinitionId = task.getProcessDefinitionId();

        Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);

        FlowNode sourceFlowElement = (FlowNode) process.getFlowElement(sourceActivityId, true);
        // 只支持從用戶任務退回
        if (!(sourceFlowElement instanceof UserTask)) {
            throw new FlowableException("Task with id:" + taskId + " is not a UserTask");
        }

        FlowNode targetFlowElement = (FlowNode) process.getFlowElement(targetActivityId, true);
        // 退回節(jié)點到當前節(jié)點不可達到民褂,不允許退回
        if (!FlowableUtils.isReachable(process, targetFlowElement, sourceFlowElement)) {
            throw new FlowableException("Cannot back to [" + targetActivityId + "]");
        }

        // ps:目標節(jié)點如果相對當前節(jié)點是在子流程內(nèi)部,則無法直接退回,目前處理是只能退回到子流程開始節(jié)點
        String[] sourceAndTargetRealActivityId = FlowableUtils.getSourceAndTargetRealActivityId(sourceFlowElement, targetFlowElement);
        // 實際應操作的當前節(jié)點ID
        String sourceRealActivityId = sourceAndTargetRealActivityId[0];
        // 實際應操作的目標節(jié)點ID
        String targetRealActivityId = sourceAndTargetRealActivityId[1];

        Map<String, Set<String>> specialGatewayNodes = FlowableUtils.getSpecialGatewayElements(process);
        // 當前節(jié)點處在的并行網(wǎng)關list
        List<String> sourceInSpecialGatewayList = new ArrayList<>();
        // 目標節(jié)點處在的并行網(wǎng)關list
        List<String> targetInSpecialGatewayList = new ArrayList<>();
        setSpecialGatewayList(sourceRealActivityId, targetRealActivityId, specialGatewayNodes, sourceInSpecialGatewayList,
                targetInSpecialGatewayList);

        // 實際應篩選的節(jié)點ID
        Set<String> sourceRealAcitivtyIds = null;
        // 若退回目標節(jié)點相對當前節(jié)點在并行網(wǎng)關中赊堪,則要找到相對當前節(jié)點最近的這個并行網(wǎng)關面殖,后續(xù)做特殊處理
        String targetRealSpecialGateway = null;

        // 1.目標節(jié)點和當前節(jié)點都不在并行網(wǎng)關中
        if (targetInSpecialGatewayList.isEmpty() && sourceInSpecialGatewayList.isEmpty()) {
            sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
        }
        // 2.目標節(jié)點不在并行網(wǎng)關中、當前節(jié)點在并行網(wǎng)關中
        else if (targetInSpecialGatewayList.isEmpty() && !sourceInSpecialGatewayList.isEmpty()) {
            sourceRealAcitivtyIds = specialGatewayNodes.get(sourceInSpecialGatewayList.get(0));
        }
        // 3.目標節(jié)點在并行網(wǎng)關中哭廉、當前節(jié)點不在并行網(wǎng)關中
        else if (!targetInSpecialGatewayList.isEmpty() && sourceInSpecialGatewayList.isEmpty()) {
            sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
            targetRealSpecialGateway = targetInSpecialGatewayList.get(0);
        }
        // 4.目標節(jié)點和當前節(jié)點都在并行網(wǎng)關中
        else {
            int diffSpecialGatewayLevel = FlowableUtils.getDiffLevel(sourceInSpecialGatewayList, targetInSpecialGatewayList);
            // 在并行網(wǎng)關同一層且在同一分支
            if (diffSpecialGatewayLevel == -1) {
                sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
            } else {
                // 當前節(jié)點最內(nèi)層并行網(wǎng)關不被目標節(jié)點最內(nèi)層并行網(wǎng)關包含
                // 或理解為當前節(jié)點相對目標節(jié)點在并行網(wǎng)關外
                // 只篩選當前節(jié)點的execution
                if (sourceInSpecialGatewayList.size() == diffSpecialGatewayLevel) {
                    sourceRealAcitivtyIds = Sets.newHashSet(sourceRealActivityId);
                }
                // 當前節(jié)點相對目標節(jié)點在并行網(wǎng)關內(nèi)脊僚,應篩選相對目標節(jié)點最近的并行網(wǎng)關的所有節(jié)點的execution
                else {
                    sourceRealAcitivtyIds = specialGatewayNodes.get(sourceInSpecialGatewayList.get(diffSpecialGatewayLevel));
                }
                // 目標節(jié)點最內(nèi)層并行網(wǎng)關包含當前節(jié)點最內(nèi)層并行網(wǎng)關
                // 或理解為目標節(jié)點相對當前節(jié)點在并行網(wǎng)關外
                // 不做處理
                if (targetInSpecialGatewayList.size() == diffSpecialGatewayLevel) {
                }
                // 目標節(jié)點相對當前節(jié)點在并行網(wǎng)關內(nèi)
                else {
                    targetRealSpecialGateway = targetInSpecialGatewayList.get(diffSpecialGatewayLevel);
                }
            }
        }

        // 篩選需要處理的execution
        List<ExecutionEntity> realExecutions = this.getRealExecutions(commandContext, processInstanceId, task.getExecutionId(), sourceRealActivityId,
                sourceRealAcitivtyIds);
        // 執(zhí)行退回,直接跳轉(zhuǎn)到實際的 targetRealActivityId
        List<String> realExecutionIds = realExecutions.stream().map(ExecutionEntity::getId).collect(Collectors.toList());
        runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId)
                .moveExecutionsToSingleActivityId(realExecutionIds, targetRealActivityId).changeState();
        // 目標節(jié)點相對當前節(jié)點處于并行網(wǎng)關內(nèi)遵绰,需要特殊處理辽幌,需要手動生成并行網(wǎng)關匯聚節(jié)點(_end)的execution數(shù)據(jù)
        if (targetRealSpecialGateway != null) {
            createTargetInSpecialGatewayEndExecutions(commandContext, realExecutions, process, targetInSpecialGatewayList, targetRealSpecialGateway);
        }

        return targetRealActivityId;
    }

    private void setSpecialGatewayList(String sourceActivityId, String targetActivityId, Map<String, Set<String>> specialGatewayNodes,
            List<String> sourceInSpecialGatewayList, List<String> targetInSpecialGatewayList) {
        for (Map.Entry<String, Set<String>> entry : specialGatewayNodes.entrySet()) {
            if (entry.getValue().contains(sourceActivityId)) {
                sourceInSpecialGatewayList.add(entry.getKey());
            }
            if (entry.getValue().contains(targetActivityId)) {
                targetInSpecialGatewayList.add(entry.getKey());
            }
        }
    }

    private void createTargetInSpecialGatewayEndExecutions(CommandContext commandContext, List<ExecutionEntity> excutionEntitys, Process process,
            List<String> targetInSpecialGatewayList, String targetRealSpecialGateway) {
        // 目標節(jié)點相對當前節(jié)點處于并行網(wǎng)關,需要手動生成并行網(wǎng)關匯聚節(jié)點(_end)的execution數(shù)據(jù)
        String parentExecutionId = excutionEntitys.iterator().next().getParentId();
        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
        ExecutionEntity parentExecutionEntity = executionEntityManager.findById(parentExecutionId);

        int index = targetInSpecialGatewayList.indexOf(targetRealSpecialGateway);
        for (; index < targetInSpecialGatewayList.size(); index++) {
            String targetInSpecialGateway = targetInSpecialGatewayList.get(index);
            String targetInSpecialGatewayEndId = targetInSpecialGateway + FlowableConstant.SPECIAL_GATEWAY_END_SUFFIX;
            FlowNode targetInSpecialGatewayEnd = (FlowNode) process.getFlowElement(targetInSpecialGatewayEndId, true);
            int nbrOfExecutionsToJoin = targetInSpecialGatewayEnd.getIncomingFlows().size();
            // 處理目標節(jié)點所處的分支以外的分支,即 總分枝數(shù)-1 = nbrOfExecutionsToJoin - 1
            for (int i = 0; i < nbrOfExecutionsToJoin - 1; i++) {
                ExecutionEntity childExecution = executionEntityManager.createChildExecution(parentExecutionEntity);
                childExecution.setCurrentFlowElement(targetInSpecialGatewayEnd);
                ActivityBehavior activityBehavior = (ActivityBehavior) targetInSpecialGatewayEnd.getBehavior();
                activityBehavior.execute(childExecution);
            }
        }
    }

    private List<ExecutionEntity> getRealExecutions(CommandContext commandContext, String processInstanceId, String taskExecutionId,
            String sourceRealActivityId, Set<String> activityIds) {
        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
        ExecutionEntity taskExecution = executionEntityManager.findById(taskExecutionId);
        List<ExecutionEntity> executions = executionEntityManager.findChildExecutionsByProcessInstanceId(processInstanceId);
        Set<String> parentExecutionIds = FlowableUtils.getParentExecutionIdsByActivityId(executions, sourceRealActivityId);
        String realParentExecutionId = FlowableUtils.getParentExecutionIdFromParentIds(taskExecution, parentExecutionIds);

        List<ExecutionEntity> childExecutions = executionEntityManager.findExecutionsByParentExecutionAndActivityIds(realParentExecutionId,
                activityIds);
        return childExecutions;
    }
}
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椿访,一起剝皮案震驚了整個濱河市舶衬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赎离,老刑警劉巖逛犹,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異梁剔,居然都是意外死亡虽画,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門荣病,熙熙樓的掌柜王于貴愁眉苦臉地迎上來码撰,“玉大人,你說我怎么就攤上這事个盆〔钡海” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵颊亮,是天一觀的道長柴梆。 經(jīng)常有香客問我,道長终惑,這世上最難降的妖魔是什么绍在? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮雹有,結果婚禮上偿渡,老公的妹妹穿的比我還像新娘。我一直安慰自己霸奕,他們只是感情好溜宽,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著质帅,像睡著了一般适揉。 火紅的嫁衣襯著肌膚如雪留攒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天涡扼,我揣著相機與錄音稼跳,去河邊找鬼。 笑死吃沪,一個胖子當著我的面吹牛汤善,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播票彪,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼红淡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了降铸?” 一聲冷哼從身側響起在旱,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎推掸,沒想到半個月后桶蝎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡谅畅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年登渣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毡泻。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡胜茧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仇味,到底是詐尸還是另有隱情呻顽,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布丹墨,位于F島的核電站廊遍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏带到。R本人自食惡果不足惜昧碉,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揽惹。 院中可真熱鬧,春花似錦四康、人聲如沸搪搏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疯溺。三九已至论颅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間囱嫩,已是汗流浹背恃疯。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留墨闲,地道東北人今妄。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像鸳碧,于是被迫代替她去往敵國和親盾鳞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容