flowable中流程实例添加并行临时任务

flowable中流程实例添加并行临时任务

背景

flowable-ui中可以通过前端绘画的方式,组成流程定义,但是实际需求中,会存在需要给流程实例临时添加临时任务,并且不改变原本流程定义的需求。流程定义修改比较简单,但是流程发起后,给流程实例添加临时节点,较为复杂。
专栏上篇文章,讲解了如何添加简单串行临时任务
本文中,详细讲解在流程实例中添加并行临时任务,临时任务支持自定义标签,多实例,任务监听器。
具体情境如下:
原始流程实例流程图
原始流程实例流程图,添加并行任务后变为:
添加并行临时任务后的流程实例流程图

逻辑

通过查看flowable的源码,发现已经存在流程实例添加临时任务的方法(org.flowable.engine.impl.cmd.InjectUserTaskInProcessInstanceCmd),但是比较简单,只能在流程的最初节点添加临时任务,通过参考该源码,我们可以进行继承重写,实现需求中的任意节点的并行临时任务。
主要过程:
1、继承 InjectUserTaskInProcessInstanceCmd,将需要的参数注入,然后重写updateBpmnProcess()和updateExecutions()两个方法。
2、判断并行起始节点后和并行结束节点前,是否存在并行网关,如果存在则复用,如果不存在则新建。
3、新建临时任务,和网关连线。
4、将现存节点和网关连线,组成逻辑正确的流程图。

具体代码

package com.zxy.manage.flow.vo.flowable.processinstance;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * 并行临时任务
 */
@Data
public class TempUserTaskDTO implements Serializable {
    @ApiModelProperty(value = "当前节点id")
    private String sourceActivityId;
    @ApiModelProperty(value = "目标节点id")
    private String targetActivityId;
    @ApiModelProperty(value = "当前流程实例id")
    private String processInsId;
    @ApiModelProperty(value = "用户任务配置,list中的task只能够是串行的")
    private List<UserTaskDTO> userTaskDTOList;
package com.zxy.manage.flow.vo.flowable.processinstance;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * 单个临时任务
 */
@Data
public class UserTaskDTO implements Serializable {
    @ApiModelProperty(value = "节点id")
    private String activityId;
    @ApiModelProperty(value = "任务名称")
    private String name;
    @ApiModelProperty(value = "任务责任人-单实例选填")
    private String assignee;
    @ApiModelProperty(value = "描述")
    private String documentation;
    @ApiModelProperty(value = "任务类型-多实例选填:USERS、DEPTS、STEMS")
    private String dataType;
    @ApiModelProperty(value = "顺序执行-多实例选填,true/false")
    private Boolean sequential;
    @ApiModelProperty(value = "签收人员")
    private List<String> candidateUsers;
    @ApiModelProperty(value = "预设分组-多实例选填:dataType为DEPTS、STEMS")
    private List<String> candidateGroups;
    @ApiModelProperty(value = "抄送人员")
    private List<String> copyUsers;
    @ApiModelProperty(value = "完成条件")
    private String completionCondition;
    @ApiModelProperty(value = "分组名称")
    private String text;
    @ApiModelProperty(value = "任务监听器列表")
    private List<TaskListenerDTO> taskListenerList;
}
package com.zxy.manage.flow.vo.flowable.processinstance;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * 任务监听器
 */
@Data
public class TaskListenerDTO implements Serializable {
    @ApiModelProperty(value = "事件:create")
    private String event;
    @ApiModelProperty(value = "继承类,对应class属性")
    private String implementation;
    @ApiModelProperty(value = "字段扩展列表")
    private List<FieldExtensionDTO> fieldExtensions = new ArrayList<>();
}

临时任务添加cmd类
注意事项:
1、多实例实现的获取用户列表,${multiInstanceHandler.getUserIds(execution)},依赖多实例用户列表获取类MultiInstanceHandler。
2、绘制流程实例的bpmn图,是简单绘制的,布局较乱,如果需要较为可观的图形,前端使用bpmn组件,可以自动优化流程图布局。

package com.cas.manage.flow.cmd;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.cas.iot.framework.common.error.BusinessException;
import com.cas.manage.flow.constant.CustomConstant;
import com.cas.manage.flow.constant.DataTypeConstant;
import com.cas.manage.flow.constant.FlowableConstant;
import com.cas.manage.flow.model.flowable.ExtendHisprocinst;
import com.cas.manage.flow.service.flowable.IExtendHisprocinstService;
import com.cas.manage.flow.vo.flowable.processinstance.TempUserTaskDTO;
import com.cas.manage.flow.vo.flowable.processinstance.UserTaskDTO;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.cmd.InjectUserTaskInProcessInstanceCmd;
import org.flowable.engine.impl.dynamic.BaseDynamicSubProcessInjectUtil;
import org.flowable.engine.impl.dynamic.DynamicUserTaskBuilder;
import org.flowable.engine.impl.persistence.entity.DeploymentEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程实例中增加临时任务
 */
public class InjectMultiInstanceUserTaskCmd extends InjectUserTaskInProcessInstanceCmd {
    private final int taskHeight = 80;
    private final int taskWidth = 100;
    private final int x = 160;
    private final int y = -160;
    private TempUserTaskDTO tempUserTaskDTO;
    private IExtendHisprocinstService extendHisprocinstService;

