diff --git a/.windsurfrules b/.windsurfrules new file mode 100644 index 00000000..a241965e --- /dev/null +++ b/.windsurfrules @@ -0,0 +1,18 @@ +你是一名java前端开发工程师,对你有以下要求 +1. 缺陷修正: +- 在提出修复建议前,应充分分析问题 +- 提供精准、有针对性的解决方案 +- 解释bug的根本原因 + +2. 保持简单: +- 优先考虑可读性和可维护性 +- 避免过度工程化的解决方案 +- 尽可能使用标准库和模式 +- 遵循正确、最佳实践、DRY原则、无错误、功能齐全的代码编写原则 + +3. 代码更改: +- 在做出改变之前提出一个清晰的计划 +- 一次将所有修改应用于单个文件 +- 请勿修改不相关的文件 + +记住要始终考虑每个项目的背景和特定需求。 \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java index a3f268bd..7405e0f4 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java @@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.dto; import com.fasterxml.jackson.databind.JsonNode; 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 lombok.Data; import lombok.EqualsAndHashCode; @@ -39,7 +39,7 @@ public class WorkflowDefinitionDTO extends BaseDTO { /** * 图形数据 */ - private WorkflowGraph graph; + private WorkflowDefinitionGraph graph; /** * 表单配置 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/NodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/NodeConfig.java deleted file mode 100644 index ceb23baa..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/NodeConfig.java +++ /dev/null @@ -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 { - - - -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowEdge.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionEdge.java similarity index 77% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowEdge.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionEdge.java index 6c953336..ab4013e7 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowEdge.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionEdge.java @@ -9,7 +9,7 @@ import java.util.Map; * @date 2024-12-11 */ @Data -public class WorkflowEdge { +public class WorkflowDefinitionEdge { /** * 边ID */ @@ -18,12 +18,12 @@ public class WorkflowEdge { /** * 源节点ID */ - private String source; + private String from; /** * 目标节点ID */ - private String target; + private String to; /** * 边名称 @@ -33,7 +33,7 @@ public class WorkflowEdge { /** * 边配置 */ - private EdgeConfig config; + private WorkflowDefinitionEdgeConfig config; /** * 边属性 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/EdgeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionEdgeConfig.java similarity index 54% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/EdgeConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionEdgeConfig.java index 327d192f..f73903a7 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/EdgeConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionEdgeConfig.java @@ -10,7 +10,7 @@ import lombok.Data; */ @Data @JsonInclude(JsonInclude.Include.NON_NULL) // 只序列化非空字段 -public class EdgeConfig { +public class WorkflowDefinitionEdgeConfig { /** * 条件 */ @@ -31,14 +31,4 @@ public class EdgeConfig { */ 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; - } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/Size.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionEdgeSize.java similarity index 86% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/Size.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionEdgeSize.java index b840eac2..427123b8 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/Size.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionEdgeSize.java @@ -8,7 +8,7 @@ import lombok.Data; * @date 2024-12-11 */ @Data -public class Size { +public class WorkflowDefinitionEdgeSize { /** * 宽度 */ diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowGraph.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionGraph.java similarity index 66% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowGraph.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionGraph.java index 0592d7b8..e554a045 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowGraph.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionGraph.java @@ -2,7 +2,6 @@ package com.qqchen.deploy.backend.workflow.dto.graph; import lombok.Data; import java.util.List; -import java.util.Map; /** * 工作流图形数据传输对象 @@ -10,15 +9,15 @@ import java.util.Map; * @date 2024-12-11 */ @Data -public class WorkflowGraph { +public class WorkflowDefinitionGraph { /** * 节点列表 */ - private List nodes; + private List nodes; /** * 边列表 */ - private List edges; + private List edges; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowNode.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionNode.java similarity index 61% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowNode.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionNode.java index bf4bdf97..f639d8ce 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowNode.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionNode.java @@ -2,47 +2,36 @@ package com.qqchen.deploy.backend.workflow.dto.graph; import com.qqchen.deploy.backend.workflow.enums.NodeTypeEnums; import lombok.Data; + import java.util.Map; /** * 工作流节点数据传输对象 + * * @author cascade * @date 2024-12-11 */ @Data -public class WorkflowNode { +public class WorkflowDefinitionNode { /** * 节点ID */ private String id; - + /** * 节点类型 */ private NodeTypeEnums type; - + /** * 节点名称 */ private String name; - - /** - * 节点位置 - */ - private Position position; - + + + private WorkflowNodeGraph graph; /** * 节点配置 */ - private NodeConfig config; - - /** - * 节点属性 - */ - private Map properties; - - /** - * 节点大小 - */ - private Size size; + private Map config; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/Position.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionPosition.java similarity index 89% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/Position.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionPosition.java index 69965605..161a5928 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/Position.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowDefinitionPosition.java @@ -12,7 +12,7 @@ import lombok.AllArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor -public class Position { +public class WorkflowDefinitionPosition { /** * X坐标 */ diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/config/NodeUiConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowNodeGraph.java similarity index 73% rename from backend/src/main/java/com/qqchen/deploy/backend/workflow/config/NodeUiConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowNodeGraph.java index a8088c72..ddf8c0a8 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/config/NodeUiConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowNodeGraph.java @@ -1,8 +1,9 @@ -package com.qqchen.deploy.backend.workflow.config; +package com.qqchen.deploy.backend.workflow.dto.graph; import lombok.Data; import lombok.experimental.Accessors; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -13,42 +14,46 @@ import java.util.Map; */ @Data @Accessors(chain = true) -public class NodeUiConfig { +public class WorkflowNodeGraph { + private String shape; + private Size size; + private Style style; + private Ports ports; - public NodeUiConfig setSize(int width, int height) { + public WorkflowNodeGraph setSize(int width, int height) { this.size = new Size(width, height); 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); return this; } - public NodeUiConfig setPorts(List types) { + public WorkflowNodeGraph configPorts(List types) { this.ports = new Ports(); Map groups = new HashMap<>(); - + types.forEach(type -> { PortGroup group = new PortGroup(); group.setPosition(type.equals("in") ? "left" : "right"); - + PortCircle circle = new PortCircle(); circle.setR(4); circle.setFill("#ffffff"); circle.setStroke("#1890ff"); - + PortAttrs attrs = new PortAttrs(); attrs.setCircle(circle); - + group.setAttrs(attrs); groups.put(type, group); }); - + this.ports.setGroups(groups); return this; } @@ -59,6 +64,7 @@ public class NodeUiConfig { @Data public static class Size { private int width; + private int height; public Size(int width, int height) { @@ -73,9 +79,13 @@ public class NodeUiConfig { @Data public static class Style { private String fill; // 填充颜色 + private String stroke; // 边框颜色 + private String icon; // 图标名称 + private String iconColor; // 图标颜色 + private int strokeWidth = 2;// 边框宽度 public Style(String fill, String stroke, String icon) { @@ -92,32 +102,34 @@ public class NodeUiConfig { @Data public static class Ports { private Map groups; + + /** + * 获取端口类型列表 + * @return 端口类型列表("in"/"out") + */ + public List getTypes() { + if (groups == null) { + return new ArrayList<>(); + } + return new ArrayList<>(groups.keySet()); + } } - /** - * 连接点分组配置 - */ @Data public static class PortGroup { - private String position; // 位置:left, right, top, bottom - private PortAttrs attrs; // 连接点属性 + private String position; + private PortAttrs attrs; } - /** - * 连接点属性配置 - */ @Data public static class PortAttrs { - private PortCircle circle; // 圆形连接点 + private PortCircle circle; } - /** - * 圆形连接点配置 - */ @Data public static class PortCircle { - private int r; // 半径 - private String fill; // 填充颜色 - private String stroke; // 边框颜色 + private int r; + private String fill; + private String stroke; } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowProperties.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowProperties.java deleted file mode 100644 index 88ec74de..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/graph/WorkflowProperties.java +++ /dev/null @@ -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; -} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/BaseEventNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/BaseEventNodeConfig.java index 678c0e5b..bdc84487 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/BaseEventNodeConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/BaseEventNodeConfig.java @@ -7,6 +7,6 @@ import lombok.Data; * 事件节点基础配置 */ @Data -public class BaseEventNodeConfig extends BaseNodeConfig { +public abstract class BaseEventNodeConfig extends BaseNodeConfig { } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java index 3355c7a7..f576644f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java @@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.workflow.entity; import com.fasterxml.jackson.databind.JsonNode; 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.hibernate.WorkflowGraphType; import com.qqchen.deploy.backend.framework.domain.Entity; @@ -54,7 +54,7 @@ public class WorkflowDefinition extends Entity { */ @Type(WorkflowGraphType.class) @Column(name = "graph", columnDefinition = "json") - private WorkflowGraph graph; + private WorkflowDefinitionGraph graph; /** * 表单配置 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeTypeEnums.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeTypeEnums.java index 01ae15d9..a65b4183 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeTypeEnums.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/NodeTypeEnums.java @@ -1,10 +1,11 @@ package com.qqchen.deploy.backend.workflow.enums; 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 java.util.Arrays; +import java.util.List; /** * 工作流节点类型枚举 @@ -20,11 +21,27 @@ public enum NodeTypeEnums { * - 只能有出边,不能有入边 * - 用于触发工作流的执行 */ - START_EVENT("START_EVENT", "开始节点", new NodeUiConfig() - .setShape("circle") - .setSize(40, 40) - .setStyle("#e8f7ff", "#1890ff", "play-circle") - .setPorts(Arrays.asList("out"))), + START_EVENT( + "START_EVENT", // 节点类型编码 + "开始节点", // 节点显示名称 + "工作流的起点", // 节点简要描述 + "标记流程的开始位置,可以定义流程启动条件和初始化流程变量", // 节点详细描述 + 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() - .setShape("circle") - .setSize(40, 40) - .setStyle("#fff1f0", "#ff4d4f", "stop") - .setPorts(Arrays.asList("in"))), + END_EVENT( + "END_EVENT", + "结束节点", + "工作流的终点", + "标记流程的结束位置,可以定义流程结束时的清理操作和设置返回值", + 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() - .setShape("rectangle") - .setSize(120, 60) - .setStyle("#ffffff", "#1890ff", "user") - .setPorts(Arrays.asList("in", "out"))), + USER_TASK( + "USER_TASK", + "用户任务", + "人工处理任务", + "需要人工处理的任务节点,支持任务分配、表单填写、处理期限等功能", + 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 * - 支持异步执行 */ - SERVICE_TASK("SERVICE_TASK", "服务任务", new NodeUiConfig() - .setShape("rectangle") - .setSize(120, 60) - .setStyle("#ffffff", "#1890ff", "api") - .setPorts(Arrays.asList("in", "out"))), + SERVICE_TASK( + "SERVICE_TASK", + "服务任务", + "系统服务调用", + "自动执行的系统服务任务,支持同步/异步调用外部服务和系统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() - .setShape("rectangle") - .setSize(120, 60) - .setStyle("#ffffff", "#1890ff", "code") - .setPorts(Arrays.asList("in", "out"))), + SCRIPT_TASK( + "SCRIPT_TASK", + "脚本任务", + "脚本执行任务", + "执行自定义脚本的任务节点,支持多种脚本语言和复杂的业务逻辑", + 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() - .setShape("diamond") - .setSize(50, 50) - .setStyle("#fff7e6", "#faad14", "fork") - .setPorts(Arrays.asList("in", "out"))), + EXCLUSIVE_GATEWAY( + "EXCLUSIVE_GATEWAY", + "排他网关", + "条件分支控制", + "基于条件的分支控制,只会选择一个分支执行", + 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() - .setShape("diamond") - .setSize(50, 50) - .setStyle("#fff7e6", "#faad14", "branches") - .setPorts(Arrays.asList("in", "out"))), + PARALLEL_GATEWAY( + "PARALLEL_GATEWAY", + "并行网关", + "并行分支控制", + "将流程分成多个并行分支同时执行,等待所有分支完成后合并", + 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() - .setShape("rectangle") - .setSize(120, 60) - .setStyle("#ffffff", "#1890ff", "apartment") - .setPorts(Arrays.asList("in", "out"))), + SUBPROCESS( + "SUB_PROCESS", + "子流程", + "嵌入式子流程", + "在当前流程中嵌入子流程,支持流程的模块化和复用", + 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() - .setShape("rectangle") - .setSize(120, 60) - .setStyle("#ffffff", "#1890ff", "api") - .setPorts(Arrays.asList("in", "out"))); + CALL_ACTIVITY( + "CALL_ACTIVITY", + "调用活动", + "外部流程调用", + "调用外部定义的流程,支持跨系统流程调用和参数传递", + Arrays.asList( + "调用外部定义的流程", + "支持跨系统流程调用", + "传递和接收参数", + "支持异步调用" + ), + Arrays.asList( + "跨系统流程集成", + "公共流程复用", + "分布式流程处理", + "大型流程解耦" + ), + new WorkflowNodeGraph() + .setShape("rectangle") + .setSize(120, 60) + .setStyle("#ffffff", "#1890ff", "api") + .configPorts(Arrays.asList("in", "out")) + ); @JsonValue - private final String code; - private final String name; - private final NodeUiConfig uiConfig; + private final String code; // 节点类型编码 - NodeTypeEnums(String code, String name, NodeUiConfig uiConfig) { + private final String name; // 节点显示名称 + + private final String shortDesc; // 节点简要描述 + + private final String description; // 节点详细描述 + + private final List features; // 节点功能列表 + + private final List scenarios; // 使用场景列表 + + private final WorkflowNodeGraph uiConfig; // UI配置 + + NodeTypeEnums( + String code, + String name, + String shortDesc, + String description, + List features, + List scenarios, + WorkflowNodeGraph uiConfig) { this.code = code; this.name = name; + this.shortDesc = shortDesc; + this.description = description; + this.features = features; + this.scenarios = scenarios; this.uiConfig = uiConfig; } /** - * 根据值查找对应的枚举 + * 根据编码查找对应的枚举 * - * @param code 枚举值 + * @param code 节点类型编码 * @return 对应的枚举实例 * @throws IllegalArgumentException 当找不到对应的枚举值时抛出 */ @@ -161,6 +337,6 @@ public enum NodeTypeEnums { return type; } } - throw new IllegalArgumentException("Unknown NodeType value: " + code); + throw new IllegalArgumentException("Unknown NodeType code: " + code); } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/hibernate/WorkflowGraphType.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/hibernate/WorkflowGraphType.java index 07d0dc05..26632a84 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/hibernate/WorkflowGraphType.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/hibernate/WorkflowGraphType.java @@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.workflow.hibernate; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; 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.engine.spi.SharedSessionContractImplementor; import org.hibernate.usertype.UserType; @@ -17,7 +17,7 @@ import java.sql.Types; /** * 自定义 Hibernate 类型,用于处理 WorkflowGraph 的序列化和反序列化 */ -public class WorkflowGraphType implements UserType { +public class WorkflowGraphType implements UserType { private final ObjectMapper objectMapper; public WorkflowGraphType() { @@ -32,12 +32,12 @@ public class WorkflowGraphType implements UserType { } @Override - public Class returnedClass() { - return WorkflowGraph.class; + public Class returnedClass() { + return WorkflowDefinitionGraph.class; } @Override - public boolean equals(WorkflowGraph x, WorkflowGraph y) { + public boolean equals(WorkflowDefinitionGraph x, WorkflowDefinitionGraph y) { if (x == y) { return true; } @@ -48,26 +48,26 @@ public class WorkflowGraphType implements UserType { } @Override - public int hashCode(WorkflowGraph x) { + public int hashCode(WorkflowDefinitionGraph x) { return x == null ? 0 : x.hashCode(); } @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 { String value = rs.getString(position); if (value == null) { return null; } try { - return objectMapper.readValue(value, WorkflowGraph.class); + return objectMapper.readValue(value, WorkflowDefinitionGraph.class); } catch (JsonProcessingException e) { throw new HibernateException("Failed to convert String to WorkflowGraph: " + value, e); } } @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 { if (value == null) { st.setNull(index, Types.VARCHAR); @@ -81,12 +81,12 @@ public class WorkflowGraphType implements UserType { } @Override - public WorkflowGraph deepCopy(WorkflowGraph value) { + public WorkflowDefinitionGraph deepCopy(WorkflowDefinitionGraph value) { if (value == null) { return null; } try { - return objectMapper.readValue(objectMapper.writeValueAsString(value), WorkflowGraph.class); + return objectMapper.readValue(objectMapper.writeValueAsString(value), WorkflowDefinitionGraph.class); } catch (JsonProcessingException e) { throw new HibernateException("Failed to deep copy WorkflowGraph", e); } @@ -98,7 +98,7 @@ public class WorkflowGraphType implements UserType { } @Override - public Serializable disassemble(WorkflowGraph value) { + public Serializable disassemble(WorkflowDefinitionGraph value) { try { return value == null ? null : objectMapper.writeValueAsString(value); } catch (JsonProcessingException e) { @@ -107,16 +107,16 @@ public class WorkflowGraphType implements UserType { } @Override - public WorkflowGraph assemble(Serializable cached, Object owner) { + public WorkflowDefinitionGraph assemble(Serializable cached, Object owner) { try { - return cached == null ? null : objectMapper.readValue(cached.toString(), WorkflowGraph.class); + return cached == null ? null : objectMapper.readValue(cached.toString(), WorkflowDefinitionGraph.class); } catch (JsonProcessingException e) { throw new HibernateException("Failed to assemble WorkflowGraph", e); } } @Override - public WorkflowGraph replace(WorkflowGraph detached, WorkflowGraph managed, Object owner) { + public WorkflowDefinitionGraph replace(WorkflowDefinitionGraph detached, WorkflowDefinitionGraph managed, Object owner) { return deepCopy(detached); } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java index cbcc30e6..c7ce4b39 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java @@ -70,7 +70,7 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl 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); - } +// if (graph.getProperties() != null) { +// Map 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); - } - } +// for (WorkflowDefinitionNode node : graph.getNodes()) { +// FlowElement element = convertNode(node); +// if (element != null) { +// process.addFlowElement(element); +// } +// } +// +// // 转换边 +// for (WorkflowDefinitionEdge edge : graph.getEdges()) { +// SequenceFlow flow = convertEdge(edge); +// if (flow != null) { +// process.addFlowElement(flow); +// } +// } bpmnModel.addProcess(process); return bpmnModel; @@ -75,15 +73,15 @@ public class BpmnConverter { * @param graph 工作流图数据 * @return BPMN模型 */ - public BpmnModel convertToBpmnModel(WorkflowGraph graph) { + public BpmnModel convertToBpmnModel(WorkflowDefinitionGraph graph) { String processKey = null; - if (graph.getProperties() != null) { - Object key = graph.getProperties().get("key"); - processKey = key != null ? key.toString() : null; - } - if (processKey == null) { - processKey = "process_" + System.currentTimeMillis(); - } +// if (graph.getProperties() != null) { +// Object key = graph.getProperties().get("key"); +// processKey = key != null ? key.toString() : null; +// } +// if (processKey == null) { +// processKey = "process_" + System.currentTimeMillis(); +// } return convertToBpmnModel(graph, processKey); } @@ -92,7 +90,7 @@ public class BpmnConverter { * @param node 工作流节点 * @return 流程元素 */ - private FlowElement convertNode(WorkflowNode node) { + private FlowElement convertNode(WorkflowDefinitionNode node) { if (node.getType() == null) { return null; } @@ -126,7 +124,7 @@ public class BpmnConverter { * @param node 节点数据 * @return 开始事件 */ - private StartEvent createStartEvent(WorkflowNode node) { + private StartEvent createStartEvent(WorkflowDefinitionNode node) { StartEvent startEvent = new StartEvent(); startEvent.setId(node.getId()); startEvent.setName(node.getName()); @@ -138,7 +136,7 @@ public class BpmnConverter { * @param node 节点数据 * @return 结束事件 */ - private EndEvent createEndEvent(WorkflowNode node) { + private EndEvent createEndEvent(WorkflowDefinitionNode node) { EndEvent endEvent = new EndEvent(); endEvent.setId(node.getId()); endEvent.setName(node.getName()); @@ -150,13 +148,13 @@ public class BpmnConverter { * @param node 节点数据 * @return 服务任务 */ - private ServiceTask createServiceTask(WorkflowNode node) { + private ServiceTask createServiceTask(WorkflowDefinitionNode node) { ServiceTask serviceTask = new ServiceTask(); serviceTask.setId(node.getId()); serviceTask.setName(node.getName()); if (node.getConfig() != null) { - NodeConfig config = node.getConfig(); +// WorkflowDefinitionEdgeNodeConfig config = node.getConfig(); // serviceTask.setImplementation(config.getImplementation()); // if (config.getFields() != null) { // config.getFields().forEach((key, value) -> { @@ -176,7 +174,7 @@ public class BpmnConverter { * @param node 节点数据 * @return 用户任务 */ - private UserTask createUserTask(WorkflowNode node) { + private UserTask createUserTask(WorkflowDefinitionNode node) { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); @@ -200,7 +198,7 @@ public class BpmnConverter { * @param node 节点数据 * @return 脚本任务 */ - private ScriptTask createScriptTask(WorkflowNode node) { + private ScriptTask createScriptTask(WorkflowDefinitionNode node) { ScriptTask scriptTask = new ScriptTask(); scriptTask.setId(node.getId()); scriptTask.setName(node.getName()); @@ -221,7 +219,7 @@ public class BpmnConverter { * @param node 节点数据 * @return 排他网关 */ - private ExclusiveGateway createExclusiveGateway(WorkflowNode node) { + private ExclusiveGateway createExclusiveGateway(WorkflowDefinitionNode node) { ExclusiveGateway gateway = new ExclusiveGateway(); gateway.setId(node.getId()); gateway.setName(node.getName()); @@ -233,7 +231,7 @@ public class BpmnConverter { * @param node 节点数据 * @return 并行网关 */ - private ParallelGateway createParallelGateway(WorkflowNode node) { + private ParallelGateway createParallelGateway(WorkflowDefinitionNode node) { ParallelGateway gateway = new ParallelGateway(); gateway.setId(node.getId()); gateway.setName(node.getName()); @@ -245,7 +243,7 @@ public class BpmnConverter { * @param node 节点数据 * @return 子流程 */ - private SubProcess createSubProcess(WorkflowNode node) { + private SubProcess createSubProcess(WorkflowDefinitionNode node) { SubProcess subProcess = new SubProcess(); subProcess.setId(node.getId()); subProcess.setName(node.getName()); @@ -257,13 +255,13 @@ public class BpmnConverter { * @param node 节点数据 * @return 调用活动 */ - private CallActivity createCallActivity(WorkflowNode node) { + private CallActivity createCallActivity(WorkflowDefinitionNode node) { CallActivity callActivity = new CallActivity(); callActivity.setId(node.getId()); callActivity.setName(node.getName()); if (node.getConfig() != null) { - NodeConfig config = node.getConfig(); +// WorkflowDefinitionEdgeNodeConfig config = node.getConfig(); // callActivity.setCalledElement(config.getImplementation()); // if (config.getFields() != null) { // config.getFields().forEach((key, value) -> { @@ -283,15 +281,15 @@ public class BpmnConverter { * @param edge 工作流边 * @return 序列流 */ - private SequenceFlow convertEdge(WorkflowEdge edge) { + private SequenceFlow convertEdge(WorkflowDefinitionEdge edge) { SequenceFlow flow = new SequenceFlow(); flow.setId(edge.getId()); flow.setName(edge.getName()); - flow.setSourceRef(edge.getSource()); - flow.setTargetRef(edge.getTarget()); +// flow.setSourceRef(edge.getSource()); +// flow.setTargetRef(edge.getTarget()); if (edge.getConfig() != null) { - EdgeConfig config = edge.getConfig(); + WorkflowDefinitionEdgeConfig config = edge.getConfig(); if ("sequence".equals(config.getType())) { if (config.getCondition() != null) { ((SequenceFlow) flow).setConditionExpression(config.getCondition()); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/SchemaGenerator.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/SchemaGenerator.java index 382c0044..a2781748 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/SchemaGenerator.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/SchemaGenerator.java @@ -5,6 +5,7 @@ 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.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.enums.NodeTypeEnums; @@ -16,6 +17,7 @@ import java.util.Set; /** * Schema生成器 + * 用于生成工作流节点的配置Schema和UI Schema */ public class SchemaGenerator { private static final ObjectMapper mapper = new ObjectMapper(); @@ -72,15 +74,77 @@ public class SchemaGenerator { // 设置基本信息 node.put("code", nodeType.getCode()); 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字段 ObjectNode configSchema = generateConfigSchema(configClass); node.set("configSchema", configSchema); + // 生成UI schema并设置到uiSchema字段 + ObjectNode uiSchema = generateUiSchema(nodeType); + node.set("uiSchema", uiSchema); + 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 */ @@ -99,9 +163,12 @@ public class SchemaGenerator { processRemainingFields(properties, required, configClass); schema.set("properties", properties); + + // 如果有必填字段,添加到schema中 if (!required.isEmpty()) { - ArrayNode requiredArray = schema.putArray("required"); + ArrayNode requiredArray = mapper.createArrayNode(); required.forEach(requiredArray::add); + schema.set("required", requiredArray); } return schema; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/WorkflowDefinitionGraph.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/WorkflowDefinitionGraph.java new file mode 100644 index 00000000..81f98ac7 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/util/WorkflowDefinitionGraph.java @@ -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 nodes; + + /** + * 边列表 + */ + private List edges; + + /** + * 生成简单工作流 + * 开始 -> 脚本任务 -> 结束 + */ + public static WorkflowDefinitionGraph generateSimpleWorkflow() { + WorkflowDefinitionGraph graph = new WorkflowDefinitionGraph(); + List nodes = new ArrayList<>(); + List edges = new ArrayList<>(); + + // 开始节点 + WorkflowDefinitionNode startNode = createNode( + "startEvent1", + NodeTypeEnums.START_EVENT, + "开始", + 100, 100, + createNodeConfig("开始节点", "启动流程") + ); + nodes.add(startNode); + + // 脚本任务节点 + Map 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 nodes = new ArrayList<>(); + List edges = new ArrayList<>(); + + // 开始节点 + WorkflowDefinitionNode startNode = createNode( + "startEvent1", + NodeTypeEnums.START_EVENT, + "开始", + 100, 100, + createNodeConfig("开始节点", "启动流程") + ); + nodes.add(startNode); + + // 服务任务节点 + Map 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 userConfig = createNodeConfig("用户任务", "人工审批"); + userConfig.put("assignee", "admin"); + + WorkflowDefinitionNode userNode = createNode( + "userTask1", + NodeTypeEnums.USER_TASK, + "人工审批", + 500, 100, + userConfig + ); + nodes.add(userNode); + + // 脚本任务节点 + Map 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 nodes = new ArrayList<>(); + List edges = new ArrayList<>(); + + // 开始节点 + WorkflowDefinitionNode startNode = createNode( + "startEvent1", + NodeTypeEnums.START_EVENT, + "开始", + 100, 150, + createNodeConfig("开始节点", "启动流程") + ); + nodes.add(startNode); + + // 服务任务节点 + Map 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 userConfigA = createNodeConfig("用户任务A", "人工处理"); + userConfigA.put("assignee", "expert"); + + WorkflowDefinitionNode userNodeA = createNode( + "userTask1", + NodeTypeEnums.USER_TASK, + "人工处理", + 550, 50, + userConfigA + ); + nodes.add(userNodeA); + + // 用户任务B(小数据量) + Map 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 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 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 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 createNodeConfig(String name, String description) { + Map 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); + } + } +} diff --git a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql index f335c668..7c96b55a 100644 --- a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql +++ b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql @@ -490,7 +490,6 @@ CREATE TABLE workflow_node_definition ( name VARCHAR(100) NOT NULL COMMENT '节点名称', description VARCHAR(500) COMMENT '节点描述', category VARCHAR(50) NOT NULL COMMENT '节点分类', - flowable_config TEXT COMMENT 'Flowable引擎配置JSON', graph_config TEXT NOT NULL COMMENT 'X6图形配置JSON', order_num INT NOT NULL DEFAULT 0 COMMENT '排序号', enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用', diff --git a/backend/src/main/resources/db/migration/V1.0.1__init_data.sql b/backend/src/main/resources/db/migration/V1.0.1__init_data.sql index 15f28e3c..e542d678 100644 --- a/backend/src/main/resources/db/migration/V1.0.1__init_data.sql +++ b/backend/src/main/resources/db/migration/V1.0.1__init_data.sql @@ -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 --- 开始事件 -('startEvent', '开始事件', '流程的开始节点', 'EVENT', -'{}', -'{ - "shape": "circle", - "width": 40, - "height": 40, - "ports": { - "groups": { - "top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} - } +(1, NOW(), 'START_EVENT', '开始节点', '工作流的起点', 'EVENT', '{ + "code": "START_EVENT", + "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": "工作流节点的详细描述"} }, - "attrs": { - "body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, - "label": {"text": "开始", "fill": "#333333"} + "required": ["code", "name"] + }, + "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" + } + } + } + } } -}', -'{ - "properties": [ - {"name": "id", "label": "节点标识", "type": "string", "required": true}, - {"name": "name", "label": "节点名称", "type": "string", "required": true} - ] -}', -10, true, NOW(), 'system', NOW(), 'system', 1, false), + } +}', 1), --- 结束事件 -('endEvent', '结束事件', '流程的结束节点', 'EVENT', -'{}', -'{ - "shape": "circle", - "width": 40, - "height": 40, - "ports": { - "groups": { - "top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} - } +(2, NOW(), 'END_EVENT', '结束节点', '工作流的终点', 'EVENT', '{ + "code": "END_EVENT", + "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": "工作流节点的详细描述"} }, - "attrs": { - "body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 4}, - "label": {"text": "结束", "fill": "#333333"} + "required": ["code", "name"] + }, + "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" + } + } + } + } } -}', -'{ - "properties": [ - {"name": "id", "label": "节点标识", "type": "string", "required": true}, - {"name": "name", "label": "节点名称", "type": "string", "required": true} - ] -}', -20, true, NOW(), 'system', NOW(), 'system', 1, false), + } +}', 1); -- 任务节点 --- 用户任务 -('userTask', '用户任务', '需要人工处理的任务节点', 'TASK', -'{ - "assignee": "$${assignee}", - "candidateUsers": "$${candidateUsers}", - "candidateGroups": "$${candidateGroups}" -}', -'{ - "shape": "rect", - "width": 120, - "height": 60, - "ports": { - "groups": { - "top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} - } +INSERT INTO workflow_node_definition (id, create_time, type, name, description, category, graph_config, enabled) +VALUES +(3, NOW(), 'USER_TASK', '用户任务', '人工处理任务', 'TASK', '{ + "code": "USER_TASK", + "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": "工作流节点的详细描述"} }, - "attrs": { - "body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, - "label": {"text": "用户任务", "fill": "#333333"} + "required": ["code", "name"] + }, + "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" + } + } + } + } } -}', -'{ - "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), + } +}', 1), --- 服务任务 -('serviceTask', '服务任务', '自动执行的系统服务任务', 'TASK', -'{ - "class": "com.example.ServiceTaskDelegate" -}', -'{ - "shape": "rect", - "width": 120, - "height": 60, - "ports": { - "groups": { - "top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} - } +(4, NOW(), 'SERVICE_TASK', '服务任务', '系统服务调用', 'TASK', '{ + "code": "SERVICE_TASK", + "name": "服务任务", + "description": "系统服务调用", + "details": { + "description": "自动执行的系统服务任务,支持同步/异步调用外部服务和系统API", + "features": ["调用系统服务或外部接口", "执行自动化操作", "支持异步执行和结果回调", "数据转换和处理"], + "scenarios": ["调用外部系统API", "发送通知消息", "数据同步处理", "自动化操作"] + }, + "configSchema": { + "type": "object", + "properties": { + "code": {"type": "string", "title": "节点Code", "description": "工作流节点的Code"}, + "name": {"type": "string", "title": "节点名称", "description": "工作流节点的显示名称"}, + "description": {"type": "string", "title": "节点描述", "description": "工作流节点的详细描述"} }, - "attrs": { - "body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, - "label": {"text": "服务任务", "fill": "#333333"} + "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" + } + } + } + } } -}', -'{ - "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), + } +}', 1), --- Shell任务 -('shellTask', 'Shell任务', '执行Shell命令或脚本的任务节点', 'TASK', -'{ - "class": "com.qqchen.deploy.backend.workflow.delegate.ShellTaskDelegate" -}', -'{ - "shape": "rect", - "width": 120, - "height": 60, - "ports": { - "groups": { - "top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} - } +(5, NOW(), 'SCRIPT_TASK', '脚本任务', '脚本执行任务', 'TASK', '{ + "code": "SCRIPT_TASK", + "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": "工作流节点的详细描述"}, + "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": { - "body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, - "label": {"text": "Shell任务", "fill": "#333333"} + "required": ["code", "name", "script", "language"] + }, + "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" + } + } + } + } } -}', -'{ - "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), + } +}', 1); -- 网关节点 --- 排他网关 -('exclusiveGateway', '排他网关', '基于条件的分支网关,只会选择一个分支执行', 'GATEWAY', -'{}', -'{ - "shape": "diamond", - "width": 60, - "height": 60, - "ports": { - "groups": { - "top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} - } +INSERT INTO workflow_node_definition (id, create_time, type, name, description, category, graph_config, enabled) +VALUES +(6, NOW(), 'EXCLUSIVE_GATEWAY', '排他网关', '条件分支控制', 'GATEWAY', '{ + "code": "EXCLUSIVE_GATEWAY", + "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": "工作流节点的详细描述"} }, - "attrs": { - "body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, - "label": {"text": "×", "fill": "#333333", "fontSize": 40} + "required": ["code", "name"] + }, + "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" + } + } + } + } } -}', -'{ - "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), + } +}', 1), --- 并行网关 -('parallelGateway', '并行网关', '并行执行所有分支', 'GATEWAY', -'{}', -'{ - "shape": "diamond", - "width": 60, - "height": 60, - "ports": { - "groups": { - "top": {"position": "top", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "right": {"position": "right", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "bottom": {"position": "bottom", "attrs": {"circle": {"r": 4, "magnet": true}}}, - "left": {"position": "left", "attrs": {"circle": {"r": 4, "magnet": true}}} - } +(7, NOW(), 'PARALLEL_GATEWAY', '并行网关', '并行分支控制', 'GATEWAY', '{ + "code": "PARALLEL_GATEWAY", + "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": "工作流节点的详细描述"} }, - "attrs": { - "body": {"fill": "#ffffff", "stroke": "#333333", "strokeWidth": 2}, - "label": {"text": "+", "fill": "#333333", "fontSize": 40} + "required": ["code", "name"] + }, + "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" + } + } + } + } } -}', -'{ - "properties": [ - {"name": "id", "label": "节点标识", "type": "string", "required": true}, - {"name": "name", "label": "网关名称", "type": "string", "required": true} - ] -}', -70, true, NOW(), 'system', NOW(), 'system', 1, false); \ No newline at end of file + } +}', 1); + +-- 容器节点 +INSERT INTO workflow_node_definition (id, create_time, type, name, description, category, graph_config, enabled) +VALUES +(8, NOW(), 'SUB_PROCESS', '子流程', '嵌入式子流程', 'CONTAINER', '{ + "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); diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/util/BpmnConverterTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/util/BpmnConverterTest.java deleted file mode 100644 index ae81cdce..00000000 --- a/backend/src/test/java/com/qqchen/deploy/backend/workflow/util/BpmnConverterTest.java +++ /dev/null @@ -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 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 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 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 fieldExtensions = serviceTask.getFieldExtensions(); - assertNotNull(fieldExtensions); - assertEquals(3, fieldExtensions.size()); - - Map 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 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 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 properties = new HashMap<>(); - properties.put("name", "测试流程"); - properties.put("key", "test_process"); - properties.put("description", "这是一个测试流程"); - graph.setProperties(properties); - - // 创建节点 - List 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 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 candidateUsers = Arrays.asList("user1", "user2"); - List 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 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 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()); - } -} \ No newline at end of file diff --git a/frontend/.windsurfrules b/frontend/.windsurfrules new file mode 100644 index 00000000..a241965e --- /dev/null +++ b/frontend/.windsurfrules @@ -0,0 +1,18 @@ +你是一名java前端开发工程师,对你有以下要求 +1. 缺陷修正: +- 在提出修复建议前,应充分分析问题 +- 提供精准、有针对性的解决方案 +- 解释bug的根本原因 + +2. 保持简单: +- 优先考虑可读性和可维护性 +- 避免过度工程化的解决方案 +- 尽可能使用标准库和模式 +- 遵循正确、最佳实践、DRY原则、无错误、功能齐全的代码编写原则 + +3. 代码更改: +- 在做出改变之前提出一个清晰的计划 +- 一次将所有修改应用于单个文件 +- 请勿修改不相关的文件 + +记住要始终考虑每个项目的背景和特定需求。 \ No newline at end of file