Activiti6.0 动态改变流程节点类型(普通-> 会签)

业务场景:在流程开发中,需要临时变更节点,将节点从 普通节点 -> 会签节点
环境:springboot +activiti6.0
GitHub https://github.com/oldguys/ActivitiDemo

业务需求:将 测试节点 转换为 会签节点

测试节点

实现思路:

/**
 *
 * 业务功能描述:将 普通节点 -> 会签节点
 *
 *  描述:
 *   流程: 节点1 - 节点2 - 节点3
 *   需求: 完成节点1 时,根据 表单参数确定是否转换 节点2(普通节点) 为 会签节点
 * 注意:
 *      1. EventListener 存在于 进行时任务, 这时已经根据 Bpmn Model 生成了流程 任务,此时改动无效。
 *      2. 经过测试, 会签任务生成的时,会把 入参 Map<"AssingeeList",List<"assignee">> 转换 成为多个任务。
 *      在 EventListener 中 assignee 已经被赋值。
 *      3. 根据测试,在 Activiti 使用 org.activiti.engine.impl.cmd.CompleteTaskCmd 时,会根据 Bpmn Model 进行任务完成,
 *      如果是会签任务,则会进入到会签的 behavior ,如果不具备 behavior 则会把任务安装普通任务执行,导致出现 多个 下一节点任务。
 *      所以需要解决方式 需要把 普通节点 -> 会签节点
 * 实现原理:
 *      1. 在完成 节点1 任务前,获取到Bpmn Model > Process > FlowElement(UseTask)。
 *      2. 根据 UseTask (节点1) 获取 OutgoingFlow 连线 以获取 到下一个节点 节点2(需要从 普通 转换到 会签)
 *      3. 根据 会签节点模型 ,添加需要的组件: MultiInstanceLoopCharacteristics  ParallelMultiInstanceBehavior。注意参数需要与 XML 配置中一致
 *      4. 完成节点1 任务,在完成任务时 传入需要会签的任务列表。此时 根据Bpmn Model 会创建 会签任务。
 *      5. 完成任务后,将 已添加组件 移除,将节点2 从 会签节点转换 成为 普通节点。
 *
 *      以上为 普通 - > 会签 生成会签任务阶段
 *
 *      6. 完成会签任务的时候,需要在完成任务之前 修改 BpmnModel - > UseTask (current Node) ,
 *      将 会签任务的核心组件 Behavior 和 LoopCharacteristics 重新注入到节点之中
 *      7. 完成任务
 *      8. 将  Behavior 和 LoopCharacteristics 从节点中移除 将 会签节点 -> 普通节点
 **/

中间表:用户保存被转换的流程以及被转换的节点


transform_entity_info

实现接口

public interface DefaultInstanceConvertToMultiInstance extends MultiWorkFLow {

    String ASSIGNEE_USER = "assignee";

    String DEFAULT_ASSIGNEE_LIST_EXP = "${assigneeList}";

    String ASSIGNEE_USER_EXP = "${" + ASSIGNEE_USER + "}";

    /**
     * 将 普通节点转换成为会签 任务
     *
     * @param taskId
     * @param sequential
     * @param data
     */
    void covertToMultiInstance(String taskId, boolean sequential, Map<String, Object> data);

    /**
     *  将 普通节点转换成为会签 任务
     * @param taskId
     * @param sequential
     * @param assigneeExp 任务执行人表达式
     * @param data
     */
    void covertToMultiInstance(String taskId, boolean sequential, String assigneeExp, Map<String, Object> data);

    /**
     * 创建 多实例 行为解释器
     *
     * @param userTask
     * @param sequential
     * @return
     */
    MultiInstanceActivityBehavior createMultiInstanceBehavior(UserTask userTask, boolean sequential);