    public InjectMultiInstanceUserTaskCmd(TempUserTaskDTO tempUserTaskDTO, IExtendHisprocinstService extendHisprocinstService, DynamicUserTaskBuilder dynamicUserTaskBuilder) {
        super(tempUserTaskDTO.getProcessInsId(), dynamicUserTaskBuilder);
        this.tempUserTaskDTO = tempUserTaskDTO;
        this.extendHisprocinstService = extendHisprocinstService;
    }

    @Override
    protected void updateBpmnProcess(CommandContext commandContext, Process process, BpmnModel bpmnModel, ProcessDefinitionEntity originalProcessDefinitionEntity, DeploymentEntity newDeploymentEntity) {
        String sourceActivityId = tempUserTaskDTO.getSourceActivityId();
        FlowElement sourceElement = process.getFlowElement(sourceActivityId);
        if (!(sourceElement instanceof UserTask)) {
            throw new BusinessException("开始节点必须为任务节点");
        }
        UserTask sourceUserTask = (UserTask) sourceElement;
        if (CollectionUtil.isEmpty(sourceUserTask.getOutgoingFlows())) {
            throw new BusinessException("fromUserTask 不存在出口连线");
        }
        if (sourceUserTask.getOutgoingFlows().size() > 1) {
            throw new BusinessException("不支持多条出口连线");
        }

        // prev -> task -> task -> next
        // 修改为
        //                 -> dynamicTask -> dynamicTask ->
        // prev -> gateway -> task        -> task        ->  gateway -> next
        //
        //                 -> dynamicTask -> dynamicTask ->
        // 获取 prev 节点和 next 节点
        String targetActivityId = tempUserTaskDTO.getTargetActivityId();
        UserTask targetUserTask = (UserTask) process.getFlowElement(targetActivityId);
        // 如果 prev 节点的出口连线是多条,不支持
        if (CollectionUtil.isEmpty(targetUserTask.getIncomingFlows())) {
            throw new BusinessException("targetUserTask 不存在入口连线");
        }
        if (targetUserTask.getIncomingFlows().size() > 1) {
            throw new BusinessException("不支持多条入口连线");
        }
        List<UserTaskDTO> userTaskDTOList = tempUserTaskDTO.getUserTaskDTOList();
        // 如果 prev 节点的出口存在网关,则复用网关
        // 需要添加的连线
        ParallelGateway inGateway;
        ParallelGateway outGateway;
        FlowElement prevNextElement = sourceUserTask.getOutgoingFlows().get(0).getTargetFlowElement();
        FlowElement nextPrevElement = targetUserTask.getIncomingFlows().get(0).getSourceFlowElement();
        if (prevNextElement instanceof ParallelGateway && nextPrevElement instanceof ParallelGateway) {
            inGateway = (ParallelGateway) prevNextElement;
            outGateway = (ParallelGateway) nextPrevElement;
            this.buildDynamicTaskChain(process, bpmnModel, inGateway, outGateway);
        } else if (prevNextElement instanceof UserTask && nextPrevElement instanceof UserTask) {
            inGateway = new ParallelGateway();
            inGateway.setId("Gateway_zxy_" + IdUtil.fastSimpleUUID());
            process.addFlowElement(inGateway);
            GraphicInfo graphicInfo1 = bpmnModel.getGraphicInfo(sourceUserTask.getId());
            double startX1 = graphicInfo1.getX() + graphicInfo1.getWidth() + x;
            double startY1 = graphicInfo1.getY();
            this.drawUserTask(bpmnModel, startX1, startY1, inGateway.getId());

            outGateway = new ParallelGateway();
            outGateway.setId("Gateway_zxy_" + IdUtil.fastSimpleUUID());
            process.addFlowElement(outGateway);
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(targetUserTask.getId());
            double startX = graphicInfo.getX() - graphicInfo.getWidth() - x;
            double startY = graphicInfo.getY();
            this.drawUserTask(bpmnModel, startX, startY, outGateway.getId());

            // 将sourceUserTask的出线连接至网关
            SequenceFlow flow1 = sourceUserTask.getOutgoingFlows().get(0);
            flow1.setTargetFlowElement(inGateway);
            flow1.setTargetRef(inGateway.getId());
            List<GraphicInfo> list1 = bpmnModel.getFlowLocationGraphicInfo(flow1.getId());
            list1.get(0).setHeight(list1.get(0).getHeight() + taskHeight);
            list1.get(0).setWidth(list1.get(0).getWidth() + taskWidth);
            // 将targetUserTask的入线连接至网关
            SequenceFlow flow2 = targetUserTask.getIncomingFlows().get(0);
            flow2.setSourceRef(outGateway.getId());
            flow2.setSourceFlowElement(outGateway);
            List<GraphicInfo> list2 = bpmnModel.getFlowLocationGraphicInfo(flow1.getId());
            list2.get(0).setX(list2.get(0).getX() + x * userTaskDTOList.size());
            list2.get(0).setY(list2.get(0).getY() + y * userTaskDTOList.size());
            // 进入网关至原本userTask连线
            // 推出网关至原本userTask连线
            this.buildAndDrawSequenceFlow(process, bpmnModel, inGateway, prevNextElement);
            this.buildAndDrawSequenceFlow(process, bpmnModel, nextPrevElement, outGateway);
            this.buildDynamicTaskChain(process, bpmnModel, inGateway, outGateway);
        } else {
            throw new BusinessException("需要两侧存在并行网关,或者不存在并行网关");
        }
        BaseDynamicSubProcessInjectUtil.processFlowElements(commandContext, process, bpmnModel, originalProcessDefinitionEntity, newDeploymentEntity);
    }

