背景
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