Activiti6.0 實現(xiàn)會簽的 加減簽 功能

業(yè)務(wù)描述: 進行會簽任務(wù)時候其徙,時常會遇到任務(wù)執(zhí)行中,需要進行加簽,減簽。本章主要描述如何實現(xiàn)
版本:SpringBoot 1.5.19.RELEASE + Activiti 6.0
GitHub https://github.com/oldguys/ActivitiDemo

前置知識:

  1. org.activiti.engine.impl.interceptor.Command:任務(wù)命令接口滤钱,Activiti具有任務(wù)執(zhí)行機制,都是基于這個接口進行實現(xiàn)枷踏。如:
    org.activiti.engine.impl.cmd.CompleteTaskCmd:任務(wù)完成命令
  2. org.activiti.engine.ManagementService: 任務(wù)管理服務(wù)接口菩暗,負責(zé)管理服務(wù),用于完成 Command 任務(wù)
  3. org.activiti.engine.impl.persistence.entity.ExecutionEntityManager:用于管理Execution。
  4. Context.getAgenda().planContinueMultiInstanceOperation(newExecution); : 任務(wù)時間軸
  5. 多實例任務(wù)行為解釋器
    org.activiti.bpmn.model.MultiInstanceLoopCharacteristics: 用于生成多實例任務(wù)
    org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior:并行任務(wù)解釋器;
    org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior: 串行任務(wù)解釋器
  6. Activiti 是根據(jù)execution進行 流轉(zhuǎn)的旭蠕,在會簽任務(wù)的時候,會在生成 子級(多個)execution旷坦,當(dāng)所有子任務(wù)都完成的時候掏熬,銷毀 子 execution,回到 父級 execution 進行流轉(zhuǎn)秒梅。
  7. 參數(shù) ( act_ru_task):

(父級 execution 變量)

  • nrOfInstances :子任務(wù)總個數(shù)
  • nrOfCompletedInstances: 當(dāng)前已完成子任務(wù)個數(shù)
  • nrOfActiveInstances: 當(dāng)前活躍任務(wù)個數(shù)(未完成)

(子級 execution 變量)

  • loopCounter: 任務(wù)列表下標(biāo)
  • assignee: 任務(wù)執(zhí)行人(可以根據(jù)需要配置不同變量 XML)

實現(xiàn)思路:

會簽 加簽

并行:Activiti會基于父級 execution 創(chuàng)建多個子 execution 再根據(jù)子execution 創(chuàng)建多個任務(wù)旗芬,所以實現(xiàn)加簽的時候,根據(jù) 父級 execution 和 節(jié)點 生成新的 execution 捆蜀,再生成任務(wù)疮丛。

串行:Activiti會基于 父級 execution 只創(chuàng)建一個 子execution幔嫂,每完成一個任務(wù),創(chuàng)建下一個任務(wù)誊薄。在開始串行會簽任務(wù)前履恩,需要傳入一個變量 assigneeList,而這個變量會被 序列號 到 act_ru_task 中呢蔫。在運行階段切心,根據(jù) loopCounter (數(shù)組下標(biāo)),從assigneeList中獲取 任務(wù)執(zhí)行人片吊。所以需要進行加簽時绽昏,只需要傳入列表并修改才行。

注意: 不論串行并行俏脊,在修改完任務(wù)之后全谤,都需要修改父級變量計數(shù)器

測試流程圖:


測試流程

加簽任務(wù): com.oldguy.example.modules.workflow.commands.AddMultiInstanceExecutionCmd

public class AddMultiInstanceExecutionCmd extends AbstractCountersignCmd implements Command<String>, CountersigningVariables {

    /**
     * 當(dāng)前任務(wù)ID
     */
    private String taskId;

    /**
     * 審核人
     */
    private List<String> assigneeList;

    /**
     * 任務(wù)執(zhí)行人
     */
    private String assignee;

    public AddMultiInstanceExecutionCmd(String taskId, List<String> assigneeList) {

        super();

        if (ObjectUtils.isEmpty(assigneeList)) {
            throw new RuntimeException("assigneeList 不能為空!");
        }

        this.taskId = taskId;
        this.assigneeList = assigneeList;
    }

    public AddMultiInstanceExecutionCmd(String taskId, List<String> assigneeList, String assignee) {

        super();

        if (ObjectUtils.isEmpty(assigneeList)) {
            throw new RuntimeException("assigneeList 不能為空!");
        }

        this.taskId = taskId;
        this.assigneeList = assigneeList;
        this.assignee = assignee;
    }