    private void buildDynamicTaskChain(Process process, BpmnModel bpmnModel, FlowElement sourceElement, FlowElement targetElement) {
        List<UserTaskDTO> userTaskDTOList = tempUserTaskDTO.getUserTaskDTOList();
        List<UserTask> userTaskList = userTaskDTOList.stream()
                .map(userTaskDTO -> this.buildUserTask(process, userTaskDTO))
                .collect(Collectors.toList());
        // 画任务
        for (int i = 0; i < userTaskList.size(); i++) {
            if (i == 0) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(sourceElement.getId());
                double startX = graphicInfo.getX() + graphicInfo.getWidth() + x;
                double startY = graphicInfo.getY() + y;
                this.drawUserTask(bpmnModel, startX, startY, userTaskList.get(i).getId());
            } else {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(userTaskList.get(i - 1).getId());
                double startX = graphicInfo.getX() + graphicInfo.getWidth() + x;
                double startY = graphicInfo.getY();
                this.drawUserTask(bpmnModel, startX, startY, userTaskList.get(i).getId());
            }
        }
        // 画任务连线
        for (int i = 0; i < userTaskList.size(); i++) {
            if (i == 0) {
                this.buildAndDrawSequenceFlow(process, bpmnModel, sourceElement, userTaskList.get(i));
            }
            if (i == userTaskList.size() - 1) {
                this.buildAndDrawSequenceFlow(process, bpmnModel, userTaskList.get(i), targetElement);
            } else {
                this.buildAndDrawSequenceFlow(process, bpmnModel, userTaskList.get(i), userTaskList.get(i + 1));
            }

        }
    }

