反序列化问题。

This commit is contained in:
dengqichen 2024-12-12 13:11:33 +08:00
parent 78c35ce6e4
commit 09489b3e11
23 changed files with 1313 additions and 878 deletions

18
.windsurfrules Normal file
View File

@ -0,0 +1,18 @@
你是一名java前端开发工程师对你有以下要求
1. 缺陷修正:
- 在提出修复建议前,应充分分析问题
- 提供精准、有针对性的解决方案
- 解释bug的根本原因
2. 保持简单:
- 优先考虑可读性和可维护性
- 避免过度工程化的解决方案
- 尽可能使用标准库和模式
- 遵循正确、最佳实践、DRY原则、无错误、功能齐全的代码编写原则
3. 代码更改:
- 在做出改变之前提出一个清晰的计划
- 一次将所有修改应用于单个文件
- 请勿修改不相关的文件
记住要始终考虑每个项目的背景和特定需求。

View File

@ -2,7 +2,7 @@ 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.dto.graph.WorkflowDefinitionGraph;
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;
@ -39,7 +39,7 @@ public class WorkflowDefinitionDTO extends BaseDTO {
/** /**
* 图形数据 * 图形数据
*/ */
private WorkflowGraph graph; private WorkflowDefinitionGraph graph;
/** /**
* 表单配置 * 表单配置

View File

@ -1,19 +0,0 @@
package com.qqchen.deploy.backend.workflow.dto.graph;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* 节点配置
* @author cascade
* @date 2024-12-11
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL) // 只序列化非空字段
public class NodeConfig {
}

View File

@ -9,7 +9,7 @@ import java.util.Map;
* @date 2024-12-11 * @date 2024-12-11
*/ */
@Data @Data
public class WorkflowEdge { public class WorkflowDefinitionEdge {
/** /**
* 边ID * 边ID
*/ */
@ -18,12 +18,12 @@ public class WorkflowEdge {
/** /**
* 源节点ID * 源节点ID
*/ */
private String source; private String from;
/** /**
* 目标节点ID * 目标节点ID
*/ */
private String target; private String to;
/** /**
* 边名称 * 边名称
@ -33,7 +33,7 @@ public class WorkflowEdge {
/** /**
* 边配置 * 边配置
*/ */
private EdgeConfig config; private WorkflowDefinitionEdgeConfig config;
/** /**
* 边属性 * 边属性

View File

@ -10,7 +10,7 @@ import lombok.Data;
*/ */
@Data @Data
@JsonInclude(JsonInclude.Include.NON_NULL) // 只序列化非空字段 @JsonInclude(JsonInclude.Include.NON_NULL) // 只序列化非空字段
public class EdgeConfig { public class WorkflowDefinitionEdgeConfig {
/** /**
* 条件 * 条件
*/ */
@ -31,14 +31,4 @@ public class EdgeConfig {
*/ */
private String type; private String type;
public void setConditionExpression(String conditionExpression) {
this.conditionExpression = conditionExpression;
// 同时设置 condition 字段保持兼容性
this.condition = conditionExpression;
}
public String getConditionExpression() {
// 如果 conditionExpression 为空但 condition 不为空返回 condition
return conditionExpression != null ? conditionExpression : condition;
}
} }

View File

@ -8,7 +8,7 @@ import lombok.Data;
* @date 2024-12-11 * @date 2024-12-11
*/ */
@Data @Data
public class Size { public class WorkflowDefinitionEdgeSize {
/** /**
* 宽度 * 宽度
*/ */

View File

@ -2,7 +2,6 @@ package com.qqchen.deploy.backend.workflow.dto.graph;
import lombok.Data; import lombok.Data;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 工作流图形数据传输对象 * 工作流图形数据传输对象
@ -10,15 +9,15 @@ import java.util.Map;
* @date 2024-12-11 * @date 2024-12-11
*/ */
@Data @Data
public class WorkflowGraph { public class WorkflowDefinitionGraph {
/** /**
* 节点列表 * 节点列表
*/ */
private List<WorkflowNode> nodes; private List<WorkflowDefinitionNode> nodes;
/** /**
* 边列表 * 边列表
*/ */
private List<WorkflowEdge> edges; private List<WorkflowDefinitionEdge> edges;
} }

View File

@ -2,15 +2,17 @@ package com.qqchen.deploy.backend.workflow.dto.graph;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnums; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnums;
import lombok.Data; import lombok.Data;
import java.util.Map; import java.util.Map;
/** /**
* 工作流节点数据传输对象 * 工作流节点数据传输对象
*
* @author cascade * @author cascade
* @date 2024-12-11 * @date 2024-12-11
*/ */
@Data @Data
public class WorkflowNode { public class WorkflowDefinitionNode {
/** /**
* 节点ID * 节点ID
*/ */
@ -26,23 +28,10 @@ public class WorkflowNode {
*/ */
private String name; private String name;
/**
* 节点位置
*/
private Position position;
private WorkflowNodeGraph graph;
/** /**
* 节点配置 * 节点配置
*/ */
private NodeConfig config; private Map<String, Object> config;
/**
* 节点属性
*/
private Map<String, Object> properties;
/**
* 节点大小
*/
private Size size;
} }

View File

@ -12,7 +12,7 @@ import lombok.AllArgsConstructor;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class Position { public class WorkflowDefinitionPosition {
/** /**
* X坐标 * X坐标
*/ */

View File

@ -1,8 +1,9 @@
package com.qqchen.deploy.backend.workflow.config; package com.qqchen.deploy.backend.workflow.dto.graph;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -13,23 +14,27 @@ import java.util.Map;
*/ */
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
public class NodeUiConfig { public class WorkflowNodeGraph {
private String shape; private String shape;
private Size size; private Size size;
private Style style; private Style style;
private Ports ports; private Ports ports;
public NodeUiConfig setSize(int width, int height) { public WorkflowNodeGraph setSize(int width, int height) {
this.size = new Size(width, height); this.size = new Size(width, height);
return this; return this;
} }
public NodeUiConfig setStyle(String fill, String stroke, String icon) { public WorkflowNodeGraph setStyle(String fill, String stroke, String icon) {
this.style = new Style(fill, stroke, icon); this.style = new Style(fill, stroke, icon);
return this; return this;
} }
public NodeUiConfig setPorts(List<String> types) { public WorkflowNodeGraph configPorts(List<String> types) {
this.ports = new Ports(); this.ports = new Ports();
Map<String, PortGroup> groups = new HashMap<>(); Map<String, PortGroup> groups = new HashMap<>();
@ -59,6 +64,7 @@ public class NodeUiConfig {
@Data @Data
public static class Size { public static class Size {
private int width; private int width;
private int height; private int height;
public Size(int width, int height) { public Size(int width, int height) {
@ -73,9 +79,13 @@ public class NodeUiConfig {
@Data @Data
public static class Style { public static class Style {
private String fill; // 填充颜色 private String fill; // 填充颜色
private String stroke; // 边框颜色 private String stroke; // 边框颜色
private String icon; // 图标名称 private String icon; // 图标名称
private String iconColor; // 图标颜色 private String iconColor; // 图标颜色
private int strokeWidth = 2;// 边框宽度 private int strokeWidth = 2;// 边框宽度
public Style(String fill, String stroke, String icon) { public Style(String fill, String stroke, String icon) {
@ -92,32 +102,34 @@ public class NodeUiConfig {
@Data @Data
public static class Ports { public static class Ports {
private Map<String, PortGroup> groups; private Map<String, PortGroup> groups;
/**
* 获取端口类型列表
* @return 端口类型列表"in"/"out"
*/
public List<String> getTypes() {
if (groups == null) {
return new ArrayList<>();
}
return new ArrayList<>(groups.keySet());
}
} }
/**
* 连接点分组配置
*/
@Data @Data
public static class PortGroup { public static class PortGroup {
private String position; // 位置left, right, top, bottom private String position;
private PortAttrs attrs; // 连接点属性 private PortAttrs attrs;
} }
/**
* 连接点属性配置
*/
@Data @Data
public static class PortAttrs { public static class PortAttrs {
private PortCircle circle; // 圆形连接点 private PortCircle circle;
} }
/**
* 圆形连接点配置
*/
@Data @Data
public static class PortCircle { public static class PortCircle {
private int r; // 半径 private int r;
private String fill; // 填充颜色 private String fill;
private String stroke; // 边框颜色 private String stroke;
} }
} }

View File

@ -1,36 +0,0 @@
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;
}

View File

@ -7,6 +7,6 @@ import lombok.Data;
* 事件节点基础配置 * 事件节点基础配置
*/ */
@Data @Data
public class BaseEventNodeConfig extends BaseNodeConfig { public abstract class BaseEventNodeConfig extends BaseNodeConfig {
} }

View File

@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.entity;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete; import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.workflow.dto.graph.WorkflowGraph; import com.qqchen.deploy.backend.workflow.dto.graph.WorkflowDefinitionGraph;
import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums; import com.qqchen.deploy.backend.workflow.enums.WorkflowStatusEnums;
import com.qqchen.deploy.backend.workflow.hibernate.WorkflowGraphType; import com.qqchen.deploy.backend.workflow.hibernate.WorkflowGraphType;
import com.qqchen.deploy.backend.framework.domain.Entity; import com.qqchen.deploy.backend.framework.domain.Entity;
@ -54,7 +54,7 @@ public class WorkflowDefinition extends Entity<Long> {
*/ */
@Type(WorkflowGraphType.class) @Type(WorkflowGraphType.class)
@Column(name = "graph", columnDefinition = "json") @Column(name = "graph", columnDefinition = "json")
private WorkflowGraph graph; private WorkflowDefinitionGraph graph;
/** /**
* 表单配置 * 表单配置

View File

@ -1,10 +1,11 @@
package com.qqchen.deploy.backend.workflow.enums; package com.qqchen.deploy.backend.workflow.enums;
import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.JsonValue;
import com.qqchen.deploy.backend.workflow.config.NodeUiConfig; import com.qqchen.deploy.backend.workflow.dto.graph.WorkflowNodeGraph;
import lombok.Getter; import lombok.Getter;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
/** /**
* 工作流节点类型枚举 * 工作流节点类型枚举
@ -20,11 +21,27 @@ public enum NodeTypeEnums {
* - 只能有出边不能有入边 * - 只能有出边不能有入边
* - 用于触发工作流的执行 * - 用于触发工作流的执行
*/ */
START_EVENT("START_EVENT", "开始节点", new NodeUiConfig() START_EVENT(
.setShape("circle") "START_EVENT", // 节点类型编码
.setSize(40, 40) "开始节点", // 节点显示名称
.setStyle("#e8f7ff", "#1890ff", "play-circle") "工作流的起点", // 节点简要描述
.setPorts(Arrays.asList("out"))), "标记流程的开始位置,可以定义流程启动条件和初始化流程变量", // 节点详细描述
Arrays.asList( // 节点功能列表
"标记流程的开始位置",
"定义流程启动条件",
"初始化流程变量"
),
Arrays.asList( // 使用场景列表
"用户手动启动流程",
"定时触发流程",
"外部系统调用启动"
),
new WorkflowNodeGraph() // UI配置
.setShape("circle")
.setSize(40, 40)
.setStyle("#e8f7ff", "#1890ff", "play-circle")
.configPorts(Arrays.asList("out"))
),
/** /**
* 结束节点 * 结束节点
@ -33,11 +50,27 @@ public enum NodeTypeEnums {
* - 只能有入边不能有出边 * - 只能有入边不能有出边
* - 标志着工作流的结束 * - 标志着工作流的结束
*/ */
END_EVENT("END_EVENT", "结束节点", new NodeUiConfig() END_EVENT(
.setShape("circle") "END_EVENT",
.setSize(40, 40) "结束节点",
.setStyle("#fff1f0", "#ff4d4f", "stop") "工作流的终点",
.setPorts(Arrays.asList("in"))), "标记流程的结束位置,可以定义流程结束时的清理操作和设置返回值",
Arrays.asList(
"标记流程的结束位置",
"定义结束时清理操作",
"设置流程结果和返回值"
),
Arrays.asList(
"流程正常结束",
"流程异常终止",
"需要返回处理结果"
),
new WorkflowNodeGraph()
.setShape("circle")
.setSize(40, 40)
.setStyle("#fff1f0", "#ff4d4f", "stop")
.configPorts(Arrays.asList("in"))
),
/** /**
* 用户任务节点 * 用户任务节点
@ -47,11 +80,29 @@ public enum NodeTypeEnums {
* - 可以分配给特定用户或角色 * - 可以分配给特定用户或角色
* - 支持审批填写表单等操作 * - 支持审批填写表单等操作
*/ */
USER_TASK("USER_TASK", "用户任务", new NodeUiConfig() USER_TASK(
.setShape("rectangle") "USER_TASK",
.setSize(120, 60) "用户任务",
.setStyle("#ffffff", "#1890ff", "user") "人工处理任务",
.setPorts(Arrays.asList("in", "out"))), "需要人工处理的任务节点,支持任务分配、表单填写、处理期限等功能",
Arrays.asList(
"分配任务给指定用户或角色",
"支持任务表单的填写",
"设置处理期限和提醒",
"支持任务的转办、委托、退回"
),
Arrays.asList(
"审批流程",
"表单填写",
"人工审核",
"数据确认"
),
new WorkflowNodeGraph()
.setShape("rectangle")
.setSize(120, 60)
.setStyle("#ffffff", "#1890ff", "user")
.configPorts(Arrays.asList("in", "out"))
),
/** /**
* 服务任务节点 * 服务任务节点
@ -61,11 +112,29 @@ public enum NodeTypeEnums {
* - 可以调用外部服务或系统API * - 可以调用外部服务或系统API
* - 支持异步执行 * - 支持异步执行
*/ */
SERVICE_TASK("SERVICE_TASK", "服务任务", new NodeUiConfig() SERVICE_TASK(
.setShape("rectangle") "SERVICE_TASK",
.setSize(120, 60) "服务任务",
.setStyle("#ffffff", "#1890ff", "api") "系统服务调用",
.setPorts(Arrays.asList("in", "out"))), "自动执行的系统服务任务,支持同步/异步调用外部服务和系统API",
Arrays.asList(
"调用系统服务或外部接口",
"执行自动化操作",
"支持异步执行和结果回调",
"数据转换和处理"
),
Arrays.asList(
"调用外部系统API",
"发送通知消息",
"数据同步处理",
"自动化操作"
),
new WorkflowNodeGraph()
.setShape("rectangle")
.setSize(120, 60)
.setStyle("#ffffff", "#1890ff", "api")
.configPorts(Arrays.asList("in", "out"))
),
/** /**
* 脚本任务节点 * 脚本任务节点
@ -75,11 +144,29 @@ public enum NodeTypeEnums {
* - 可以执行自定义业务逻辑 * - 可以执行自定义业务逻辑
* - 适合复杂的数据处理和计算 * - 适合复杂的数据处理和计算
*/ */
SCRIPT_TASK("SCRIPT_TASK", "脚本任务", new NodeUiConfig() SCRIPT_TASK(
.setShape("rectangle") "SCRIPT_TASK",
.setSize(120, 60) "脚本任务",
.setStyle("#ffffff", "#1890ff", "code") "脚本执行任务",
.setPorts(Arrays.asList("in", "out"))), "执行自定义脚本的任务节点,支持多种脚本语言和复杂的业务逻辑",
Arrays.asList(
"执行自定义脚本代码",
"支持多种脚本语言",
"访问流程变量",
"支持复杂的业务逻辑"
),
Arrays.asList(
"数据处理和转换",
"条件判断",
"自定义业务规则",
"系统集成"
),
new WorkflowNodeGraph()
.setShape("rectangle")
.setSize(120, 60)
.setStyle("#ffffff", "#1890ff", "code")
.configPorts(Arrays.asList("in", "out"))
),
/** /**
* 排他网关 * 排他网关
@ -89,11 +176,27 @@ public enum NodeTypeEnums {
* - 需要设置分支条件 * - 需要设置分支条件
* - 适合互斥的业务场景 * - 适合互斥的业务场景
*/ */
EXCLUSIVE_GATEWAY("EXCLUSIVE_GATEWAY", "排他网关", new NodeUiConfig() EXCLUSIVE_GATEWAY(
.setShape("diamond") "EXCLUSIVE_GATEWAY",
.setSize(50, 50) "排他网关",
.setStyle("#fff7e6", "#faad14", "fork") "条件分支控制",
.setPorts(Arrays.asList("in", "out"))), "基于条件的分支控制,只会选择一个分支执行",
Arrays.asList(
"根据条件选择一个分支执行",
"支持复杂的条件表达式",
"可以设置默认分支"
),
Arrays.asList(
"条件判断",
"分支选择",
"业务规则路由"
),
new WorkflowNodeGraph()
.setShape("diamond")
.setSize(50, 50)
.setStyle("#fff7e6", "#faad14", "fork")
.configPorts(Arrays.asList("in", "out"))
),
/** /**
* 并行网关 * 并行网关
@ -103,11 +206,27 @@ public enum NodeTypeEnums {
* - 等待所有分支完成才继续 * - 等待所有分支完成才继续
* - 适合并行处理的业务场景 * - 适合并行处理的业务场景
*/ */
PARALLEL_GATEWAY("PARALLEL_GATEWAY", "并行网关", new NodeUiConfig() PARALLEL_GATEWAY(
.setShape("diamond") "PARALLEL_GATEWAY",
.setSize(50, 50) "并行网关",
.setStyle("#fff7e6", "#faad14", "branches") "并行分支控制",
.setPorts(Arrays.asList("in", "out"))), "将流程分成多个并行分支同时执行,等待所有分支完成后合并",
Arrays.asList(
"将流程分成多个并行分支",
"等待所有分支完成后合并",
"支持复杂的并行处理"
),
Arrays.asList(
"并行审批",
"多任务同时处理",
"并行数据处理"
),
new WorkflowNodeGraph()
.setShape("diamond")
.setSize(50, 50)
.setStyle("#fff7e6", "#faad14", "branches")
.configPorts(Arrays.asList("in", "out"))
),
/** /**
* 子流程节点 * 子流程节点
@ -117,11 +236,29 @@ public enum NodeTypeEnums {
* - 支持流程的模块化和复用 * - 支持流程的模块化和复用
* - 可以独立部署和版本控制 * - 可以独立部署和版本控制
*/ */
SUBPROCESS("SUB_PROCESS", "子流程", new NodeUiConfig() SUBPROCESS(
.setShape("rectangle") "SUB_PROCESS",
.setSize(120, 60) "子流程",
.setStyle("#ffffff", "#1890ff", "apartment") "嵌入式子流程",
.setPorts(Arrays.asList("in", "out"))), "在当前流程中嵌入子流程,支持流程的模块化和复用",
Arrays.asList(
"在当前流程中嵌入子流程",
"重用流程片段",
"支持事务处理",
"独立的变量范围"
),
Arrays.asList(
"流程复用",
"模块化处理",
"事务管理",
"错误处理"
),
new WorkflowNodeGraph()
.setShape("rectangle")
.setSize(120, 60)
.setStyle("#ffffff", "#1890ff", "apartment")
.configPorts(Arrays.asList("in", "out"))
),
/** /**
* 调用活动节点 * 调用活动节点
@ -131,27 +268,66 @@ public enum NodeTypeEnums {
* - 支持流程的复用 * - 支持流程的复用
* - 可以传递参数和接收返回值 * - 可以传递参数和接收返回值
*/ */
CALL_ACTIVITY("CALL_ACTIVITY", "调用活动", new NodeUiConfig() CALL_ACTIVITY(
.setShape("rectangle") "CALL_ACTIVITY",
.setSize(120, 60) "调用活动",
.setStyle("#ffffff", "#1890ff", "api") "外部流程调用",
.setPorts(Arrays.asList("in", "out"))); "调用外部定义的流程,支持跨系统流程调用和参数传递",
Arrays.asList(
"调用外部定义的流程",
"支持跨系统流程调用",
"传递和接收参数",
"支持异步调用"
),
Arrays.asList(
"跨系统流程集成",
"公共流程复用",
"分布式流程处理",
"大型流程解耦"
),
new WorkflowNodeGraph()
.setShape("rectangle")
.setSize(120, 60)
.setStyle("#ffffff", "#1890ff", "api")
.configPorts(Arrays.asList("in", "out"))
);
@JsonValue @JsonValue
private final String code; private final String code; // 节点类型编码
private final String name;
private final NodeUiConfig uiConfig;
NodeTypeEnums(String code, String name, NodeUiConfig uiConfig) { private final String name; // 节点显示名称
private final String shortDesc; // 节点简要描述
private final String description; // 节点详细描述
private final List<String> features; // 节点功能列表
private final List<String> scenarios; // 使用场景列表
private final WorkflowNodeGraph uiConfig; // UI配置
NodeTypeEnums(
String code,
String name,
String shortDesc,
String description,
List<String> features,
List<String> scenarios,
WorkflowNodeGraph uiConfig) {
this.code = code; this.code = code;
this.name = name; this.name = name;
this.shortDesc = shortDesc;
this.description = description;
this.features = features;
this.scenarios = scenarios;
this.uiConfig = uiConfig; this.uiConfig = uiConfig;
} }
/** /**
* 根据值查找对应的枚举 * 根据编码查找对应的枚举
* *
* @param code 枚举值 * @param code 节点类型编码
* @return 对应的枚举实例 * @return 对应的枚举实例
* @throws IllegalArgumentException 当找不到对应的枚举值时抛出 * @throws IllegalArgumentException 当找不到对应的枚举值时抛出
*/ */
@ -161,6 +337,6 @@ public enum NodeTypeEnums {
return type; return type;
} }
} }
throw new IllegalArgumentException("Unknown NodeType value: " + code); throw new IllegalArgumentException("Unknown NodeType code: " + code);
} }
} }

View File

@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.hibernate;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.workflow.dto.graph.WorkflowGraph; import com.qqchen.deploy.backend.workflow.dto.graph.WorkflowDefinitionGraph;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType; import org.hibernate.usertype.UserType;
@ -17,7 +17,7 @@ import java.sql.Types;
/** /**
* 自定义 Hibernate 类型用于处理 WorkflowGraph 的序列化和反序列化 * 自定义 Hibernate 类型用于处理 WorkflowGraph 的序列化和反序列化
*/ */
public class WorkflowGraphType implements UserType<WorkflowGraph> { public class WorkflowGraphType implements UserType<WorkflowDefinitionGraph> {
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
public WorkflowGraphType() { public WorkflowGraphType() {
@ -32,12 +32,12 @@ public class WorkflowGraphType implements UserType<WorkflowGraph> {
} }
@Override @Override
public Class<WorkflowGraph> returnedClass() { public Class<WorkflowDefinitionGraph> returnedClass() {
return WorkflowGraph.class; return WorkflowDefinitionGraph.class;
} }
@Override @Override
public boolean equals(WorkflowGraph x, WorkflowGraph y) { public boolean equals(WorkflowDefinitionGraph x, WorkflowDefinitionGraph y) {
if (x == y) { if (x == y) {
return true; return true;
} }
@ -48,26 +48,26 @@ public class WorkflowGraphType implements UserType<WorkflowGraph> {
} }
@Override @Override
public int hashCode(WorkflowGraph x) { public int hashCode(WorkflowDefinitionGraph x) {
return x == null ? 0 : x.hashCode(); return x == null ? 0 : x.hashCode();
} }
@Override @Override
public WorkflowGraph nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) public WorkflowDefinitionGraph nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner)
throws SQLException { throws SQLException {
String value = rs.getString(position); String value = rs.getString(position);
if (value == null) { if (value == null) {
return null; return null;
} }
try { try {
return objectMapper.readValue(value, WorkflowGraph.class); return objectMapper.readValue(value, WorkflowDefinitionGraph.class);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new HibernateException("Failed to convert String to WorkflowGraph: " + value, e); throw new HibernateException("Failed to convert String to WorkflowGraph: " + value, e);
} }
} }
@Override @Override
public void nullSafeSet(PreparedStatement st, WorkflowGraph value, int index, SharedSessionContractImplementor session) public void nullSafeSet(PreparedStatement st, WorkflowDefinitionGraph value, int index, SharedSessionContractImplementor session)
throws SQLException { throws SQLException {
if (value == null) { if (value == null) {
st.setNull(index, Types.VARCHAR); st.setNull(index, Types.VARCHAR);
@ -81,12 +81,12 @@ public class WorkflowGraphType implements UserType<WorkflowGraph> {
} }
@Override @Override
public WorkflowGraph deepCopy(WorkflowGraph value) { public WorkflowDefinitionGraph deepCopy(WorkflowDefinitionGraph value) {
if (value == null) { if (value == null) {
return null; return null;
} }
try { try {
return objectMapper.readValue(objectMapper.writeValueAsString(value), WorkflowGraph.class); return objectMapper.readValue(objectMapper.writeValueAsString(value), WorkflowDefinitionGraph.class);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new HibernateException("Failed to deep copy WorkflowGraph", e); throw new HibernateException("Failed to deep copy WorkflowGraph", e);
} }
@ -98,7 +98,7 @@ public class WorkflowGraphType implements UserType<WorkflowGraph> {
} }
@Override @Override
public Serializable disassemble(WorkflowGraph value) { public Serializable disassemble(WorkflowDefinitionGraph value) {
try { try {
return value == null ? null : objectMapper.writeValueAsString(value); return value == null ? null : objectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
@ -107,16 +107,16 @@ public class WorkflowGraphType implements UserType<WorkflowGraph> {
} }
@Override @Override
public WorkflowGraph assemble(Serializable cached, Object owner) { public WorkflowDefinitionGraph assemble(Serializable cached, Object owner) {
try { try {
return cached == null ? null : objectMapper.readValue(cached.toString(), WorkflowGraph.class); return cached == null ? null : objectMapper.readValue(cached.toString(), WorkflowDefinitionGraph.class);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new HibernateException("Failed to assemble WorkflowGraph", e); throw new HibernateException("Failed to assemble WorkflowGraph", e);
} }
} }
@Override @Override
public WorkflowGraph replace(WorkflowGraph detached, WorkflowGraph managed, Object owner) { public WorkflowDefinitionGraph replace(WorkflowDefinitionGraph detached, WorkflowDefinitionGraph managed, Object owner) {
return deepCopy(detached); return deepCopy(detached);
} }
} }

View File

@ -70,7 +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.convertToXml(dto.getGraph(), dto.getKey()); String bpmnXml = bpmnConverter.convertToXml(null, dto.getKey());
dto.setFlowVersion(1); dto.setFlowVersion(1);
dto.setBpmnXml(bpmnXml); dto.setBpmnXml(bpmnXml);
// // 创建工作流定义 // // 创建工作流定义

View File

@ -7,8 +7,6 @@ import org.flowable.bpmn.model.Process;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/** /**
* BPMN 模型转换工具 * BPMN 模型转换工具
@ -26,7 +24,7 @@ public class BpmnConverter {
* @param processKey 流程标识 * @param processKey 流程标识
* @return BPMN XML字符串 * @return BPMN XML字符串
*/ */
public String convertToXml(WorkflowGraph graph, String processKey) { public String convertToXml(WorkflowDefinitionGraph graph, String processKey) {
BpmnModel bpmnModel = convertToBpmnModel(graph, processKey); BpmnModel bpmnModel = convertToBpmnModel(graph, processKey);
byte[] xmlBytes = bpmnXmlConverter.convertToXML(bpmnModel); byte[] xmlBytes = bpmnXmlConverter.convertToXML(bpmnModel);
return new String(xmlBytes, StandardCharsets.UTF_8); return new String(xmlBytes, StandardCharsets.UTF_8);
@ -38,33 +36,33 @@ public class BpmnConverter {
* @param processKey 流程标识 * @param processKey 流程标识
* @return BPMN模型 * @return BPMN模型
*/ */
public BpmnModel convertToBpmnModel(WorkflowGraph graph, String processKey) { public BpmnModel convertToBpmnModel(WorkflowDefinitionGraph graph, String processKey) {
BpmnModel bpmnModel = new BpmnModel(); BpmnModel bpmnModel = new BpmnModel();
Process process = new Process(); Process process = new Process();
// 设置流程属性 // 设置流程属性
process.setId(processKey); process.setId(processKey);
if (graph.getProperties() != null) { // if (graph.getProperties() != null) {
Map<String, Object> properties = graph.getProperties(); // Map<String, Object> properties = graph.getProperties();
process.setName(properties.get("name") != null ? properties.get("name").toString() : null); // process.setName(properties.get("name") != null ? properties.get("name").toString() : null);
process.setDocumentation(properties.get("description") != null ? properties.get("description").toString() : null); // process.setDocumentation(properties.get("description") != null ? properties.get("description").toString() : null);
} // }
// 转换节点 // 转换节点
for (WorkflowNode node : graph.getNodes()) { // for (WorkflowDefinitionNode node : graph.getNodes()) {
FlowElement element = convertNode(node); // FlowElement element = convertNode(node);
if (element != null) { // if (element != null) {
process.addFlowElement(element); // process.addFlowElement(element);
} // }
} // }
//
// 转换边 // // 转换边
for (WorkflowEdge edge : graph.getEdges()) { // for (WorkflowDefinitionEdge edge : graph.getEdges()) {
SequenceFlow flow = convertEdge(edge); // SequenceFlow flow = convertEdge(edge);
if (flow != null) { // if (flow != null) {
process.addFlowElement(flow); // process.addFlowElement(flow);
} // }
} // }
bpmnModel.addProcess(process); bpmnModel.addProcess(process);
return bpmnModel; return bpmnModel;
@ -75,15 +73,15 @@ public class BpmnConverter {
* @param graph 工作流图数据 * @param graph 工作流图数据
* @return BPMN模型 * @return BPMN模型
*/ */
public BpmnModel convertToBpmnModel(WorkflowGraph graph) { public BpmnModel convertToBpmnModel(WorkflowDefinitionGraph graph) {
String processKey = null; String processKey = null;
if (graph.getProperties() != null) { // if (graph.getProperties() != null) {
Object key = graph.getProperties().get("key"); // Object key = graph.getProperties().get("key");
processKey = key != null ? key.toString() : null; // processKey = key != null ? key.toString() : null;
} // }
if (processKey == null) { // if (processKey == null) {
processKey = "process_" + System.currentTimeMillis(); // processKey = "process_" + System.currentTimeMillis();
} // }
return convertToBpmnModel(graph, processKey); return convertToBpmnModel(graph, processKey);
} }
@ -92,7 +90,7 @@ public class BpmnConverter {
* @param node 工作流节点 * @param node 工作流节点
* @return 流程元素 * @return 流程元素
*/ */
private FlowElement convertNode(WorkflowNode node) { private FlowElement convertNode(WorkflowDefinitionNode node) {
if (node.getType() == null) { if (node.getType() == null) {
return null; return null;
} }
@ -126,7 +124,7 @@ public class BpmnConverter {
* @param node 节点数据 * @param node 节点数据
* @return 开始事件 * @return 开始事件
*/ */
private StartEvent createStartEvent(WorkflowNode node) { private StartEvent createStartEvent(WorkflowDefinitionNode node) {
StartEvent startEvent = new StartEvent(); StartEvent startEvent = new StartEvent();
startEvent.setId(node.getId()); startEvent.setId(node.getId());
startEvent.setName(node.getName()); startEvent.setName(node.getName());
@ -138,7 +136,7 @@ public class BpmnConverter {
* @param node 节点数据 * @param node 节点数据
* @return 结束事件 * @return 结束事件
*/ */
private EndEvent createEndEvent(WorkflowNode node) { private EndEvent createEndEvent(WorkflowDefinitionNode node) {
EndEvent endEvent = new EndEvent(); EndEvent endEvent = new EndEvent();
endEvent.setId(node.getId()); endEvent.setId(node.getId());
endEvent.setName(node.getName()); endEvent.setName(node.getName());
@ -150,13 +148,13 @@ public class BpmnConverter {
* @param node 节点数据 * @param node 节点数据
* @return 服务任务 * @return 服务任务
*/ */
private ServiceTask createServiceTask(WorkflowNode node) { private ServiceTask createServiceTask(WorkflowDefinitionNode node) {
ServiceTask serviceTask = new ServiceTask(); ServiceTask serviceTask = new ServiceTask();
serviceTask.setId(node.getId()); serviceTask.setId(node.getId());
serviceTask.setName(node.getName()); serviceTask.setName(node.getName());
if (node.getConfig() != null) { if (node.getConfig() != null) {
NodeConfig config = node.getConfig(); // WorkflowDefinitionEdgeNodeConfig config = node.getConfig();
// serviceTask.setImplementation(config.getImplementation()); // serviceTask.setImplementation(config.getImplementation());
// if (config.getFields() != null) { // if (config.getFields() != null) {
// config.getFields().forEach((key, value) -> { // config.getFields().forEach((key, value) -> {
@ -176,7 +174,7 @@ public class BpmnConverter {
* @param node 节点数据 * @param node 节点数据
* @return 用户任务 * @return 用户任务
*/ */
private UserTask createUserTask(WorkflowNode node) { private UserTask createUserTask(WorkflowDefinitionNode node) {
UserTask userTask = new UserTask(); UserTask userTask = new UserTask();
userTask.setId(node.getId()); userTask.setId(node.getId());
userTask.setName(node.getName()); userTask.setName(node.getName());
@ -200,7 +198,7 @@ public class BpmnConverter {
* @param node 节点数据 * @param node 节点数据
* @return 脚本任务 * @return 脚本任务
*/ */
private ScriptTask createScriptTask(WorkflowNode node) { private ScriptTask createScriptTask(WorkflowDefinitionNode node) {
ScriptTask scriptTask = new ScriptTask(); ScriptTask scriptTask = new ScriptTask();
scriptTask.setId(node.getId()); scriptTask.setId(node.getId());
scriptTask.setName(node.getName()); scriptTask.setName(node.getName());
@ -221,7 +219,7 @@ public class BpmnConverter {
* @param node 节点数据 * @param node 节点数据
* @return 排他网关 * @return 排他网关
*/ */
private ExclusiveGateway createExclusiveGateway(WorkflowNode node) { private ExclusiveGateway createExclusiveGateway(WorkflowDefinitionNode node) {
ExclusiveGateway gateway = new ExclusiveGateway(); ExclusiveGateway gateway = new ExclusiveGateway();
gateway.setId(node.getId()); gateway.setId(node.getId());
gateway.setName(node.getName()); gateway.setName(node.getName());
@ -233,7 +231,7 @@ public class BpmnConverter {
* @param node 节点数据 * @param node 节点数据
* @return 并行网关 * @return 并行网关
*/ */
private ParallelGateway createParallelGateway(WorkflowNode node) { private ParallelGateway createParallelGateway(WorkflowDefinitionNode node) {
ParallelGateway gateway = new ParallelGateway(); ParallelGateway gateway = new ParallelGateway();
gateway.setId(node.getId()); gateway.setId(node.getId());
gateway.setName(node.getName()); gateway.setName(node.getName());
@ -245,7 +243,7 @@ public class BpmnConverter {
* @param node 节点数据 * @param node 节点数据
* @return 子流程 * @return 子流程
*/ */
private SubProcess createSubProcess(WorkflowNode node) { private SubProcess createSubProcess(WorkflowDefinitionNode node) {
SubProcess subProcess = new SubProcess(); SubProcess subProcess = new SubProcess();
subProcess.setId(node.getId()); subProcess.setId(node.getId());
subProcess.setName(node.getName()); subProcess.setName(node.getName());
@ -257,13 +255,13 @@ public class BpmnConverter {
* @param node 节点数据 * @param node 节点数据
* @return 调用活动 * @return 调用活动
*/ */
private CallActivity createCallActivity(WorkflowNode node) { private CallActivity createCallActivity(WorkflowDefinitionNode node) {
CallActivity callActivity = new CallActivity(); CallActivity callActivity = new CallActivity();
callActivity.setId(node.getId()); callActivity.setId(node.getId());
callActivity.setName(node.getName()); callActivity.setName(node.getName());
if (node.getConfig() != null) { if (node.getConfig() != null) {
NodeConfig config = node.getConfig(); // WorkflowDefinitionEdgeNodeConfig config = node.getConfig();
// callActivity.setCalledElement(config.getImplementation()); // callActivity.setCalledElement(config.getImplementation());
// if (config.getFields() != null) { // if (config.getFields() != null) {
// config.getFields().forEach((key, value) -> { // config.getFields().forEach((key, value) -> {
@ -283,15 +281,15 @@ public class BpmnConverter {
* @param edge 工作流边 * @param edge 工作流边
* @return 序列流 * @return 序列流
*/ */
private SequenceFlow convertEdge(WorkflowEdge edge) { private SequenceFlow convertEdge(WorkflowDefinitionEdge edge) {
SequenceFlow flow = new SequenceFlow(); SequenceFlow flow = new SequenceFlow();
flow.setId(edge.getId()); flow.setId(edge.getId());
flow.setName(edge.getName()); flow.setName(edge.getName());
flow.setSourceRef(edge.getSource()); // flow.setSourceRef(edge.getSource());
flow.setTargetRef(edge.getTarget()); // flow.setTargetRef(edge.getTarget());
if (edge.getConfig() != null) { if (edge.getConfig() != null) {
EdgeConfig config = edge.getConfig(); WorkflowDefinitionEdgeConfig config = edge.getConfig();
if ("sequence".equals(config.getType())) { if ("sequence".equals(config.getType())) {
if (config.getCondition() != null) { if (config.getCondition() != null) {
((SequenceFlow) flow).setConditionExpression(config.getCondition()); ((SequenceFlow) flow).setConditionExpression(config.getCondition());

View File

@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import com.qqchen.deploy.backend.workflow.dto.graph.WorkflowNodeGraph;
import com.qqchen.deploy.backend.workflow.dto.nodeConfig.*; import com.qqchen.deploy.backend.workflow.dto.nodeConfig.*;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnums; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnums;
@ -16,6 +17,7 @@ import java.util.Set;
/** /**
* Schema生成器 * Schema生成器
* 用于生成工作流节点的配置Schema和UI Schema
*/ */
public class SchemaGenerator { public class SchemaGenerator {
private static final ObjectMapper mapper = new ObjectMapper(); private static final ObjectMapper mapper = new ObjectMapper();
@ -72,15 +74,77 @@ public class SchemaGenerator {
// 设置基本信息 // 设置基本信息
node.put("code", nodeType.getCode()); node.put("code", nodeType.getCode());
node.put("name", nodeType.getName()); node.put("name", nodeType.getName());
node.put("description", nodeType.getName()); node.put("description", nodeType.getShortDesc());
// 添加详细信息
ObjectNode details = mapper.createObjectNode();
details.put("description", nodeType.getDescription());
ArrayNode features = mapper.createArrayNode();
nodeType.getFeatures().forEach(features::add);
details.set("features", features);
ArrayNode scenarios = mapper.createArrayNode();
nodeType.getScenarios().forEach(scenarios::add);
details.set("scenarios", scenarios);
node.set("details", details);
// 生成配置schema并设置到configSchema字段 // 生成配置schema并设置到configSchema字段
ObjectNode configSchema = generateConfigSchema(configClass); ObjectNode configSchema = generateConfigSchema(configClass);
node.set("configSchema", configSchema); node.set("configSchema", configSchema);
// 生成UI schema并设置到uiSchema字段
ObjectNode uiSchema = generateUiSchema(nodeType);
node.set("uiSchema", uiSchema);
return node; return node;
} }
/**
* 生成UI Schema
* 包含节点的UI相关配置如形状大小样式等
*/
private static ObjectNode generateUiSchema(NodeTypeEnums nodeType) {
ObjectNode uiSchema = mapper.createObjectNode();
WorkflowNodeGraph uiConfig = nodeType.getUiConfig();
// 设置基本形状和大小
uiSchema.put("shape", uiConfig.getShape());
ObjectNode size = mapper.createObjectNode();
size.put("width", uiConfig.getSize().getWidth());
size.put("height", uiConfig.getSize().getHeight());
uiSchema.set("size", size);
// 设置样式
ObjectNode style = mapper.createObjectNode();
style.put("fill", uiConfig.getStyle().getFill());
style.put("stroke", uiConfig.getStyle().getStroke());
style.put("strokeWidth", uiConfig.getStyle().getStrokeWidth());
style.put("icon", uiConfig.getStyle().getIcon());
style.put("iconColor", uiConfig.getStyle().getIconColor());
uiSchema.set("style", style);
// 设置连接点
ObjectNode ports = mapper.createObjectNode();
ObjectNode groups = mapper.createObjectNode();
uiConfig.getPorts().getGroups().forEach((key, group) -> {
ObjectNode groupNode = mapper.createObjectNode();
groupNode.put("position", group.getPosition());
ObjectNode attrs = mapper.createObjectNode();
ObjectNode circle = mapper.createObjectNode();
circle.put("r", group.getAttrs().getCircle().getR());
circle.put("fill", group.getAttrs().getCircle().getFill());
circle.put("stroke", group.getAttrs().getCircle().getStroke());
attrs.set("circle", circle);
groupNode.set("attrs", attrs);
groups.set(key, groupNode);
});
ports.set("groups", groups);
uiSchema.set("ports", ports);
return uiSchema;
}
/** /**
* 根据配置类生成schema * 根据配置类生成schema
*/ */
@ -99,9 +163,12 @@ public class SchemaGenerator {
processRemainingFields(properties, required, configClass); processRemainingFields(properties, required, configClass);
schema.set("properties", properties); schema.set("properties", properties);
// 如果有必填字段添加到schema中
if (!required.isEmpty()) { if (!required.isEmpty()) {
ArrayNode requiredArray = schema.putArray("required"); ArrayNode requiredArray = mapper.createArrayNode();
required.forEach(requiredArray::add); required.forEach(requiredArray::add);
schema.set("required", requiredArray);
} }
return schema; return schema;

View File

@ -0,0 +1,381 @@
package com.qqchen.deploy.backend.workflow.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.qqchen.deploy.backend.workflow.dto.graph.*;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnums;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 工作流定义图形生成工具类
* 用于生成示例工作流定义包括
* 1. 简单工作流开始 -> 脚本任务 -> 结束
* 2. 复杂工作流开始 -> 服务任务 -> 用户任务 -> 脚本任务 -> 结束
* 3. 网关工作流开始 -> 服务任务 -> 排他网关 -> (用户任务A/用户任务B) -> 并行网关 -> (脚本任务A/脚本任务B) -> 结束
*/
@Data
@Slf4j
public class WorkflowDefinitionGraph {
private static final ObjectMapper mapper = new ObjectMapper();
/**
* 节点列表
*/
private List<WorkflowDefinitionNode> nodes;
/**
* 边列表
*/
private List<WorkflowDefinitionEdge> edges;
/**
* 生成简单工作流
* 开始 -> 脚本任务 -> 结束
*/
public static WorkflowDefinitionGraph generateSimpleWorkflow() {
WorkflowDefinitionGraph graph = new WorkflowDefinitionGraph();
List<WorkflowDefinitionNode> nodes = new ArrayList<>();
List<WorkflowDefinitionEdge> edges = new ArrayList<>();
// 开始节点
WorkflowDefinitionNode startNode = createNode(
"startEvent1",
NodeTypeEnums.START_EVENT,
"开始",
100, 100,
createNodeConfig("开始节点", "启动流程")
);
nodes.add(startNode);
// 脚本任务节点
Map<String, Object> scriptConfig = createNodeConfig("脚本任务", "执行一个简单的Shell脚本");
scriptConfig.put("script", "echo 'Hello World'");
scriptConfig.put("language", "shell");
WorkflowDefinitionNode scriptNode = createNode(
"scriptTask1",
NodeTypeEnums.SCRIPT_TASK,
"执行脚本",
300, 100,
scriptConfig
);
nodes.add(scriptNode);
// 结束节点
WorkflowDefinitionNode endNode = createNode(
"endEvent1",
NodeTypeEnums.END_EVENT,
"结束",
500, 100,
createNodeConfig("结束节点", "流程结束")
);
nodes.add(endNode);
// 添加连线
edges.add(createEdge("flow1", "startEvent1", "scriptTask1", "开始到脚本"));
edges.add(createEdge("flow2", "scriptTask1", "endEvent1", "脚本到结束"));
graph.setNodes(nodes);
graph.setEdges(edges);
return graph;
}
/**
* 生成复杂工作流
* 开始 -> 服务任务 -> 用户任务 -> 脚本任务 -> 结束
*/
public static WorkflowDefinitionGraph generateComplexWorkflow() {
WorkflowDefinitionGraph graph = new WorkflowDefinitionGraph();
List<WorkflowDefinitionNode> nodes = new ArrayList<>();
List<WorkflowDefinitionEdge> edges = new ArrayList<>();
// 开始节点
WorkflowDefinitionNode startNode = createNode(
"startEvent1",
NodeTypeEnums.START_EVENT,
"开始",
100, 100,
createNodeConfig("开始节点", "启动流程")
);
nodes.add(startNode);
// 服务任务节点
Map<String, Object> serviceConfig = createNodeConfig("服务任务", "调用外部服务");
serviceConfig.put("url", "http://api.example.com/service");
serviceConfig.put("method", "POST");
WorkflowDefinitionNode serviceNode = createNode(
"serviceTask1",
NodeTypeEnums.SERVICE_TASK,
"调用服务",
300, 100,
serviceConfig
);
nodes.add(serviceNode);
// 用户任务节点
Map<String, Object> userConfig = createNodeConfig("用户任务", "人工审批");
userConfig.put("assignee", "admin");
WorkflowDefinitionNode userNode = createNode(
"userTask1",
NodeTypeEnums.USER_TASK,
"人工审批",
500, 100,
userConfig
);
nodes.add(userNode);
// 脚本任务节点
Map<String, Object> scriptConfig = createNodeConfig("脚本任务", "执行脚本");
scriptConfig.put("script", "process_data.sh");
scriptConfig.put("language", "shell");
WorkflowDefinitionNode scriptNode = createNode(
"scriptTask1",
NodeTypeEnums.SCRIPT_TASK,
"执行脚本",
700, 100,
scriptConfig
);
nodes.add(scriptNode);
// 结束节点
WorkflowDefinitionNode endNode = createNode(
"endEvent1",
NodeTypeEnums.END_EVENT,
"结束",
900, 100,
createNodeConfig("结束节点", "流程结束")
);
nodes.add(endNode);
// 添加连线
edges.add(createEdge("flow1", "startEvent1", "serviceTask1", "开始到服务"));
edges.add(createEdge("flow2", "serviceTask1", "userTask1", "服务到用户"));
edges.add(createEdge("flow3", "userTask1", "scriptTask1", "用户到脚本"));
edges.add(createEdge("flow4", "scriptTask1", "endEvent1", "脚本到结束"));
graph.setNodes(nodes);
graph.setEdges(edges);
return graph;
}
/**
* 生成网关工作流
* 开始 -> 服务任务 -> 排他网关 -> (用户任务A/用户任务B) -> 并行网关 -> (脚本任务A/脚本任务B) -> 结束
*/
public static WorkflowDefinitionGraph generateGatewayWorkflow() {
WorkflowDefinitionGraph graph = new WorkflowDefinitionGraph();
List<WorkflowDefinitionNode> nodes = new ArrayList<>();
List<WorkflowDefinitionEdge> edges = new ArrayList<>();
// 开始节点
WorkflowDefinitionNode startNode = createNode(
"startEvent1",
NodeTypeEnums.START_EVENT,
"开始",
100, 150,
createNodeConfig("开始节点", "启动流程")
);
nodes.add(startNode);
// 服务任务节点
Map<String, Object> serviceConfig = createNodeConfig("服务任务", "获取数据");
serviceConfig.put("url", "http://api.example.com/data");
serviceConfig.put("method", "GET");
WorkflowDefinitionNode serviceNode = createNode(
"serviceTask1",
NodeTypeEnums.SERVICE_TASK,
"获取数据",
250, 150,
serviceConfig
);
nodes.add(serviceNode);
// 排他网关
WorkflowDefinitionNode exclusiveGateway = createNode(
"exclusiveGateway1",
NodeTypeEnums.EXCLUSIVE_GATEWAY,
"数据路由",
400, 150,
createNodeConfig("排他网关", "根据数据量选择处理方式")
);
nodes.add(exclusiveGateway);
// 用户任务A大数据量
Map<String, Object> userConfigA = createNodeConfig("用户任务A", "人工处理");
userConfigA.put("assignee", "expert");
WorkflowDefinitionNode userNodeA = createNode(
"userTask1",
NodeTypeEnums.USER_TASK,
"人工处理",
550, 50,
userConfigA
);
nodes.add(userNodeA);
// 用户任务B小数据量
Map<String, Object> userConfigB = createNodeConfig("用户任务B", "快速处理");
userConfigB.put("assignee", "operator");
WorkflowDefinitionNode userNodeB = createNode(
"userTask2",
NodeTypeEnums.USER_TASK,
"快速处理",
550, 250,
userConfigB
);
nodes.add(userNodeB);
// 并行网关合并
WorkflowDefinitionNode parallelGateway = createNode(
"parallelGateway1",
NodeTypeEnums.PARALLEL_GATEWAY,
"并行处理",
700, 150,
createNodeConfig("并行网关", "并行处理数据")
);
nodes.add(parallelGateway);
// 脚本任务A
Map<String, Object> scriptConfigA = createNodeConfig("脚本任务A", "数据分析");
scriptConfigA.put("script", "analyze_data.py");
scriptConfigA.put("language", "python");
WorkflowDefinitionNode scriptNodeA = createNode(
"scriptTask1",
NodeTypeEnums.SCRIPT_TASK,
"数据分析",
850, 50,
scriptConfigA
);
nodes.add(scriptNodeA);
// 脚本任务B
Map<String, Object> scriptConfigB = createNodeConfig("脚本任务B", "生成报告");
scriptConfigB.put("script", "generate_report.py");
scriptConfigB.put("language", "python");
WorkflowDefinitionNode scriptNodeB = createNode(
"scriptTask2",
NodeTypeEnums.SCRIPT_TASK,
"生成报告",
850, 250,
scriptConfigB
);
nodes.add(scriptNodeB);
// 结束节点
WorkflowDefinitionNode endNode = createNode(
"endEvent1",
NodeTypeEnums.END_EVENT,
"结束",
1000, 150,
createNodeConfig("结束节点", "流程结束")
);
nodes.add(endNode);
// 添加连线
edges.add(createEdge("flow1", "startEvent1", "serviceTask1", "开始到服务"));
edges.add(createEdge("flow2", "serviceTask1", "exclusiveGateway1", "服务到网关"));
edges.add(createEdge("flow3", "exclusiveGateway1", "userTask1", "大数据量处理", "#{dataSize > 1000}"));
edges.add(createEdge("flow4", "exclusiveGateway1", "userTask2", "小数据量处理", "#{dataSize <= 1000}"));
edges.add(createEdge("flow5", "userTask1", "parallelGateway1", "处理完成"));
edges.add(createEdge("flow6", "userTask2", "parallelGateway1", "处理完成"));
edges.add(createEdge("flow7", "parallelGateway1", "scriptTask1", "执行分析"));
edges.add(createEdge("flow8", "parallelGateway1", "scriptTask2", "生成报告"));
edges.add(createEdge("flow9", "scriptTask1", "endEvent1", "分析完成"));
edges.add(createEdge("flow10", "scriptTask2", "endEvent1", "报告完成"));
graph.setNodes(nodes);
graph.setEdges(edges);
return graph;
}
/**
* 创建节点
*/
private static WorkflowDefinitionNode createNode(String id, NodeTypeEnums type, String name, int x, int y, Map<String, Object> config) {
WorkflowDefinitionNode node = new WorkflowDefinitionNode();
node.setId(id);
node.setType(type);
node.setName(name);
node.setConfig(config);
// 从枚举的uiConfig中获取配置并创建新的图形配置
WorkflowNodeGraph originalConfig = type.getUiConfig();
WorkflowNodeGraph nodeGraph = new WorkflowNodeGraph()
.setShape(originalConfig.getShape())
.setSize(originalConfig.getSize().getWidth(), originalConfig.getSize().getHeight())
.setStyle(originalConfig.getStyle().getFill(),
originalConfig.getStyle().getStroke(),
originalConfig.getStyle().getIcon())
.configPorts(originalConfig.getPorts().getTypes());
node.setGraph(nodeGraph);
return node;
}
/**
* 创建节点配置
*/
private static Map<String, Object> createNodeConfig(String name, String description) {
Map<String, Object> config = new HashMap<>();
config.put("name", name);
config.put("description", description);
return config;
}
/**
* 创建连线
*/
private static WorkflowDefinitionEdge createEdge(String id, String from, String to, String name) {
return createEdge(id, from, to, name, null);
}
/**
* 创建带条件的连线
*/
private static WorkflowDefinitionEdge createEdge(String id, String from, String to, String name, String condition) {
WorkflowDefinitionEdge edge = new WorkflowDefinitionEdge();
edge.setId(id);
edge.setFrom(from);
edge.setTo(to);
edge.setName(name);
// 设置边配置
WorkflowDefinitionEdgeConfig config = new WorkflowDefinitionEdgeConfig();
config.setType("sequence");
if (condition != null) {
config.setCondition(condition);
config.setConditionExpression(condition);
}
edge.setConfig(config);
return edge;
}
public static void main(String[] args) {
try {
System.out.println("=== 简单工作流 ===");
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(generateSimpleWorkflow()));
System.out.println("\n=== 复杂工作流 ===");
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(generateComplexWorkflow()));
System.out.println("\n=== 网关工作流 ===");
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(generateGatewayWorkflow()));
} catch (Exception e) {
log.error("生成工作流定义失败", e);
}
}
}

View File

@ -490,7 +490,6 @@ CREATE TABLE workflow_node_definition (
name VARCHAR(100) NOT NULL COMMENT '节点名称', name VARCHAR(100) NOT NULL COMMENT '节点名称',
description VARCHAR(500) COMMENT '节点描述', description VARCHAR(500) COMMENT '节点描述',
category VARCHAR(50) NOT NULL COMMENT '节点分类', category VARCHAR(50) NOT NULL COMMENT '节点分类',
flowable_config TEXT COMMENT 'Flowable引擎配置JSON',
graph_config TEXT NOT NULL COMMENT 'X6图形配置JSON', graph_config TEXT NOT NULL COMMENT 'X6图形配置JSON',
order_num INT NOT NULL DEFAULT 0 COMMENT '排序号', order_num INT NOT NULL DEFAULT 0 COMMENT '排序号',
enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用', enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用',

View File

@ -201,220 +201,502 @@ INSERT INTO workflow_definition (
-- -------------------------------------------------------------------------------------- -- --------------------------------------------------------------------------------------
-- 事件节点 -- 事件节点
INSERT INTO workflow_node_definition (type, name, description, category, flowable_config, graph_config, form_config, order_num, enabled, create_time, create_by, update_time, update_by, version, deleted) INSERT INTO workflow_node_definition (id, create_time, type, name, description, category, graph_config, enabled)
VALUES VALUES
-- 开始事件 (1, NOW(), 'START_EVENT', '开始节点', '工作流的起点', 'EVENT', '{
('startEvent', '开始事件', '流程的开始节点', 'EVENT', "code": "START_EVENT",
'{}', "name": "开始节点",
'{ "description": "工作流的起点",
"shape": "circle", "details": {
"width": 40, "description": "标记流程的开始位置,可以定义流程启动条件和初始化流程变量",
"height": 40, "features": ["标记流程的开始位置", "定义流程启动条件", "初始化流程变量"],
"ports": { "scenarios": ["用户手动启动流程", "定时触发流程", "外部系统调用启动"]
"groups": { },
"top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, "configSchema": {
"right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, "type": "object",
"bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, "properties": {
"left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} "code": {"type": "string", "title": "节点Code", "description": "工作流节点的Code"},
} "name": {"type": "string", "title": "节点名称", "description": "工作流节点的显示名称"},
"description": {"type": "string", "title": "节点描述", "description": "工作流节点的详细描述"}
}, },
"attrs": { "required": ["code", "name"]
"body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, },
"label": {"text": "开始", "fill": "#333333"} "uiSchema": {
"shape": "circle",
"size": {"width": 40, "height": 40},
"style": {
"fill": "#e8f7ff",
"stroke": "#1890ff",
"strokeWidth": 2,
"icon": "play-circle",
"iconColor": "#1890ff"
},
"ports": {
"groups": {
"out": {
"position": "right",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
}
}
} }
}', }
'{ }', 1),
"properties": [
{"name": "id", "label": "节点标识", "type": "string", "required": true},
{"name": "name", "label": "节点名称", "type": "string", "required": true}
]
}',
10, true, NOW(), 'system', NOW(), 'system', 1, false),
-- 结束事件 (2, NOW(), 'END_EVENT', '结束节点', '工作流的终点', 'EVENT', '{
('endEvent', '结束事件', '流程的结束节点', 'EVENT', "code": "END_EVENT",
'{}', "name": "结束节点",
'{ "description": "工作流的终点",
"shape": "circle", "details": {
"width": 40, "description": "标记流程的结束位置,可以定义流程结束时的清理操作和设置返回值",
"height": 40, "features": ["标记流程的结束位置", "定义结束时清理操作", "设置流程结果和返回值"],
"ports": { "scenarios": ["流程正常结束", "流程异常终止", "需要返回处理结果"]
"groups": { },
"top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, "configSchema": {
"right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, "type": "object",
"bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, "properties": {
"left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} "code": {"type": "string", "title": "节点Code", "description": "工作流节点的Code"},
} "name": {"type": "string", "title": "节点名称", "description": "工作流节点的显示名称"},
"description": {"type": "string", "title": "节点描述", "description": "工作流节点的详细描述"}
}, },
"attrs": { "required": ["code", "name"]
"body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 4}, },
"label": {"text": "结束", "fill": "#333333"} "uiSchema": {
"shape": "circle",
"size": {"width": 40, "height": 40},
"style": {
"fill": "#fff1f0",
"stroke": "#ff4d4f",
"strokeWidth": 2,
"icon": "stop",
"iconColor": "#ff4d4f"
},
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
}
}
} }
}', }
'{ }', 1);
"properties": [
{"name": "id", "label": "节点标识", "type": "string", "required": true},
{"name": "name", "label": "节点名称", "type": "string", "required": true}
]
}',
20, true, NOW(), 'system', NOW(), 'system', 1, false),
-- 任务节点 -- 任务节点
-- 用户任务 INSERT INTO workflow_node_definition (id, create_time, type, name, description, category, graph_config, enabled)
('userTask', '用户任务', '需要人工处理的任务节点', 'TASK', VALUES
'{ (3, NOW(), 'USER_TASK', '用户任务', '人工处理任务', 'TASK', '{
"assignee": "$${assignee}", "code": "USER_TASK",
"candidateUsers": "$${candidateUsers}", "name": "用户任务",
"candidateGroups": "$${candidateGroups}" "description": "人工处理任务",
}', "details": {
'{ "description": "需要人工处理的任务节点,支持任务分配、表单填写、处理期限等功能",
"shape": "rect", "features": ["分配任务给指定用户或角色", "支持任务表单的填写", "设置处理期限和提醒", "支持任务的转办、委托、退回"],
"width": 120, "scenarios": ["审批流程", "表单填写", "人工审核", "数据确认"]
"height": 60, },
"ports": { "configSchema": {
"groups": { "type": "object",
"top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, "properties": {
"right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, "code": {"type": "string", "title": "节点Code", "description": "工作流节点的Code"},
"bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, "name": {"type": "string", "title": "节点名称", "description": "工作流节点的显示名称"},
"left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} "description": {"type": "string", "title": "节点描述", "description": "工作流节点的详细描述"}
}
}, },
"attrs": { "required": ["code", "name"]
"body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, },
"label": {"text": "用户任务", "fill": "#333333"} "uiSchema": {
"shape": "rectangle",
"size": {"width": 120, "height": 60},
"style": {
"fill": "#ffffff",
"stroke": "#1890ff",
"strokeWidth": 2,
"icon": "user",
"iconColor": "#1890ff"
},
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
},
"out": {
"position": "right",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
}
}
} }
}', }
'{ }', 1),
"properties": [
{"name": "id", "label": "节点标识", "type": "string", "required": true},
{"name": "name", "label": "节点名称", "type": "string", "required": true},
{"name": "assignee", "label": "处理人", "type": "string"},
{"name": "candidateUsers", "label": "候选用户", "type": "string"},
{"name": "candidateGroups", "label": "候选组", "type": "string"},
{"name": "dueDate", "label": "到期时间", "type": "date"}
]
}',
30, true, NOW(), 'system', NOW(), 'system', 1, false),
-- 服务任务 (4, NOW(), 'SERVICE_TASK', '服务任务', '系统服务调用', 'TASK', '{
('serviceTask', '服务任务', '自动执行的系统服务任务', 'TASK', "code": "SERVICE_TASK",
'{ "name": "服务任务",
"class": "com.example.ServiceTaskDelegate" "description": "系统服务调用",
}', "details": {
'{ "description": "自动执行的系统服务任务,支持同步/异步调用外部服务和系统API",
"shape": "rect", "features": ["调用系统服务或外部接口", "执行自动化操作", "支持异步执行和结果回调", "数据转换和处理"],
"width": 120, "scenarios": ["调用外部系统API", "发送通知消息", "数据同步处理", "自动化操作"]
"height": 60, },
"ports": { "configSchema": {
"groups": { "type": "object",
"top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, "properties": {
"right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, "code": {"type": "string", "title": "节点Code", "description": "工作流节点的Code"},
"bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, "name": {"type": "string", "title": "节点名称", "description": "工作流节点的显示名称"},
"left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} "description": {"type": "string", "title": "节点描述", "description": "工作流节点的详细描述"}
}
}, },
"attrs": { "required": ["code", "name"]
"body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, },
"label": {"text": "服务任务", "fill": "#333333"} "uiSchema": {
"shape": "rectangle",
"size": {"width": 120, "height": 60},
"style": {
"fill": "#ffffff",
"stroke": "#1890ff",
"strokeWidth": 2,
"icon": "api",
"iconColor": "#1890ff"
},
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
},
"out": {
"position": "right",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
}
}
} }
}', }
'{ }', 1),
"properties": [
{"name": "id", "label": "节点标识", "type": "string", "required": true},
{"name": "name", "label": "节点名称", "type": "string", "required": true},
{"name": "class", "label": "实现类", "type": "string", "required": true},
{"name": "async", "label": "异步执行", "type": "boolean"}
]
}',
40, true, NOW(), 'system', NOW(), 'system', 1, false),
-- Shell任务 (5, NOW(), 'SCRIPT_TASK', '脚本任务', '脚本执行任务', 'TASK', '{
('shellTask', 'Shell任务', '执行Shell命令或脚本的任务节点', 'TASK', "code": "SCRIPT_TASK",
'{ "name": "脚本任务",
"class": "com.qqchen.deploy.backend.workflow.delegate.ShellTaskDelegate" "description": "脚本执行任务",
}', "details": {
'{ "description": "执行自定义脚本的任务节点,支持多种脚本语言和复杂的业务逻辑",
"shape": "rect", "features": ["执行自定义脚本代码", "支持多种脚本语言", "访问流程变量", "支持复杂的业务逻辑"],
"width": 120, "scenarios": ["数据处理和转换", "条件判断", "自定义业务规则", "系统集成"]
"height": 60, },
"ports": { "configSchema": {
"groups": { "type": "object",
"top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, "properties": {
"right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, "code": {"type": "string", "title": "节点Code", "description": "工作流节点的Code"},
"bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, "name": {"type": "string", "title": "节点名称", "description": "工作流节点的显示名称"},
"left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} "description": {"type": "string", "title": "节点描述", "description": "工作流节点的详细描述"},
} "script": {
"type": "string",
"title": "脚本内容",
"description": "需要执行的脚本内容",
"format": "textarea"
},
"language": {
"type": "string",
"title": "脚本语言",
"description": "脚本语言类型",
"default": "shell",
"enum": ["shell", "python", "javascript", "groovy"],
"enumNames": ["Shell脚本 (已支持)", "Python脚本 (开发中)", "JavaScript脚本 (开发中)", "Groovy脚本 (开发中)"]
}
}, },
"attrs": { "required": ["code", "name", "script", "language"]
"body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, },
"label": {"text": "Shell任务", "fill": "#333333"} "uiSchema": {
"shape": "rectangle",
"size": {"width": 120, "height": 60},
"style": {
"fill": "#ffffff",
"stroke": "#1890ff",
"strokeWidth": 2,
"icon": "code",
"iconColor": "#1890ff"
},
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
},
"out": {
"position": "right",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
}
}
} }
}', }
'{ }', 1);
"properties": [
{"name": "id", "label": "节点标识", "type": "string", "required": true},
{"name": "name", "label": "节点名称", "type": "string", "required": true},
{"name": "script", "label": "脚本内容", "type": "textarea", "required": true},
{"name": "workDir", "label": "工作目录", "type": "string", "required": true},
{"name": "async", "label": "异步执行", "type": "boolean"}
]
}',
50, true, NOW(), 'system', NOW(), 'system', 1, false),
-- 网关节点 -- 网关节点
-- 排他网关 INSERT INTO workflow_node_definition (id, create_time, type, name, description, category, graph_config, enabled)
('exclusiveGateway', '排他网关', '基于条件的分支网关,只会选择一个分支执行', 'GATEWAY', VALUES
'{}', (6, NOW(), 'EXCLUSIVE_GATEWAY', '排他网关', '条件分支控制', 'GATEWAY', '{
'{ "code": "EXCLUSIVE_GATEWAY",
"shape": "diamond", "name": "排他网关",
"width": 60, "description": "条件分支控制",
"height": 60, "details": {
"ports": { "description": "基于条件的分支控制,只会选择一个分支执行",
"groups": { "features": ["根据条件选择一个分支执行", "支持复杂的条件表达式", "可以设置默认分支"],
"top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, "scenarios": ["条件判断", "分支选择", "业务规则路由"]
"right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, },
"bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, "configSchema": {
"left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} "type": "object",
} "properties": {
"code": {"type": "string", "title": "节点Code", "description": "工作流节点的Code"},
"name": {"type": "string", "title": "节点名称", "description": "工作流节点的显示名称"},
"description": {"type": "string", "title": "节点描述", "description": "工作流节点的详细描述"}
}, },
"attrs": { "required": ["code", "name"]
"body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, },
"label": {"text": "×", "fill": "#333333", "fontSize": 40} "uiSchema": {
"shape": "diamond",
"size": {"width": 50, "height": 50},
"style": {
"fill": "#fff7e6",
"stroke": "#faad14",
"strokeWidth": 2,
"icon": "fork",
"iconColor": "#faad14"
},
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
},
"out": {
"position": "right",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
}
}
} }
}', }
'{ }', 1),
"properties": [
{"name": "id", "label": "节点标识", "type": "string", "required": true},
{"name": "name", "label": "网关名称", "type": "string", "required": true},
{"name": "defaultFlow", "label": "默认路径", "type": "string"}
]
}',
60, true, NOW(), 'system', NOW(), 'system', 1, false),
-- 并行网关 (7, NOW(), 'PARALLEL_GATEWAY', '并行网关', '并行分支控制', 'GATEWAY', '{
('parallelGateway', '并行网关', '并行执行所有分支', 'GATEWAY', "code": "PARALLEL_GATEWAY",
'{}', "name": "并行网关",
'{ "description": "并行分支控制",
"shape": "diamond", "details": {
"width": 60, "description": "将流程分成多个并行分支同时执行,等待所有分支完成后合并",
"height": 60, "features": ["将流程分成多个并行分支", "等待所有分支完成后合并", "支持复杂的并行处理"],
"ports": { "scenarios": ["并行审批", "多任务同时处理", "并行数据处理"]
"groups": { },
"top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, "configSchema": {
"right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, "type": "object",
"bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, "properties": {
"left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} "code": {"type": "string", "title": "节点Code", "description": "工作流节点的Code"},
} "name": {"type": "string", "title": "节点名称", "description": "工作流节点的显示名称"},
"description": {"type": "string", "title": "节点描述", "description": "工作流节点的详细描述"}
}, },
"attrs": { "required": ["code", "name"]
"body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, },
"label": {"text": "+", "fill": "#333333", "fontSize": 40} "uiSchema": {
"shape": "diamond",
"size": {"width": 50, "height": 50},
"style": {
"fill": "#fff7e6",
"stroke": "#faad14",
"strokeWidth": 2,
"icon": "branches",
"iconColor": "#faad14"
},
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
},
"out": {
"position": "right",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
}
}
} }
}', }
'{ }', 1);
"properties": [
{"name": "id", "label": "节点标识", "type": "string", "required": true}, -- 容器节点
{"name": "name", "label": "网关名称", "type": "string", "required": true} INSERT INTO workflow_node_definition (id, create_time, type, name, description, category, graph_config, enabled)
] VALUES
}', (8, NOW(), 'SUB_PROCESS', '子流程', '嵌入式子流程', 'CONTAINER', '{
70, true, NOW(), 'system', NOW(), 'system', 1, false); "code": "SUB_PROCESS",
"name": "子流程",
"description": "嵌入式子流程",
"details": {
"description": "在当前流程中嵌入子流程,支持流程的模块化和复用",
"features": ["在当前流程中嵌入子流程", "重用流程片段", "支持事务处理", "独立的变量范围"],
"scenarios": ["流程复用", "模块化处理", "事务管理", "错误处理"]
},
"configSchema": {
"type": "object",
"properties": {
"code": {"type": "string", "title": "节点Code", "description": "工作流节点的Code"},
"name": {"type": "string", "title": "节点名称", "description": "工作流节点的显示名称"},
"description": {"type": "string", "title": "节点描述", "description": "工作流节点的详细描述"}
},
"required": ["code", "name"]
},
"uiSchema": {
"shape": "rectangle",
"size": {"width": 120, "height": 60},
"style": {
"fill": "#ffffff",
"stroke": "#1890ff",
"strokeWidth": 2,
"icon": "apartment",
"iconColor": "#1890ff"
},
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
},
"out": {
"position": "right",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
}
}
}
}
}', 1),
(9, NOW(), 'CALL_ACTIVITY', '调用活动', '外部流程调用', 'CONTAINER', '{
"code": "CALL_ACTIVITY",
"name": "调用活动",
"description": "外部流程调用",
"details": {
"description": "调用外部定义的流程,支持跨系统流程调用和参数传递",
"features": ["调用外部定义的流程", "支持跨系统流程调用", "传递和接收参数", "支持异步调用"],
"scenarios": ["跨系统流程集成", "公共流程复用", "分布式流程处理", "大型流程解耦"]
},
"configSchema": {
"type": "object",
"properties": {
"code": {"type": "string", "title": "节点Code", "description": "工作流节点的Code"},
"name": {"type": "string", "title": "节点名称", "description": "工作流节点的显示名称"},
"description": {"type": "string", "title": "节点描述", "description": "工作流节点的详细描述"}
},
"required": ["code", "name"]
},
"uiSchema": {
"shape": "rectangle",
"size": {"width": 120, "height": 60},
"style": {
"fill": "#ffffff",
"stroke": "#1890ff",
"strokeWidth": 2,
"icon": "api",
"iconColor": "#1890ff"
},
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
},
"out": {
"position": "right",
"attrs": {
"circle": {
"r": 4,
"fill": "#ffffff",
"stroke": "#1890ff"
}
}
}
}
}
}
}', 1);

View File

@ -1,439 +0,0 @@
package com.qqchen.deploy.backend.workflow.util;
import com.qqchen.deploy.backend.workflow.dto.graph.*;
import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnums;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* BPMN转换器测试
*/
@SpringBootTest(properties = {
"spring.flyway.enabled=false"
})
class BpmnConverterTest {
@Autowired
private BpmnConverter bpmnConverter;
@Test
void testConvertSimpleWorkflow() {
// 创建一个简单的工作流图开始 -> 服务任务 -> 结束
WorkflowGraph graph = new WorkflowGraph();
// 创建节点
List<WorkflowNode> nodes = new ArrayList<>();
// 开始节点
WorkflowNode startNode = new WorkflowNode();
startNode.setId("startEvent1");
startNode.setType(NodeTypeEnums.START_EVENT);
startNode.setName("开始");
startNode.setPosition(new Position(100.0, 100.0));
nodes.add(startNode);
// 服务任务节点
WorkflowNode serviceNode = new WorkflowNode();
serviceNode.setId("service1");
serviceNode.setType(NodeTypeEnums.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("endEvent1");
endNode.setType(NodeTypeEnums.END_EVENT);
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("startEvent1");
edge1.setTarget("service1");
edge1.setName("流转到服务任务");
edges.add(edge1);
// 服务任务 -> 结束
WorkflowEdge edge2 = new WorkflowEdge();
edge2.setId("flow2");
edge2.setSource("service1");
edge2.setTarget("endEvent1");
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("startEvent1");
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("endEvent1");
assertNotNull(endEvent);
assertEquals("结束", endEvent.getName());
// 验证边
SequenceFlow flow1 = (SequenceFlow) process.getFlowElement("flow1");
assertNotNull(flow1);
assertEquals("流转到服务任务", flow1.getName());
assertEquals("startEvent1", flow1.getSourceRef());
assertEquals("service1", flow1.getTargetRef());
SequenceFlow flow2 = (SequenceFlow) process.getFlowElement("flow2");
assertNotNull(flow2);
assertEquals("流转到结束", flow2.getName());
assertEquals("service1", flow2.getSourceRef());
assertEquals("endEvent1", flow2.getTargetRef());
}
@Test
void testConvertUserTask() {
// 创建一个包含用户任务的工作流图
WorkflowGraph graph = new WorkflowGraph();
// 创建节点
List<WorkflowNode> nodes = new ArrayList<>();
// 开始节点
WorkflowNode startNode = new WorkflowNode();
startNode.setId("startEvent1");
startNode.setType(NodeTypeEnums.START_EVENT);
startNode.setName("开始");
startNode.setPosition(new Position(100.0, 100.0));
nodes.add(startNode);
// 用户任务节点
WorkflowNode userNode = new WorkflowNode();
userNode.setId("user1");
userNode.setType(NodeTypeEnums.USER_TASK);
userNode.setName("审批任务");
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"));
userConfig.setDueDate("$${dueDate}");
userConfig.setPriority(1);
userConfig.setFormKey("form1");
userNode.setConfig(userConfig);
nodes.add(userNode);
// 结束节点
WorkflowNode endNode = new WorkflowNode();
endNode.setId("endEvent1");
endNode.setType(NodeTypeEnums.END_EVENT);
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("startEvent1");
edge1.setTarget("user1");
edge1.setName("提交审批");
edges.add(edge1);
// 用户任务 -> 结束
WorkflowEdge edge2 = new WorkflowEdge();
edge2.setId("flow2");
edge2.setSource("user1");
edge2.setTarget("endEvent1");
edge2.setName("审批完成");
edges.add(edge2);
graph.setNodes(nodes);
graph.setEdges(edges);
// 执行转换
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(bpmnModel);
Process process = bpmnModel.getMainProcess();
assertNotNull(process);
// 验证用户任务节点
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());
assertEquals("$${dueDate}", userTask.getDueDate());
assertEquals("1", userTask.getPriority());
assertEquals("form1", userTask.getFormKey());
}
@Test
void testConvertComplexWorkflow() {
// 创建一个复杂的工作流图开始 -> 服务任务 -> 用户任务 -> 脚本任务 -> 结束
WorkflowGraph graph = new WorkflowGraph();
// 设置流程属性
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("startEvent1");
startNode.setType(NodeTypeEnums.START_EVENT);
startNode.setName("开始");
startNode.setPosition(new Position(100.0, 100.0));
nodes.add(startNode);
// 服务任务节点
WorkflowNode serviceNode = new WorkflowNode();
serviceNode.setId("service1");
serviceNode.setType(NodeTypeEnums.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(NodeTypeEnums.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(NodeTypeEnums.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("endEvent1");
endNode.setType(NodeTypeEnums.END_EVENT);
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("startEvent1");
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("endEvent1");
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);
// 执行转换
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(bpmnModel);
Process process = bpmnModel.getMainProcess();
assertNotNull(process);
// 验证流程属性
assertEquals("测试流程", process.getName());
assertEquals("test_process", process.getId());
assertEquals("这是一个测试流程", process.getDocumentation());
// 验证节点数量
assertEquals(9, process.getFlowElements().size()); // 5个节点 + 4个连线
// 验证开始节点
StartEvent startEvent = (StartEvent) process.getFlowElement("startEvent1");
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("endEvent1");
assertNotNull(endEvent);
assertEquals("结束", endEvent.getName());
// 验证连线
SequenceFlow flow2 = (SequenceFlow) process.getFlowElement("flow2");
assertNotNull(flow2);
assertEquals("$${approved}", flow2.getConditionExpression());
}
}

18
frontend/.windsurfrules Normal file
View File

@ -0,0 +1,18 @@
你是一名java前端开发工程师对你有以下要求
1. 缺陷修正:
- 在提出修复建议前,应充分分析问题
- 提供精准、有针对性的解决方案
- 解释bug的根本原因
2. 保持简单:
- 优先考虑可读性和可维护性
- 避免过度工程化的解决方案
- 尽可能使用标准库和模式
- 遵循正确、最佳实践、DRY原则、无错误、功能齐全的代码编写原则
3. 代码更改:
- 在做出改变之前提出一个清晰的计划
- 一次将所有修改应用于单个文件
- 请勿修改不相关的文件
记住要始终考虑每个项目的背景和特定需求。