通用解析

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;
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<ListenerConfig> listeners;
/**
* 是否异步
*/
@SchemaProperty(
title = "是否异步",
description = "节点是否以异步方式执行",
defaultValue = "false"
)
private Boolean async;
/**
* 是否独占
*/
@SchemaProperty(
title = "是否独占",
description = "节点是否以独占方式执行",
defaultValue = "true"
)
private Boolean exclusive;
/**
* 自定义属性
*/
@SchemaProperty(
title = "自定义属性",
description = "节点的自定义属性配置"
)
private Map<String, Object> customProperties;
/**
* 文档说明
*/
@SchemaProperty(
title = "文档说明",
description = "节点的详细文档说明"
)
private String documentation;
}

View File

@ -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<String, Object> outputVariables;
/**
* 是否终止所有活动分支
*/
@SchemaProperty(
title = "终止所有分支",
description = "是否终止所有正在运行的并行分支",
defaultValue = "false",
required = false
)
private Boolean terminateAll;
}

View File

@ -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;
/**
* 事件节点如STARTEND的基础配置
*/
@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;
}

View File

@ -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;
}

View File

@ -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<String, Object> properties;
/**
* 是否必填
*/
@SchemaProperty(
title = "是否必填",
description = "表单是否必须填写",
defaultValue = "true"
)
private Boolean required;
/**
* 表单验证规则
*/
@SchemaProperty(
title = "验证规则",
description = "表单字段的验证规则"
)
private Map<String, Object> validations;
}

View File

@ -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<String, String> gitConfig;
public GitCloneConfig() {
setTaskType("gitCloneTask");
}
}

View File

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

View File

@ -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;
}

View File

@ -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<String, Object> fields;
}

View File

@ -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<String, Object> fields;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<String, Object> fields;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<String, String> environment;
/**
* 成功退出码
*/
@SchemaProperty(
title = "成功退出码",
description = "脚本执行成功时的退出码",
defaultValue = "0"
)
private Integer successExitCode;
}

View File

@ -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<String> supportedLanguages;
}

View File

@ -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;
}

View File

@ -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));
// 生成开始节点schema
ObjectNode startNode = generateNodeSchema("START", "开始", "开始节点", StartEventConfig.class);
schemas.set("START", startNode);
// SCRIPT节点
schemas.set("SCRIPT", generateNodeTypeSchema("SCRIPT", "脚本执行", "执行脚本", ScriptExecutorConfig.class));
// 生成脚本节点schema
ObjectNode scriptNode = generateNodeSchema("SCRIPT", "脚本执行", "执行脚本任务", ScriptExecutorConfig.class);
schemas.set("SCRIPT", scriptNode);
// GIT_CLONE节点
schemas.set("GIT_CLONE", generateNodeTypeSchema("GIT_CLONE", "Git克隆", "克隆Git仓库到指定目录", GitCloneConfig.class));
// 生成Git克隆节点schema
ObjectNode gitNode = generateNodeSchema("GIT_CLONE", "Git克隆", "克隆Git仓库", GitCloneConfig.class);
schemas.set("GIT_CLONE", gitNode);
// END节点
schemas.set("END", generateNodeTypeSchema("END", "结束", "结束节点", EndEventConfig.class));
// 生成任务节点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<String> 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<String> required) {
// 添加name字段
ObjectNode nameProperty = properties.putObject("name");
nameProperty.put("type", "string");
nameProperty.put("title", "节点名称");
required.add("name");
private static void processField(ObjectNode properties, List<String> 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)) {
private static void setDefaultValue(ObjectNode property, String type, String defaultValue) {
switch (type) {
case "string":
property.put("default", defaultValue);
} else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) {
break;
case "integer":
try {
property.put("default", Integer.parseInt(defaultValue));
} 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));
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.*;
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"));
}
}