    private void drawUserTask(BpmnModel bpmnModel, Double startX, Double startY, String activityId) {
        GraphicInfo newTaskGraphicInfo = new GraphicInfo(startX, startY, taskHeight, taskWidth);
        bpmnModel.addGraphicInfo(activityId, newTaskGraphicInfo);
    }

    private void buildAndDrawSequenceFlow(Process process, BpmnModel bpmnModel, FlowElement sourceFlowElement, FlowElement targetFlowElement) {
        SequenceFlow flow = new SequenceFlow();
        flow.setId("Flow_" + IdUtil.fastSimpleUUID());
        flow.setSourceRef(sourceFlowElement.getId());
        flow.setSourceFlowElement(sourceFlowElement);
        flow.setTargetRef(targetFlowElement.getId());
        flow.setTargetFlowElement(targetFlowElement);
        process.addFlowElement(flow);

        GraphicInfo sourceGraphicInfo = bpmnModel.getGraphicInfo(flow.getSourceRef());
        GraphicInfo targetGraphicInfo = bpmnModel.getGraphicInfo(flow.getTargetRef());

        List<GraphicInfo> wayPoints = createWayPoints(sourceGraphicInfo.getX(), sourceGraphicInfo.getY(), targetGraphicInfo.getX(), targetGraphicInfo.getY());
        bpmnModel.addFlowGraphicInfoList(flow.getId(), wayPoints);
    }

