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 8776a4e0..78b7237b 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 @@ -66,7 +66,6 @@ public class JenkinsBaseBuildVariables { private String jobId; - @SchemaProperty( title = "Pipeline script", description = "流水线脚本", @@ -84,8 +83,59 @@ public class JenkinsBaseBuildVariables { autoComplete = true, folding = true ), - order = 6 + order = 5 ) 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/entity/DeployAppConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/DeployAppConfig.java index 2ad4989a..e54a0479 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/DeployAppConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/DeployAppConfig.java @@ -21,8 +21,8 @@ import org.hibernate.annotations.Type; @Table(name = "deploy_app_config") public class DeployAppConfig extends Entity { - @Column(name = "branch", nullable = false) - private String branch; +// @Column(name = "branch", nullable = false) +// private String branch; @Column(name = "build_type", nullable = false) @Enumerated(EnumType.STRING) diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java index b8a6120b..f86f6df4 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java @@ -28,6 +28,19 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import org.apache.commons.lang3.StringUtils; +import java.io.StringReader; +import java.io.StringWriter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.xml.sax.InputSource; import java.util.Base64; import java.util.Collections; @@ -103,10 +116,6 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration return new HttpEntity<>(headers); } - @Override - public ExternalSystemTypeEnum getSystemType() { - return ExternalSystemTypeEnum.JENKINS; - } @Override public JenkinsCrumbIssuerResponse getJenkinsCrumbIssue() { @@ -120,6 +129,11 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration @Override public String buildWithParameters(ExternalSystem externalSystem, String jobName, Map parameters) { try { + // 如果参数中包含PIPELINE_SCRIPT,确保Job配置中有该参数 + if (parameters.containsKey("PIPELINE_SCRIPT")) { + ensurePipelineScriptParameter(externalSystem, jobName); + } + // 1. 获取Crumb JenkinsCrumbIssuerResponse jenkinsCrumbIssue = getJenkinsCrumbIssue(); @@ -162,6 +176,177 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration } } + /** + * 确保Jenkins Job配置中包含PIPELINE_SCRIPT参数 + */ + private void ensurePipelineScriptParameter(ExternalSystem externalSystem, String jobName) { + try { + // 获取 Crumb + JenkinsCrumbIssuerResponse jenkinsCrumbIssue = getJenkinsCrumbIssue(); + + // 获取Job配置 + String configUrl = String.format("%s/job/%s/config.xml", externalSystem.getUrl(), jobName); + HttpHeaders headers = createHeaders(externalSystem); + headers.set(HttpHeaders.ACCEPT_CHARSET, "UTF-8"); + headers.set("Jenkins-Crumb", jenkinsCrumbIssue.getCrumb()); + headers.set("Cookie", jenkinsCrumbIssue.getCookie()); + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange( + configUrl, + HttpMethod.GET, + entity, + String.class + ); + + if (response.getBody() == null) { + throw new RuntimeException("无法获取Jenkins Job配置"); + } + + // 解析XML + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + DocumentBuilder builder = factory.newDocumentBuilder(); + + // 使用UTF-8编码解析XML + InputSource is = new InputSource(new StringReader(response.getBody())); + is.setEncoding("UTF-8"); + Document doc = builder.parse(is); + + // 检查是否已存在PIPELINE_SCRIPT参数 + NodeList parameterDefinitions = doc.getElementsByTagName("parameterDefinitions"); + if (parameterDefinitions.getLength() > 0) { + NodeList parameters = parameterDefinitions.item(0).getChildNodes(); + for (int i = 0; i < parameters.getLength(); i++) { + Node parameter = parameters.item(i); + if (parameter.getNodeType() == Node.ELEMENT_NODE) { + NodeList parameterChildren = parameter.getChildNodes(); + for (int j = 0; j < parameterChildren.getLength(); j++) { + Node child = parameterChildren.item(j); + if ("name".equals(child.getNodeName()) && "PIPELINE_SCRIPT".equals(child.getTextContent())) { + // 参数已存在,无需添加 + return; + } + } + } + } + } + + // 需要添加PIPELINE_SCRIPT参数 + Element root = doc.getDocumentElement(); + + // 获取或创建properties元素 + NodeList propertiesList = doc.getElementsByTagName("properties"); + Element properties; + if (propertiesList.getLength() == 0) { + properties = doc.createElement("properties"); + root.appendChild(properties); + } else { + properties = (Element) propertiesList.item(0); + } + + // 获取或创建ParametersDefinitionProperty元素 + NodeList paramDefProps = properties.getElementsByTagName("hudson.model.ParametersDefinitionProperty"); + Element paramDefProp; + if (paramDefProps.getLength() == 0) { + paramDefProp = doc.createElement("hudson.model.ParametersDefinitionProperty"); + properties.appendChild(paramDefProp); + } else { + paramDefProp = (Element) paramDefProps.item(0); + } + + // 获取或创建parameterDefinitions元素 + NodeList paramDefs = paramDefProp.getElementsByTagName("parameterDefinitions"); + Element parameterDefs; + if (paramDefs.getLength() == 0) { + parameterDefs = doc.createElement("parameterDefinitions"); + paramDefProp.appendChild(parameterDefs); + } else { + parameterDefs = (Element) paramDefs.item(0); + } + + // 创建PIPELINE_SCRIPT参数定义 + Element stringParam = doc.createElement("hudson.model.StringParameterDefinition"); + + Element name = doc.createElement("name"); + name.setTextContent("PIPELINE_SCRIPT"); + stringParam.appendChild(name); + + Element description = doc.createElement("description"); + description.setTextContent("Pipeline脚本内容"); + stringParam.appendChild(description); + + Element defaultValue = doc.createElement("defaultValue"); + defaultValue.setTextContent(""); + stringParam.appendChild(defaultValue); + + Element trim = doc.createElement("trim"); + trim.setTextContent("false"); + stringParam.appendChild(trim); + + parameterDefs.appendChild(stringParam); + + // 更新Job配置时设置正确的编码 + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty(javax.xml.transform.OutputKeys.VERSION, "1.1"); + transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(javax.xml.transform.OutputKeys.STANDALONE, "no"); + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(doc), new StreamResult(writer)); + String updatedConfig = writer.toString(); + + // 更新Job配置 + HttpHeaders updateHeaders = createHeaders(externalSystem); + updateHeaders.setContentType(MediaType.APPLICATION_XML); + updateHeaders.set(HttpHeaders.ACCEPT_CHARSET, "UTF-8"); + updateHeaders.set("Content-Type", "application/xml; charset=utf-8"); + updateHeaders.set("Jenkins-Crumb", jenkinsCrumbIssue.getCrumb()); + updateHeaders.set("Cookie", jenkinsCrumbIssue.getCookie()); + HttpEntity updateEntity = new HttpEntity<>(updatedConfig, updateHeaders); + restTemplate.exchange( + configUrl, + HttpMethod.POST, + updateEntity, + String.class + ); + + log.info("Successfully added PIPELINE_SCRIPT parameter to Jenkins job: {}", jobName); + } catch (Exception e) { + log.error("Failed to ensure PIPELINE_SCRIPT parameter for Jenkins job: {}, error: {}", jobName, e.getMessage(), e); + throw new RuntimeException("确保Jenkins Job PIPELINE_SCRIPT参数失败", e); + } + } + + /** + * 清理XML内容,移除无效字符 + */ + private String cleanXmlContent(String xml) { + if (xml == null) { + return ""; + } + + // 移除XML中的无效字符 + StringBuilder cleanXml = new StringBuilder(); + for (int i = 0; i < xml.length(); i++) { + char c = xml.charAt(i); + // XML 1.1规范允许的字符范围(比1.0更宽松) + if ((c == 0x9) || + (c == 0xA) || + (c == 0xD) || + ((c >= 0x20) && (c <= 0xD7FF)) || + ((c >= 0xE000) && (c <= 0xFFFD)) || + ((c >= 0x10000) && (c <= 0x10FFFF)) || + // 允许一些常见的控制字符 + (c >= 0x1) && (c <= 0x1F)) { + cleanXml.append(c); + } + } + return cleanXml.toString(); + } + @Override public JenkinsQueueBuildInfoResponse getQueuedBuildInfo(String queueId) { ExternalSystem externalSystem = systemRepository.findById(1L).orElseThrow(() -> new RuntimeException("没有找到三方系统")); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/RepositoryBranchQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/RepositoryBranchQuery.java index baeb510f..5c6510b6 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/RepositoryBranchQuery.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/RepositoryBranchQuery.java @@ -16,12 +16,6 @@ public class RepositoryBranchQuery extends BaseQuery { @QueryField(field = "name", type = QueryType.LIKE) private String name; - @QueryField(field = "project_id") - private Long projectId; - - @QueryField(field = "external_system_id") - private Long externalSystemId; - @QueryField(field = "is_default_branch") private Boolean isDefaultBranch; @@ -30,4 +24,13 @@ public class RepositoryBranchQuery extends BaseQuery { @QueryField(field = "commit_author", type = QueryType.LIKE) private String commitAuthor; + + @QueryField(field = "externalSystemId", type = QueryType.EQUAL) + private Long externalSystemId; + + @QueryField(field = "projectId", type = QueryType.EQUAL) + private Long projectId; + + @QueryField(field = "repoProjectId", type = QueryType.EQUAL) + private Long repoProjectId; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/RepositoryProjectQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/RepositoryProjectQuery.java index cbea0be6..870f3cc7 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/RepositoryProjectQuery.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/RepositoryProjectQuery.java @@ -22,15 +22,15 @@ public class RepositoryProjectQuery extends BaseQuery { @QueryField(field = "visibility") private String visibility; - @QueryField(field = "externalSystemId") + @QueryField(field = "externalSystemId", type = QueryType.EQUAL) private Long externalSystemId; - @QueryField(field = "projectId") + @QueryField(field = "projectId", type = QueryType.EQUAL) private Long projectId; - @QueryField(field = "repoGroupId") + @QueryField(field = "repoGroupId", type = QueryType.EQUAL) private Long repoGroupId; - @QueryField(field = "repoProjectId") + @QueryField(field = "repoProjectId", type = QueryType.EQUAL) private Long repoProjectId; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/DeployNodeDelegate.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/DeployNodeDelegate.java index 10528502..cbf89f61 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/DeployNodeDelegate.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/DeployNodeDelegate.java @@ -62,6 +62,7 @@ public class DeployNodeDelegate extends BaseNodeDelegate new RuntimeException("ExternalSystem not found!!!")); JenkinsJob jenkinsJob = jenkinsJobRepository.findById(localVariables.getJobId()).orElseThrow(() -> new RuntimeException("Jenkins job not found!!!")); + // Pipeline脚本模板 Map parameters = new HashMap<>(); parameters.put("PIPELINE_SCRIPT", localVariables.getScript()); String queueId = jenkinsServiceIntegration.buildWithParameters(externalSystem, jenkinsJob.getJobName(), parameters); diff --git a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql index 0f273ee3..5e024509 100644 --- a/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql +++ b/backend/src/main/resources/db/migration/V1.0.0__init_schema.sql @@ -652,7 +652,6 @@ CREATE TABLE deploy_app_config form_variables_schema TEXT NULL, language_type VARCHAR(50) NOT NULL, workflow_definition_id BIGINT NOT NULL, - branch VARCHAR(50) NOT NULL, environment_id BIGINT NOT NULL, application_id BIGINT NOT NULL,