    @Override
    public String execute(CommandContext commandContext) {

        TaskEntityImpl task = (TaskEntityImpl) taskService.createTaskQuery().taskId(taskId).singleResult();
        ExecutionEntityImpl execution = (ExecutionEntityImpl) runtimeService.createExecutionQuery().executionId(task.getExecutionId()).singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        Process process = bpmnModel.getProcesses().get(0);

        UserTask userTask = (UserTask) process.getFlowElement(task.getTaskDefinitionKey());

        if (userTask.getLoopCharacteristics() == null) {
            // TODO
            Log4jUtils.getInstance(getClass()).error("task:[" + task.getId() + "] 不是會簽節(jié)任務(wù)");
        }

        /**
         *  獲取父級
         */
        ExecutionEntityImpl parentNode = execution.getParent();

        /**
         *  獲取流程變量
         */
        int nrOfInstances = (int) runtimeService.getVariable(parentNode.getId(), NUMBER_OF_INSTANCES);
        int nrOfActiveInstances = (int) runtimeService.getVariable(parentNode.getId(), NUMBER_OF_ACTIVE_INSTANCES);

        /**
         *  獲取管理器
         */
        ExecutionEntityManager executionEntityManager = Context.getCommandContext().getExecutionEntityManager();

        Object behavior = userTask.getBehavior();
        if (behavior instanceof ParallelMultiInstanceBehavior) {

            Log4jUtils.getInstance(getClass()).info("task:[" + task.getId() + "] 并行會簽 加簽 任務(wù)");
            /**
             *  設(shè)置循環(huán)標(biāo)志變量
             */
            runtimeService.setVariable(parentNode.getId(), NUMBER_OF_INSTANCES, nrOfInstances + assigneeList.size());
            runtimeService.setVariable(parentNode.getId(), NUMBER_OF_ACTIVE_INSTANCES, nrOfActiveInstances + assigneeList.size());

            /**
             *  新建任務(wù)列表
             */
            for (String assignee : this.assigneeList) {

                /**
                 *  創(chuàng)建 子 execution
                 */
                ExecutionEntity newExecution = executionEntityManager.createChildExecution(parentNode);

                newExecution.setActive(true);
                newExecution.setVariableLocal(LOOP_COUNTER, nrOfInstances);
                newExecution.setVariableLocal(ASSIGNEE_USER, assignee);
                newExecution.setCurrentFlowElement(userTask);

                /**
                 * 任務(wù)總數(shù) +1
                 */
                nrOfInstances++;

                /**
                 * 推入時間表序列
                  */
                Context.getAgenda().planContinueMultiInstanceOperation(newExecution);
            }

        } else if (behavior instanceof SequentialMultiInstanceBehavior) {
            Log4jUtils.getInstance(getClass()).info("task:[" + task.getId() + "] 串行會簽 加簽 任務(wù)");

            /**
             *  是否需要替換審批人
             */
            boolean changeAssignee = false;
            if (StringUtils.isEmpty(assignee)) {
                assignee = task.getAssignee();
                changeAssignee = true;
            }
            /**
             *  當(dāng)前任務(wù)執(zhí)行位置
             */
            int loopCounterIndex = -1;

            for (int i = 0; i < assigneeList.size(); i++) {

                String temp = assigneeList.get(i);
                if (assignee.equals(temp)) {
                    loopCounterIndex = i;
                }
            }

            if (loopCounterIndex == -1) {
                throw new RuntimeException("任務(wù)審批人不存在于任務(wù)執(zhí)行人列表中");
            }

            /**
             *  修改當(dāng)前任務(wù)執(zhí)行人
             */
            if (changeAssignee) {
                taskService.setAssignee(taskId, assignee);
                execution.setVariableLocal(ASSIGNEE_USER, assignee);
            }

            /**
             *  修改 計數(shù)器位置
             */
            execution.setVariableLocal(LOOP_COUNTER, loopCounterIndex);

            /**
             *  修改全局變量
             */
            Map<String, Object> variables = new HashMap<>(3);
            variables.put(NUMBER_OF_INSTANCES, assigneeList.size());
            variables.put(NUMBER_OF_COMPLETED_INSTANCES, loopCounterIndex);
            variables.put(ASSIGNEE_LIST, assigneeList);

            runtimeService.setVariables(parentNode.getId(), variables);
        }


        return "加簽成功";
    }
}

會簽 減簽

并行:根據(jù)上面的解釋,同理爷贫,在進行會簽減簽的時候啼县,只需要刪除 相關(guān)的 子 execution 并且修改 父級計數(shù)器值就可以完成減簽。

串行:串行減簽與加簽邏輯相似沸久,只不過把加變成減而已

