通用解析

This commit is contained in:
戚辰先生 2024-12-11 23:57:36 +08:00
parent 205c87dc5a
commit 631c048c95
20 changed files with 577 additions and 228 deletions

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig; 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 com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener.ListenerConfig;
import lombok.Data; import lombok.Data;
import java.util.List; import java.util.List;
@ -13,25 +14,47 @@ public class BaseNodeConfig {
/** /**
* 监听器配置列表 * 监听器配置列表
*/ */
@SchemaProperty(
title = "监听器配置",
description = "工作流节点的事件监听器配置列表"
)
private List<ListenerConfig> listeners; private List<ListenerConfig> listeners;
/** /**
* 是否异步 * 是否异步
*/ */
@SchemaProperty(
title = "是否异步",
description = "节点是否以异步方式执行",
defaultValue = "false"
)
private Boolean async; private Boolean async;
/** /**
* 是否独占 * 是否独占
*/ */
@SchemaProperty(
title = "是否独占",
description = "节点是否以独占方式执行",
defaultValue = "true"
)
private Boolean exclusive; private Boolean exclusive;
/** /**
* 自定义属性 * 自定义属性
*/ */
@SchemaProperty(
title = "自定义属性",
description = "节点的自定义属性配置"
)
private Map<String, Object> customProperties; private Map<String, Object> customProperties;
/** /**
* 文档说明 * 文档说明
*/ */
@SchemaProperty(
title = "文档说明",
description = "节点的详细文档说明"
)
private String documentation; private String documentation;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig; package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.Map; import java.util.Map;
@ -13,25 +14,54 @@ public class EndEventConfig extends EventNodeConfig {
/** /**
* 结束类型normal, terminate, error, etc. * 结束类型normal, terminate, error, etc.
*/ */
@SchemaProperty(
title = "结束类型",
description = "工作流的结束类型",
required = true,
enumValues = {"NORMAL", "TERMINATE", "ERROR"},
enumNames = {"正常结束", "强制终止", "错误结束"},
defaultValue = "NORMAL"
)
private String terminateType; private String terminateType;
/** /**
* 错误代码当terminateType为error时使用 * 错误代码当terminateType为error时使用
*/ */
@SchemaProperty(
title = "错误代码",
description = "当结束类型为错误结束时的错误代码",
required = false
)
private String errorCode; private String errorCode;
/** /**
* 错误消息当terminateType为error时使用 * 错误消息当terminateType为error时使用
*/ */
@SchemaProperty(
title = "错误消息",
description = "当结束类型为错误结束时的错误描述信息",
required = false
)
private String errorMessage; private String errorMessage;
/** /**
* 结束时的输出变量 * 结束时的输出变量
*/ */
@SchemaProperty(
title = "输出变量",
description = "工作流结束时的输出变量",
required = false
)
private Map<String, Object> outputVariables; private Map<String, Object> outputVariables;
/** /**
* 是否终止所有活动分支 * 是否终止所有活动分支
*/ */
@SchemaProperty(
title = "终止所有分支",
description = "是否终止所有正在运行的并行分支",
defaultValue = "false",
required = false
)
private Boolean terminateAll; private Boolean terminateAll;
} }

View File

