diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/variables/build/JenkinsBaseBuildVariables.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/variables/build/JenkinsBaseBuildVariables.java index 78b7237b..2470c839 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/variables/build/JenkinsBaseBuildVariables.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/variables/build/JenkinsBaseBuildVariables.java @@ -1,141 +1,114 @@ package com.qqchen.deploy.backend.deploy.dto.variables.build; -import com.qqchen.deploy.backend.workflow.annotation.CodeEditorConfig; -import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; -import com.qqchen.deploy.backend.workflow.annotation.SchemaPropertyDataSource; -import com.qqchen.deploy.backend.workflow.annotation.SchemaPropertyDataSourceParam; +import com.qqchen.deploy.backend.framework.annotation.formily.*; import lombok.Data; /** * Jenkins构建变量 */ @Data +@FormilyForm(name = "Jenkins构建配置") public class JenkinsBaseBuildVariables { - @SchemaProperty( + @FormilyField( title = "绑定三方Jenkins系统", - description = "请选择三方Jenkins系统", - required = true, - dataSource = @SchemaPropertyDataSource( - type = "api", - url = "/api/v1/external-system/list?type=JENKINS", + type = "string", + component = "Select", + props = @FormilyComponentProps( + api = "/api/v1/external-system/list?type=JENKINS", + labelField = "name", valueField = "id", - labelField = "name" + showSearch = true, + allowClear = true, + placeholder = "请选择Jenkins系统" ), - order = 2 + validators = { + @FormilyValidator( + required = true, + message = "请选择Jenkins系统" + ) + } ) private String externalSystemId; - - @SchemaProperty( + @FormilyField( title = "绑定Jenkins视图", - description = "Jenkins视图", - required = true, - dataSource = @SchemaPropertyDataSource( - type = "api", - url = "/api/v1/jenkins-view/list", - valueField = "id", + type = "string", + component = "Select", + props = @FormilyComponentProps( labelField = "viewName", - dependsOn = {"externalSystemId"}, - params = { - @SchemaPropertyDataSourceParam(name = "externalSystemId", value = "${externalSystemId}") - } + valueField = "id", + showSearch = true, + allowClear = true, + placeholder = "请选择Jenkins视图" ), - order = 3 + validators = { + @FormilyValidator( + required = true, + message = "请选择Jenkins视图" + ) + }, + reactions = { + @FormilyReaction( + dependencies = {"externalSystemId"}, + state = "dataSource", + value = "{{$fetch('/api/v1/jenkins-view/list?externalSystemId=' + $deps.externalSystemId).then(data => data.data)}}" + ) + } ) private String viewId; - - @SchemaProperty( + @FormilyField( title = "绑定Jenkins任务", - description = "Jenkins任务", - required = true, - dataSource = @SchemaPropertyDataSource( - type = "api", - url = "/api/v1/jenkins-job/list", - valueField = "id", + type = "string", + component = "Select", + props = @FormilyComponentProps( labelField = "jobName", - dependsOn = {"externalSystemId", "viewId"}, - params = { - @SchemaPropertyDataSourceParam(name = "externalSystemId", value = "${externalSystemId}"), - @SchemaPropertyDataSourceParam(name = "viewId", value = "${viewId}") - } + valueField = "id", + showSearch = true, + allowClear = true, + placeholder = "请选择Jenkins任务" ), - order = 4 + validators = { + @FormilyValidator( + required = true, + message = "请选择Jenkins任务" + ) + }, + reactions = { + @FormilyReaction( + dependencies = {"externalSystemId", "viewId"}, + state = "dataSource", + value = "{{$fetch('/api/v1/jenkins-job/list?externalSystemId=' + $deps.externalSystemId + '&viewId=' + $deps.viewId).then(data => data.data)}}" + ) + } ) private String jobId; - - @SchemaProperty( + @FormilyField( title = "Pipeline script", - description = "流水线脚本", - required = true, - format = "monaco-editor", // 使用 Monaco Editor - defaultValue = "#!/bin/bash\n\necho \"Hello World\"", - codeEditor = @CodeEditorConfig( - language = "groovy", - theme = "vs-dark", - minimap = false, - lineNumbers = true, - wordWrap = true, - fontSize = 14, - tabSize = 2, - autoComplete = true, - folding = true + type = "string", + component = "MonacoEditor", + props = @FormilyComponentProps( + editor = @FormilyEditorProps( + language = "groovy", + theme = "vs-dark", + minimap = false, + lineNumbers = true, + wordWrap = true, + fontSize = 14, + tabSize = 4, + automaticLayout = true, + folding = true, + placeholder = "请输入Pipeline脚本" + ) ), - order = 5 + validators = { + @FormilyValidator( + required = true, + message = "请输入Pipeline脚本" + ) + } ) private String script; -// -// @SchemaProperty( -// title = "绑定三方Git系统", -// description = "请选择三方Git系统", -// required = true, -// dataSource = @SchemaPropertyDataSource( -// type = "api", -// url = "/api/v1/external-system/list?type=GIT", -// valueField = "id", -// labelField = "name" -// ), -// order = 6 -// ) -// private String gitExternalSystemId; -// -// @SchemaProperty( -// title = "绑定Git项目", -// description = "绑定Git项目", -// required = true, -// dataSource = @SchemaPropertyDataSource( -// type = "api", -// url = "/api/v1/repository-project/list", -// valueField = "repoProjectId", -// labelField = "name", -// dependsOn = {"gitExternalSystemId"}, -// params = { -// @SchemaPropertyDataSourceParam(name = "externalSystemId", value = "${gitExternalSystemId}") -// } -// ), -// order = 7 -// ) -// private String repoProjectId; -// -// @SchemaProperty( -// title = "绑定代码分支", -// description = "请选择代码分支", -// required = true, -// dataSource = @SchemaPropertyDataSource( -// type = "api", -// url = "/api/v1/repository-branch/list", -// valueField = "name", -// labelField = "name", -// dependsOn = {"gitExternalSystemId", "repoProjectId"}, -// params = { -// @SchemaPropertyDataSourceParam(name = "externalSystemId", value = "${gitExternalSystemId}"), -// @SchemaPropertyDataSourceParam(name = "repoProjectId", value = "${repoProjectId}") -// } -// ), -// order = 8 -// ) -// private String branch; - } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/variables/build/JenkinsJavaBuildVariables.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/variables/build/JenkinsJavaBuildVariables.java index 64e48934..16a21ed5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/variables/build/JenkinsJavaBuildVariables.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/variables/build/JenkinsJavaBuildVariables.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.deploy.dto.variables.build; +import com.qqchen.deploy.backend.framework.annotation.formily.FormilyForm; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployAppConfigServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployAppConfigServiceImpl.java index ffd77b3e..7b4d4241 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployAppConfigServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/DeployAppConfigServiceImpl.java @@ -1,6 +1,7 @@ package com.qqchen.deploy.backend.deploy.service.impl; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.qqchen.deploy.backend.deploy.converter.ApplicationConverter; import com.qqchen.deploy.backend.deploy.converter.DeployLogConverter; @@ -20,6 +21,7 @@ import com.qqchen.deploy.backend.deploy.repository.IDeployLogRepository; import com.qqchen.deploy.backend.deploy.repository.IEnvironmentRepository; import com.qqchen.deploy.backend.deploy.service.IDeployAppConfigService; import com.qqchen.deploy.backend.deploy.service.IDeployLogService; +import com.qqchen.deploy.backend.framework.annotation.formily.FormilySchemaFactory; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; import com.qqchen.deploy.backend.workflow.converter.WorkflowDefinitionConverter; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; @@ -40,7 +42,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import static com.qqchen.deploy.backend.workflow.util.GenerateSchemaUtils.generateSchema; import static java.util.stream.Collectors.toList; /** @@ -129,7 +130,9 @@ public class DeployAppConfigServiceImpl extends BaseServiceImpl clazz) { - FormilyForm formDef = clazz.getAnnotation(FormilyForm.class); - if (formDef == null) { - throw new IllegalArgumentException("Class must be annotated with @FormilyForm"); + if (clazz == null) { + throw new IllegalArgumentException("Class cannot be null"); } + // 获取所有字段,包括父类的字段 + List allFields = getAllFields(clazz); + if (allFields.isEmpty()) { + throw new IllegalArgumentException("No fields found in class: " + clazz.getName()); + } + + // 获取最近的带有FormilyForm注解的类(可能是父类) + Class formClass = findNearestFormClass(clazz); + if (formClass == null) { + throw new IllegalArgumentException("No @FormilyForm annotation found in class hierarchy: " + clazz.getName()); + } + + FormilyForm formDef = formClass.getAnnotation(FormilyForm.class); ObjectNode schema = objectMapper.createObjectNode(); schema.put("type", "object"); schema.put("name", formDef.name()); @@ -30,17 +44,58 @@ public class FormilySchemaFactory { } ObjectNode properties = schema.putObject("properties"); + boolean hasFields = false; - for (Field field : clazz.getDeclaredFields()) { + for (Field field : allFields) { FormilyField fieldDef = field.getAnnotation(FormilyField.class); if (fieldDef != null) { properties.set(field.getName(), generateFieldSchema(fieldDef)); + hasFields = true; } } + if (!hasFields) { + throw new IllegalArgumentException("No @FormilyField annotations found in class hierarchy: " + clazz.getName()); + } + return schema; } + /** + * 获取类的所有字段,包括父类的字段 + * @param clazz 要获取字段的类 + * @return 所有字段的列表 + */ + private static List getAllFields(Class clazz) { + List fields = new ArrayList<>(); + Class currentClass = clazz; + + while (currentClass != null && currentClass != Object.class) { + fields.addAll(Arrays.asList(currentClass.getDeclaredFields())); + currentClass = currentClass.getSuperclass(); + } + + return fields; + } + + /** + * 查找最近的带有FormilyForm注解的类 + * @param clazz 要查找的类 + * @return 带有FormilyForm注解的最近的类,如果没有找到则返回null + */ + private static Class findNearestFormClass(Class clazz) { + Class currentClass = clazz; + + while (currentClass != null && currentClass != Object.class) { + if (currentClass.isAnnotationPresent(FormilyForm.class)) { + return currentClass; + } + currentClass = currentClass.getSuperclass(); + } + + return null; + } + private static JsonNode generateFieldSchema(FormilyField field) { ObjectNode fieldSchema = objectMapper.createObjectNode(); @@ -56,7 +111,7 @@ public class FormilySchemaFactory { fieldSchema.put("x-component", field.component()); // 组件属性配置 - ObjectNode componentProps = generateComponentProps(field.props()); + ObjectNode componentProps = generateComponentProps(field.props(), field.component()); if (componentProps.size() > 0) { fieldSchema.set("x-component-props", componentProps); } @@ -91,37 +146,64 @@ public class FormilySchemaFactory { return fieldSchema; } - private static ObjectNode generateComponentProps(FormilyComponentProps props) { + private static ObjectNode generateComponentProps(FormilyComponentProps props, String componentType) { ObjectNode node = objectMapper.createObjectNode(); - if (!props.api().isEmpty()) { - node.put("api", props.api()); + // Select组件属性 + if ("Select".equals(componentType)) { + if (!props.api().isEmpty()) { + node.put("api", props.api()); + } + if (!props.labelField().equals("label")) { + node.put("labelField", props.labelField()); + } + if (!props.valueField().equals("value")) { + node.put("valueField", props.valueField()); + } + if (props.allowClear()) { + node.put("allowClear", true); + } + if (props.showSearch()) { + node.put("showSearch", true); + } + if (!props.placeholder().isEmpty()) { + node.put("placeholder", props.placeholder()); + } + if (!props.mode().isEmpty()) { + node.put("mode", props.mode()); + } + if (props.enum_().length > 0) { + ArrayNode enumValues = node.putArray("enum"); + Arrays.stream(props.enum_()).forEach(enumValues::add); + } + if (props.enumNames().length > 0) { + ArrayNode enumNames = node.putArray("enumNames"); + Arrays.stream(props.enumNames()).forEach(enumNames::add); + } } - if (!props.labelField().equals("label")) { - node.put("labelField", props.labelField()); - } - if (!props.valueField().equals("value")) { - node.put("valueField", props.valueField()); - } - if (props.allowClear()) { - node.put("allowClear", true); - } - if (props.showSearch()) { - node.put("showSearch", true); - } - if (!props.placeholder().isEmpty()) { - node.put("placeholder", props.placeholder()); - } - if (!props.mode().isEmpty()) { - node.put("mode", props.mode()); - } - if (props.enum_().length > 0) { - ArrayNode enumValues = node.putArray("enum"); - Arrays.stream(props.enum_()).forEach(enumValues::add); - } - if (props.enumNames().length > 0) { - ArrayNode enumNames = node.putArray("enumNames"); - Arrays.stream(props.enumNames()).forEach(enumNames::add); + + // MonacoEditor组件属性 + if ("MonacoEditor".equals(componentType)) { + FormilyEditorProps editorProps = props.editor(); + if (editorProps != null) { + if (!editorProps.placeholder().isEmpty()) { + node.put("placeholder", editorProps.placeholder()); + } + + ObjectNode options = node.putObject("options"); + options.put("language", editorProps.language()); + options.put("theme", editorProps.theme()); + + ObjectNode minimap = options.putObject("minimap"); + minimap.put("enabled", editorProps.minimap()); + + options.put("lineNumbers", editorProps.lineNumbers() ? "on" : "off"); + options.put("wordWrap", editorProps.wordWrap() ? "on" : "off"); + options.put("fontSize", editorProps.fontSize()); + options.put("tabSize", editorProps.tabSize()); + options.put("automaticLayout", editorProps.automaticLayout()); + options.put("folding", editorProps.folding()); + } } return node; diff --git a/backend/src/test/java/com/qqchen/deploy/backend/framework/annotation/formily/FormilySchemaFactoryTest.java b/backend/src/test/java/com/qqchen/deploy/backend/framework/annotation/formily/FormilySchemaFactoryTest.java index c99a5e13..e174cdb7 100644 --- a/backend/src/test/java/com/qqchen/deploy/backend/framework/annotation/formily/FormilySchemaFactoryTest.java +++ b/backend/src/test/java/com/qqchen/deploy/backend/framework/annotation/formily/FormilySchemaFactoryTest.java @@ -42,7 +42,7 @@ public class FormilySchemaFactoryTest { JsonNode fulfillB = reactionB.get("fulfill"); JsonNode stateB = fulfillB.get("state"); - assertEquals("{{$fetch('/api/options/B?a=' + $deps.a).then(data => data.data)}}", + assertEquals("{{$fetch('/api/v1/jenkins-view/list?externalSystemId=' + $deps.a).then(data => data.data)}}", stateB.get("dataSource").asText()); // 验证字段C的联动 @@ -57,8 +57,28 @@ public class FormilySchemaFactoryTest { JsonNode fulfillC = reactionC.get("fulfill"); JsonNode stateC = fulfillC.get("state"); - assertEquals("{{$fetch('/api/options/C?a=' + $deps.a + '&b=' + $deps.b).then(data => data.data)}}", + assertEquals("{{$fetch('/api/v1/jenkins-job/list?externalSystemId=' + $deps.a + '&viewId=' + $deps.b).then(data => data.data)}}", stateC.get("dataSource").asText()); + + // 验证编辑器字段 + JsonNode fieldScript = schema.get("properties").get("script"); + assertEquals("string", fieldScript.get("type").asText()); + assertEquals("Pipeline script", fieldScript.get("title").asText()); + assertEquals("MonacoEditor", fieldScript.get("x-component").asText()); + + // 验证编辑器属性 + JsonNode scriptProps = fieldScript.get("x-component-props"); + JsonNode options = scriptProps.get("options"); + assertEquals("groovy", options.get("language").asText()); + assertEquals("vs-dark", options.get("theme").asText()); + assertFalse(options.get("minimap").get("enabled").asBoolean()); + assertEquals("on", options.get("lineNumbers").asText()); + assertEquals("on", options.get("wordWrap").asText()); + assertEquals(14, options.get("fontSize").asInt()); + assertEquals(4, options.get("tabSize").asInt()); + assertTrue(options.get("automaticLayout").asBoolean()); + assertTrue(options.get("folding").asBoolean()); + assertEquals("请输入Pipeline脚本", scriptProps.get("placeholder").asText()); // 打印生成的Schema以便查看 System.out.println(schema.toPrettyString()); @@ -89,7 +109,7 @@ class TestForm { title = "选择B", component = "Select", props = @FormilyComponentProps( - labelField = "name", + labelField = "viewName", valueField = "id" ), validators = { @@ -102,7 +122,7 @@ class TestForm { @FormilyReaction( dependencies = {"a"}, state = "dataSource", - value = "{{$fetch('/api/options/B?a=' + $deps.a).then(data => data.data)}}" + value = "{{$fetch('/api/v1/jenkins-view/list?externalSystemId=' + $deps.a).then(data => data.data)}}" ) } ) @@ -112,7 +132,7 @@ class TestForm { title = "选择C", component = "Select", props = @FormilyComponentProps( - labelField = "name", + labelField = "jobName", valueField = "id" ), validators = { @@ -125,9 +145,36 @@ class TestForm { @FormilyReaction( dependencies = {"a", "b"}, state = "dataSource", - value = "{{$fetch('/api/options/C?a=' + $deps.a + '&b=' + $deps.b).then(data => data.data)}}" + value = "{{$fetch('/api/v1/jenkins-job/list?externalSystemId=' + $deps.a + '&viewId=' + $deps.b).then(data => data.data)}}" ) } ) private String c; + + @FormilyField( + title = "Pipeline script", + type = "string", + component = "MonacoEditor", + props = @FormilyComponentProps( + editor = @FormilyEditorProps( + language = "groovy", + theme = "vs-dark", + minimap = false, + lineNumbers = true, + wordWrap = true, + fontSize = 14, + tabSize = 4, + automaticLayout = true, + folding = true, + placeholder = "请输入Pipeline脚本" + ) + ), + validators = { + @FormilyValidator( + required = true, + message = "请输入Pipeline脚本" + ) + } + ) + private String script; } \ No newline at end of file