Activiti6.0 实现会签的 加减签 功能

业务描述: 进行会签任务时候,时常会遇到任务执行中,需要进行加签,减签。本章主要描述如何实现
版本:SpringBoot 1.5.19.RELEASE + Activiti 6.0
GitHub https://github.com/oldguys/ActivitiDemo

前置知识:

  1. org.activiti.engine.impl.interceptor.Command:任务命令接口,Activiti具有任务执行机制,都是基于这个接口进行实现。如:
    org.activiti.engine.impl.cmd.CompleteTaskCmd:任务完成命令
  2. org.activiti.engine.ManagementService: 任务管理服务接口,负责管理服务,用于完成 Command 任务
  3. org.activiti.engine.impl.persistence.entity.ExecutionEntityManager:用于管理Execution。
  4. Context.getAgenda().planContinueMultiInstanceOperation(newExecution); : 任务时间轴
  5. 多实例任务行为解释器
    org.activiti.bpmn.model.MultiInstanceLoopCharacteristics: 用于生成多实例任务
    org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior:并行任务解释器;
    org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior: 串行任务解释器
  6. Activiti 是根据execution进行 流转的,在会签任务的时候,会在生成 子级(多个)execution,当所有子任务都完成的时候,销毁 子 execution,回到 父级 execution 进行流转。
  7. 参数 ( act_ru_task):

(父级 execution 变量)

  • nrOfInstances :子任务总个数
  • nrOfCompletedInstances: 当前已完成子任务个数
  • nrOfActiveInstances: 当前活跃任务个数(未完成)

(子级 execution 变量)

  • loopCounter: 任务列表下标
  • assignee: 任务执行人(可以根据需要配置不同变量 XML)

实现思路:

会签 加签

并行:Activiti会基于父级 execution 创建多个子 execution 再根据子execution 创建多个任务,所以实现加签的时候,根据 父级 execution 和 节点 生成新的 execution ,再生成任务。

串行:Activiti会基于 父级 execution 只创建一个 子execution,每完成一个任务,创建下一个任务。在开始串行会签任务前,需要传入一个变量 assigneeList,而这个变量会被 序列号 到 act_ru_task 中。在运行阶段,根据 loopCounter (数组下标),从assigneeList中获取 任务执行人。所以需要进行加签时,只需要传入列表并修改才行。

注意: 不论串行并行,在修改完任务之后,都需要修改父级变量计数器

测试流程图:


测试流程

加签任务: com.oldguy.example.modules.workflow.commands.AddMultiInstanceExecutionCmd

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

    /**
     * 当前任务ID
     */
    private String taskId;

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

    /**
     * 任务执行人
     */
    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() + "] 不是会签节任务");
        }

        /**
         *  获取父级
         */
        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() + "] 并行会签 加签 任务");
            /**
             *  设置循环标志变量
             */
            runtimeService.setVariable(parentNode.getId(), NUMBER_OF_INSTANCES, nrOfInstances + assigneeList.size());
            runtimeService.setVariable(parentNode.getId(), NUMBER_OF_ACTIVE_INSTANCES, nrOfActiveInstances + assigneeList.size());

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

                /**
                 *  创建 子 execution
                 */
                ExecutionEntity newExecution = executionEntityManager.createChildExecution(parentNode);

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

                /**
                 * 任务总数 +1
                 */
                nrOfInstances++;

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

        } else if (behavior instanceof SequentialMultiInstanceBehavior) {
            Log4jUtils.getInstance(getClass()).info("task:[" + task.getId() + "] 串行会签 加签 任务");

            /**
             *  是否需要替换审批人
             */
            boolean changeAssignee = false;
            if (StringUtils.isEmpty(assignee)) {
                assignee = task.getAssignee();
                changeAssignee = true;
            }
            /**
             *  当前任务执行位置
             */
            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("任务审批人不存在于任务执行人列表中");
            }

            /**
             *  修改当前任务执行人
             */
            if (changeAssignee) {
                taskService.setAssignee(taskId, assignee);
                execution.setVariableLocal(ASSIGNEE_USER, assignee);
            }

            /**
             *  修改 计数器位置
             */
            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 "加签成功";
    }
}

会签 减签

并行:根据上面的解释,同理,在进行会签减签的时候,只需要删除 相关的 子 execution 并且修改 父级计数器值就可以完成减签。

串行:串行减签与加签逻辑相似,只不过把加变成减而已

减签: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 {

    /**
     * 当前任务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() + "] 不是会签节任务");
        }

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

        /**
         *  获取任务完成数
         */
        int nrOfCompletedInstances = (int) runtimeService.getVariable(parentNode.getId(), NUMBER_OF_COMPLETED_INSTANCES);

        /**
         *  转换判断标识
         */
        Set<String> assigneeSet = new HashSet<>(assigneeList);
        ExecutionEntityManager executionEntityManager = Context.getCommandContext().getExecutionEntityManager();

        Object behavior = userTask.getBehavior();
        /**
         *  进行并行任务 减签
         */
        if (behavior instanceof ParallelMultiInstanceBehavior) {

            Log4jUtils.getInstance(getClass()).info("task:[" + task.getId() + "] 并行会签 减签 任务");

            /**
             *  当前任务列表
             */
            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);
                }
            });

            /**
             *  修改已完成任务变量,增加被删减任务
             */
            runtimeService.setVariable(parentNode.getId(), NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances + removeTaskList.size());


        } else if (behavior instanceof SequentialMultiInstanceBehavior) {
            Log4jUtils.getInstance(getClass()).info("task:[" + task.getId() + "] 串行会签 减签 任务");

            Object obj = parentNode.getVariable(ASSIGNEE_LIST);
            if (obj == null || !(obj instanceof ArrayList)) {
                throw new RuntimeException("没有找到任务执行人列表");
            }


            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;
                    }
                }
            }

            /**
             *  修改计数器变量
             */
            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);

            /**
             *  当前任务需要被删除,需要替换下一个任务审批人
             */
            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);
    }

}

通用接口参数: com.oldguy.example.modules.workflow.commands.CountersigningVariables

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

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

/**
 *  Activiti 会签任务中变量标志
 *
 * {@link MultiInstanceActivityBehavior}
 */
public interface CountersigningVariables {

    /**
     *  默认审核人
     */
    String ASSIGNEE_USER = "assignee";

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

    /**
     *  会签任务总数
     */
    String NUMBER_OF_INSTANCES = "nrOfInstances";

    /**
     *  正在执行的会签总数
     */
    String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances";

    /**
     *  已完成的会签任务总数
     */
    String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances";

    /**
     *  会签任务表示
     *  collectionElementIndexVariable
     */
    String LOOP_COUNTER = "loopCounter";
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容