減簽:com.oldguy.example.modules.workflow.commands.DeleteMultiInstanceExecutionCmd

package com.oldguy.example.modules.workflow.commands;

import com.oldguy.example.modules.common.utils.Log4jUtils;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager;
import org.activiti.engine.impl.persistence.entity.TaskEntityImpl;
import org.activiti.engine.task.Task;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @ClassName: DeleteMultiInstanceExecutionCmd
 * @Author: ren
 * @Description: 進行會簽減簽 flowable:org.flowable.engine.impl.cmd.DeleteMultiInstanceExecutionCmd
 * @CreateTIme: 2019/5/9 0009 下午 3:05
 **/
public class DeleteMultiInstanceExecutionCmd extends AbstractCountersignCmd implements Command<String>, CountersigningVariables {

    /**
     * 當(dāng)前任務(wù)ID
     */
    private String taskId;

    /**
     * 審核人
     */
    private List<String> assigneeList;

    public DeleteMultiInstanceExecutionCmd(String taskId, List<String> assigneeList) {

        super();

        if (ObjectUtils.isEmpty(assigneeList)) {
            throw new RuntimeException("assigneeList 不能為空!");
        }

        this.taskId = taskId;
        this.assigneeList = assigneeList;
    }

    @Override
    public String execute(CommandContext commandContext) {

        TaskEntityImpl task = (TaskEntityImpl) taskService.createTaskQuery().taskId(taskId).singleResult();

        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        Process process = bpmnModel.getProcesses().get(0);

        UserTask userTask = (UserTask) process.getFlowElement(task.getTaskDefinitionKey());

        if (userTask.getLoopCharacteristics() == null) {
            // TODO
            Log4jUtils.getInstance(getClass()).error("task:[" + task.getId() + "] 不是會簽節(jié)任務(wù)");
        }

        ExecutionEntityImpl execution = (ExecutionEntityImpl) runtimeService.createExecutionQuery().executionId(task.getExecutionId()).singleResult();
        ExecutionEntityImpl parentNode = execution.getParent();

        /**
         *  獲取任務(wù)完成數(shù)
         */
        int nrOfCompletedInstances = (int) runtimeService.getVariable(parentNode.getId(), NUMBER_OF_COMPLETED_INSTANCES);

        /**
         *  轉(zhuǎn)換判斷標(biāo)識
         */
        Set<String> assigneeSet = new HashSet<>(assigneeList);
        ExecutionEntityManager executionEntityManager = Context.getCommandContext().getExecutionEntityManager();

        Object behavior = userTask.getBehavior();
        /**
         *  進行并行任務(wù) 減簽
         */
        if (behavior instanceof ParallelMultiInstanceBehavior) {

            Log4jUtils.getInstance(getClass()).info("task:[" + task.getId() + "] 并行會簽 減簽 任務(wù)");

            /**
             *  當(dāng)前任務(wù)列表
             */
            List<Task> taskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstance().getProcessInstanceId()).list();

            List<Task> removeTaskList = new ArrayList<>(assigneeSet.size());
            List<Task> existTaskList = new ArrayList<>(taskList.size() - assigneeSet.size());

            taskList.forEach(obj -> {

                if (assigneeSet.contains(obj.getAssignee())) {
                    removeTaskList.add(obj);

                    ExecutionEntityImpl temp = (ExecutionEntityImpl) runtimeService.createExecutionQuery().executionId(obj.getExecutionId()).singleResult();
                    executionEntityManager.deleteExecutionAndRelatedData(temp, "會簽減簽", true);

                } else {
                    existTaskList.add(obj);
                }
            });

            /**
             *  修改已完成任務(wù)變量,增加被刪減任務(wù)
             */
            runtimeService.setVariable(parentNode.getId(), NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances + removeTaskList.size());


        } else if (behavior instanceof SequentialMultiInstanceBehavior) {
            Log4jUtils.getInstance(getClass()).info("task:[" + task.getId() + "] 串行會簽 減簽 任務(wù)");

            Object obj = parentNode.getVariable(ASSIGNEE_LIST);
            if (obj == null || !(obj instanceof ArrayList)) {
                throw new RuntimeException("沒有找到任務(wù)執(zhí)行人列表");
            }


            ArrayList<String> sourceAssigneeList = (ArrayList) obj;
            List<String> newAssigneeList = new ArrayList<>();
            boolean flag = false;
            int loopCounterIndex = -1;
            String newAssignee = "";
            for (String temp : sourceAssigneeList) {
                if (!assigneeSet.contains(temp)) {
                    newAssigneeList.add(temp);
                }

                if (flag) {
                    newAssignee = temp;
                    flag = false;
                }

                if (temp.equals(task.getAssignee())) {

                    if (assigneeSet.contains(temp)) {
                        flag = true;
                        loopCounterIndex = newAssigneeList.size();
                    } else {
                        loopCounterIndex = newAssigneeList.size() - 1;
                    }
                }
            }

            /**
             *  修改計數(shù)器變量
             */
            Map<String, Object> variables = new HashMap<>();
            variables.put(NUMBER_OF_INSTANCES, newAssigneeList.size());
            variables.put(NUMBER_OF_COMPLETED_INSTANCES, loopCounterIndex > 0 ? loopCounterIndex - 1 : 0);
            variables.put(ASSIGNEE_LIST, newAssigneeList);
            runtimeService.setVariables(parentNode.getId(), variables);

            /**
             *  當(dāng)前任務(wù)需要被刪除季眷,需要替換下一個任務(wù)審批人
             */
            if (!StringUtils.isEmpty(newAssignee)) {
                taskService.setAssignee(taskId, newAssignee);
                execution.setVariable(LOOP_COUNTER, loopCounterIndex);
                execution.setVariable(ASSIGNEE_USER, newAssignee);
            }
        }
        return "減簽成功";
    }
}

