修改工作流数据结构。
This commit is contained in:
parent
1d2b3eea5d
commit
40e2aba12c
@ -2,12 +2,15 @@ package com.qqchen.deploy.backend.workflow.dto;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import com.qqchen.deploy.backend.workflow.dto.graph.WorkflowGraph;
|
||||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums;
|
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流定义DTO
|
* 工作流定义DTO
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ -32,13 +35,20 @@ public class WorkflowDefinitionDTO extends BaseDTO {
|
|||||||
* BPMN XML内容
|
* BPMN XML内容
|
||||||
*/
|
*/
|
||||||
private String bpmnXml;
|
private String bpmnXml;
|
||||||
|
|
||||||
private JsonNode graphConfig;
|
/**
|
||||||
|
* 图形数据
|
||||||
private JsonNode flowableConfig;
|
*/
|
||||||
|
private WorkflowGraph graph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单配置
|
||||||
|
*/
|
||||||
private JsonNode formConfig;
|
private JsonNode formConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程状态
|
||||||
|
*/
|
||||||
private WorkflowStatusEnums status;
|
private WorkflowStatusEnums status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto.graph;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 边配置
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class EdgeConfig {
|
||||||
|
/**
|
||||||
|
* 条件
|
||||||
|
*/
|
||||||
|
private String condition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表达式
|
||||||
|
*/
|
||||||
|
private String expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型(sequence/message/association)
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
}
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto.graph;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class NodeConfig {
|
||||||
|
/**
|
||||||
|
* 配置类型
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现类
|
||||||
|
*/
|
||||||
|
private String implementation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字段配置
|
||||||
|
*/
|
||||||
|
private Map<String, Object> fields;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务处理人
|
||||||
|
*/
|
||||||
|
private String assignee;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 候选用户列表
|
||||||
|
*/
|
||||||
|
private List<String> candidateUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 候选组列表
|
||||||
|
*/
|
||||||
|
private List<String> candidateGroups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 到期时间
|
||||||
|
*/
|
||||||
|
private String dueDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级
|
||||||
|
*/
|
||||||
|
private Integer priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单标识
|
||||||
|
*/
|
||||||
|
private String formKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳过表达式
|
||||||
|
*/
|
||||||
|
private String skipExpression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否异步
|
||||||
|
*/
|
||||||
|
private Boolean isAsync;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否排他
|
||||||
|
*/
|
||||||
|
private Boolean exclusive;
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto.graph;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 位置数据传输对象
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Position {
|
||||||
|
/**
|
||||||
|
* X坐标
|
||||||
|
*/
|
||||||
|
private Double x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Y坐标
|
||||||
|
*/
|
||||||
|
private Double y;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto.graph;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点大小
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class Size {
|
||||||
|
/**
|
||||||
|
* 宽度
|
||||||
|
*/
|
||||||
|
private Integer width;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高度
|
||||||
|
*/
|
||||||
|
private Integer height;
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto.graph;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流边
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WorkflowEdge {
|
||||||
|
/**
|
||||||
|
* 边ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源节点ID
|
||||||
|
*/
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目标节点ID
|
||||||
|
*/
|
||||||
|
private String target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 边名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 边配置
|
||||||
|
*/
|
||||||
|
private EdgeConfig config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 边属性
|
||||||
|
*/
|
||||||
|
private Map<String, Object> properties;
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto.graph;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流图形数据传输对象
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WorkflowGraph {
|
||||||
|
/**
|
||||||
|
* 节点列表
|
||||||
|
*/
|
||||||
|
private List<WorkflowNode> nodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 边列表
|
||||||
|
*/
|
||||||
|
private List<WorkflowEdge> edges;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流属性
|
||||||
|
*/
|
||||||
|
private Map<String, Object> properties;
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto.graph;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.workflow.enums.NodeType;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流节点数据传输对象
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WorkflowNode {
|
||||||
|
/**
|
||||||
|
* 节点ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型
|
||||||
|
*/
|
||||||
|
private NodeType type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点位置
|
||||||
|
*/
|
||||||
|
private Position position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点配置
|
||||||
|
*/
|
||||||
|
private NodeConfig config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点属性
|
||||||
|
*/
|
||||||
|
private Map<String, Object> properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点大小
|
||||||
|
*/
|
||||||
|
private Size size;
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.dto.graph;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流属性
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WorkflowProperties {
|
||||||
|
/**
|
||||||
|
* 工作流名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流标识
|
||||||
|
*/
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流版本
|
||||||
|
*/
|
||||||
|
private Integer version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作流类别
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
}
|
||||||
@ -48,24 +48,22 @@ public class WorkflowDefinition extends Entity<Long> {
|
|||||||
private String bpmnXml;
|
private String bpmnXml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程图JSON数据
|
* 流程图数据,包含节点和连线的位置、样式等信息
|
||||||
*/
|
*/
|
||||||
@Type(JsonType.class)
|
@Type(JsonType.class)
|
||||||
@Column(name = "graph_json", columnDefinition = "json")
|
@Column(name = "graph_data", columnDefinition = "json")
|
||||||
private JsonNode graphJson;
|
private JsonNode graphData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单配置JSON
|
* 表单配置
|
||||||
* 包含:
|
|
||||||
* - 流程级别的表单配置
|
|
||||||
* - 流程变量定义
|
|
||||||
* - 表单验证规则
|
|
||||||
* - 表单布局配置
|
|
||||||
*/
|
*/
|
||||||
@Type(JsonType.class)
|
@Type(JsonType.class)
|
||||||
@Column(name = "form_config", columnDefinition = "json")
|
@Column(name = "form_config", columnDefinition = "json")
|
||||||
private JsonNode formConfig;
|
private JsonNode formConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程状态
|
||||||
|
*/
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private WorkflowStatusEnums status;
|
private WorkflowStatusEnums status;
|
||||||
@ -73,5 +71,31 @@ public class WorkflowDefinition extends Entity<Long> {
|
|||||||
/**
|
/**
|
||||||
* 流程描述
|
* 流程描述
|
||||||
*/
|
*/
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可执行
|
||||||
|
*/
|
||||||
|
@Column(name = "is_executable", nullable = false)
|
||||||
|
private Boolean isExecutable = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目标命名空间
|
||||||
|
*/
|
||||||
|
@Column(name = "target_namespace")
|
||||||
|
private String targetNamespace = "http://www.flowable.org/test";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程分类
|
||||||
|
*/
|
||||||
|
@Column(name = "category")
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程标签,用于分组和过滤
|
||||||
|
*/
|
||||||
|
@Type(JsonType.class)
|
||||||
|
@Column(name = "tags", columnDefinition = "json")
|
||||||
|
private JsonNode tags;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,7 +92,6 @@ public class WorkflowNodeDefinition extends Entity<Long> {
|
|||||||
/**
|
/**
|
||||||
* 排序号
|
* 排序号
|
||||||
*/
|
*/
|
||||||
@Type(JsonType.class)
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private Integer orderNum = 0;
|
private Integer orderNum = 0;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型枚举
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
|
*/
|
||||||
|
public enum NodeType {
|
||||||
|
START("start"),
|
||||||
|
END("end"),
|
||||||
|
USER_TASK("userTask"),
|
||||||
|
SERVICE_TASK("serviceTask"),
|
||||||
|
SCRIPT_TASK("scriptTask"),
|
||||||
|
EXCLUSIVE_GATEWAY("exclusiveGateway"),
|
||||||
|
PARALLEL_GATEWAY("parallelGateway"),
|
||||||
|
SUBPROCESS("subProcess"),
|
||||||
|
CALL_ACTIVITY("callActivity");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
NodeType(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package com.qqchen.deploy.backend.workflow.exception;
|
||||||
|
|
||||||
|
public class WorkflowValidationException extends RuntimeException {
|
||||||
|
|
||||||
|
public WorkflowValidationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkflowValidationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,6 @@ package com.qqchen.deploy.backend.workflow.service.impl;
|
|||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDesignDTO;
|
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowExecutionDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowExecutionDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO;
|
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO;
|
||||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||||
@ -71,8 +70,7 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl<WorkflowDefin
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public WorkflowDefinitionDTO saveWorkflowDesign(WorkflowDefinitionDTO dto) throws Exception {
|
public WorkflowDefinitionDTO saveWorkflowDesign(WorkflowDefinitionDTO dto) throws Exception {
|
||||||
// 转换图形JSON为BPMN XML
|
// 转换图形JSON为BPMN XML
|
||||||
String bpmnXml = bpmnConverter.convertToBpmnXml(dto.getGraphConfig().toString(), dto.getKey());
|
String bpmnXml = bpmnConverter.convertToXml(dto.getGraph(), dto.getKey());
|
||||||
|
|
||||||
dto.setFlowVersion(1);
|
dto.setFlowVersion(1);
|
||||||
dto.setBpmnXml(bpmnXml);
|
dto.setBpmnXml(bpmnXml);
|
||||||
// // 创建工作流定义
|
// // 创建工作流定义
|
||||||
|
|||||||
@ -1,294 +1,305 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.util;
|
package com.qqchen.deploy.backend.workflow.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.qqchen.deploy.backend.workflow.dto.graph.*;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.qqchen.deploy.backend.workflow.enums.NodeType;
|
||||||
import com.qqchen.deploy.backend.workflow.constants.BpmnConstants;
|
|
||||||
import com.qqchen.deploy.backend.workflow.enums.BpmnNodeTypeEnums;
|
|
||||||
import com.qqchen.deploy.backend.workflow.handler.BpmnNodeHandler;
|
|
||||||
import com.qqchen.deploy.backend.workflow.model.BpmnNodeConfig;
|
|
||||||
import com.qqchen.deploy.backend.workflow.parser.BpmnNodeConfigParser;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.flowable.bpmn.BpmnAutoLayout;
|
|
||||||
import org.flowable.bpmn.converter.BpmnXMLConverter;
|
import org.flowable.bpmn.converter.BpmnXMLConverter;
|
||||||
import org.flowable.bpmn.model.*;
|
import org.flowable.bpmn.model.*;
|
||||||
import org.flowable.bpmn.model.Process;
|
import org.flowable.bpmn.model.Process;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BPMN转换工具类
|
* BPMN 模型转换工具
|
||||||
|
* @author cascade
|
||||||
|
* @date 2024-12-11
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
@Component
|
@Component
|
||||||
public class BpmnConverter {
|
public class BpmnConverter {
|
||||||
|
|
||||||
private final Map<BpmnNodeTypeEnums, BpmnNodeHandler<?>> handlers;
|
private final BpmnXMLConverter bpmnXmlConverter = new BpmnXMLConverter();
|
||||||
private final ObjectMapper objectMapper;
|
|
||||||
private final BpmnNodeConfigParser configParser;
|
|
||||||
private final BpmnXMLConverter bpmnXMLConverter;
|
|
||||||
|
|
||||||
public BpmnConverter(ObjectMapper objectMapper,
|
/**
|
||||||
List<BpmnNodeHandler<?>> nodeHandlers,
|
* 将工作流图转换为BPMN XML
|
||||||
BpmnNodeConfigParser configParser) {
|
* @param graph 工作流图数据
|
||||||
this.objectMapper = objectMapper;
|
* @param processKey 流程标识
|
||||||
this.configParser = configParser;
|
* @return BPMN XML字符串
|
||||||
this.bpmnXMLConverter = new BpmnXMLConverter();
|
*/
|
||||||
this.handlers = nodeHandlers.stream()
|
public String convertToXml(WorkflowGraph graph, String processKey) {
|
||||||
.collect(Collectors.toMap(BpmnNodeHandler::getType, Function.identity()));
|
BpmnModel bpmnModel = convertToBpmnModel(graph, processKey);
|
||||||
}
|
byte[] xmlBytes = bpmnXmlConverter.convertToXML(bpmnModel);
|
||||||
|
return new String(xmlBytes, StandardCharsets.UTF_8);
|
||||||
public String convertToBpmnXml(String x6Json, String processId) throws Exception {
|
|
||||||
JsonNode jsonNode = objectMapper.readTree(x6Json);
|
|
||||||
BpmnModel bpmnModel = createBpmnModel(processId);
|
|
||||||
Process process = bpmnModel.getMainProcess();
|
|
||||||
|
|
||||||
// 处理节点
|
|
||||||
Map<String, FlowElement> elementMap = new HashMap<>();
|
|
||||||
JsonNode cells = jsonNode.path(BpmnConstants.ProcessAttribute.CELLS);
|
|
||||||
|
|
||||||
if (!cells.isMissingNode()) {
|
|
||||||
// 处理所有节点
|
|
||||||
processNodes(cells, process, elementMap);
|
|
||||||
|
|
||||||
// 确保存在开始事件和结束事件(如果没有显式定义)
|
|
||||||
ensureStartAndEndEvents(process, elementMap);
|
|
||||||
|
|
||||||
// 处理所有连线
|
|
||||||
processSequenceFlows(cells, process, elementMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自动布局
|
|
||||||
new BpmnAutoLayout(bpmnModel).execute();
|
|
||||||
|
|
||||||
// 转换为XML
|
|
||||||
return new String(bpmnXMLConverter.convertToXML(bpmnModel));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建BPMN模型
|
* 将工作流图转换为BPMN模型
|
||||||
|
* @param graph 工作流图数据
|
||||||
|
* @param processKey 流程标识
|
||||||
|
* @return BPMN模型
|
||||||
*/
|
*/
|
||||||
private BpmnModel createBpmnModel(String processId) {
|
public BpmnModel convertToBpmnModel(WorkflowGraph graph, String processKey) {
|
||||||
BpmnModel bpmnModel = new BpmnModel();
|
BpmnModel bpmnModel = new BpmnModel();
|
||||||
Process process = new Process();
|
Process process = new Process();
|
||||||
process.setId(processId);
|
|
||||||
process.setName(processId);
|
// 设置流程属性
|
||||||
process.setExecutable(Boolean.parseBoolean(BpmnConstants.ProcessAttribute.EXECUTABLE));
|
process.setId(processKey);
|
||||||
|
if (graph.getProperties() != null) {
|
||||||
|
Map<String, Object> properties = graph.getProperties();
|
||||||
|
process.setName(properties.get("name") != null ? properties.get("name").toString() : null);
|
||||||
|
process.setDocumentation(properties.get("description") != null ? properties.get("description").toString() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换节点
|
||||||
|
for (WorkflowNode node : graph.getNodes()) {
|
||||||
|
FlowElement element = convertNode(node);
|
||||||
|
if (element != null) {
|
||||||
|
process.addFlowElement(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换边
|
||||||
|
for (WorkflowEdge edge : graph.getEdges()) {
|
||||||
|
SequenceFlow flow = convertEdge(edge);
|
||||||
|
if (flow != null) {
|
||||||
|
process.addFlowElement(flow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bpmnModel.addProcess(process);
|
bpmnModel.addProcess(process);
|
||||||
return bpmnModel;
|
return bpmnModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理所有节点
|
* 将工作流图转换为BPMN模型
|
||||||
|
* @param graph 工作流图数据
|
||||||
|
* @return BPMN模型
|
||||||
*/
|
*/
|
||||||
private void processNodes(JsonNode cells, Process process, Map<String, FlowElement> elementMap) {
|
public BpmnModel convertToBpmnModel(WorkflowGraph graph) {
|
||||||
cells.forEach(cell -> {
|
String processKey = null;
|
||||||
if (isNode(cell)) {
|
if (graph.getProperties() != null) {
|
||||||
processNode(cell, process, elementMap);
|
Object key = graph.getProperties().get("key");
|
||||||
}
|
processKey = key != null ? key.toString() : null;
|
||||||
});
|
}
|
||||||
|
if (processKey == null) {
|
||||||
|
processKey = "process_" + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
return convertToBpmnModel(graph, processKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理单个节点
|
* 转换节点
|
||||||
|
* @param node 工作流节点
|
||||||
|
* @return 流程元素
|
||||||
*/
|
*/
|
||||||
private void processNode(JsonNode cell, Process process, Map<String, FlowElement> elementMap) {
|
private FlowElement convertNode(WorkflowNode node) {
|
||||||
String shape = cell.path(BpmnConstants.NodeAttribute.SHAPE).asText();
|
if (node.getType() == null) {
|
||||||
String id = cell.path(BpmnConstants.NodeAttribute.ID).asText();
|
return null;
|
||||||
String label = cell.path(BpmnConstants.NodeAttribute.DATA)
|
|
||||||
.path(BpmnConstants.NodeAttribute.LABEL).asText("");
|
|
||||||
|
|
||||||
// 处理特殊节点类型
|
|
||||||
Optional<FlowElement> specialElement = createSpecialElement(shape, id, label);
|
|
||||||
if (specialElement.isPresent()) {
|
|
||||||
process.addFlowElement(specialElement.get());
|
|
||||||
elementMap.put(id, specialElement.get());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理普通节点
|
switch (node.getType()) {
|
||||||
processRegularNode(cell, process, elementMap);
|
case START:
|
||||||
}
|
return createStartEvent(node);
|
||||||
|
case END:
|
||||||
/**
|
return createEndEvent(node);
|
||||||
* 创建特殊元素(开始/结束事件)
|
case SERVICE_TASK:
|
||||||
*/
|
return createServiceTask(node);
|
||||||
private Optional<FlowElement> createSpecialElement(String shape, String id, String label) {
|
case USER_TASK:
|
||||||
FlowElement element = null;
|
return createUserTask(node);
|
||||||
|
case SCRIPT_TASK:
|
||||||
if (BpmnConstants.NodeShape.START.equals(shape)) {
|
return createScriptTask(node);
|
||||||
StartEvent startEvent = new StartEvent();
|
case EXCLUSIVE_GATEWAY:
|
||||||
startEvent.setId(id);
|
return createExclusiveGateway(node);
|
||||||
startEvent.setName(label);
|
case PARALLEL_GATEWAY:
|
||||||
element = startEvent;
|
return createParallelGateway(node);
|
||||||
} else if (BpmnConstants.NodeShape.END.equals(shape)) {
|
case SUBPROCESS:
|
||||||
EndEvent endEvent = new EndEvent();
|
return createSubProcess(node);
|
||||||
endEvent.setId(id);
|
case CALL_ACTIVITY:
|
||||||
endEvent.setName(label);
|
return createCallActivity(node);
|
||||||
element = endEvent;
|
default:
|
||||||
}
|
return null;
|
||||||
|
|
||||||
return Optional.ofNullable(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理普通节点
|
|
||||||
*/
|
|
||||||
private void processRegularNode(JsonNode cell, Process process, Map<String, FlowElement> elementMap) {
|
|
||||||
BpmnNodeConfig config = configParser.parse(cell);
|
|
||||||
if (config.getType() != null) {
|
|
||||||
BpmnNodeHandler<?> handler = handlers.get(config.getType());
|
|
||||||
if (handler != null) {
|
|
||||||
FlowElement element = createAndHandleElement(handler, cell, config);
|
|
||||||
process.addFlowElement(element);
|
|
||||||
elementMap.put(config.getId(), element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理所有连线
|
|
||||||
*/
|
|
||||||
private void processSequenceFlows(JsonNode cells, Process process, Map<String, FlowElement> elementMap) {
|
|
||||||
cells.forEach(cell -> {
|
|
||||||
if (isEdge(cell)) {
|
|
||||||
handleSequenceFlow(cell, process, elementMap);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureStartAndEndEvents(Process process, Map<String, FlowElement> elementMap) {
|
|
||||||
// 查找现有的开始和结束事件
|
|
||||||
StartEvent startEvent = null;
|
|
||||||
EndEvent endEvent = null;
|
|
||||||
FlowElement firstElement = null;
|
|
||||||
FlowElement lastElement = null;
|
|
||||||
|
|
||||||
// 遍历所有节点,找到开始和结束事件,以及第一个和最后一个任务节点
|
|
||||||
for (FlowElement element : process.getFlowElements()) {
|
|
||||||
if (element instanceof StartEvent) {
|
|
||||||
startEvent = (StartEvent) element;
|
|
||||||
} else if (element instanceof EndEvent) {
|
|
||||||
endEvent = (EndEvent) element;
|
|
||||||
} else if (!(element instanceof SequenceFlow)) {
|
|
||||||
if (firstElement == null) {
|
|
||||||
firstElement = element;
|
|
||||||
}
|
|
||||||
lastElement = element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有开始事件,创建一个并连接到第一个任务
|
|
||||||
if (startEvent == null && firstElement != null) {
|
|
||||||
startEvent = createStartEvent();
|
|
||||||
process.addFlowElement(startEvent);
|
|
||||||
elementMap.put(startEvent.getId(), startEvent);
|
|
||||||
|
|
||||||
// 创建从开始事件到第一个任务的连线
|
|
||||||
SequenceFlow startFlow = createSequenceFlow(
|
|
||||||
BpmnConstants.DefaultNodeId.START_FLOW,
|
|
||||||
startEvent.getId(),
|
|
||||||
firstElement.getId()
|
|
||||||
);
|
|
||||||
process.addFlowElement(startFlow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有结束事件,创建一个并从最后一个任务连接到它
|
|
||||||
if (endEvent == null && lastElement != null) {
|
|
||||||
endEvent = createEndEvent();
|
|
||||||
process.addFlowElement(endEvent);
|
|
||||||
elementMap.put(endEvent.getId(), endEvent);
|
|
||||||
|
|
||||||
// 创建从最后一个任务到结束事件的连线
|
|
||||||
SequenceFlow endFlow = createSequenceFlow(
|
|
||||||
BpmnConstants.DefaultNodeId.END_FLOW,
|
|
||||||
lastElement.getId(),
|
|
||||||
endEvent.getId()
|
|
||||||
);
|
|
||||||
process.addFlowElement(endFlow);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建开始事件
|
* 创建开始事件
|
||||||
|
* @param node 节点数据
|
||||||
|
* @return 开始事件
|
||||||
*/
|
*/
|
||||||
private StartEvent createStartEvent() {
|
private StartEvent createStartEvent(WorkflowNode node) {
|
||||||
StartEvent startEvent = new StartEvent();
|
StartEvent startEvent = new StartEvent();
|
||||||
startEvent.setId(BpmnConstants.DefaultNodeId.START_EVENT);
|
startEvent.setId(node.getId());
|
||||||
startEvent.setName(BpmnConstants.DefaultNodeName.START_EVENT);
|
startEvent.setName(node.getName());
|
||||||
return startEvent;
|
return startEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建结束事件
|
* 创建结束事件
|
||||||
|
* @param node 节点数据
|
||||||
|
* @return 结束事件
|
||||||
*/
|
*/
|
||||||
private EndEvent createEndEvent() {
|
private EndEvent createEndEvent(WorkflowNode node) {
|
||||||
EndEvent endEvent = new EndEvent();
|
EndEvent endEvent = new EndEvent();
|
||||||
endEvent.setId(BpmnConstants.DefaultNodeId.END_EVENT);
|
endEvent.setId(node.getId());
|
||||||
endEvent.setName(BpmnConstants.DefaultNodeName.END_EVENT);
|
endEvent.setName(node.getName());
|
||||||
return endEvent;
|
return endEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建连线
|
* 创建服务任务
|
||||||
|
* @param node 节点数据
|
||||||
|
* @return 服务任务
|
||||||
*/
|
*/
|
||||||
private SequenceFlow createSequenceFlow(String id, String sourceRef, String targetRef) {
|
private ServiceTask createServiceTask(WorkflowNode node) {
|
||||||
|
ServiceTask serviceTask = new ServiceTask();
|
||||||
|
serviceTask.setId(node.getId());
|
||||||
|
serviceTask.setName(node.getName());
|
||||||
|
|
||||||
|
if (node.getConfig() != null) {
|
||||||
|
NodeConfig config = node.getConfig();
|
||||||
|
serviceTask.setImplementation(config.getImplementation());
|
||||||
|
if (config.getFields() != null) {
|
||||||
|
config.getFields().forEach((key, value) -> {
|
||||||
|
FieldExtension field = new FieldExtension();
|
||||||
|
field.setFieldName(key);
|
||||||
|
field.setStringValue(value != null ? value.toString() : null);
|
||||||
|
serviceTask.getFieldExtensions().add(field);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用户任务
|
||||||
|
* @param node 节点数据
|
||||||
|
* @return 用户任务
|
||||||
|
*/
|
||||||
|
private UserTask createUserTask(WorkflowNode node) {
|
||||||
|
UserTask userTask = new UserTask();
|
||||||
|
userTask.setId(node.getId());
|
||||||
|
userTask.setName(node.getName());
|
||||||
|
|
||||||
|
if (node.getConfig() != null) {
|
||||||
|
NodeConfig config = node.getConfig();
|
||||||
|
userTask.setAssignee(config.getAssignee());
|
||||||
|
userTask.setCandidateUsers(config.getCandidateUsers());
|
||||||
|
userTask.setCandidateGroups(config.getCandidateGroups());
|
||||||
|
userTask.setDueDate(config.getDueDate());
|
||||||
|
userTask.setPriority(config.getPriority() != null ? config.getPriority().toString() : null);
|
||||||
|
userTask.setFormKey(config.getFormKey());
|
||||||
|
userTask.setSkipExpression(config.getSkipExpression());
|
||||||
|
}
|
||||||
|
|
||||||
|
return userTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建脚本任务
|
||||||
|
* @param node 节点数据
|
||||||
|
* @return 脚本任务
|
||||||
|
*/
|
||||||
|
private ScriptTask createScriptTask(WorkflowNode node) {
|
||||||
|
ScriptTask scriptTask = new ScriptTask();
|
||||||
|
scriptTask.setId(node.getId());
|
||||||
|
scriptTask.setName(node.getName());
|
||||||
|
|
||||||
|
if (node.getConfig() != null) {
|
||||||
|
NodeConfig config = node.getConfig();
|
||||||
|
scriptTask.setScriptFormat(config.getImplementation());
|
||||||
|
if (config.getFields() != null && config.getFields().containsKey("script")) {
|
||||||
|
scriptTask.setScript(config.getFields().get("script").toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建排他网关
|
||||||
|
* @param node 节点数据
|
||||||
|
* @return 排他网关
|
||||||
|
*/
|
||||||
|
private ExclusiveGateway createExclusiveGateway(WorkflowNode node) {
|
||||||
|
ExclusiveGateway gateway = new ExclusiveGateway();
|
||||||
|
gateway.setId(node.getId());
|
||||||
|
gateway.setName(node.getName());
|
||||||
|
return gateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建并行网关
|
||||||
|
* @param node 节点数据
|
||||||
|
* @return 并行网关
|
||||||
|
*/
|
||||||
|
private ParallelGateway createParallelGateway(WorkflowNode node) {
|
||||||
|
ParallelGateway gateway = new ParallelGateway();
|
||||||
|
gateway.setId(node.getId());
|
||||||
|
gateway.setName(node.getName());
|
||||||
|
return gateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建子流程
|
||||||
|
* @param node 节点数据
|
||||||
|
* @return 子流程
|
||||||
|
*/
|
||||||
|
private SubProcess createSubProcess(WorkflowNode node) {
|
||||||
|
SubProcess subProcess = new SubProcess();
|
||||||
|
subProcess.setId(node.getId());
|
||||||
|
subProcess.setName(node.getName());
|
||||||
|
return subProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建调用活动
|
||||||
|
* @param node 节点数据
|
||||||
|
* @return 调用活动
|
||||||
|
*/
|
||||||
|
private CallActivity createCallActivity(WorkflowNode node) {
|
||||||
|
CallActivity callActivity = new CallActivity();
|
||||||
|
callActivity.setId(node.getId());
|
||||||
|
callActivity.setName(node.getName());
|
||||||
|
|
||||||
|
if (node.getConfig() != null) {
|
||||||
|
NodeConfig config = node.getConfig();
|
||||||
|
callActivity.setCalledElement(config.getImplementation());
|
||||||
|
if (config.getFields() != null) {
|
||||||
|
config.getFields().forEach((key, value) -> {
|
||||||
|
IOParameter parameter = new IOParameter();
|
||||||
|
parameter.setSource(key);
|
||||||
|
parameter.setTarget(value != null ? value.toString() : null);
|
||||||
|
callActivity.getInParameters().add(parameter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return callActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换边
|
||||||
|
* @param edge 工作流边
|
||||||
|
* @return 序列流
|
||||||
|
*/
|
||||||
|
private SequenceFlow convertEdge(WorkflowEdge edge) {
|
||||||
SequenceFlow flow = new SequenceFlow();
|
SequenceFlow flow = new SequenceFlow();
|
||||||
flow.setId(id);
|
flow.setId(edge.getId());
|
||||||
flow.setSourceRef(sourceRef);
|
flow.setName(edge.getName());
|
||||||
flow.setTargetRef(targetRef);
|
flow.setSourceRef(edge.getSource());
|
||||||
|
flow.setTargetRef(edge.getTarget());
|
||||||
|
|
||||||
|
if (edge.getConfig() != null) {
|
||||||
|
EdgeConfig config = edge.getConfig();
|
||||||
|
if ("sequence".equals(config.getType())) {
|
||||||
|
if (config.getCondition() != null) {
|
||||||
|
((SequenceFlow) flow).setConditionExpression(config.getCondition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return flow;
|
return flow;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isNode(JsonNode cell) {
|
|
||||||
return cell.has(BpmnConstants.NodeAttribute.SHAPE) &&
|
|
||||||
!BpmnConstants.NodeShape.EDGE.equals(cell.path(BpmnConstants.NodeAttribute.SHAPE).asText());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEdge(JsonNode cell) {
|
|
||||||
return cell.has(BpmnConstants.NodeAttribute.SHAPE) &&
|
|
||||||
BpmnConstants.NodeShape.EDGE.equals(cell.path(BpmnConstants.NodeAttribute.SHAPE).asText());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T extends FlowElement> T createAndHandleElement(
|
|
||||||
BpmnNodeHandler<T> handler, JsonNode nodeData, BpmnNodeConfig config) {
|
|
||||||
T element = handler.createElement(config);
|
|
||||||
handler.handle(nodeData, element, config);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSequenceFlow(JsonNode cell, Process process, Map<String, FlowElement> elementMap) {
|
|
||||||
String sourceId = cell.path(BpmnConstants.NodeAttribute.SOURCE).asText();
|
|
||||||
String targetId = cell.path(BpmnConstants.NodeAttribute.TARGET).asText();
|
|
||||||
String id = cell.path(BpmnConstants.NodeAttribute.ID).asText();
|
|
||||||
|
|
||||||
FlowElement sourceElement = elementMap.get(sourceId);
|
|
||||||
FlowElement targetElement = elementMap.get(targetId);
|
|
||||||
|
|
||||||
if (sourceElement != null && targetElement != null) {
|
|
||||||
SequenceFlow sequenceFlow = createSequenceFlow(id, sourceId, targetId);
|
|
||||||
|
|
||||||
// 设置连线名称
|
|
||||||
JsonNode label = cell.path(BpmnConstants.NodeAttribute.DATA)
|
|
||||||
.path(BpmnConstants.NodeAttribute.LABEL);
|
|
||||||
if (!label.isMissingNode()) {
|
|
||||||
sequenceFlow.setName(label.asText());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置条件表达式
|
|
||||||
JsonNode condition = cell.path(BpmnConstants.NodeAttribute.DATA)
|
|
||||||
.path(BpmnConstants.NodeAttribute.CONDITION);
|
|
||||||
if (!condition.isMissingNode()) {
|
|
||||||
sequenceFlow.setConditionExpression(condition.asText());
|
|
||||||
}
|
|
||||||
|
|
||||||
process.addFlowElement(sequenceFlow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -383,26 +383,41 @@ CREATE TABLE deploy_repo_branch (
|
|||||||
-- --------------------------------------------------------------------------------------
|
-- --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- 工作流定义表
|
-- 工作流定义表
|
||||||
CREATE TABLE workflow_definition (
|
CREATE TABLE workflow_definition
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
(
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
-- 主键
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
-- 基础信息
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
name VARCHAR(255) NOT NULL COMMENT '流程名称',
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
`key` VARCHAR(255) NOT NULL COMMENT '流程标识',
|
||||||
|
flow_version INT NOT NULL COMMENT '流程版本',
|
||||||
name VARCHAR(100) NOT NULL COMMENT '流程名称',
|
description TEXT COMMENT '流程描述',
|
||||||
`key` VARCHAR(50) NOT NULL COMMENT '流程标识',
|
category VARCHAR(100) COMMENT '流程分类',
|
||||||
flow_version INT NOT NULL COMMENT '流程版本',
|
|
||||||
bpmn_xml TEXT COMMENT 'BPMN XML内容',
|
-- 流程配置
|
||||||
graph_json JSON COMMENT 'x6 JSON内容',
|
bpmn_xml TEXT COMMENT 'BPMN XML内容',
|
||||||
status VARCHAR(32) NOT NULL COMMENT '状态',
|
graph_data JSON COMMENT '流程图数据,包含节点和连线的位置、样式等信息',
|
||||||
description VARCHAR(255) NULL COMMENT '流程描述',
|
form_config JSON COMMENT '表单配置',
|
||||||
form_config JSON COMMENT '表单配置JSON',
|
tags JSON COMMENT '流程标签',
|
||||||
|
|
||||||
CONSTRAINT UK_workflow_definition_key_version UNIQUE (`key`, flow_version)
|
-- 流程属性
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流定义表';
|
status VARCHAR(50) NOT NULL COMMENT '流程状态(DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用)',
|
||||||
|
is_executable BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否可执行',
|
||||||
|
target_namespace VARCHAR(255) DEFAULT 'http://www.flowable.org/test' COMMENT '目标命名空间',
|
||||||
|
|
||||||
|
-- 审计字段
|
||||||
|
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
|
||||||
|
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新时间',
|
||||||
|
created_by BIGINT COMMENT '创建人',
|
||||||
|
updated_by BIGINT COMMENT '更新人',
|
||||||
|
is_deleted BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否删除',
|
||||||
|
|
||||||
|
-- 约束
|
||||||
|
UNIQUE KEY uk_key_version (`key`, flow_version)
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4
|
||||||
|
COLLATE = utf8mb4_unicode_ci COMMENT ='工作流定义表';
|
||||||
|
|
||||||
-- 工作流实例表
|
-- 工作流实例表
|
||||||
CREATE TABLE workflow_instance (
|
CREATE TABLE workflow_instance (
|
||||||
|
|||||||
@ -156,305 +156,141 @@ INSERT INTO sys_external_system (
|
|||||||
-- 初始化工作流相关数据
|
-- 初始化工作流相关数据
|
||||||
-- --------------------------------------------------------------------------------------
|
-- --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- 工作流定义测试数据
|
||||||
|
INSERT INTO workflow_definition (
|
||||||
|
name, `key`, flow_version, description, category,
|
||||||
|
bpmn_xml, graph_data, form_config, tags,
|
||||||
|
status, is_executable, target_namespace,
|
||||||
|
created_at, updated_at, created_by, updated_by, is_deleted
|
||||||
|
) VALUES
|
||||||
|
-- 简单流程:开始 -> 服务任务 -> 结束
|
||||||
|
(
|
||||||
|
'简单服务任务流程', 'simple_service_flow', 1, '一个包含服务任务的简单流程', 'test',
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/test"><process id="simple_service_flow" name="简单服务任务流程" isExecutable="true"><startEvent id="start1" name="开始"></startEvent><serviceTask id="service1" name="服务任务"><extensionElements><flowable:field name="script"><flowable:string><![CDATA[echo "Hello World"]]></flowable:string></flowable:field></extensionElements></serviceTask><endEvent id="end1" name="结束"></endEvent><sequenceFlow id="flow1" sourceRef="start1" targetRef="service1"></sequenceFlow><sequenceFlow id="flow2" sourceRef="service1" targetRef="end1"></sequenceFlow></process></definitions>',
|
||||||
|
'{"nodes":[{"id":"start1","type":"start","position":{"x":100,"y":100}},{"id":"service1","type":"serviceTask","position":{"x":300,"y":100}},{"id":"end1","type":"end","position":{"x":500,"y":100}}],"edges":[{"id":"flow1","source":"start1","target":"service1"},{"id":"flow2","source":"service1","target":"end1"}]}',
|
||||||
|
'{"formItems":[]}',
|
||||||
|
'["simple","test","service"]',
|
||||||
|
'PUBLISHED', TRUE, 'http://www.flowable.org/test',
|
||||||
|
NOW(), NOW(), 1, 1, FALSE
|
||||||
|
),
|
||||||
|
|
||||||
|
-- 用户任务流程:开始 -> 用户任务 -> 结束
|
||||||
|
(
|
||||||
|
'用户审批流程', 'user_approval_flow', 1, '一个简单的用户审批流程', 'approval',
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/test"><process id="user_approval_flow" name="用户审批流程"
|
||||||
|
isExecutable="true"><startEvent id="start1" name="开始"></startEvent><userTask id="user1" name="审批任务" flowable:assignee="$${initiator}" flowable:candidateUsers="user1,user2" flowable:candidateGroups="group1,group2" flowable:dueDate="$${dueDate}" flowable:formKey="form1"
|
||||||
|
flowable:priority="1"></userTask><endEvent id="end1" name="结束"></endEvent><sequenceFlow id="flow1" name="提交审批" sourceRef="start1" targetRef="user1"></sequenceFlow><sequenceFlow id="flow2" name="审批完成" sourceRef="user1" targetRef="end1"></sequenceFlow></process></definitions>',
|
||||||
|
'{"nodes":[{"id":"start1","type":"start","position":{"x":100,"y":100}},{"id":"user1","type":"userTask","position":{"x":300,"y":100}},{"id":"end1","type":"end","position":{"x":500,"y":100}}],"edges":[{"id":"flow1","source":"start1","target":"user1","label":"提交审批"},{"id":"flow2","source":"user1","target":"end1","label":"审批完成"}]}',
|
||||||
|
'{"formItems":[{"type":"input","label":"意见","name":"comment","required":true}]}',
|
||||||
|
'["approval","user-task"]',
|
||||||
|
'PUBLISHED', TRUE, 'http://www.flowable.org/test',
|
||||||
|
NOW(), NOW(), 1, 1, FALSE
|
||||||
|
),
|
||||||
|
|
||||||
|
-- 复杂流程:开始 -> 服务任务 -> 用户任务 -> 脚本任务 -> 结束
|
||||||
|
(
|
||||||
|
'复杂业务流程', 'complex_business_flow', 1, '包含多种任务节点的复杂业务流程', 'business',
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://www.flowable.org/test"><process id="complex_business_flow"
|
||||||
|
name="复杂业务流程" isExecutable="true"><startEvent id="start1" name="开始"></startEvent><serviceTask id="service1" name="服务任务"><extensionElements><flowable:field name="implementation"><flowable:string><![CDATA[com.example.ServiceTask]]></flowable:string></flowable:field></extensionElements></serviceTask><userTask id="user1" name="用户任务" flowable:assignee="user1"></userTask><scriptTask id="script1" name="脚本任务" scriptFormat="groovy"><script><![CDATA[println "Hello"]]></script></scriptTask><endEvent id="end1" name="结束"></endEvent><sequenceFlow id="flow1" sourceRef="start1" targetRef="service1"></sequenceFlow><sequenceFlow id="flow2" sourceRef="service1" targetRef="user1"><conditionExpression xsi:type="tFormalExpression">$${condition}</conditionExpression></sequenceFlow><sequenceFlow id="flow3" sourceRef="user1" targetRef="script1"></sequenceFlow><sequenceFlow id="flow4" sourceRef="script1" targetRef="end1"></sequenceFlow></process></definitions>',
|
||||||
|
'{"nodes":[{"id":"start1","type":"start","position":{"x":100,"y":100}},{"id":"service1","type":"serviceTask","position":{"x":300,"y":100}},{"id":"user1","type":"userTask","position":{"x":500,"y":100}},{"id":"script1","type":"scriptTask","position":{"x":700,"y":100}},{"id":"end1","type":"end","position":{"x":900,"y":100}}],"edges":[{"id":"flow1","source":"start1","target":"service1"},{"id":"flow2","source":"service1","target":"user1","label":"条件分支"},{"id":"flow3","source":"user1","target":"script1"},{"id":"flow4","source":"script1","target":"end1"}]}',
|
||||||
|
'{"formItems":[{"type":"input","label":"业务参数","name":"businessParam","required":true},{"type":"select","label":"审批结果","name":"approvalResult","options":[{"label":"同意","value":"approve"},{"label":"拒绝","value":"reject"}]}]}',
|
||||||
|
'["complex","business","multi-task"]',
|
||||||
|
'PUBLISHED', TRUE, 'http://www.flowable.org/test',
|
||||||
|
NOW(), NOW(), 1, 1, FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- --------------------------------------------------------------------------------------
|
||||||
-- 初始化工作流节点定义数据
|
-- 初始化工作流节点定义数据
|
||||||
INSERT INTO workflow_node_definition (
|
-- --------------------------------------------------------------------------------------
|
||||||
id, type, name, description, category,
|
|
||||||
flowable_config, graph_config, form_config,
|
|
||||||
order_num, enabled,
|
|
||||||
create_time, create_by, update_time, update_by, version, deleted
|
|
||||||
) VALUES
|
|
||||||
-- 开始节点
|
|
||||||
(1, 'startEvent', '开始节点', '流程的开始节点', 'EVENT',
|
|
||||||
-- Flowable配置
|
|
||||||
'{
|
|
||||||
"type": "startEvent"
|
|
||||||
}',
|
|
||||||
-- X6图形配置
|
|
||||||
'{
|
|
||||||
"shape": "flow-circle",
|
|
||||||
"width": 40,
|
|
||||||
"height": 40,
|
|
||||||
"ports": {
|
|
||||||
"groups": {
|
|
||||||
"bottom": {
|
|
||||||
"position": "bottom",
|
|
||||||
"attrs": {
|
|
||||||
"circle": {
|
|
||||||
"r": 4,
|
|
||||||
"magnet": true,
|
|
||||||
"stroke": "#5F95FF",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"fill": "#fff"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"items": [
|
|
||||||
{ "group": "bottom", "id": "bottom" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"attrs": {
|
|
||||||
"body": {
|
|
||||||
"fill": "#67C23A",
|
|
||||||
"stroke": "#5F95FF",
|
|
||||||
"strokeWidth": 1
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"xlinkHref": "path/to/start-icon.svg"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}',
|
|
||||||
-- 表单配置(开始节点一般不需要配置)
|
|
||||||
NULL,
|
|
||||||
10, true,
|
|
||||||
NOW(), 'system', NOW(), 'system', 1, false
|
|
||||||
),
|
|
||||||
|
|
||||||
-- 结束节点
|
-- Shell任务节点定义
|
||||||
(2, 'endEvent', '结束节点', '流程的结束节点', 'EVENT',
|
INSERT INTO workflow_node_definition (id, create_time, create_by, update_time, update_by, type, name, description, category, flowable_config, graph_config, form_config, order_num, enabled)
|
||||||
-- Flowable配置
|
VALUES (
|
||||||
|
1,
|
||||||
|
NOW(),
|
||||||
|
'system',
|
||||||
|
NOW(),
|
||||||
|
'system',
|
||||||
|
'shell',
|
||||||
|
'Shell脚本',
|
||||||
|
'Shell脚本执行节点,用于执行Shell命令或脚本',
|
||||||
|
'TASK',
|
||||||
'{
|
'{
|
||||||
"type": "endEvent"
|
"type": "object",
|
||||||
}',
|
"required": ["type", "implementation", "fields"],
|
||||||
-- X6图形配置
|
"properties": {
|
||||||
'{
|
"type": {
|
||||||
"shape": "flow-circle",
|
"type": "string",
|
||||||
"width": 40,
|
"enum": ["shell"],
|
||||||
"height": 40,
|
"description": "任务类型"
|
||||||
"ports": {
|
},
|
||||||
"groups": {
|
"implementation": {
|
||||||
"top": {
|
"type": "string",
|
||||||
"position": "top",
|
"const": "$${shellTaskDelegate}",
|
||||||
"attrs": {
|
"description": "任务实现类"
|
||||||
"circle": {
|
},
|
||||||
"r": 4,
|
"fields": {
|
||||||
"magnet": true,
|
"type": "object",
|
||||||
"stroke": "#5F95FF",
|
"required": ["script", "workDir"],
|
||||||
"strokeWidth": 1,
|
"properties": {
|
||||||
"fill": "#fff"
|
"script": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "要执行的Shell脚本内容"
|
||||||
|
},
|
||||||
|
"workDir": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "脚本执行的工作目录"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "环境变量",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"items": [
|
|
||||||
{ "group": "top", "id": "top" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"attrs": {
|
|
||||||
"body": {
|
|
||||||
"fill": "#F56C6C",
|
|
||||||
"stroke": "#5F95FF",
|
|
||||||
"strokeWidth": 2
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"xlinkHref": "path/to/end-icon.svg"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}',
|
}',
|
||||||
-- 表单配置(结束节点一般不需要配置)
|
|
||||||
NULL,
|
|
||||||
90, true,
|
|
||||||
NOW(), 'system', NOW(), 'system', 1, false
|
|
||||||
),
|
|
||||||
|
|
||||||
-- Shell脚本节点
|
|
||||||
(3, 'shellTask', 'Shell脚本', '执行Shell命令的节点', 'TASK',
|
|
||||||
-- Flowable配置
|
|
||||||
'{
|
'{
|
||||||
"type": "shellTask",
|
"type": "object",
|
||||||
"delegateExpression": "$${shellTaskDelegate}",
|
"required": ["shape"],
|
||||||
"listeners": [
|
"properties": {
|
||||||
{
|
"shape": {
|
||||||
"event": "start",
|
"type": "string",
|
||||||
"delegateExpression": "$${shellTaskStartListener}"
|
"const": "serviceTask",
|
||||||
},
|
"description": "节点形状"
|
||||||
{
|
|
||||||
"event": "end",
|
|
||||||
"delegateExpression": "$${shellTaskEndListener}"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}',
|
}',
|
||||||
-- X6图形配置
|
|
||||||
'{
|
'{
|
||||||
"shape": "flow-rect",
|
"type": "object",
|
||||||
"width": 120,
|
"required": ["items"],
|
||||||
"height": 60,
|
"properties": {
|
||||||
"ports": {
|
"items": {
|
||||||
"groups": {
|
"type": "array",
|
||||||
"top": {
|
"items": {
|
||||||
"position": "top",
|
"type": "object",
|
||||||
"attrs": {
|
"required": ["id", "type", "label"],
|
||||||
"circle": {
|
"properties": {
|
||||||
"r": 4,
|
"id": {
|
||||||
"magnet": true,
|
"type": "string"
|
||||||
"stroke": "#5F95FF",
|
},
|
||||||
"strokeWidth": 1,
|
"type": {
|
||||||
"fill": "#fff"
|
"type": "string",
|
||||||
}
|
"enum": ["input", "textarea", "select"]
|
||||||
}
|
},
|
||||||
},
|
"label": {
|
||||||
"bottom": {
|
"type": "string"
|
||||||
"position": "bottom",
|
},
|
||||||
"attrs": {
|
"required": {
|
||||||
"circle": {
|
"type": "boolean"
|
||||||
"r": 4,
|
|
||||||
"magnet": true,
|
|
||||||
"stroke": "#5F95FF",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"fill": "#fff"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"items": [
|
|
||||||
{ "group": "top", "id": "top" },
|
|
||||||
{ "group": "bottom", "id": "bottom" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"attrs": {
|
|
||||||
"body": {
|
|
||||||
"fill": "#E6A23C",
|
|
||||||
"stroke": "#5F95FF",
|
|
||||||
"strokeWidth": 1
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"xlinkHref": "path/to/shell-icon.svg"
|
|
||||||
},
|
|
||||||
"label": {
|
|
||||||
"text": "Shell脚本",
|
|
||||||
"fill": "#ffffff",
|
|
||||||
"fontSize": 12,
|
|
||||||
"textAnchor": "middle"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}',
|
}',
|
||||||
-- 表单配置
|
10,
|
||||||
'{
|
TRUE
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"name": "script",
|
|
||||||
"label": "脚本内容",
|
|
||||||
"type": "textarea",
|
|
||||||
"required": true,
|
|
||||||
"placeholder": "请输入shell脚本内容"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "workDir",
|
|
||||||
"label": "工作目录",
|
|
||||||
"type": "input",
|
|
||||||
"required": false,
|
|
||||||
"placeholder": "请输入工作目录路径"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "env",
|
|
||||||
"label": "环境变量",
|
|
||||||
"type": "keyValue",
|
|
||||||
"required": false,
|
|
||||||
"placeholder": "请配置环境变量"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"script": [
|
|
||||||
{ "required": true, "message": "请输入脚本内容" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}',
|
|
||||||
20, true,
|
|
||||||
NOW(), 'system', NOW(), 'system', 1, false
|
|
||||||
),
|
|
||||||
|
|
||||||
-- 排他网关
|
|
||||||
(4, 'exclusiveGateway', '排他网关', '基于条件的分支网关', 'GATEWAY',
|
|
||||||
-- Flowable配置
|
|
||||||
'{
|
|
||||||
"type": "exclusiveGateway"
|
|
||||||
}',
|
|
||||||
-- X6图形配置
|
|
||||||
'{
|
|
||||||
"shape": "flow-rhombus",
|
|
||||||
"width": 60,
|
|
||||||
"height": 60,
|
|
||||||
"ports": {
|
|
||||||
"groups": {
|
|
||||||
"top": {
|
|
||||||
"position": "top",
|
|
||||||
"attrs": {
|
|
||||||
"circle": {
|
|
||||||
"r": 4,
|
|
||||||
"magnet": true,
|
|
||||||
"stroke": "#5F95FF",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"fill": "#fff"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bottom": {
|
|
||||||
"position": "bottom",
|
|
||||||
"attrs": {
|
|
||||||
"circle": {
|
|
||||||
"r": 4,
|
|
||||||
"magnet": true,
|
|
||||||
"stroke": "#5F95FF",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"fill": "#fff"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"left": {
|
|
||||||
"position": "left",
|
|
||||||
"attrs": {
|
|
||||||
"circle": {
|
|
||||||
"r": 4,
|
|
||||||
"magnet": true,
|
|
||||||
"stroke": "#5F95FF",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"fill": "#fff"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"position": "right",
|
|
||||||
"attrs": {
|
|
||||||
"circle": {
|
|
||||||
"r": 4,
|
|
||||||
"magnet": true,
|
|
||||||
"stroke": "#5F95FF",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"fill": "#fff"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"items": [
|
|
||||||
{ "group": "top", "id": "top" },
|
|
||||||
{ "group": "bottom", "id": "bottom" },
|
|
||||||
{ "group": "left", "id": "left" },
|
|
||||||
{ "group": "right", "id": "right" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"attrs": {
|
|
||||||
"body": {
|
|
||||||
"fill": "#9B59B6",
|
|
||||||
"stroke": "#5F95FF",
|
|
||||||
"strokeWidth": 1
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"xlinkHref": "path/to/exclusive-gateway-icon.svg"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}',
|
|
||||||
-- 表单配置
|
|
||||||
'{
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"name": "defaultFlow",
|
|
||||||
"label": "默认流转路径",
|
|
||||||
"type": "select",
|
|
||||||
"required": false,
|
|
||||||
"placeholder": "请选择默认流转路径"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}',
|
|
||||||
30, true,
|
|
||||||
NOW(), 'system', NOW(), 'system', 1, false
|
|
||||||
);
|
);
|
||||||
@ -1,144 +1,439 @@
|
|||||||
package com.qqchen.deploy.backend.workflow.util;
|
package com.qqchen.deploy.backend.workflow.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.qqchen.deploy.backend.workflow.dto.graph.*;
|
||||||
import com.qqchen.deploy.backend.workflow.config.BpmnNodeManager;
|
import com.qqchen.deploy.backend.workflow.enums.NodeType;
|
||||||
import com.qqchen.deploy.backend.workflow.handler.impl.ShellTaskHandler;
|
import org.flowable.bpmn.converter.BpmnXMLConverter;
|
||||||
import com.qqchen.deploy.backend.workflow.parser.BpmnNodeConfigParser;
|
import org.flowable.bpmn.model.*;
|
||||||
import org.flowable.bpmn.model.BpmnModel;
|
|
||||||
import org.flowable.bpmn.model.Process;
|
import org.flowable.bpmn.model.Process;
|
||||||
import org.flowable.bpmn.model.ServiceTask;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BPMN转换器测试
|
* BPMN转换器测试
|
||||||
*/
|
*/
|
||||||
@ContextConfiguration(classes = {BpmnNodeManager.class})
|
@SpringBootTest(properties = {
|
||||||
|
"spring.flyway.enabled=false"
|
||||||
|
})
|
||||||
class BpmnConverterTest {
|
class BpmnConverterTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
private BpmnConverter bpmnConverter;
|
private BpmnConverter bpmnConverter;
|
||||||
private ObjectMapper objectMapper;
|
|
||||||
private BpmnNodeManager nodeManager;
|
|
||||||
|
|
||||||
@BeforeEach
|
@Test
|
||||||
void setUp() {
|
void testConvertSimpleWorkflow() {
|
||||||
// 创建Spring上下文
|
// 创建一个简单的工作流图:开始 -> 服务任务 -> 结束
|
||||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
WorkflowGraph graph = new WorkflowGraph();
|
||||||
context.register(BpmnNodeManager.class, ShellTaskHandler.class);
|
|
||||||
context.refresh();
|
|
||||||
|
|
||||||
// 获取必要的Bean
|
// 创建节点
|
||||||
objectMapper = new ObjectMapper();
|
List<WorkflowNode> nodes = new ArrayList<>();
|
||||||
nodeManager = context.getBean(BpmnNodeManager.class);
|
|
||||||
|
|
||||||
// 创建BpmnConverter
|
// 开始节点
|
||||||
bpmnConverter = new BpmnConverter(
|
WorkflowNode startNode = new WorkflowNode();
|
||||||
objectMapper,
|
startNode.setId("start");
|
||||||
Collections.singletonList(context.getBean(ShellTaskHandler.class)),
|
startNode.setType(NodeType.START);
|
||||||
new BpmnNodeConfigParser()
|
startNode.setName("开始");
|
||||||
);
|
startNode.setPosition(new Position(100.0, 100.0));
|
||||||
|
nodes.add(startNode);
|
||||||
|
|
||||||
|
// 服务任务节点
|
||||||
|
WorkflowNode serviceNode = new WorkflowNode();
|
||||||
|
serviceNode.setId("service1");
|
||||||
|
serviceNode.setType(NodeType.SERVICE_TASK);
|
||||||
|
serviceNode.setName("服务任务");
|
||||||
|
serviceNode.setPosition(new Position(300.0, 100.0));
|
||||||
|
|
||||||
|
NodeConfig serviceConfig = new NodeConfig();
|
||||||
|
serviceConfig.setImplementation("${shellTaskDelegate}");
|
||||||
|
Map<String, Object> fields = new HashMap<>();
|
||||||
|
fields.put("script", "echo 'Hello World'");
|
||||||
|
fields.put("workDir", "/tmp");
|
||||||
|
fields.put("timeout", "30");
|
||||||
|
serviceConfig.setFields(fields);
|
||||||
|
serviceNode.setConfig(serviceConfig);
|
||||||
|
nodes.add(serviceNode);
|
||||||
|
|
||||||
|
// 结束节点
|
||||||
|
WorkflowNode endNode = new WorkflowNode();
|
||||||
|
endNode.setId("end");
|
||||||
|
endNode.setType(NodeType.END);
|
||||||
|
endNode.setName("结束");
|
||||||
|
endNode.setPosition(new Position(500.0, 100.0));
|
||||||
|
nodes.add(endNode);
|
||||||
|
|
||||||
|
// 创建边
|
||||||
|
List<WorkflowEdge> edges = new ArrayList<>();
|
||||||
|
|
||||||
|
// 开始 -> 服务任务
|
||||||
|
WorkflowEdge edge1 = new WorkflowEdge();
|
||||||
|
edge1.setId("flow1");
|
||||||
|
edge1.setSource("start");
|
||||||
|
edge1.setTarget("service1");
|
||||||
|
edge1.setName("流转到服务任务");
|
||||||
|
edges.add(edge1);
|
||||||
|
|
||||||
|
// 服务任务 -> 结束
|
||||||
|
WorkflowEdge edge2 = new WorkflowEdge();
|
||||||
|
edge2.setId("flow2");
|
||||||
|
edge2.setSource("service1");
|
||||||
|
edge2.setTarget("end");
|
||||||
|
edge2.setName("流转到结束");
|
||||||
|
edges.add(edge2);
|
||||||
|
|
||||||
|
graph.setNodes(nodes);
|
||||||
|
graph.setEdges(edges);
|
||||||
|
|
||||||
|
// 执行转换
|
||||||
|
BpmnModel bpmnModel = bpmnConverter.convertToBpmnModel(graph);
|
||||||
|
|
||||||
|
// 打印XML
|
||||||
|
try {
|
||||||
|
System.out.println("\n=== Simple Workflow XML ===");
|
||||||
|
System.out.println(new String(new BpmnXMLConverter().convertToXML(bpmnModel)));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertNotNull(bpmnModel);
|
||||||
|
Process process = bpmnModel.getMainProcess();
|
||||||
|
assertNotNull(process);
|
||||||
|
|
||||||
|
// 验证节点数量
|
||||||
|
assertEquals(5, process.getFlowElements().size());
|
||||||
|
|
||||||
|
// 验证开始节点
|
||||||
|
StartEvent startEvent = (StartEvent) process.getFlowElement("start");
|
||||||
|
assertNotNull(startEvent);
|
||||||
|
assertEquals("开始", startEvent.getName());
|
||||||
|
|
||||||
|
// 验证服务任务节点
|
||||||
|
ServiceTask serviceTask = (ServiceTask) process.getFlowElement("service1");
|
||||||
|
assertNotNull(serviceTask);
|
||||||
|
assertEquals("服务任务", serviceTask.getName());
|
||||||
|
assertEquals("${shellTaskDelegate}", serviceTask.getImplementation());
|
||||||
|
|
||||||
|
// 验证服务任务字段
|
||||||
|
List<FieldExtension> fieldExtensions = serviceTask.getFieldExtensions();
|
||||||
|
assertNotNull(fieldExtensions);
|
||||||
|
assertEquals(3, fieldExtensions.size());
|
||||||
|
|
||||||
|
Map<String, String> fieldMap = new HashMap<>();
|
||||||
|
for (FieldExtension field : fieldExtensions) {
|
||||||
|
fieldMap.put(field.getFieldName(), field.getStringValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("echo 'Hello World'", fieldMap.get("script"));
|
||||||
|
assertEquals("/tmp", fieldMap.get("workDir"));
|
||||||
|
assertEquals("30", fieldMap.get("timeout"));
|
||||||
|
|
||||||
|
// 验证结束节点
|
||||||
|
EndEvent endEvent = (EndEvent) process.getFlowElement("end");
|
||||||
|
assertNotNull(endEvent);
|
||||||
|
assertEquals("结束", endEvent.getName());
|
||||||
|
|
||||||
|
// 验证边
|
||||||
|
SequenceFlow flow1 = (SequenceFlow) process.getFlowElement("flow1");
|
||||||
|
assertNotNull(flow1);
|
||||||
|
assertEquals("流转到服务任务", flow1.getName());
|
||||||
|
assertEquals("start", flow1.getSourceRef());
|
||||||
|
assertEquals("service1", flow1.getTargetRef());
|
||||||
|
|
||||||
|
SequenceFlow flow2 = (SequenceFlow) process.getFlowElement("flow2");
|
||||||
|
assertNotNull(flow2);
|
||||||
|
assertEquals("流转到结束", flow2.getName());
|
||||||
|
assertEquals("service1", flow2.getSourceRef());
|
||||||
|
assertEquals("end", flow2.getTargetRef());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConvertShellTask() throws Exception {
|
void testConvertUserTask() {
|
||||||
// 准备测试数据
|
// 创建一个包含用户任务的工作流图
|
||||||
String json = """
|
WorkflowGraph graph = new WorkflowGraph();
|
||||||
{
|
|
||||||
"cells": [
|
// 创建节点
|
||||||
{
|
List<WorkflowNode> nodes = new ArrayList<>();
|
||||||
"id": "start",
|
|
||||||
"shape": "start",
|
// 开始节点
|
||||||
"data": {
|
WorkflowNode startNode = new WorkflowNode();
|
||||||
"label": "开始"
|
startNode.setId("start");
|
||||||
}
|
startNode.setType(NodeType.START);
|
||||||
},
|
startNode.setName("开始");
|
||||||
{
|
startNode.setPosition(new Position(100.0, 100.0));
|
||||||
"id": "shell1",
|
nodes.add(startNode);
|
||||||
"shape": "shellTask",
|
|
||||||
"data": {
|
// 用户任务节点
|
||||||
"label": "Shell脚本",
|
WorkflowNode userNode = new WorkflowNode();
|
||||||
"serviceTask": {
|
userNode.setId("user1");
|
||||||
"fields": {
|
userNode.setType(NodeType.USER_TASK);
|
||||||
"script": "echo 'Hello World'",
|
userNode.setName("审批任务");
|
||||||
"workDir": "/tmp"
|
userNode.setPosition(new Position(300.0, 100.0));
|
||||||
}
|
|
||||||
}
|
NodeConfig userConfig = new NodeConfig();
|
||||||
}
|
userConfig.setAssignee("${initiator}");
|
||||||
},
|
userConfig.setCandidateUsers(Arrays.asList("user1", "user2"));
|
||||||
{
|
userConfig.setCandidateGroups(Arrays.asList("group1", "group2"));
|
||||||
"id": "flow1",
|
userConfig.setDueDate("${dueDate}");
|
||||||
"shape": "edge",
|
userConfig.setPriority(1);
|
||||||
"source": "start",
|
userConfig.setFormKey("form1");
|
||||||
"target": "shell1",
|
userNode.setConfig(userConfig);
|
||||||
"data": {
|
nodes.add(userNode);
|
||||||
"label": "流转到Shell"
|
|
||||||
}
|
// 结束节点
|
||||||
}
|
WorkflowNode endNode = new WorkflowNode();
|
||||||
]
|
endNode.setId("end");
|
||||||
}
|
endNode.setType(NodeType.END);
|
||||||
""";
|
endNode.setName("结束");
|
||||||
|
endNode.setPosition(new Position(500.0, 100.0));
|
||||||
|
nodes.add(endNode);
|
||||||
|
|
||||||
|
// 创建边
|
||||||
|
List<WorkflowEdge> edges = new ArrayList<>();
|
||||||
|
|
||||||
|
// 开始 -> 用户任务
|
||||||
|
WorkflowEdge edge1 = new WorkflowEdge();
|
||||||
|
edge1.setId("flow1");
|
||||||
|
edge1.setSource("start");
|
||||||
|
edge1.setTarget("user1");
|
||||||
|
edge1.setName("提交审批");
|
||||||
|
edges.add(edge1);
|
||||||
|
|
||||||
|
// 用户任务 -> 结束
|
||||||
|
WorkflowEdge edge2 = new WorkflowEdge();
|
||||||
|
edge2.setId("flow2");
|
||||||
|
edge2.setSource("user1");
|
||||||
|
edge2.setTarget("end");
|
||||||
|
edge2.setName("审批完成");
|
||||||
|
edges.add(edge2);
|
||||||
|
|
||||||
|
graph.setNodes(nodes);
|
||||||
|
graph.setEdges(edges);
|
||||||
|
|
||||||
// 执行转换
|
// 执行转换
|
||||||
String xml = bpmnConverter.convertToBpmnXml(json, "test_process");
|
BpmnModel bpmnModel = bpmnConverter.convertToBpmnModel(graph);
|
||||||
|
|
||||||
|
// 打印XML
|
||||||
|
try {
|
||||||
|
System.out.println("\n=== User Task Workflow XML ===");
|
||||||
|
System.out.println(new String(new BpmnXMLConverter().convertToXML(bpmnModel)));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
// 验证结果
|
// 验证结果
|
||||||
assertNotNull(xml);
|
assertNotNull(bpmnModel);
|
||||||
assertTrue(xml.contains("flowable:delegateExpression=\"${shellTaskDelegate}\""));
|
Process process = bpmnModel.getMainProcess();
|
||||||
assertTrue(xml.contains("<flowable:field name=\"script\">"));
|
assertNotNull(process);
|
||||||
assertTrue(xml.contains("<flowable:field name=\"workDir\">"));
|
|
||||||
|
|
||||||
// 验证节点配置
|
// 验证用户任务节点
|
||||||
assertTrue(nodeManager.hasNodeType("shellTask"));
|
UserTask userTask = (UserTask) process.getFlowElement("user1");
|
||||||
assertNotNull(nodeManager.getNodeConfig("shellTask"));
|
assertNotNull(userTask);
|
||||||
assertEquals("Shell脚本", nodeManager.getNodeConfig("shellTask").getName());
|
assertEquals("审批任务", userTask.getName());
|
||||||
assertTrue(nodeManager.getNodeConfig("shellTask").isAsync());
|
assertEquals("${initiator}", userTask.getAssignee());
|
||||||
|
assertEquals(Arrays.asList("user1", "user2"), userTask.getCandidateUsers());
|
||||||
|
assertEquals(Arrays.asList("group1", "group2"), userTask.getCandidateGroups());
|
||||||
|
assertEquals("${dueDate}", userTask.getDueDate());
|
||||||
|
assertEquals("1", userTask.getPriority());
|
||||||
|
assertEquals("form1", userTask.getFormKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConvertComplexProcess() throws Exception {
|
void testConvertComplexWorkflow() {
|
||||||
// 从测试资源文件加载复杂流程JSON
|
// 创建一个复杂的工作流图:开始 -> 服务任务 -> 用户任务 -> 脚本任务 -> 结束
|
||||||
ClassPathResource resource = new ClassPathResource("test-process.json");
|
WorkflowGraph graph = new WorkflowGraph();
|
||||||
String json = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
|
||||||
|
// 设置流程属性
|
||||||
|
Map<String, Object> properties = new HashMap<>();
|
||||||
|
properties.put("name", "测试流程");
|
||||||
|
properties.put("key", "test_process");
|
||||||
|
properties.put("description", "这是一个测试流程");
|
||||||
|
graph.setProperties(properties);
|
||||||
|
|
||||||
|
// 创建节点
|
||||||
|
List<WorkflowNode> nodes = new ArrayList<>();
|
||||||
|
|
||||||
|
// 开始节点
|
||||||
|
WorkflowNode startNode = new WorkflowNode();
|
||||||
|
startNode.setId("start");
|
||||||
|
startNode.setType(NodeType.START);
|
||||||
|
startNode.setName("开始");
|
||||||
|
startNode.setPosition(new Position(100.0, 100.0));
|
||||||
|
nodes.add(startNode);
|
||||||
|
|
||||||
|
// 服务任务节点
|
||||||
|
WorkflowNode serviceNode = new WorkflowNode();
|
||||||
|
serviceNode.setId("service1");
|
||||||
|
serviceNode.setType(NodeType.SERVICE_TASK);
|
||||||
|
serviceNode.setName("服务任务");
|
||||||
|
serviceNode.setPosition(new Position(250.0, 100.0));
|
||||||
|
|
||||||
|
NodeConfig serviceConfig = new NodeConfig();
|
||||||
|
serviceConfig.setImplementation("${shellTaskDelegate}");
|
||||||
|
Map<String, Object> serviceFields = new HashMap<>();
|
||||||
|
serviceFields.put("script", "echo 'Hello World'");
|
||||||
|
serviceFields.put("workDir", "/tmp");
|
||||||
|
serviceConfig.setFields(serviceFields);
|
||||||
|
serviceNode.setConfig(serviceConfig);
|
||||||
|
nodes.add(serviceNode);
|
||||||
|
|
||||||
|
// 用户任务节点
|
||||||
|
WorkflowNode userNode = new WorkflowNode();
|
||||||
|
userNode.setId("user1");
|
||||||
|
userNode.setType(NodeType.USER_TASK);
|
||||||
|
userNode.setName("用户任务");
|
||||||
|
userNode.setPosition(new Position(400.0, 100.0));
|
||||||
|
|
||||||
|
NodeConfig userConfig = new NodeConfig();
|
||||||
|
userConfig.setAssignee("${initiator}");
|
||||||
|
List<String> candidateUsers = Arrays.asList("user1", "user2");
|
||||||
|
List<String> candidateGroups = Arrays.asList("group1", "group2");
|
||||||
|
userConfig.setCandidateUsers(candidateUsers);
|
||||||
|
userConfig.setCandidateGroups(candidateGroups);
|
||||||
|
userConfig.setPriority(1);
|
||||||
|
userConfig.setFormKey("form1");
|
||||||
|
userNode.setConfig(userConfig);
|
||||||
|
nodes.add(userNode);
|
||||||
|
|
||||||
|
// 脚本任务节点
|
||||||
|
WorkflowNode scriptNode = new WorkflowNode();
|
||||||
|
scriptNode.setId("script1");
|
||||||
|
scriptNode.setType(NodeType.SCRIPT_TASK);
|
||||||
|
scriptNode.setName("脚本任务");
|
||||||
|
scriptNode.setPosition(new Position(550.0, 100.0));
|
||||||
|
|
||||||
|
NodeConfig scriptConfig = new NodeConfig();
|
||||||
|
scriptConfig.setImplementation("groovy");
|
||||||
|
Map<String, Object> scriptFields = new HashMap<>();
|
||||||
|
scriptFields.put("script", "println 'Hello from Groovy'");
|
||||||
|
scriptConfig.setFields(scriptFields);
|
||||||
|
scriptNode.setConfig(scriptConfig);
|
||||||
|
nodes.add(scriptNode);
|
||||||
|
|
||||||
|
// 结束节点
|
||||||
|
WorkflowNode endNode = new WorkflowNode();
|
||||||
|
endNode.setId("end");
|
||||||
|
endNode.setType(NodeType.END);
|
||||||
|
endNode.setName("结束");
|
||||||
|
endNode.setPosition(new Position(700.0, 100.0));
|
||||||
|
nodes.add(endNode);
|
||||||
|
|
||||||
|
// 创建边
|
||||||
|
List<WorkflowEdge> edges = new ArrayList<>();
|
||||||
|
|
||||||
|
// 开始 -> 服务任务
|
||||||
|
WorkflowEdge edge1 = new WorkflowEdge();
|
||||||
|
edge1.setId("flow1");
|
||||||
|
edge1.setSource("start");
|
||||||
|
edge1.setTarget("service1");
|
||||||
|
edge1.setName("流转到服务任务");
|
||||||
|
EdgeConfig edgeConfig1 = new EdgeConfig();
|
||||||
|
edgeConfig1.setType("sequence");
|
||||||
|
edge1.setConfig(edgeConfig1);
|
||||||
|
edges.add(edge1);
|
||||||
|
|
||||||
|
// 服务任务 -> 用户任务
|
||||||
|
WorkflowEdge edge2 = new WorkflowEdge();
|
||||||
|
edge2.setId("flow2");
|
||||||
|
edge2.setSource("service1");
|
||||||
|
edge2.setTarget("user1");
|
||||||
|
edge2.setName("流转到用户任务");
|
||||||
|
EdgeConfig edgeConfig2 = new EdgeConfig();
|
||||||
|
edgeConfig2.setType("sequence");
|
||||||
|
edgeConfig2.setCondition("${approved}");
|
||||||
|
edge2.setConfig(edgeConfig2);
|
||||||
|
edges.add(edge2);
|
||||||
|
|
||||||
|
// 用户任务 -> 脚本任务
|
||||||
|
WorkflowEdge edge3 = new WorkflowEdge();
|
||||||
|
edge3.setId("flow3");
|
||||||
|
edge3.setSource("user1");
|
||||||
|
edge3.setTarget("script1");
|
||||||
|
edge3.setName("流转到脚本任务");
|
||||||
|
EdgeConfig edgeConfig3 = new EdgeConfig();
|
||||||
|
edgeConfig3.setType("sequence");
|
||||||
|
edge3.setConfig(edgeConfig3);
|
||||||
|
edges.add(edge3);
|
||||||
|
|
||||||
|
// 脚本任务 -> 结束
|
||||||
|
WorkflowEdge edge4 = new WorkflowEdge();
|
||||||
|
edge4.setId("flow4");
|
||||||
|
edge4.setSource("script1");
|
||||||
|
edge4.setTarget("end");
|
||||||
|
edge4.setName("流转到结束");
|
||||||
|
EdgeConfig edgeConfig4 = new EdgeConfig();
|
||||||
|
edgeConfig4.setType("sequence");
|
||||||
|
edge4.setConfig(edgeConfig4);
|
||||||
|
edges.add(edge4);
|
||||||
|
|
||||||
|
graph.setNodes(nodes);
|
||||||
|
graph.setEdges(edges);
|
||||||
|
|
||||||
|
// 执行转换并获取XML
|
||||||
|
String xml = bpmnConverter.convertToXml(graph, "test_process");
|
||||||
|
System.out.println("Generated BPMN XML:");
|
||||||
|
System.out.println(xml);
|
||||||
|
|
||||||
// 执行转换
|
// 执行转换
|
||||||
String xml = bpmnConverter.convertToBpmnXml(json, "complex_process");
|
BpmnModel bpmnModel = bpmnConverter.convertToBpmnModel(graph);
|
||||||
|
|
||||||
|
// 打印XML
|
||||||
|
try {
|
||||||
|
System.out.println("\n=== Complex Workflow XML ===");
|
||||||
|
System.out.println(new String(new BpmnXMLConverter().convertToXML(bpmnModel)));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
// 验证结果
|
// 验证结果
|
||||||
assertNotNull(xml);
|
assertNotNull(bpmnModel);
|
||||||
// 验证基本结构
|
Process process = bpmnModel.getMainProcess();
|
||||||
assertTrue(xml.contains("<process id=\"complex_process\""));
|
assertNotNull(process);
|
||||||
// 验证节点
|
|
||||||
assertTrue(xml.contains("<startEvent"));
|
// 验证流程属性
|
||||||
assertTrue(xml.contains("<serviceTask"));
|
assertEquals("测试流程", process.getName());
|
||||||
assertTrue(xml.contains("<endEvent"));
|
assertEquals("test_process", process.getId());
|
||||||
|
assertEquals("这是一个测试流程", process.getDocumentation());
|
||||||
|
|
||||||
|
// 验证节点数量
|
||||||
|
assertEquals(9, process.getFlowElements().size()); // 5个节点 + 4个连线
|
||||||
|
|
||||||
|
// 验证开始节点
|
||||||
|
StartEvent startEvent = (StartEvent) process.getFlowElement("start");
|
||||||
|
assertNotNull(startEvent);
|
||||||
|
assertEquals("开始", startEvent.getName());
|
||||||
|
|
||||||
|
// 验证服务任务
|
||||||
|
ServiceTask serviceTask = (ServiceTask) process.getFlowElement("service1");
|
||||||
|
assertNotNull(serviceTask);
|
||||||
|
assertEquals("服务任务", serviceTask.getName());
|
||||||
|
assertEquals("${shellTaskDelegate}", serviceTask.getImplementation());
|
||||||
|
|
||||||
|
// 验证用户任务
|
||||||
|
UserTask userTask = (UserTask) process.getFlowElement("user1");
|
||||||
|
assertNotNull(userTask);
|
||||||
|
assertEquals("用户任务", userTask.getName());
|
||||||
|
assertEquals("${initiator}", userTask.getAssignee());
|
||||||
|
assertEquals(Arrays.asList("user1", "user2"), userTask.getCandidateUsers());
|
||||||
|
assertEquals(Arrays.asList("group1", "group2"), userTask.getCandidateGroups());
|
||||||
|
|
||||||
|
// 验证脚本任务
|
||||||
|
ScriptTask scriptTask = (ScriptTask) process.getFlowElement("script1");
|
||||||
|
assertNotNull(scriptTask);
|
||||||
|
assertEquals("脚本任务", scriptTask.getName());
|
||||||
|
assertEquals("groovy", scriptTask.getScriptFormat());
|
||||||
|
|
||||||
|
// 验证结束节点
|
||||||
|
EndEvent endEvent = (EndEvent) process.getFlowElement("end");
|
||||||
|
assertNotNull(endEvent);
|
||||||
|
assertEquals("结束", endEvent.getName());
|
||||||
|
|
||||||
// 验证连线
|
// 验证连线
|
||||||
assertTrue(xml.contains("<sequenceFlow"));
|
SequenceFlow flow2 = (SequenceFlow) process.getFlowElement("flow2");
|
||||||
|
assertNotNull(flow2);
|
||||||
// 验证节点配置
|
assertEquals("${approved}", flow2.getConditionExpression());
|
||||||
assertTrue(nodeManager.hasNodeType("shellTask"));
|
|
||||||
assertNotNull(nodeManager.getNodeConfig("shellTask"));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Test
|
|
||||||
void testNodeRegistration() {
|
|
||||||
// 验证节点注册
|
|
||||||
assertTrue(nodeManager.hasNodeType("shellTask"));
|
|
||||||
|
|
||||||
// 验证Shell任务节点配置
|
|
||||||
var config = nodeManager.getNodeConfig("shellTask");
|
|
||||||
assertNotNull(config);
|
|
||||||
assertEquals("Shell脚本", config.getName());
|
|
||||||
assertTrue(config.isAsync());
|
|
||||||
assertTrue(config.getRequiredFields().contains("script"));
|
|
||||||
assertTrue(config.getRequiredFields().contains("workDir"));
|
|
||||||
assertEquals("${shellTaskDelegate}", config.getDelegateExpression());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
import { Graph } from '@antv/x6';
|
||||||
|
import { NodeData } from '../types';
|
||||||
|
|
||||||
|
export interface GraphJsonCell {
|
||||||
|
id: string;
|
||||||
|
shape: string;
|
||||||
|
data?: {
|
||||||
|
label: string;
|
||||||
|
serviceTask?: {
|
||||||
|
type: string;
|
||||||
|
implementation: string;
|
||||||
|
fields: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
position?: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
source?: string;
|
||||||
|
target?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GraphJson {
|
||||||
|
cells: GraphJsonCell[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadGraphData = (graph: Graph, graphJson: GraphJson) => {
|
||||||
|
// 清空现有图形
|
||||||
|
graph.clearCells();
|
||||||
|
|
||||||
|
// 先添加所有节点
|
||||||
|
const nodes = graphJson.cells.filter(cell => cell.shape !== 'edge');
|
||||||
|
nodes.forEach(node => {
|
||||||
|
graph.addNode({
|
||||||
|
id: node.id,
|
||||||
|
shape: node.shape,
|
||||||
|
x: node.position?.x || 0,
|
||||||
|
y: node.position?.y || 0,
|
||||||
|
label: node.data?.label || '',
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
type: node.shape,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 再添加所有边
|
||||||
|
const edges = graphJson.cells.filter(cell => cell.shape === 'edge');
|
||||||
|
edges.forEach(edge => {
|
||||||
|
graph.addEdge({
|
||||||
|
id: edge.id,
|
||||||
|
source: edge.source,
|
||||||
|
target: edge.target,
|
||||||
|
label: edge.data?.label || '',
|
||||||
|
attrs: {
|
||||||
|
line: {
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 1,
|
||||||
|
targetMarker: {
|
||||||
|
name: 'classic',
|
||||||
|
size: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 自动布局
|
||||||
|
graph.centerContent();
|
||||||
|
graph.zoomToFit({ padding: 20 });
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user