@ -1,16 +1,36 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig; package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data;
/** /**
* 事件节点如STARTEND的基础配置 * 事件节点如STARTEND的基础配置
*/ */
@Data
public class EventConfig { public class EventConfig {
@SchemaProperty(
title = "节点名称",
description = "工作流节点的显示名称",
required = true
)
@JsonProperty(required = true) @JsonProperty(required = true)
private String name; private String name;
@SchemaProperty(
title = "节点描述",
description = "工作流节点的详细描述"
)
private String description; private String description;
@SchemaProperty(
title = "事件类型",
description = "工作流事件的类型",
enumValues = {"message", "signal", "timer", "error"},
enumNames = {"消息事件", "信号事件", "定时事件", "错误事件"}
)
private String eventType;
public String getName() { public String getName() {
return name; return name;
} }

View File

@ -13,6 +13,7 @@ public class EventNodeConfig {
*/ */
@SchemaProperty( @SchemaProperty(
title = "节点名称", title = "节点名称",
description = "工作流节点的显示名称",
required = true required = true
) )
private String name; private String name;
@ -21,7 +22,8 @@ public class EventNodeConfig {
* 节点描述 * 节点描述
*/ */
@SchemaProperty( @SchemaProperty(
title = "节点描述" title = "节点描述",
description = "工作流节点的详细描述"
) )
private String description; private String description;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig; package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import java.util.Map; import java.util.Map;
@ -11,20 +12,38 @@ public class FormConfig {
/** /**
* 表单Key * 表单Key
*/ */
@SchemaProperty(
title = "表单Key",
description = "表单的唯一标识",
required = true
)
private String formKey; private String formKey;
/** /**
* 表单属性 * 表单属性
*/ */
@SchemaProperty(
title = "表单属性",
description = "表单的配置属性"
)
private Map<String, Object> properties; private Map<String, Object> properties;
/** /**
* 是否必填 * 是否必填
*/ */
@SchemaProperty(
title = "是否必填",
description = "表单是否必须填写",
defaultValue = "true"
)
private Boolean required; private Boolean required;
/** /**
* 表单验证规则 * 表单验证规则
*/ */
@SchemaProperty(
title = "验证规则",
description = "表单字段的验证规则"
)
private Map<String, Object> validations; private Map<String, Object> validations;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig; package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.Map; import java.util.Map;
@ -14,53 +15,104 @@ public class GitCloneConfig extends TaskConfig {
/** /**
* 仓库URL * 仓库URL
*/ */
@SchemaProperty(
title = "仓库URL",
description = "Git仓库的URL地址",
required = true
)
private String repositoryUrl; private String repositoryUrl;
/** /**
* 分支或标签 * 分支或标签
*/ */
@SchemaProperty(
title = "分支或标签",
description = "要克隆的分支、标签或提交ID",
defaultValue = "main"
)
private String ref; private String ref;
/** /**
* 目标目录 * 目标目录
*/ */
@SchemaProperty(
title = "目标目录",
description = "代码克隆到的目标目录",
required = true
)
private String targetDirectory; private String targetDirectory;
/** /**
* 是否递归克隆子模块 * 是否递归克隆子模块
*/ */
@SchemaProperty(
title = "递归克隆子模块",
description = "是否递归克隆Git子模块",
defaultValue = "false"
)
private Boolean recursive; private Boolean recursive;
/** /**
* 克隆深度0表示完整克隆 * 克隆深度0表示完整克隆
*/ */
@SchemaProperty(
title = "克隆深度",
description = "克隆的历史深度0表示完整克隆",
defaultValue = "1",
minimum = 0
)
private Integer depth; private Integer depth;
/** /**
* 身份验证类型NONE, SSH_KEY, USERNAME_PASSWORD * 身份验证类型NONE, SSH_KEY, USERNAME_PASSWORD
*/ */
@SchemaProperty(
title = "身份验证类型",
description = "Git仓库的身份验证方式",
enumValues = {"NONE", "SSH_KEY", "USERNAME_PASSWORD"},
enumNames = {"无验证", "SSH密钥", "用户名密码"},
defaultValue = "NONE"
)
private String authenticationType; private String authenticationType;
/** /**
* SSH私钥当authenticationType为SSH_KEY时使用 * SSH私钥当authenticationType为SSH_KEY时使用
*/ */
@SchemaProperty(
title = "SSH私钥",
description = "SSH私钥内容当身份验证类型为SSH密钥时使用",
format = "textarea"
)
private String sshPrivateKey; private String sshPrivateKey;
/** /**
* 用户名当authenticationType为USERNAME_PASSWORD时使用 * 用户名当authenticationType为USERNAME_PASSWORD时使用
*/ */
@SchemaProperty(
title = "用户名",
description = "Git仓库的用户名当身份验证类型为用户名密码时使用"
)
private String username; private String username;
/** /**
* 密码或令牌当authenticationType为USERNAME_PASSWORD时使用 * 密码或令牌当authenticationType为USERNAME_PASSWORD时使用
*/ */
@SchemaProperty(
title = "密码或令牌",
description = "Git仓库的密码或访问令牌当身份验证类型为用户名密码时使用"
)
private String password; private String password;
/** /**
* Git配置 * Git配置
*/ */
@SchemaProperty(
title = "Git配置",
description = "额外的Git配置选项"
)
private Map<String, String> gitConfig; private Map<String, String> gitConfig;
public GitCloneConfig() { public GitCloneConfig() {
setTaskType("gitCloneTask");
} }
} }

View File

@ -41,5 +41,9 @@ public class NodeTypeConfig {
/** /**
* 节点配置Schema * 节点配置Schema
*/ */
@SchemaProperty(
title = "配置Schema",
description = "节点类型的配置Schema定义"
)
private JsonNode configSchema; private JsonNode configSchema;
} }

View File

@ -15,7 +15,9 @@ public class StartEventConfig extends EventNodeConfig {
*/ */
@SchemaProperty( @SchemaProperty(
title = "触发器类型", title = "触发器类型",
description = "工作流的触发方式" description = "工作流的触发方式",
enumValues = {"manual", "timer", "signal", "message"},
enumNames = {"手动触发", "定时触发", "信号触发", "消息触发"}
) )
private String triggerType; private String triggerType;
@ -29,7 +31,11 @@ public class StartEventConfig extends EventNodeConfig {
private String formKey; private String formKey;
/** /**
* 初始化变量 * 发起人
*/ */
@SchemaProperty(
title = "发起人",
description = "工作流的发起人"
)
private String initiator; private String initiator;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig; package com.qqchen.deploy.backend.workflow.dto.nodeConfig;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import java.util.Map; import java.util.Map;
@ -11,11 +12,23 @@ public class TaskListener {
/** /**
* 监听器类型class, expression, delegateExpression * 监听器类型class, expression, delegateExpression
*/ */
@SchemaProperty(
title = "监听器类型",
description = "监听器的实现类型",
required = true,
enumValues = {"class", "expression", "delegateExpression"},
enumNames = {"Java类", "表达式", "代理表达式"}
)
private String type; private String type;
/** /**
* 监听器实现类名表达式或代理表达式 * 监听器实现类名表达式或代理表达式
*/ */
@SchemaProperty(
title = "监听器实现",
description = "监听器的具体实现(类名、表达式或代理表达式)",
required = true
)
private String implementation; private String implementation;
/** /**
@ -26,5 +39,9 @@ public class TaskListener {
/** /**
* 监听器字段 * 监听器字段
*/ */
@SchemaProperty(
title = "监听器字段",
description = "监听器的配置字段"
)
private Map<String, Object> fields; private Map<String, Object> fields;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import java.util.Map; import java.util.Map;
@ -11,15 +12,31 @@ public class BaseListener {
/** /**
* 监听器类型class, expression, delegateExpression * 监听器类型class, expression, delegateExpression
*/ */
@SchemaProperty(
title = "监听器类型",
description = "监听器的实现类型",
required = true,
enumValues = {"class", "expression", "delegateExpression"},
enumNames = {"Java类", "表达式", "代理表达式"}
)
private String type; private String type;
/** /**
* 监听器实现类名表达式或代理表达式 * 监听器实现类名表达式或代理表达式
*/ */
@SchemaProperty(
title = "监听器实现",
description = "监听器的具体实现(类名、表达式或代理表达式)",
required = true
)
private String implementation; private String implementation;
/** /**
* 监听器字段 * 监听器字段
*/ */
@SchemaProperty(
title = "监听器字段",
description = "监听器的配置字段"
)
private Map<String, Object> fields; private Map<String, Object> fields;
} }

View File

@ -1,28 +1,45 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
/** /**
* 事件监听器配置 * 事件监听器
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class EventListener extends BaseListener { public class EventListener extends TaskListener {
/** /**
* 事件类型 * 事件类型
* start: 事件开始 * start: 事件开始
* end: 事件结束 * end: 事件结束
*/ */
@SchemaProperty(
title = "事件类型",
description = "监听器的触发事件类型",
required = true,
enumValues = {"start", "end"},
enumNames = {"事件开始", "事件结束"}
)
private String event; private String event;
/** /**
* 是否中断事件 * 是否中断事件
*/ */
@SchemaProperty(
title = "是否中断事件",
description = "事件触发后是否中断当前活动",
defaultValue = "false"
)
private Boolean cancelActivity; private Boolean cancelActivity;
/** /**
* 事件抛出的变量 * 事件抛出的变量
*/ */
@SchemaProperty(
title = "事件变量",
description = "事件触发时抛出的变量名"
)
private String eventVariable; private String eventVariable;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -14,15 +15,31 @@ public class EventListenerConfig extends ListenerConfig {
* start: 事件开始 * start: 事件开始
* end: 事件结束 * end: 事件结束
*/ */
@SchemaProperty(
title = "事件类型",
description = "监听器的触发事件类型",
required = true,
enumValues = {"start", "end"},
enumNames = {"事件开始", "事件结束"}
)
private String event; private String event;
/** /**
* 是否中断事件 * 是否中断事件
*/ */
@SchemaProperty(
title = "是否中断事件",
description = "事件触发后是否中断当前活动",
defaultValue = "false"
)
private Boolean cancelActivity; private Boolean cancelActivity;
/** /**
* 事件抛出的变量 * 事件抛出的变量
*/ */
@SchemaProperty(
title = "事件变量",
description = "事件触发时抛出的变量名"
)
private String eventVariable; private String eventVariable;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import java.util.Map; import java.util.Map;
@ -11,15 +12,31 @@ public class ListenerConfig {
/** /**
* 监听器类型class, expression, delegateExpression * 监听器类型class, expression, delegateExpression
*/ */
@SchemaProperty(
title = "监听器类型",
description = "监听器的实现类型",
required = true,
enumValues = {"class", "expression", "delegateExpression"},
enumNames = {"Java类", "表达式", "代理表达式"}
)
private String type; private String type;
/** /**
* 监听器实现类名表达式或代理表达式 * 监听器实现类名表达式或代理表达式
*/ */
@SchemaProperty(
title = "监听器实现",
description = "监听器的具体实现(类名、表达式或代理表达式)",
required = true
)
private String implementation; private String implementation;
/** /**
* 监听器字段 * 监听器字段
*/ */
@SchemaProperty(
title = "监听器字段",
description = "监听器的配置字段"
)
private Map<String, Object> fields; private Map<String, Object> fields;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -18,15 +19,32 @@ public class TaskListener extends BaseListener {
* update: 任务更新 * update: 任务更新
* timeout: 任务超时 * timeout: 任务超时
*/ */
@SchemaProperty(
title = "事件类型",
description = "任务监听器的触发事件类型",
required = true,
enumValues = {"create", "assignment", "complete", "delete", "update", "timeout"},
enumNames = {"任务创建", "任务分配", "任务完成", "任务删除", "任务更新", "任务超时"}
)
private String event; private String event;
/** /**
* 任务相关的用户或组 * 任务相关的用户或组
*/ */
@SchemaProperty(
title = "执行人",
description = "任务相关的用户或组标识"
)
private String assignee; private String assignee;
/** /**
* 任务优先级 * 任务优先级
*/ */
@SchemaProperty(
title = "优先级",
description = "任务的优先级",
minimum = 0,
maximum = 100
)
private Integer priority; private Integer priority;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener; package com.qqchen.deploy.backend.workflow.dto.nodeConfig.listener;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -18,15 +19,32 @@ public class TaskListenerConfig extends ListenerConfig {
* update: 任务更新 * update: 任务更新
* timeout: 任务超时 * timeout: 任务超时
*/ */
@SchemaProperty(
title = "事件类型",
description = "任务监听器的触发事件类型",
required = true,
enumValues = {"create", "assignment", "complete", "delete", "update", "timeout"},
enumNames = {"任务创建", "任务分配", "任务完成", "任务删除", "任务更新", "任务超时"}
)
private String event; private String event;
/** /**
* 任务相关的用户或组 * 任务相关的用户或组
*/ */
@SchemaProperty(
title = "执行人",
description = "任务相关的用户或组标识"
)
private String assignee; private String assignee;
/** /**
* 任务优先级 * 任务优先级
*/ */
@SchemaProperty(
title = "优先级",
description = "任务的优先级",
minimum = 0,
maximum = 100
)
private Integer priority; private Integer priority;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script; package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import java.util.Map; import java.util.Map;
@ -11,25 +12,52 @@ public class ScriptExecutionConfig {
/** /**
* 脚本内容 * 脚本内容
*/ */
@SchemaProperty(
title = "脚本内容",
description = "需要执行的脚本内容",
required = true,
format = "textarea"
)
private String script; private String script;
/** /**
* 工作目录 * 工作目录
*/ */
@SchemaProperty(
title = "工作目录",
description = "脚本执行的工作目录",
defaultValue = "/tmp"
)
private String workingDirectory; private String workingDirectory;
/** /**
* 超时时间 * 超时时间
*/ */
@SchemaProperty(
title = "超时时间",
description = "脚本执行的最大时间(秒)",
minimum = 1,
maximum = 3600,
defaultValue = "300"
)
private Integer timeout; private Integer timeout;
/** /**
* 环境变量 * 环境变量
*/ */
@SchemaProperty(
title = "环境变量",
description = "脚本执行时的环境变量"
)
private Map<String, String> environment; private Map<String, String> environment;
/** /**
* 成功退出码 * 成功退出码
*/ */
@SchemaProperty(
title = "成功退出码",
description = "脚本执行成功时的退出码",
defaultValue = "0"
)
private Integer successExitCode; private Integer successExitCode;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script; package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import java.util.List; 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; private String language;
/** /**
* 解释器路径 * 解释器路径
*/ */
@SchemaProperty(
title = "解释器路径",
description = "脚本解释器的路径,例如:/bin/bash, /usr/bin/python3",
required = true
)
private String interpreter; private String interpreter;
/** /**
* 支持的脚本语言列表 * 支持的脚本语言列表
*/ */
@SchemaProperty(
title = "支持的语言",
description = "系统支持的脚本语言列表",
enumValues = {"shell", "python", "javascript", "groovy"}
)
private List<String> supportedLanguages; private List<String> supportedLanguages;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script; package com.qqchen.deploy.backend.workflow.dto.nodeConfig.script;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
/** /**
@ -10,10 +11,24 @@ public class ScriptRetryConfig {
/** /**
* 重试次数 * 重试次数
*/ */
@SchemaProperty(
title = "重试次数",
description = "脚本执行失败后的重试次数",
minimum = 0,
maximum = 10,
defaultValue = "0"
)
private Integer retryTimes; private Integer retryTimes;
/** /**
* 重试间隔 * 重试间隔
*/ */
@SchemaProperty(
title = "重试间隔",
description = "两次重试之间的等待时间(秒)",
minimum = 1,
maximum = 3600,
defaultValue = "60"
)
private Integer retryInterval; private Integer retryInterval;
} }

View File

@ -9,95 +9,92 @@ import com.qqchen.deploy.backend.workflow.dto.nodeConfig.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Schema生成器工具类 * Schema生成器
*/ */
public class SchemaGenerator { public class SchemaGenerator {
private static final ObjectMapper mapper = new ObjectMapper(); private static final ObjectMapper mapper = new ObjectMapper();
private SchemaGenerator() {
// 私有构造函数防止实例化
}
/** /**
* 生成所有节点类型的配置Schema * 生成所有节点类型的schema
*/ */
public static JsonNode generateAllNodeTypeSchemas() { public static JsonNode generateAllNodeTypeSchemas() {
ObjectNode schemas = mapper.createObjectNode(); ObjectNode schemas = mapper.createObjectNode();
// START节点 // 生成开始节点schema
schemas.set("START", generateNodeTypeSchema("START", "开始", "开始节点", StartEventConfig.class)); ObjectNode startNode = generateNodeSchema("START", "开始", "开始节点", StartEventConfig.class);
schemas.set("START", startNode);
// SCRIPT节点 // 生成脚本节点schema
schemas.set("SCRIPT", generateNodeTypeSchema("SCRIPT", "脚本执行", "执行脚本", ScriptExecutorConfig.class)); ObjectNode scriptNode = generateNodeSchema("SCRIPT", "脚本执行", "执行脚本任务", ScriptExecutorConfig.class);
schemas.set("SCRIPT", scriptNode);
// GIT_CLONE节点 // 生成Git克隆节点schema
schemas.set("GIT_CLONE", generateNodeTypeSchema("GIT_CLONE", "Git克隆", "克隆Git仓库到指定目录", GitCloneConfig.class)); ObjectNode gitNode = generateNodeSchema("GIT_CLONE", "Git克隆", "克隆Git仓库", GitCloneConfig.class);
schemas.set("GIT_CLONE", gitNode);
// END节点 // 生成任务节点schema
schemas.set("END", generateNodeTypeSchema("END", "结束", "结束节点", EndEventConfig.class)); 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; return schemas;
} }
/** /**
* 生成节点类型的配置Schema * 生成节点schema
*/ */
private static JsonNode generateNodeTypeSchema(String code, String name, String description, Class<?> configClass) { private static ObjectNode generateNodeSchema(String code, String name, String description, Class<?> configClass) {
ObjectNode schema = mapper.createObjectNode(); ObjectNode node = mapper.createObjectNode();
schema.put("code", code); node.put("code", code);
schema.put("name", name); node.put("name", name);
schema.put("description", description); node.put("description", description);
schema.set("configSchema", generateNodeConfigSchema(configClass));
return schema; 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(); ObjectNode schema = mapper.createObjectNode();
schema.put("type", "object"); schema.put("type", "object");
ObjectNode properties = schema.putObject("properties");
ObjectNode properties = mapper.createObjectNode();
List<String> required = new ArrayList<>(); List<String> required = new ArrayList<>();
// 只有TaskConfig的子类才添加任务相关字段 // 处理所有字段
if (TaskConfig.class.isAssignableFrom(configClass)) {
processTaskConfigFields(properties, required);
}
// 处理特定配置类的字段
for (Field field : configClass.getDeclaredFields()) { for (Field field : configClass.getDeclaredFields()) {
SchemaProperty annotation = field.getAnnotation(SchemaProperty.class); SchemaProperty annotation = field.getAnnotation(SchemaProperty.class);
if (annotation != null) { if (annotation != null) {
ObjectNode property = properties.putObject(field.getName()); processField(properties, required, field, annotation);
generateFieldSchema(property, field, annotation);
if (annotation.required()) {
required.add(field.getName());
}
} }
} }
// 处理父类字段除了TaskConfig // 处理父类字段
Class<?> superClass = configClass.getSuperclass(); 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()) { for (Field field : superClass.getDeclaredFields()) {
SchemaProperty annotation = field.getAnnotation(SchemaProperty.class); SchemaProperty annotation = field.getAnnotation(SchemaProperty.class);
if (annotation != null) { if (annotation != null) {
ObjectNode property = properties.putObject(field.getName()); processField(properties, required, field, annotation);
generateFieldSchema(property, field, annotation);
if (annotation.required()) {
required.add(field.getName());
} }
} }
} }
superClass = superClass.getSuperclass();
}
schema.set("properties", properties);
if (!required.isEmpty()) { if (!required.isEmpty()) {
ArrayNode requiredArray = schema.putArray("required"); ArrayNode requiredArray = schema.putArray("required");
required.forEach(requiredArray::add); required.forEach(requiredArray::add);
@ -107,103 +104,40 @@ public class SchemaGenerator {
} }
/** /**
* 处理TaskConfig中的基础字段 * 处理字段
*/ */
private static void processTaskConfigFields(ObjectNode properties, List<String> required) { private static void processField(ObjectNode properties, List<String> required, Field field, SchemaProperty annotation) {
// 添加name字段 ObjectNode property = properties.putObject(field.getName());
ObjectNode nameProperty = properties.putObject("name");
nameProperty.put("type", "string");
nameProperty.put("title", "节点名称");
required.add("name");
// 添加description字段 // 设置类型
ObjectNode descProperty = properties.putObject("description"); String type = getJsonSchemaType(field.getType());
descProperty.put("type", "string"); property.put("type", type);
descProperty.put("title", "节点描述");
// 添加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()) { if (!annotation.title().isEmpty()) {
property.put("title", annotation.title()); property.put("title", annotation.title());
} }
if (!annotation.description().isEmpty()) { if (!annotation.description().isEmpty()) {
property.put("description", annotation.description()); property.put("description", annotation.description());
} }
// 设置必填
if (annotation.required()) {
required.add(field.getName());
}
// 设置格式
if (!annotation.format().isEmpty()) { if (!annotation.format().isEmpty()) {
property.put("format", annotation.format()); property.put("format", annotation.format());
} }
// 设置默认值
if (!annotation.defaultValue().isEmpty()) { if (!annotation.defaultValue().isEmpty()) {
setDefaultValue(property, field, annotation.defaultValue()); setDefaultValue(property, type, annotation.defaultValue());
} }
// 设置字段类型
setFieldType(property, field);
// 设置数值范围 // 设置数值范围
if (isNumericType(field.getType()) && if ("integer".equals(type) || "number".equals(type)) {
(annotation.minimum() != Integer.MIN_VALUE || annotation.maximum() != Integer.MAX_VALUE)) {
if (annotation.minimum() != Integer.MIN_VALUE) { if (annotation.minimum() != Integer.MIN_VALUE) {
property.put("minimum", annotation.minimum()); property.put("minimum", annotation.minimum());
} }
@ -214,79 +148,64 @@ public class SchemaGenerator {
// 设置枚举值 // 设置枚举值
if (annotation.enumValues().length > 0) { if (annotation.enumValues().length > 0) {
ArrayNode enumNode = property.putArray("enum"); ArrayNode enumArray = property.putArray("enum");
Arrays.stream(annotation.enumValues()).forEach(enumNode::add); for (String value : annotation.enumValues()) {
enumArray.add(value);
}
if (annotation.enumNames().length > 0) { if (annotation.enumNames().length > 0) {
ArrayNode enumNamesNode = property.putArray("enumNames"); ArrayNode enumNamesArray = property.putArray("enumNames");
Arrays.stream(annotation.enumNames()).forEach(enumNamesNode::add); for (String name : annotation.enumNames()) {
enumNamesArray.add(name);
// 生成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);
}
} }
} }
} }
} }
/** /**
* 设置字段类型 * 获取JSON Schema类型
*/ */
private static void setFieldType(ObjectNode property, Field field) { private static String getJsonSchemaType(Class<?> type) {
Class<?> type = field.getType(); if (type == String.class) {
if (String.class.isAssignableFrom(type)) { return "string";
property.put("type", "string"); } else if (type == Integer.class || type == int.class) {
} else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) { return "integer";
property.put("type", "integer"); } else if (type == Double.class || type == double.class || type == Float.class || type == float.class) {
} else if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) { return "number";
property.put("type", "boolean"); } else if (type == Boolean.class || type == boolean.class) {
} else if (List.class.isAssignableFrom(type)) { return "boolean";
property.put("type", "array"); } else if (type.isArray() || List.class.isAssignableFrom(type)) {
// 如果需要可以在这里添加items的schema return "array";
} else if (Map.class.isAssignableFrom(type)) { } else {
property.put("type", "object"); return "object";
ObjectNode additionalProperties = property.putObject("additionalProperties");
additionalProperties.put("type", "string");
} }
} }
/** /**
* 设置默认值 * 设置默认值
*/ */
private static void setDefaultValue(ObjectNode property, Field field, String defaultValue) { private static void setDefaultValue(ObjectNode property, String type, String defaultValue) {
Class<?> type = field.getType(); switch (type) {
if (String.class.isAssignableFrom(type)) { case "string":
property.put("default", defaultValue); property.put("default", defaultValue);
} else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) { break;
case "integer":
try { try {
property.put("default", Integer.parseInt(defaultValue)); property.put("default", Integer.parseInt(defaultValue));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
// 忽略无效的默认值 // 忽略无效的默认值
} }
} else if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) { break;
case "number":
try {
property.put("default", Double.parseDouble(defaultValue));
} catch (NumberFormatException e) {
// 忽略无效的默认值
}
break;
case "boolean":
property.put("default", Boolean.parseBoolean(defaultValue)); 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);
}
} }

View File

@ -6,47 +6,138 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class SchemaGeneratorTest { class SchemaGeneratorTest {
private final SchemaGenerator generator = new SchemaGenerator(); private final ObjectMapper mapper = new ObjectMapper();
private final ObjectMapper objectMapper = new ObjectMapper();
@Test @Test
void testGenerateNodeTypeSchema() throws Exception { void testGenerateAllNodeTypeSchemas() throws Exception {
JsonNode schema = generator.generateNodeTypeSchema(); JsonNode schemas = SchemaGenerator.generateAllNodeTypeSchemas();
// 将生成的schema打印出来以便查看 // 将生成的schema打印出来以便查看
String prettySchema = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema); System.out.println("Generated Schemas:");
System.out.println(prettySchema); System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schemas));
// 基本验证 // 验证基本结构
assertTrue(schema.isArray()); assertTrue(schemas.isObject());
assertEquals(1, schema.size()); 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); // 验证START节点
assertEquals("SCRIPT", firstNode.get("code").asText()); JsonNode startNode = schemas.get("START");
assertEquals("脚本执行器", firstNode.get("name").asText()); assertEquals("START", startNode.get("code").asText());
assertEquals("开始", startNode.get("name").asText());
assertEquals("开始节点", startNode.get("description").asText());
JsonNode configSchema = firstNode.get("configSchema"); JsonNode startSchema = startNode.get("configSchema");
assertTrue(configSchema.has("properties")); assertTrue(startSchema.has("properties"));
assertTrue(configSchema.has("required")); 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"));
// 验证必填字段 // 验证SCRIPT节点
JsonNode required = configSchema.get("required"); JsonNode scriptNode = schemas.get("SCRIPT");
assertTrue(required.isArray()); assertEquals("SCRIPT", scriptNode.get("code").asText());
assertTrue(required.toString().contains("script")); assertEquals("脚本执行", scriptNode.get("name").asText());
assertTrue(required.toString().contains("language")); assertEquals("执行脚本任务", scriptNode.get("description").asText());
assertTrue(required.toString().contains("interpreter"));
// 验证属性 JsonNode scriptSchema = scriptNode.get("configSchema");
JsonNode properties = configSchema.get("properties"); JsonNode scriptProps = scriptSchema.get("properties");
assertTrue(properties.has("script")); // 验证基本属性
assertTrue(properties.has("language")); assertTrue(scriptProps.has("taskType"));
assertTrue(properties.has("interpreter")); assertTrue(scriptProps.has("taskName"));
assertTrue(properties.has("workingDirectory")); assertTrue(scriptProps.has("description"));
assertTrue(properties.has("timeout")); // 验证任务相关属性
assertTrue(properties.has("retryTimes")); assertTrue(scriptProps.has("timeoutDuration"));
assertTrue(properties.has("retryInterval")); assertTrue(scriptProps.has("timeoutStrategy"));
assertTrue(properties.has("environment")); assertTrue(scriptProps.has("retryTimes"));
assertTrue(properties.has("successExitCode")); assertTrue(scriptProps.has("retryInterval"));
assertTrue(properties.has("supportedLanguages")); 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"));
} }
} }