需求来源
有的时候我们需要动态调整流程图,每次调整时都需要修改、部署 及发布等操作 才能正常生成我们想要的BpmnModel。这个时候就想,我们能不能通过数据库配置,反向生成流程图呢?当然可以,这个也可以解决动态加节点问题。
正向: 流程设计器设计-》保存到-》BpmnModel=》部署=》发布 适用与业务人员
反向: 数据库=》BpmnModel 适用与对产品非常熟悉的开发人员
数据库设计
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for oa_node
-- ----------------------------
DROP TABLE IF EXISTS `oa_node`;
CREATE TABLE `oa_node` (
`id` int(20) unsigned NOT NULL AUTO_INCREMENT,
`node_id` varchar(64) DEFAULT NULL COMMENT '节点编号',
`node_name` varchar(64) DEFAULT '' COMMENT '节点名称',
`process_id` int(20) DEFAULT NULL COMMENT '流程编号',
`process_key` varchar(64) DEFAULT '' COMMENT '流程key',
`node_type` varchar(64) DEFAULT '' COMMENT '节点类型',
`task_type` varchar(64) DEFAULT 'approve' COMMENT '任务类型',
`sort` int(10) unsigned DEFAULT '1' COMMENT '排序',
`user_id_list` varchar(255) DEFAULT '' COMMENT '节点人员编号列表',
`role_group_id` int(20) DEFAULT NULL COMMENT '节点角色组编号',
`find_user_type` tinyint(4) DEFAULT '7' COMMENT '节点用户类型',
`combine_type` tinyint(2) unsigned DEFAULT '2' COMMENT '组合方式:1 正常(找不到节点人员提示异常) 2 正常(找不到节点人员就跳过当前环节) ',
`relation_node_id` varchar(64) DEFAULT '' COMMENT '依赖节点',
`action_types` varchar(255) DEFAULT 'pass,reject' COMMENT '动作集合',
`skip_expression` varchar(1000) DEFAULT '' COMMENT '用户任务条件跳过表达式',
`expression` varchar(1000) DEFAULT '' COMMENT '连线表达式/用户任务完成表达式',
`source_ref` varchar(64) DEFAULT '' COMMENT '连线来源节点NodeId',
`target_ref` varchar(64) DEFAULT '' COMMENT '连线目标节点NodeId',
`sequential` tinyint(2) unsigned DEFAULT '1' COMMENT '1 单实例 2 串行多实例 3 并行多实例',
`countersign_type` varchar(10) DEFAULT '1' COMMENT '会签类型',
`proportion` varchar(4) DEFAULT '' COMMENT '通过比例',
`valid_state` tinyint(2) unsigned DEFAULT '1' COMMENT '状态 1 有效 0 失效',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
`operator_id` int(11) DEFAULT NULL COMMENT '操作人工号',
`operator_name` varchar(64) DEFAULT '' COMMENT '操作人姓名',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_nid_pid` (`node_id`,`process_id`),
KEY `idx_process_id` (`process_id`),
KEY `idx_process_key` (`process_key`),
KEY `idx_pid_nid` (`node_id`,`process_id`)
) ENGINE=InnoDB AUTO_INCREMENT=136 DEFAULT CHARSET=utf8mb4 COMMENT='流程节点表';
SET FOREIGN_KEY_CHECKS=1;
引入POM
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.4.1</version>
</dependency>
<!-- 如果自动布局,则需要引入该jar -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-bpmn-layout</artifactId>
<version>6.4.1</version>
</dependency>
工具类
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
/**
* todo:
*
* @author : zhoulin.zhu
* @date : 2020/3/3 10:11
*/
@Data
@ToString
public class NodeDTO implements Serializable {
private static final long serialVersionUID = 462441365348815087L;
private Integer id;
private String nodeId;
private String nodeName;
private Integer processId;
private String processKey;
private String nodeType;
private String taskType;
private Integer sort;
private String userIdList;
private Integer roleGroupId;
private Integer findUserType;
private Integer combineType;
private String relationNodeId;
private String actionTypes;
private String skipExpression;
private String sourceRef;
private String targetRef;
private String expression;
private Integer sequential;
private String countersignType;
private String proportion;
private Integer validState;
private Integer operatorId;
private String operatorName;
}
import com.alibaba.fastjson.JSONArray;
import com.zhenai.oa.flow.dto.response.NodeDTO;
import com.zhenai.oa.flow.handler.SpringContextHandler;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.engine.RepositoryService;
import org.flowable.validation.ProcessValidator;
import org.flowable.validation.ProcessValidatorFactory;
import org.flowable.validation.ValidationError;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* todo: 根据nodeList 动态生成BpmnModel
*
* @author : zhoulin.zhu
* @date : 2020/4/21 14:51
*/
public class AutoBuildModelUtils {
private static final String ELEMENT_TYPE_PREFIX = "bpmn:";
private static final String DEFAULT_ASSIGNEE_LIST_EXP = "${assigneeList}";
private static final String ASSIGNEE_USER_EXP = "assignee";
private static final String DEFAULT_ASSIGNEE_USER_EXP = "assigneeExp";
//即 只要有一个人完成任务,则当前任务也算完成
private static final String COMPLETION_CONDITION = "${nrOfCompletedInstances/nrOfInstances >= 0}";
private static final String DEFAULT_SKIP_EXPRESSION ="${approveAction == \"approveFail\"}";
private static final String ELEMENT_TYPE_USER_TASK = "UserTask";
private static final String ELEMENT_TYPE_START_EVENT = "StartEvent";
private static final String ELEMENT_TYPE_END_EVENT = "EndEvent";
private static final String ELEMENT_TYPE_EXCLUSIVE_GATEWAY = "ExclusiveGateway";
private static final String ELEMENT_TYPE_PARALLEL_GATEWAY = "ParallelGateway";
private static final String ELEMENT_TYPE_SEQUENCE_FLOW = "SequenceFlow";
private static final String ELEMENT_TYPE_LANE = "Lane";
private static final String ELEMENT_TYPE_SUB_PROCESS = "SubProcess";
/**
* 功能描述: 根据数据库配置nodeDTO,反向生成流程图模型
*
*
* @param processKey 流程KEY
* @param processName 流程Name
* @param nodeList nodeList
* @param autoDeploy 是否自动部署
* @return : org.flowable.bpmn.model.BpmnModel
* @author : zhoulin.zhu
* @date : 2020/4/21 15:21
*/
public static BpmnModel autoBulidBpmnModel(String processKey, String processName, List<NodeDTO> nodeList, boolean autoDeploy){
BpmnModel bpmnModel = new BpmnModel();
Process process = new Process();
for (NodeDTO nodeDTO : nodeList) {
if (StringUtils.isEmpty(nodeDTO.getNodeType())) {
continue;
}
/* 可根据实际需求拓展 */
switch (nodeDTO.getNodeType().replaceAll(ELEMENT_TYPE_PREFIX, "")) {
case ELEMENT_TYPE_START_EVENT:
process.addFlowElement(createStartEvent(nodeDTO.getNodeId(), nodeDTO.getNodeName()));
break;
case ELEMENT_TYPE_END_EVENT:
process.addFlowElement(createEndEvent(nodeDTO.getNodeId(), nodeDTO.getNodeName()));
break;
case ELEMENT_TYPE_USER_TASK:
process.addFlowElement(createUserTask(nodeDTO));
break;
case ELEMENT_TYPE_EXCLUSIVE_GATEWAY:
process.addFlowElement(createExclusiveGateway(nodeDTO.getNodeId(), nodeDTO.getNodeName(), null));
break;
case ELEMENT_TYPE_PARALLEL_GATEWAY:
process.addFlowElement(createParallelGateway(nodeDTO.getNodeId(), nodeDTO.getNodeName(), null));
break;
case ELEMENT_TYPE_SEQUENCE_FLOW:
process.addFlowElement(createSequenceFlow(nodeDTO));
break;
case ELEMENT_TYPE_LANE:
// not support type
//createLane(nodeDTO.getNodeId(),nodeDTO.getNodeName());
break;
case ELEMENT_TYPE_SUB_PROCESS:
process.addFlowElement(createSubProcess(nodeDTO.getNodeId(), nodeDTO.getNodeName()));
break;
default:
// logger not support type
break;
}
}
bpmnModel.addProcess(process);
/* 生成BPMN自动布局 */
BpmnAutoLayout bpmnAutoLayout = new BpmnAutoLayout(bpmnModel);
bpmnAutoLayout.execute();
//验证组装bpmnmodel是否正确
ProcessValidator defaultProcessValidator = new ProcessValidatorFactory().createDefaultProcessValidator();
List<ValidationError> validate = defaultProcessValidator.validate(bpmnModel);
if (validate.size() > 0) {
throw new FlowableException(JSONArray.toJSON(validate).toString());
}
byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
if(autoDeploy){
SpringContextHandler.getBean(RepositoryService.class).createDeployment()
.name(processName)
.key(processKey)
.addBytes(processName + ".bpmn20.xml", bpmnBytes)
.deploy();
}
return bpmnModel;
}
//开始节点
private static StartEvent createStartEvent(String nodeId, String nodeName) {
StartEvent startEvent = new StartEvent();
startEvent.setId(nodeId);
if (!StringUtils.isEmpty(nodeName)) {
startEvent.setName(nodeName);
} else {
startEvent.setName("开始");
}
return startEvent;
}
/*结束节点*/
private static EndEvent createEndEvent(String nodeId, String nodeName) {
EndEvent endEvent = new EndEvent();
endEvent.setId(nodeId);
if (!StringUtils.isEmpty(nodeName)) {
endEvent.setName(nodeName);
} else {
endEvent.setName("结束");
}
return endEvent;
}
//排他网关
private static ExclusiveGateway createExclusiveGateway(String nodeId, String nodeName, String defaultFlow) {
ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
exclusiveGateway.setId(nodeId);
exclusiveGateway.setName(nodeName);
if (!StringUtils.isEmpty(defaultFlow)) {
exclusiveGateway.setDefaultFlow(defaultFlow);
}
return exclusiveGateway;
}
//并行网关
private static ParallelGateway createParallelGateway(String nodeId, String nodeName, String defaultFlow) {
ParallelGateway parallelGateway = new ParallelGateway();
parallelGateway.setId(nodeId);
parallelGateway.setName(nodeName);
if (!StringUtils.isEmpty(defaultFlow)) {
parallelGateway.setDefaultFlow(defaultFlow);
}
return parallelGateway;
}
private static UserTask createUserTask(NodeDTO nodeDTO) {
UserTask userTask = new UserTask();
userTask.setName(nodeDTO.getNodeName());
userTask.setId(nodeDTO.getNodeId());
if (nodeDTO.getSequential() > 1) {
MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = new MultiInstanceLoopCharacteristics();
multiInstanceLoopCharacteristics.setSequential(nodeDTO.getSequential() == 2);
multiInstanceLoopCharacteristics.setInputDataItem(DEFAULT_ASSIGNEE_LIST_EXP);
multiInstanceLoopCharacteristics.setElementVariable(DEFAULT_ASSIGNEE_USER_EXP);
if(nodeDTO.getSequential() > 1 && !StringUtils.isEmpty(nodeDTO.getProportion()) ){
userTask.setSkipExpression(nodeDTO.getSkipExpression());
}
if(!StringUtils.isEmpty(nodeDTO.getExpression())){
multiInstanceLoopCharacteristics.setCompletionCondition(nodeDTO.getExpression());
} else if(!StringUtils.isEmpty(nodeDTO.getProportion())) {
multiInstanceLoopCharacteristics.setCompletionCondition(COMPLETION_CONDITION.replaceAll("0",nodeDTO.getProportion()));
}
userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics);
}
if (!StringUtils.isEmpty(nodeDTO.getSkipExpression())) {
userTask.setSkipExpression(nodeDTO.getSkipExpression());
}
return userTask;
}
private static SequenceFlow createSequenceFlow(NodeDTO nodeDTO) {
SequenceFlow sequenceFlow = new SequenceFlow();
sequenceFlow.setName(nodeDTO.getNodeName());
sequenceFlow.setId(nodeDTO.getNodeId());
sequenceFlow.setTargetRef(nodeDTO.getTargetRef());
sequenceFlow.setSourceRef(nodeDTO.getSourceRef());
if (!StringUtils.isEmpty(nodeDTO.getSkipExpression())) {
sequenceFlow.setSkipExpression(nodeDTO.getSkipExpression());
}
if (!StringUtils.isEmpty(nodeDTO.getExpression())) {
sequenceFlow.setConditionExpression(nodeDTO.getExpression());
}
return sequenceFlow;
}
//设置泳道
private static Lane createLane(String nodeId, String nodeName) {
Lane lane = new Lane();
lane.setId(nodeId);
lane.setName(nodeName);
return lane;
}
//设置子流程
private static SubProcess createSubProcess(String nodeId, String nodeName) {
SubProcess subProcess = new SubProcess();
subProcess.setId(nodeId);
subProcess.setName(nodeName);
return subProcess;
}
}
源代码地址
联系我们
邮箱: 786289666@qq.com
QQ讨论群: 957664677