    /**
     * 创建动态节点
     */
    private UserTask buildUserTask(Process process, UserTaskDTO userTaskDTO) {
        UserTask userTask = new UserTask();
        userTask.setId(userTaskDTO.getActivityId());
        userTask.setName(userTaskDTO.getName());
        userTask.setAssignee(userTaskDTO.getAssignee());

        // 设置任务监听器
        if (CollectionUtil.isNotEmpty(userTaskDTO.getTaskListenerList())) {
            List<FlowableListener> taskListeners = userTaskDTO.getTaskListenerList().stream()
                    .map(taskListenerDTO -> {
                        FlowableListener listener = new FlowableListener();
                        listener.setImplementationType("class");
                        listener.setEvent(taskListenerDTO.getEvent());
                        listener.setImplementation(taskListenerDTO.getImplementation());
                        if (CollectionUtil.isNotEmpty(taskListenerDTO.getFieldExtensions())) {
                            List<FieldExtension> fieldExtensions = taskListenerDTO.getFieldExtensions().stream()
                                    .map(fieldExtensionDTO -> {
                                        FieldExtension fieldExtension = new FieldExtension();
                                        fieldExtension.setFieldName(fieldExtensionDTO.getFieldName());
                                        fieldExtension.setStringValue(fieldExtensionDTO.getStringValue());
                                        return fieldExtension;
                                    }).collect(Collectors.toList());
                            listener.setFieldExtensions(fieldExtensions);
                        }
                        return listener;
                    }).collect(Collectors.toList());
            userTask.setTaskListeners(taskListeners);
        }

        Map<String, List<ExtensionAttribute>> attributeMap = new HashMap<>();
        // 设置 userTask 标签的自定义属性
        ExtensionAttribute attribute1 = new ExtensionAttribute();
        attribute1.setName("flowable:" + CustomConstant.TASK_CUSTOM_DATA_TYPE);
        attribute1.setValue(userTaskDTO.getDataType());

        ExtensionAttribute attribute2 = new ExtensionAttribute();
        attribute2.setName("flowable:" + CustomConstant.TASK_CUSTOM_TEXT);
        attribute2.setValue(userTaskDTO.getText());
        attributeMap.put(FlowableConstant.NAMASPASE, Arrays.asList(attribute1, attribute2));
        userTask.setAttributes(attributeMap);
        // 多实例
        if (ObjectUtil.isNotEmpty(userTaskDTO.getCandidateUsers()) || ObjectUtil.isNotEmpty(userTaskDTO.getCandidateGroups())) {
            // 创建多实例对象,设置多实例条件
            MultiInstanceLoopCharacteristics characteristics = new MultiInstanceLoopCharacteristics();
            // 设置多实例用户变量
            characteristics.setElementVariable("assignee");
            // 设置获取用户信息的方法处理器
            // 这里不能使用setCollectionString()方法,根据官方说法,
            // 当使用的表达式或者类获取用户列表是,应当使用setInputDataItem代替
            characteristics.setInputDataItem("${multiInstanceHandler.getUserIds(execution)}");
            // 设置是并行还是串行
            characteristics.setSequential(userTaskDTO.getSequential());
            // 设置任务完成条件
            characteristics.setCompletionCondition(userTaskDTO.getCompletionCondition());
            if (DataTypeConstant.USER_GROUP_PREFIX.equals(userTaskDTO.getDataType())) {
                // 设置用户列表
                userTask.setCandidateUsers(userTaskDTO.getCandidateUsers());
            } else if (DataTypeConstant.DEPT_GROUP_PREFIX.equals(userTaskDTO.getDataType()) || DataTypeConstant.STEM_GROUP_PREFIX.equals(userTaskDTO.getDataType())) {
                // 设置部门列表 OR 系统列表
                userTask.setCandidateGroups(userTaskDTO.getCandidateGroups());
            }
            // 从多实例变量中取审批人
            userTask.setAssignee("${assignee}");
            userTask.setLoopCharacteristics(characteristics);
        } else {
            // 设置审批人
            userTask.setAssignee(userTaskDTO.getAssignee());
        }
        process.addFlowElement(userTask);
        return userTask;
    }
    
    @Override
    protected void updateExecutions(CommandContext commandContext, ProcessDefinitionEntity processDefinitionEntity, ExecutionEntity processInstance, List<ExecutionEntity> childExecutions) {
        // 修改 processDefinitionId
        ExtendHisprocinst extendHisprocinst = extendHisprocinstService.findExtendHisprocinstByProcessInstanceId(processInstanceId);
        extendHisprocinst.setProcessDefinitionId(processDefinitionEntity.getId());
        extendHisprocinstService.updateById(extendHisprocinst);
    }
}

方法调用

    @Autowired
    private ManagementService managementService;
    @Autowired
    private IExtendHisprocinstService extendHisprocinstService;
    
    public void addTempTask(TempUserTaskDTO tempUserTaskDTO) {
        UserTaskDTO userTaskDTO = tempUserTaskDTO.getUserTaskDTOList().get(0);
        DynamicUserTaskBuilder taskBuilder = new DynamicUserTaskBuilder();
        taskBuilder.setId(userTaskDTO.getActivityId());
        taskBuilder.setName(userTaskDTO.getName());
        taskBuilder.setAssignee(userTaskDTO.getAssignee());
        managementService.executeCommand(new InjectMultiInstanceUserTaskCmd(tempUserTaskDTO, extendHisprocinstService, taskBuilder));
    }

联系作者

如果有任何疑问,请邮件联系作者。
zhuxuanyong@163.com

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

siihmc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值