    /**
     * 创建多实例行为解释器
     * @param userTask 流程节点
     * @param sequential 是否串行
     * @param assigneeListExp 用户组表达
     * @param assigneeExp 用户标识
     * @return
     */
    MultiInstanceActivityBehavior createMultiInstanceBehavior(UserTask userTask, boolean sequential, String assigneeListExp, String assigneeExp);

    /**
     * 创建多实例 循环解释器
     *
     * @param isSequential    是否串行
     * @param assigneeListExp 用户组表达
     * @param assignee        用户标识
     * @return
     */
    MultiInstanceLoopCharacteristics createMultiInstanceLoopCharacteristics(boolean isSequential, String assigneeListExp, String assignee);

    /**
     * 创建多实例 循环解释器
     *
     * @param isSequential 是否 串行
     * @return
     */
    MultiInstanceLoopCharacteristics createMultiInstanceLoopCharacteristics(boolean isSequential);
}

继承接口

/**
 *  特定业务完成类
 */
public interface MultiWorkFLow {

    /**
     *
     * @param taskId
     * @param data
     */
    void completeTask(String taskId, Map<String, Object> data);
}

实现 抽象类: com.oldguy.example.modules.workflow.service.activiti.impls.AbstractMultiWorkFLowService

package com.oldguy.example.modules.workflow.service.activiti.impls;

import com.oldguy.example.modules.workflow.service.activiti.DefaultInstanceConvertToMultiInstance;
import org.activiti.bpmn.model.MultiInstanceLoopCharacteristics;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.el.ExpressionManager;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @ClassName: AbstractMultiWorkFLowService
 * @Author: ren
 * @Description:
 * @CreateTIme: 2019/5/23 0023 下午 4:55
 **/
public abstract class AbstractMultiWorkFLowService implements DefaultInstanceConvertToMultiInstance {

    @Autowired
    protected ProcessEngine processEngine;
    @Autowired
    protected TaskService taskService;
    @Autowired
    protected RepositoryService repositoryService;

    @Override
    public MultiInstanceLoopCharacteristics createMultiInstanceLoopCharacteristics(boolean isSequential) {
        return createMultiInstanceLoopCharacteristics(isSequential, DEFAULT_ASSIGNEE_LIST_EXP, ASSIGNEE_USER);
    }

    @Override
    public MultiInstanceLoopCharacteristics createMultiInstanceLoopCharacteristics(boolean isSequential, String assigneeListExp, String assignee) {

        MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = new MultiInstanceLoopCharacteristics();
        multiInstanceLoopCharacteristics.setSequential(isSequential);
        multiInstanceLoopCharacteristics.setInputDataItem(assigneeListExp);
        multiInstanceLoopCharacteristics.setElementVariable(assignee);

        return multiInstanceLoopCharacteristics;
    }

    @Override
    public MultiInstanceActivityBehavior createMultiInstanceBehavior(UserTask userTask, boolean sequential) {
        return createMultiInstanceBehavior(userTask, sequential, DEFAULT_ASSIGNEE_LIST_EXP, ASSIGNEE_USER);
    }

    @Override
    public MultiInstanceActivityBehavior createMultiInstanceBehavior(UserTask userTask, boolean sequential, String assigneeListExp, String assignee) {


        ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine.getProcessEngineConfiguration();
        /**
         *  创建解释器
         */
        UserTaskActivityBehavior userTaskActivityBehavior = processEngineConfiguration.getActivityBehaviorFactory().createUserTaskActivityBehavior(userTask);

        MultiInstanceActivityBehavior behavior = null;

        if (sequential) {
            behavior = new SequentialMultiInstanceBehavior(userTask, userTaskActivityBehavior);
        } else {
            behavior = new ParallelMultiInstanceBehavior(userTask, userTaskActivityBehavior);
        }

        /**
         *   注入表达式 解释器
         */
        ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();

        /**
         * 设置表达式变量
         */
        behavior.setCollectionExpression(expressionManager.createExpression(assigneeListExp));
        behavior.setCollectionElementVariable(assignee);

        return behavior;
    }

}