其他代碼

抽象父類: com.oldguy.example.modules.workflow.commands.AbstractCountersignCmd

package com.oldguy.example.modules.workflow.commands;

import com.oldguy.example.modules.common.utils.SpringContextUtils;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;


/**
 * @ClassName: AbstractCountersignCmd
 * @Author: ren
 * @Description:
 * @CreateTIme: 2019/5/13 0013 下午 11:43
 **/
public abstract class AbstractCountersignCmd {

    protected RuntimeService runtimeService;

    protected TaskService taskService;

    protected RepositoryService repositoryService;

    public AbstractCountersignCmd(){

        runtimeService = SpringContextUtils.getBean(RuntimeService.class);
        taskService = SpringContextUtils.getBean(TaskService.class);
        repositoryService = SpringContextUtils.getBean(RepositoryService.class);
    }

}

通用接口參數(shù): com.oldguy.example.modules.workflow.commands.CountersigningVariables

package com.oldguy.example.modules.workflow.commands;

import org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;

/**
 *  Activiti 會簽任務(wù)中變量標(biāo)志
 *
 * {@link MultiInstanceActivityBehavior}
 */
public interface CountersigningVariables {

    /**
     *  默認審核人
     */
    String ASSIGNEE_USER = "assignee";

    /**
     *  審核人集合
     */
    String ASSIGNEE_LIST = "assigneeList";

    /**
     *  會簽任務(wù)總數(shù)
     */
    String NUMBER_OF_INSTANCES = "nrOfInstances";

    /**
     *  正在執(zhí)行的會簽總數(shù)
     */
    String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances";

    /**
     *  已完成的會簽任務(wù)總數(shù)
     */
    String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances";

    /**
     *  會簽任務(wù)表示
     *  collectionElementIndexVariable
     */
    String LOOP_COUNTER = "loopCounter";
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市卷胯,隨后出現(xiàn)的幾起案子子刮,更是在濱河造成了極大的恐慌,老刑警劉巖窑睁,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挺峡,死亡現(xiàn)場離奇詭異,居然都是意外死亡担钮,警方通過查閱死者的電腦和手機橱赠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箫津,“玉大人狭姨,你說我怎么就攤上這事∷找#” “怎么了饼拍?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長田炭。 經(jīng)常有香客問我师抄,道長,這世上最難降的妖魔是什么教硫? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任叨吮,我火速辦了婚禮辆布,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茶鉴。我一直安慰自己锋玲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布蛤铜。 她就那樣靜靜地躺著嫩絮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪围肥。 梳的紋絲不亂的頭發(fā)上剿干,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天,我揣著相機與錄音穆刻,去河邊找鬼置尔。 笑死,一個胖子當(dāng)著我的面吹牛氢伟,可吹牛的內(nèi)容都是我干的榜轿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼朵锣,長吁一口氣:“原來是場噩夢啊……” “哼谬盐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诚些,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤飞傀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后诬烹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砸烦,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年绞吁,在試婚紗的時候發(fā)現(xiàn)自己被綠了幢痘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡家破,死狀恐怖颜说,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情员舵,我是刑警寧澤脑沿,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站马僻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏注服。R本人自食惡果不足惜韭邓,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一措近、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧女淑,春花似錦瞭郑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至袱巨,卻和暖如春阁谆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愉老。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工场绿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嫉入。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓焰盗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咒林。 傳聞我的和親對象是個殘疾皇子熬拒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,562評論 2 349

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