From 631c048c955dba5bec9c7ce9132cf68d3647066d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=9A=E8=BE=B0=E5=85=88=E7=94=9F?= Date: Wed, 11 Dec 2024 23:57:36 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=9A=E7=94=A8=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/nodeConfig/BaseNodeConfig.java | 23 ++ .../dto/nodeConfig/EndEventConfig.java | 30 ++ .../workflow/dto/nodeConfig/EventConfig.java | 20 ++ .../dto/nodeConfig/EventNodeConfig.java | 4 +- .../workflow/dto/nodeConfig/FormConfig.java | 19 ++ .../dto/nodeConfig/GitCloneConfig.java | 52 +++ .../dto/nodeConfig/NodeTypeConfig.java | 4 + .../dto/nodeConfig/StartEventConfig.java | 10 +- .../workflow/dto/nodeConfig/TaskListener.java | 17 + .../dto/nodeConfig/listener/BaseListener.java | 17 + .../nodeConfig/listener/EventListener.java | 21 +- .../listener/EventListenerConfig.java | 17 + .../nodeConfig/listener/ListenerConfig.java | 17 + .../dto/nodeConfig/listener/TaskListener.java | 18 ++ .../listener/TaskListenerConfig.java | 18 ++ .../script/ScriptExecutionConfig.java | 28 ++ .../script/ScriptLanguageConfig.java | 19 ++ .../nodeConfig/script/ScriptRetryConfig.java | 15 + .../workflow/util/SchemaGenerator.java | 299 +++++++----------- .../workflow/util/SchemaGeneratorTest.java | 157 +++++++-- 20 files changed, 577 insertions(+), 228 deletions(-) diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/BaseNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/BaseNodeConfig.java index 9a3cb12a..239abd28 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/BaseNodeConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/BaseNodeConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener.ListenerConfig; import lombok.Data; import java.util.List; @@ -13,25 +14,47 @@ public class BaseNodeConfig { /** * 监听器配置列表 */ + @SchemaProperty( + title = "监听器配置", + description = "工作流节点的事件监听器配置列表" + ) private List listeners; /** * 是否异步 */ + @SchemaProperty( + title = "是否异步", + description = "节点是否以异步方式执行", + defaultValue = "false" + ) private Boolean async; /** * 是否独占 */ + @SchemaProperty( + title = "是否独占", + description = "节点是否以独占方式执行", + defaultValue = "true" + ) private Boolean exclusive; /** * 自定义属性 */ + @SchemaProperty( + title = "自定义属性", + description = "节点的自定义属性配置" + ) private Map customProperties; /** * 文档说明 */ + @SchemaProperty( + title = "文档说明", + description = "节点的详细文档说明" + ) private String documentation; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EndEventConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EndEventConfig.java index 2ec645e6..df3bdff0 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EndEventConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EndEventConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import lombok.EqualsAndHashCode; import java.util.Map; @@ -13,25 +14,54 @@ public class EndEventConfig extends EventNodeConfig { /** * 结束类型(normal, terminate, error, etc.) */ + @SchemaProperty( + title = "结束类型", + description = "工作流的结束类型", + required = true, + enumValues = {"NORMAL", "TERMINATE", "ERROR"}, + enumNames = {"正常结束", "强制终止", "错误结束"}, + defaultValue = "NORMAL" + ) private String terminateType; /** * 错误代码(当terminateType为error时使用) */ + @SchemaProperty( + title = "错误代码", + description = "当结束类型为错误结束时的错误代码", + required = false + ) private String errorCode; /** * 错误消息(当terminateType为error时使用) */ + @SchemaProperty( + title = "错误消息", + description = "当结束类型为错误结束时的错误描述信息", + required = false + ) private String errorMessage; /** * 结束时的输出变量 */ + @SchemaProperty( + title = "输出变量", + description = "工作流结束时的输出变量", + required = false + ) private Map outputVariables; /** * 是否终止所有活动分支 */ + @SchemaProperty( + title = "终止所有分支", + description = "是否终止所有正在运行的并行分支", + defaultValue = "false", + required = false + ) private Boolean terminateAll; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EventConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EventConfig.java index 2c962815..42ea6e79 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EventConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EventConfig.java @@ -1,16 +1,36 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig; import com.fasterxml.jackson.annotation.JsonProperty; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; +import lombok.Data; /** * 事件节点(如START、END)的基础配置 */ +@Data public class EventConfig { + @SchemaProperty( + title = "节点名称", + description = "工作流节点的显示名称", + required = true + ) @JsonProperty(required = true) private String name; + @SchemaProperty( + title = "节点描述", + description = "工作流节点的详细描述" + ) private String description; + @SchemaProperty( + title = "事件类型", + description = "工作流事件的类型", + enumValues = {"message", "signal", "timer", "error"}, + enumNames = {"消息事件", "信号事件", "定时事件", "错误事件"} + ) + private String eventType; + public String getName() { return name; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EventNodeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EventNodeConfig.java index 8db6f923..7be34359 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EventNodeConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/EventNodeConfig.java @@ -13,6 +13,7 @@ public class EventNodeConfig { */ @SchemaProperty( title = "节点名称", + description = "工作流节点的显示名称", required = true ) private String name; @@ -21,7 +22,8 @@ public class EventNodeConfig { * 节点描述 */ @SchemaProperty( - title = "节点描述" + title = "节点描述", + description = "工作流节点的详细描述" ) private String description; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/FormConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/FormConfig.java index 072f5088..4b8c9e56 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/FormConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/FormConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import java.util.Map; @@ -11,20 +12,38 @@ public class FormConfig { /** * 表单Key */ + @SchemaProperty( + title = "表单Key", + description = "表单的唯一标识", + required = true + ) private String formKey; /** * 表单属性 */ + @SchemaProperty( + title = "表单属性", + description = "表单的配置属性" + ) private Map properties; /** * 是否必填 */ + @SchemaProperty( + title = "是否必填", + description = "表单是否必须填写", + defaultValue = "true" + ) private Boolean required; /** * 表单验证规则 */ + @SchemaProperty( + title = "验证规则", + description = "表单字段的验证规则" + ) private Map validations; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/GitCloneConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/GitCloneConfig.java index 97ce9030..661beab6 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/GitCloneConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/GitCloneConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import lombok.EqualsAndHashCode; import java.util.Map; @@ -14,53 +15,104 @@ public class GitCloneConfig extends TaskConfig { /** * 仓库URL */ + @SchemaProperty( + title = "仓库URL", + description = "Git仓库的URL地址", + required = true + ) private String repositoryUrl; /** * 分支或标签 */ + @SchemaProperty( + title = "分支或标签", + description = "要克隆的分支、标签或提交ID", + defaultValue = "main" + ) private String ref; /** * 目标目录 */ + @SchemaProperty( + title = "目标目录", + description = "代码克隆到的目标目录", + required = true + ) private String targetDirectory; /** * 是否递归克隆子模块 */ + @SchemaProperty( + title = "递归克隆子模块", + description = "是否递归克隆Git子模块", + defaultValue = "false" + ) private Boolean recursive; /** * 克隆深度(0表示完整克隆) */ + @SchemaProperty( + title = "克隆深度", + description = "克隆的历史深度,0表示完整克隆", + defaultValue = "1", + minimum = 0 + ) private Integer depth; /** * 身份验证类型(NONE, SSH_KEY, USERNAME_PASSWORD) */ + @SchemaProperty( + title = "身份验证类型", + description = "Git仓库的身份验证方式", + enumValues = {"NONE", "SSH_KEY", "USERNAME_PASSWORD"}, + enumNames = {"无验证", "SSH密钥", "用户名密码"}, + defaultValue = "NONE" + ) private String authenticationType; /** * SSH私钥(当authenticationType为SSH_KEY时使用) */ + @SchemaProperty( + title = "SSH私钥", + description = "SSH私钥内容(当身份验证类型为SSH密钥时使用)", + format = "textarea" + ) private String sshPrivateKey; /** * 用户名(当authenticationType为USERNAME_PASSWORD时使用) */ + @SchemaProperty( + title = "用户名", + description = "Git仓库的用户名(当身份验证类型为用户名密码时使用)" + ) private String username; /** * 密码或令牌(当authenticationType为USERNAME_PASSWORD时使用) */ + @SchemaProperty( + title = "密码或令牌", + description = "Git仓库的密码或访问令牌(当身份验证类型为用户名密码时使用)" + ) private String password; /** * Git配置 */ + @SchemaProperty( + title = "Git配置", + description = "额外的Git配置选项" + ) private Map gitConfig; public GitCloneConfig() { + setTaskType("gitCloneTask"); } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/NodeTypeConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/NodeTypeConfig.java index 46f6054c..b3fa7fcf 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/NodeTypeConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/NodeTypeConfig.java @@ -41,5 +41,9 @@ public class NodeTypeConfig { /** * 节点配置Schema */ + @SchemaProperty( + title = "配置Schema", + description = "节点类型的配置Schema定义" + ) private JsonNode configSchema; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/StartEventConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/StartEventConfig.java index 405b46cf..5b92c881 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/StartEventConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/StartEventConfig.java @@ -15,7 +15,9 @@ public class StartEventConfig extends EventNodeConfig { */ @SchemaProperty( title = "触发器类型", - description = "工作流的触发方式" + description = "工作流的触发方式", + enumValues = {"manual", "timer", "signal", "message"}, + enumNames = {"手动触发", "定时触发", "信号触发", "消息触发"} ) private String triggerType; @@ -29,7 +31,11 @@ public class StartEventConfig extends EventNodeConfig { private String formKey; /** - * 初始化变量 + * 发起人 */ + @SchemaProperty( + title = "发起人", + description = "工作流的发起人" + ) private String initiator; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/TaskListener.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/TaskListener.java index fab7064b..3768bb44 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/TaskListener.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/TaskListener.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import java.util.Map; @@ -11,11 +12,23 @@ public class TaskListener { /** * 监听器类型(如:class, expression, delegateExpression) */ + @SchemaProperty( + title = "监听器类型", + description = "监听器的实现类型", + required = true, + enumValues = {"class", "expression", "delegateExpression"}, + enumNames = {"Java类", "表达式", "代理表达式"} + ) private String type; /** * 监听器实现(类名、表达式或代理表达式) */ + @SchemaProperty( + title = "监听器实现", + description = "监听器的具体实现(类名、表达式或代理表达式)", + required = true + ) private String implementation; /** @@ -26,5 +39,9 @@ public class TaskListener { /** * 监听器字段 */ + @SchemaProperty( + title = "监听器字段", + description = "监听器的配置字段" + ) private Map fields; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/BaseListener.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/BaseListener.java index f7714cd2..a5070749 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/BaseListener.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/BaseListener.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import java.util.Map; @@ -11,15 +12,31 @@ public class BaseListener { /** * 监听器类型(class, expression, delegateExpression) */ + @SchemaProperty( + title = "监听器类型", + description = "监听器的实现类型", + required = true, + enumValues = {"class", "expression", "delegateExpression"}, + enumNames = {"Java类", "表达式", "代理表达式"} + ) private String type; /** * 监听器实现(类名、表达式或代理表达式) */ + @SchemaProperty( + title = "监听器实现", + description = "监听器的具体实现(类名、表达式或代理表达式)", + required = true + ) private String implementation; /** * 监听器字段 */ + @SchemaProperty( + title = "监听器字段", + description = "监听器的配置字段" + ) private Map fields; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/EventListener.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/EventListener.java index 22c328c3..065b6de2 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/EventListener.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/EventListener.java @@ -1,28 +1,45 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import lombok.EqualsAndHashCode; /** - * 事件监听器配置 + * 事件监听器 */ @Data @EqualsAndHashCode(callSuper = true) -public class EventListener extends BaseListener { +public class EventListener extends TaskListener { /** * 事件类型 * start: 事件开始 * end: 事件结束 */ + @SchemaProperty( + title = "事件类型", + description = "监听器的触发事件类型", + required = true, + enumValues = {"start", "end"}, + enumNames = {"事件开始", "事件结束"} + ) private String event; /** * 是否中断事件 */ + @SchemaProperty( + title = "是否中断事件", + description = "事件触发后是否中断当前活动", + defaultValue = "false" + ) private Boolean cancelActivity; /** * 事件抛出的变量 */ + @SchemaProperty( + title = "事件变量", + description = "事件触发时抛出的变量名" + ) private String eventVariable; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/EventListenerConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/EventListenerConfig.java index 90287d32..878c4eaa 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/EventListenerConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/EventListenerConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import lombok.EqualsAndHashCode; @@ -14,15 +15,31 @@ public class EventListenerConfig extends ListenerConfig { * start: 事件开始 * end: 事件结束 */ + @SchemaProperty( + title = "事件类型", + description = "监听器的触发事件类型", + required = true, + enumValues = {"start", "end"}, + enumNames = {"事件开始", "事件结束"} + ) private String event; /** * 是否中断事件 */ + @SchemaProperty( + title = "是否中断事件", + description = "事件触发后是否中断当前活动", + defaultValue = "false" + ) private Boolean cancelActivity; /** * 事件抛出的变量 */ + @SchemaProperty( + title = "事件变量", + description = "事件触发时抛出的变量名" + ) private String eventVariable; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/ListenerConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/ListenerConfig.java index a8c252e3..1b6566e3 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/ListenerConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/ListenerConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import java.util.Map; @@ -11,15 +12,31 @@ public class ListenerConfig { /** * 监听器类型(class, expression, delegateExpression) */ + @SchemaProperty( + title = "监听器类型", + description = "监听器的实现类型", + required = true, + enumValues = {"class", "expression", "delegateExpression"}, + enumNames = {"Java类", "表达式", "代理表达式"} + ) private String type; /** * 监听器实现(类名、表达式或代理表达式) */ + @SchemaProperty( + title = "监听器实现", + description = "监听器的具体实现(类名、表达式或代理表达式)", + required = true + ) private String implementation; /** * 监听器字段 */ + @SchemaProperty( + title = "监听器字段", + description = "监听器的配置字段" + ) private Map fields; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/TaskListener.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/TaskListener.java index bab88b30..dbb1cd71 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/TaskListener.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/TaskListener.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import lombok.EqualsAndHashCode; @@ -18,15 +19,32 @@ public class TaskListener extends BaseListener { * update: 任务更新 * timeout: 任务超时 */ + @SchemaProperty( + title = "事件类型", + description = "任务监听器的触发事件类型", + required = true, + enumValues = {"create", "assignment", "complete", "delete", "update", "timeout"}, + enumNames = {"任务创建", "任务分配", "任务完成", "任务删除", "任务更新", "任务超时"} + ) private String event; /** * 任务相关的用户或组 */ + @SchemaProperty( + title = "执行人", + description = "任务相关的用户或组标识" + ) private String assignee; /** * 任务优先级 */ + @SchemaProperty( + title = "优先级", + description = "任务的优先级", + minimum = 0, + maximum = 100 + ) private Integer priority; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/TaskListenerConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/TaskListenerConfig.java index 70f8b9f5..aac03073 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/TaskListenerConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/listener/TaskListenerConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import lombok.EqualsAndHashCode; @@ -18,15 +19,32 @@ public class TaskListenerConfig extends ListenerConfig { * update: 任务更新 * timeout: 任务超时 */ + @SchemaProperty( + title = "事件类型", + description = "任务监听器的触发事件类型", + required = true, + enumValues = {"create", "assignment", "complete", "delete", "update", "timeout"}, + enumNames = {"任务创建", "任务分配", "任务完成", "任务删除", "任务更新", "任务超时"} + ) private String event; /** * 任务相关的用户或组 */ + @SchemaProperty( + title = "执行人", + description = "任务相关的用户或组标识" + ) private String assignee; /** * 任务优先级 */ + @SchemaProperty( + title = "优先级", + description = "任务的优先级", + minimum = 0, + maximum = 100 + ) private Integer priority; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptExecutionConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptExecutionConfig.java index e8f4b9ee..bb53f5c0 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptExecutionConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptExecutionConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import java.util.Map; @@ -11,25 +12,52 @@ public class ScriptExecutionConfig { /** * 脚本内容 */ + @SchemaProperty( + title = "脚本内容", + description = "需要执行的脚本内容", + required = true, + format = "textarea" + ) private String script; /** * 工作目录 */ + @SchemaProperty( + title = "工作目录", + description = "脚本执行的工作目录", + defaultValue = "/tmp" + ) private String workingDirectory; /** * 超时时间(秒) */ + @SchemaProperty( + title = "超时时间", + description = "脚本执行的最大时间(秒)", + minimum = 1, + maximum = 3600, + defaultValue = "300" + ) private Integer timeout; /** * 环境变量 */ + @SchemaProperty( + title = "环境变量", + description = "脚本执行时的环境变量" + ) private Map environment; /** * 成功退出码 */ + @SchemaProperty( + title = "成功退出码", + description = "脚本执行成功时的退出码", + defaultValue = "0" + ) private Integer successExitCode; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptLanguageConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptLanguageConfig.java index 6b35bf34..2d96e188 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptLanguageConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptLanguageConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; import java.util.List; @@ -11,15 +12,33 @@ public class ScriptLanguageConfig { /** * 脚本语言类型 */ + @SchemaProperty( + title = "脚本语言", + description = "脚本的语言类型", + required = true, + enumValues = {"shell", "python", "javascript", "groovy"}, + enumNames = {"Shell脚本 (已支持)", "Python脚本 (开发中)", "JavaScript脚本 (开发中)", "Groovy脚本 (开发中)"}, + defaultValue = "shell" + ) private String language; /** * 解释器路径 */ + @SchemaProperty( + title = "解释器路径", + description = "脚本解释器的路径,例如:/bin/bash, /usr/bin/python3", + required = true + ) private String interpreter; /** * 支持的脚本语言列表 */ + @SchemaProperty( + title = "支持的语言", + description = "系统支持的脚本语言列表", + enumValues = {"shell", "python", "javascript", "groovy"} + ) private List supportedLanguages; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptRetryConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptRetryConfig.java index 1b8bceb3..8148b571 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptRetryConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/nodeConfig/script/ScriptRetryConfig.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script; +import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import lombok.Data; /** @@ -10,10 +11,24 @@ public class ScriptRetryConfig { /** * 重试次数 */ + @SchemaProperty( + title = "重试次数", + description = "脚本执行失败后的重试次数", + minimum = 0, + maximum = 10, + defaultValue = "0" + ) private Integer retryTimes; /** * 重试间隔(秒) */ + @SchemaProperty( + title = "重试间隔", + description = "两次重试之间的等待时间(秒)", + minimum = 1, + maximum = 3600, + defaultValue = "60" + ) private Integer retryInterval; } 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 4ee8a100..aecf2368 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 @@ -9,95 +9,92 @@ import com.qqchen.deploy.backend.workflow.dto.nodeConfig.*; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Map; /** - * Schema生成器工具类 + * Schema生成器 */ public class SchemaGenerator { private static final ObjectMapper mapper = new ObjectMapper(); - private SchemaGenerator() { - // 私有构造函数防止实例化 - } - /** - * 生成所有节点类型的配置Schema + * 生成所有节点类型的schema */ public static JsonNode generateAllNodeTypeSchemas() { ObjectNode schemas = mapper.createObjectNode(); - - // START节点 - schemas.set("START", generateNodeTypeSchema("START", "开始", "开始节点", StartEventConfig.class)); - - // SCRIPT节点 - schemas.set("SCRIPT", generateNodeTypeSchema("SCRIPT", "脚本执行", "执行脚本", ScriptExecutorConfig.class)); - - // GIT_CLONE节点 - schemas.set("GIT_CLONE", generateNodeTypeSchema("GIT_CLONE", "Git克隆", "克隆Git仓库到指定目录", GitCloneConfig.class)); - - // END节点 - schemas.set("END", generateNodeTypeSchema("END", "结束", "结束节点", EndEventConfig.class)); - + + // 生成开始节点schema + ObjectNode startNode = generateNodeSchema("START", "开始", "开始节点", StartEventConfig.class); + schemas.set("START", startNode); + + // 生成脚本节点schema + ObjectNode scriptNode = generateNodeSchema("SCRIPT", "脚本执行", "执行脚本任务", ScriptExecutorConfig.class); + schemas.set("SCRIPT", scriptNode); + + // 生成Git克隆节点schema + ObjectNode gitNode = generateNodeSchema("GIT_CLONE", "Git克隆", "克隆Git仓库", GitCloneConfig.class); + schemas.set("GIT_CLONE", gitNode); + + // 生成任务节点schema + ObjectNode taskNode = generateNodeSchema("TASK", "任务", "执行任务", TaskConfig.class); + schemas.set("TASK", taskNode); + + // 生成事件节点schema + ObjectNode eventNode = generateNodeSchema("EVENT", "事件", "事件节点", EventNodeConfig.class); + schemas.set("EVENT", eventNode); + + // 生成结束节点schema + ObjectNode endNode = generateNodeSchema("END", "结束", "结束节点", EndEventConfig.class); + schemas.set("END", endNode); + return schemas; } /** - * 生成节点类型的配置Schema + * 生成节点schema */ - private static JsonNode generateNodeTypeSchema(String code, String name, String description, Class configClass) { - ObjectNode schema = mapper.createObjectNode(); - schema.put("code", code); - schema.put("name", name); - schema.put("description", description); - schema.set("configSchema", generateNodeConfigSchema(configClass)); - return schema; + private static ObjectNode generateNodeSchema(String code, String name, String description, Class configClass) { + ObjectNode node = mapper.createObjectNode(); + node.put("code", code); + node.put("name", name); + node.put("description", description); + + ObjectNode schema = generateConfigSchema(configClass); + node.set("configSchema", schema); + + return node; } /** - * 生成节点配置的JSON Schema + * 根据配置类生成schema */ - public static JsonNode generateNodeConfigSchema(Class configClass) { + private static ObjectNode generateConfigSchema(Class configClass) { ObjectNode schema = mapper.createObjectNode(); schema.put("type", "object"); - ObjectNode properties = schema.putObject("properties"); + + ObjectNode properties = mapper.createObjectNode(); List required = new ArrayList<>(); - // 只有TaskConfig的子类才添加任务相关字段 - if (TaskConfig.class.isAssignableFrom(configClass)) { - processTaskConfigFields(properties, required); - } - - // 处理特定配置类的字段 + // 处理所有字段 for (Field field : configClass.getDeclaredFields()) { SchemaProperty annotation = field.getAnnotation(SchemaProperty.class); if (annotation != null) { - ObjectNode property = properties.putObject(field.getName()); - generateFieldSchema(property, field, annotation); - if (annotation.required()) { - required.add(field.getName()); - } + processField(properties, required, field, annotation); } } - // 处理父类的字段(除了TaskConfig) + // 处理父类字段 Class superClass = configClass.getSuperclass(); - while (superClass != null && superClass != TaskConfig.class && superClass != Object.class) { + if (superClass != null && !superClass.equals(Object.class)) { for (Field field : superClass.getDeclaredFields()) { SchemaProperty annotation = field.getAnnotation(SchemaProperty.class); if (annotation != null) { - ObjectNode property = properties.putObject(field.getName()); - generateFieldSchema(property, field, annotation); - if (annotation.required()) { - required.add(field.getName()); - } + processField(properties, required, field, annotation); } } - superClass = superClass.getSuperclass(); } + schema.set("properties", properties); if (!required.isEmpty()) { ArrayNode requiredArray = schema.putArray("required"); required.forEach(requiredArray::add); @@ -107,103 +104,40 @@ public class SchemaGenerator { } /** - * 处理TaskConfig中的基础字段 + * 处理字段 */ - private static void processTaskConfigFields(ObjectNode properties, List required) { - // 添加name字段 - ObjectNode nameProperty = properties.putObject("name"); - nameProperty.put("type", "string"); - nameProperty.put("title", "节点名称"); - required.add("name"); + private static void processField(ObjectNode properties, List required, Field field, SchemaProperty annotation) { + ObjectNode property = properties.putObject(field.getName()); - // 添加description字段 - ObjectNode descProperty = properties.putObject("description"); - descProperty.put("type", "string"); - descProperty.put("title", "节点描述"); + // 设置类型 + String type = getJsonSchemaType(field.getType()); + property.put("type", type); - // 添加timeout字段 - ObjectNode timeoutProperty = properties.putObject("timeout"); - timeoutProperty.put("type", "object"); - timeoutProperty.put("title", "超时配置"); - - ObjectNode timeoutProperties = timeoutProperty.putObject("properties"); - - ObjectNode durationProperty = timeoutProperties.putObject("duration"); - durationProperty.put("type", "integer"); - durationProperty.put("title", "超时时间"); - durationProperty.put("description", "任务执行的最大时间(秒)"); - durationProperty.put("minimum", 1); - durationProperty.put("maximum", 3600); - durationProperty.put("default", 300); - - ObjectNode strategyProperty = timeoutProperties.putObject("strategy"); - strategyProperty.put("type", "string"); - strategyProperty.put("title", "超时处理策略"); - strategyProperty.put("description", "任务超时后的处理策略"); - ArrayNode strategyEnum = strategyProperty.putArray("enum"); - strategyEnum.add("FAIL").add("CONTINUE").add("RETRY"); - ArrayNode strategyEnumNames = strategyProperty.putArray("enumNames"); - strategyEnumNames.add("失败").add("继续").add("重试"); - strategyProperty.put("default", "FAIL"); - - // 添加retry字段 - ObjectNode retryProperty = properties.putObject("retry"); - retryProperty.put("type", "object"); - retryProperty.put("title", "重试配置"); - - ObjectNode retryProperties = retryProperty.putObject("properties"); - - ObjectNode timesProperty = retryProperties.putObject("times"); - timesProperty.put("type", "integer"); - timesProperty.put("title", "重试次数"); - timesProperty.put("description", "任务失败后的重试次数"); - timesProperty.put("minimum", 0); - timesProperty.put("maximum", 10); - timesProperty.put("default", 0); - - ObjectNode intervalProperty = retryProperties.putObject("interval"); - intervalProperty.put("type", "integer"); - intervalProperty.put("title", "重试间隔"); - intervalProperty.put("description", "两次重试之间的等待时间(秒)"); - intervalProperty.put("minimum", 1); - intervalProperty.put("maximum", 3600); - intervalProperty.put("default", 60); - - ObjectNode retryStrategyProperty = retryProperties.putObject("strategy"); - retryStrategyProperty.put("type", "string"); - retryStrategyProperty.put("title", "重试策略"); - retryStrategyProperty.put("description", "任务重试的策略"); - ArrayNode retryStrategyEnum = retryStrategyProperty.putArray("enum"); - retryStrategyEnum.add("FIXED").add("EXPONENTIAL"); - ArrayNode retryStrategyEnumNames = retryStrategyProperty.putArray("enumNames"); - retryStrategyEnumNames.add("固定间隔").add("指数退避"); - retryStrategyProperty.put("default", "FIXED"); - } - - /** - * 生成字段的Schema - */ - private static void generateFieldSchema(ObjectNode property, Field field, SchemaProperty annotation) { - // 设置基本属性 + // 设置标题和描述 if (!annotation.title().isEmpty()) { property.put("title", annotation.title()); } if (!annotation.description().isEmpty()) { property.put("description", annotation.description()); } + + // 设置必填 + if (annotation.required()) { + required.add(field.getName()); + } + + // 设置格式 if (!annotation.format().isEmpty()) { property.put("format", annotation.format()); } + + // 设置默认值 if (!annotation.defaultValue().isEmpty()) { - setDefaultValue(property, field, annotation.defaultValue()); + setDefaultValue(property, type, annotation.defaultValue()); } - // 设置字段类型 - setFieldType(property, field); - // 设置数值范围 - if (isNumericType(field.getType()) && - (annotation.minimum() != Integer.MIN_VALUE || annotation.maximum() != Integer.MAX_VALUE)) { + if ("integer".equals(type) || "number".equals(type)) { if (annotation.minimum() != Integer.MIN_VALUE) { property.put("minimum", annotation.minimum()); } @@ -214,79 +148,64 @@ public class SchemaGenerator { // 设置枚举值 if (annotation.enumValues().length > 0) { - ArrayNode enumNode = property.putArray("enum"); - Arrays.stream(annotation.enumValues()).forEach(enumNode::add); + ArrayNode enumArray = property.putArray("enum"); + for (String value : annotation.enumValues()) { + enumArray.add(value); + } if (annotation.enumNames().length > 0) { - ArrayNode enumNamesNode = property.putArray("enumNames"); - Arrays.stream(annotation.enumNames()).forEach(enumNamesNode::add); - - // 生成oneOf结构 - ArrayNode oneOf = property.putArray("oneOf"); - for (int i = 0; i < annotation.enumValues().length; i++) { - ObjectNode oneOfItem = oneOf.addObject(); - oneOfItem.put("const", annotation.enumValues()[i]); - oneOfItem.put("title", annotation.enumNames()[i]); - - // 对于非shell的选项,设置为只读 - if (!"shell".equals(annotation.enumValues()[i])) { - oneOfItem.put("readOnly", true); - } + ArrayNode enumNamesArray = property.putArray("enumNames"); + for (String name : annotation.enumNames()) { + enumNamesArray.add(name); } } } } /** - * 设置字段类型 + * 获取JSON Schema类型 */ - private static void setFieldType(ObjectNode property, Field field) { - Class type = field.getType(); - if (String.class.isAssignableFrom(type)) { - property.put("type", "string"); - } else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) { - property.put("type", "integer"); - } else if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) { - property.put("type", "boolean"); - } else if (List.class.isAssignableFrom(type)) { - property.put("type", "array"); - // 如果需要,可以在这里添加items的schema - } else if (Map.class.isAssignableFrom(type)) { - property.put("type", "object"); - ObjectNode additionalProperties = property.putObject("additionalProperties"); - additionalProperties.put("type", "string"); + private static String getJsonSchemaType(Class type) { + if (type == String.class) { + return "string"; + } else if (type == Integer.class || type == int.class) { + return "integer"; + } else if (type == Double.class || type == double.class || type == Float.class || type == float.class) { + return "number"; + } else if (type == Boolean.class || type == boolean.class) { + return "boolean"; + } else if (type.isArray() || List.class.isAssignableFrom(type)) { + return "array"; + } else { + return "object"; } } /** * 设置默认值 */ - private static void setDefaultValue(ObjectNode property, Field field, String defaultValue) { - Class type = field.getType(); - if (String.class.isAssignableFrom(type)) { - property.put("default", defaultValue); - } else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) { - try { - property.put("default", Integer.parseInt(defaultValue)); - } catch (NumberFormatException e) { - // 忽略无效的默认值 - } - } else if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) { - property.put("default", Boolean.parseBoolean(defaultValue)); + private static void setDefaultValue(ObjectNode property, String type, String defaultValue) { + switch (type) { + case "string": + property.put("default", defaultValue); + break; + case "integer": + try { + property.put("default", Integer.parseInt(defaultValue)); + } catch (NumberFormatException e) { + // 忽略无效的默认值 + } + break; + case "number": + try { + property.put("default", Double.parseDouble(defaultValue)); + } catch (NumberFormatException e) { + // 忽略无效的默认值 + } + break; + case "boolean": + property.put("default", Boolean.parseBoolean(defaultValue)); + break; } } - - /** - * 判断是否为数值类型 - */ - private static boolean isNumericType(Class type) { - return Integer.class.isAssignableFrom(type) || - int.class.isAssignableFrom(type) || - Long.class.isAssignableFrom(type) || - long.class.isAssignableFrom(type) || - Double.class.isAssignableFrom(type) || - double.class.isAssignableFrom(type) || - Float.class.isAssignableFrom(type) || - float.class.isAssignableFrom(type); - } } diff --git a/backend/src/test/java/com/qqchen/deploy/backend/workflow/util/SchemaGeneratorTest.java b/backend/src/test/java/com/qqchen/deploy/backend/workflow/util/SchemaGeneratorTest.java index 3a6b801f..3199ba0a 100644 --- a/backend/src/test/java/com/qqchen/deploy/backend/workflow/util/SchemaGeneratorTest.java +++ b/backend/src/test/java/com/qqchen/deploy/backend/workflow/util/SchemaGeneratorTest.java @@ -6,47 +6,138 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class SchemaGeneratorTest { - private final SchemaGenerator generator = new SchemaGenerator(); - private final ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper mapper = new ObjectMapper(); @Test - void testGenerateNodeTypeSchema() throws Exception { - JsonNode schema = generator.generateNodeTypeSchema(); + void testGenerateAllNodeTypeSchemas() throws Exception { + JsonNode schemas = SchemaGenerator.generateAllNodeTypeSchemas(); // 将生成的schema打印出来以便查看 - String prettySchema = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema); - System.out.println(prettySchema); + System.out.println("Generated Schemas:"); + System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schemas)); - // 基本验证 - assertTrue(schema.isArray()); - assertEquals(1, schema.size()); + // 验证基本结构 + assertTrue(schemas.isObject()); + assertTrue(schemas.has("START")); + assertTrue(schemas.has("SCRIPT")); + assertTrue(schemas.has("GIT_CLONE")); + assertTrue(schemas.has("TASK")); + assertTrue(schemas.has("EVENT")); + assertTrue(schemas.has("END")); - JsonNode firstNode = schema.get(0); - assertEquals("SCRIPT", firstNode.get("code").asText()); - assertEquals("脚本执行器", firstNode.get("name").asText()); + // 验证START节点 + JsonNode startNode = schemas.get("START"); + assertEquals("START", startNode.get("code").asText()); + assertEquals("开始", startNode.get("name").asText()); + assertEquals("开始节点", startNode.get("description").asText()); - JsonNode configSchema = firstNode.get("configSchema"); - assertTrue(configSchema.has("properties")); - assertTrue(configSchema.has("required")); + JsonNode startSchema = startNode.get("configSchema"); + assertTrue(startSchema.has("properties")); + JsonNode startProps = startSchema.get("properties"); + assertTrue(startProps.has("name")); + assertTrue(startProps.has("description")); + assertTrue(startProps.has("triggerType")); + assertTrue(startProps.has("formKey")); + assertTrue(startProps.has("initiator")); - // 验证必填字段 - JsonNode required = configSchema.get("required"); - assertTrue(required.isArray()); - assertTrue(required.toString().contains("script")); - assertTrue(required.toString().contains("language")); - assertTrue(required.toString().contains("interpreter")); + // 验证SCRIPT节点 + JsonNode scriptNode = schemas.get("SCRIPT"); + assertEquals("SCRIPT", scriptNode.get("code").asText()); + assertEquals("脚本执行", scriptNode.get("name").asText()); + assertEquals("执行脚本任务", scriptNode.get("description").asText()); - // 验证属性 - JsonNode properties = configSchema.get("properties"); - assertTrue(properties.has("script")); - assertTrue(properties.has("language")); - assertTrue(properties.has("interpreter")); - assertTrue(properties.has("workingDirectory")); - assertTrue(properties.has("timeout")); - assertTrue(properties.has("retryTimes")); - assertTrue(properties.has("retryInterval")); - assertTrue(properties.has("environment")); - assertTrue(properties.has("successExitCode")); - assertTrue(properties.has("supportedLanguages")); + JsonNode scriptSchema = scriptNode.get("configSchema"); + JsonNode scriptProps = scriptSchema.get("properties"); + // 验证基本属性 + assertTrue(scriptProps.has("taskType")); + assertTrue(scriptProps.has("taskName")); + assertTrue(scriptProps.has("description")); + // 验证任务相关属性 + assertTrue(scriptProps.has("timeoutDuration")); + assertTrue(scriptProps.has("timeoutStrategy")); + assertTrue(scriptProps.has("retryTimes")); + assertTrue(scriptProps.has("retryInterval")); + assertTrue(scriptProps.has("retryStrategy")); + // 验证脚本特有属性 + assertTrue(scriptProps.has("script")); + assertTrue(scriptProps.has("language")); + assertTrue(scriptProps.has("interpreter")); + assertTrue(scriptProps.has("workingDirectory")); + + // 验证GIT_CLONE节点 + JsonNode gitNode = schemas.get("GIT_CLONE"); + assertEquals("GIT_CLONE", gitNode.get("code").asText()); + assertEquals("Git克隆", gitNode.get("name").asText()); + assertEquals("克隆Git仓库", gitNode.get("description").asText()); + + JsonNode gitSchema = gitNode.get("configSchema"); + JsonNode gitProps = gitSchema.get("properties"); + assertTrue(gitProps.has("taskType")); + assertTrue(gitProps.has("taskName")); + assertTrue(gitProps.has("description")); + assertTrue(gitProps.has("repositoryUrl")); + assertTrue(gitProps.has("ref")); + assertTrue(gitProps.has("targetDirectory")); + + // 验证TASK节点 + JsonNode taskNode = schemas.get("TASK"); + assertEquals("TASK", taskNode.get("code").asText()); + assertEquals("任务", taskNode.get("name").asText()); + assertEquals("执行任务", taskNode.get("description").asText()); + + JsonNode taskSchema = taskNode.get("configSchema"); + JsonNode taskProps = taskSchema.get("properties"); + assertTrue(taskProps.has("taskType")); + assertTrue(taskProps.has("taskName")); + assertTrue(taskProps.has("description")); + assertTrue(taskProps.has("enabled")); + assertTrue(taskProps.has("priority")); + assertTrue(taskProps.has("timeoutDuration")); + assertTrue(taskProps.has("timeoutStrategy")); + assertTrue(taskProps.has("retryTimes")); + assertTrue(taskProps.has("retryInterval")); + assertTrue(taskProps.has("retryStrategy")); + assertTrue(taskProps.has("assignee")); + assertTrue(taskProps.has("form")); + assertTrue(taskProps.has("inputVariables")); + assertTrue(taskProps.has("outputVariables")); + assertTrue(taskProps.has("taskListeners")); + + // 验证EVENT节点 + JsonNode eventNode = schemas.get("EVENT"); + assertEquals("EVENT", eventNode.get("code").asText()); + assertEquals("事件", eventNode.get("name").asText()); + assertEquals("事件节点", eventNode.get("description").asText()); + + JsonNode eventSchema = eventNode.get("configSchema"); + JsonNode eventProps = eventSchema.get("properties"); + assertTrue(eventProps.has("name")); + assertTrue(eventProps.has("description")); + assertTrue(eventProps.has("eventType")); + + // 验证required字段 + JsonNode eventRequired = eventSchema.get("required"); + assertTrue(eventRequired.isArray()); + assertTrue(eventRequired.toString().contains("name")); + assertTrue(eventRequired.toString().contains("eventType")); + + // 验证END节点 + JsonNode endNode = schemas.get("END"); + assertEquals("END", endNode.get("code").asText()); + assertEquals("结束", endNode.get("name").asText()); + assertEquals("结束节点", endNode.get("description").asText()); + + JsonNode endSchema = endNode.get("configSchema"); + JsonNode endProps = endSchema.get("properties"); + assertTrue(endProps.has("name")); + assertTrue(endProps.has("description")); + assertTrue(endProps.has("status")); + assertTrue(endProps.has("reason")); + + // 验证required字段 + JsonNode endRequired = endSchema.get("required"); + assertTrue(endRequired.isArray()); + assertTrue(endRequired.toString().contains("name")); + assertTrue(endRequired.toString().contains("status")); } }