实现类:

package com.oldguy.example.modules.workflow.service.activiti.impls;

import com.oldguy.example.modules.common.utils.Log4jUtils;
import com.oldguy.example.modules.workflow.dao.entities.TransformEntityInfo;
import com.oldguy.example.modules.workflow.dao.jpas.TransformEntityInfoMapper;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Map;

/**
 * @ClassName: MultiWorkFLowService
 * @Author: ren
 * @Description:
 * @CreateTIme: 2019/5/23 0023 下午 4:55
 **/
@Service
public class MultiWorkFLowService extends AbstractMultiWorkFLowService {

    @Autowired
    private TransformEntityInfoMapper transformEntityInfoMapper;


    /**
     * 完成任务
     *
     * @param taskId
     * @param data
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void completeTask(String taskId, Map<String, Object> data) {

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

        /**
         *  获取流程实例是否被转换
         */
        TransformEntityInfo transformEntityInfo = transformEntityInfoMapper.findByProcessInstance(task.getProcessInstanceId());

        /**
         *  转换进行特殊处理
         */
        if (null != transformEntityInfo && task.getTaskDefinitionKey().equals(transformEntityInfo.getTaskDefineKey())) {

            BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
            Process process = bpmnModel.getProcesses().get(0);
            /**
             *  获取当前流程节点,将当前节点 从 普通节点 - > 会签节点
             */
            UserTask currentNode = (UserTask) process.getFlowElement(task.getTaskDefinitionKey());

            currentNode.setBehavior(createMultiInstanceBehavior(currentNode, transformEntityInfo.getSequential()));
            currentNode.setLoopCharacteristics(createMultiInstanceLoopCharacteristics(transformEntityInfo.getSequential()));

            /**
             *  完成任务
             */
            taskService.complete(taskId, data);

            /**
             *  将节点 从 会签节点 - > 普通节点
             */
            currentNode.setBehavior(null);
            currentNode.setLoopCharacteristics(null);
            return;
        }

        /**
         *  默认完成任务
         */
        taskService.complete(taskId, data);
    }

    @Override
    public void covertToMultiInstance(String taskId, boolean sequential, Map<String, Object> data) {
        covertToMultiInstance(taskId, sequential, ASSIGNEE_USER_EXP, data);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void covertToMultiInstance(String taskId, boolean sequential, String assigneeExp, Map<String, Object> data) {

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

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

        // 节点 1
        UserTask currentNode = (UserTask) process.getFlowElement(task.getTaskDefinitionKey());

        // 连线
        SequenceFlow sequenceFlow = currentNode.getOutgoingFlows().get(0);

        // 测试节点 ,待转换节点
        UserTask nextNode = (UserTask) process.getFlowElement(sequenceFlow.getTargetRef());

        nextNode.setAssignee(assigneeExp);


        /**
         *  设置解释器,普通节点将会变成会签节点
         */
        nextNode.setLoopCharacteristics(createMultiInstanceLoopCharacteristics(sequential));
        nextNode.setBehavior(createMultiInstanceBehavior(nextNode, sequential));

        /**
         *  完成前置任务
         */
        taskService.complete(taskId, data);

        /**
         *  将节点变回普通节点
         */
        nextNode.setLoopCharacteristics(null);
        nextNode.setBehavior(null);


        /**
         * 持久化 节点变更信息
         */
        TransformEntityInfo entity = new TransformEntityInfo();

        entity.setTaskDefineKey(nextNode.getId());
        entity.setProcessInstanceId(task.getProcessInstanceId());
        entity.setCreateTime(new Date());
        entity.setStatus(1);
        entity.setSequential(true);
        transformEntityInfoMapper.save(entity);

    }
}


测试例子

测试节点

将 “测试节点” 普通 -> 会签

并行会签

1.jpg
2.jpg
3.jpg
4.jpg

串行会签

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

推荐阅读更多精彩内容