大声道撒旦

This commit is contained in:
dengqichen 2025-01-07 16:14:24 +08:00
parent a2ccd9012c
commit 851a560afd
4 changed files with 88 additions and 30 deletions

View File

@ -6,6 +6,7 @@ import com.qqchen.deploy.backend.deploy.integration.response.*;
import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum; import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Jenkins集成服务接口 * Jenkins集成服务接口
@ -32,7 +33,7 @@ public interface IJenkinsServiceIntegration extends IExternalSystemIntegration {
* *
* @return 构建队列ID * @return 构建队列ID
*/ */
String buildWithParameters(ExternalSystem externalSystem, String jobName); String buildWithParameters(ExternalSystem externalSystem, String jobName, Map<String, String> parameters);
/** /**
* 获取队列中的构建信息 * 获取队列中的构建信息

View File

@ -20,8 +20,11 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -31,6 +34,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@ -116,20 +120,48 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration {
} }
@Override @Override
public String buildWithParameters(ExternalSystem externalSystem, String jobName) { public String buildWithParameters(ExternalSystem externalSystem, String jobName, Map<String, String> parameters) {
JenkinsCrumbIssuerResponse jenkinsCrumbIssue = getJenkinsCrumbIssue(); try {
HttpHeaders headers = createHeaders(externalSystem); // 1. 获取Crumb
headers.set("Jenkins-Crumb", jenkinsCrumbIssue.getCrumb()); JenkinsCrumbIssuerResponse jenkinsCrumbIssue = getJenkinsCrumbIssue();
headers.set("Cookie", jenkinsCrumbIssue.getCookie());
HttpEntity<String> entity = new HttpEntity<>(headers); // 2. 构建URL
String url = externalSystem.getUrl() + String.format("/job/%s/buildWithParameters", jobName); String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl())
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); .path("/job/")
// 2. 从Location头获取队列ID .path(jobName)
String location = response.getHeaders().getFirst("Location"); .path("/buildWithParameters")
if (location == null) { .build()
throw new RuntimeException("未获取到构建队列信息"); .toUriString();
// 3. 构建请求头
HttpHeaders headers = createHeaders(externalSystem);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.set("Jenkins-Crumb", jenkinsCrumbIssue.getCrumb());
headers.set("Cookie", jenkinsCrumbIssue.getCookie());
// 4. 构建请求体
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
parameters.forEach(formData::add);
// 5. 发送请求
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(formData, headers);
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
String.class
);
// 6. 获取队列ID
String location = response.getHeaders().getFirst("Location");
if (location == null) {
throw new RuntimeException("未获取到构建队列信息");
}
return extractQueueId(location);
} catch (Exception e) {
log.error("Failed to trigger Jenkins build: job={}, error={}", jobName, e.getMessage(), e);
throw new RuntimeException("触发Jenkins构建失败: " + jobName, e);
} }
return extractQueueId(location);
} }
@Override @Override
@ -223,18 +255,18 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration {
* 通用的Jenkins API调用方法 * 通用的Jenkins API调用方法
* *
* @param externalSystem Jenkins系统配置 * @param externalSystem Jenkins系统配置
* @param path API路径 * @param path API路径
* @param treeQuery tree查询参数 * @param treeQuery tree查询参数
* @param jsonArrayKey JSON数组的key * @param jsonArrayKey JSON数组的key
* @param responseType 响应类型的Class对象用于类型安全 * @param responseType 响应类型的Class对象用于类型安全
* @param <T> 响应类型泛型 * @param <T> 响应类型泛型
* @return API响应结果 * @return API响应结果
*/ */
private <T> List<T> callJenkinsApi(ExternalSystem externalSystem, private <T> List<T> callJenkinsApi(ExternalSystem externalSystem,
String path, String path,
String treeQuery, String treeQuery,
String jsonArrayKey, String jsonArrayKey,
Class<T> responseType) { Class<T> responseType) {
try { try {
String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl()) String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl())
.path(path) .path(path)
@ -259,7 +291,7 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration {
} }
return Collections.emptyList(); return Collections.emptyList();
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to call Jenkins API: path={}, error={}, responseType={}", log.error("Failed to call Jenkins API: path={}, error={}, responseType={}",
path, e.getMessage(), responseType.getSimpleName(), e); path, e.getMessage(), responseType.getSimpleName(), e);
throw new RuntimeException("调用Jenkins API失败: " + path, e); throw new RuntimeException("调用Jenkins API失败: " + path, e);
} }
@ -302,14 +334,14 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration {
* 查询视图下的所有任务 * 查询视图下的所有任务
* *
* @param externalSystem Jenkins系统配置 * @param externalSystem Jenkins系统配置
* @param viewName 视图名称 * @param viewName 视图名称
* @return 任务列表 * @return 任务列表
*/ */
public List<JenkinsJobResponse> listJobs(ExternalSystem externalSystem, String viewName) { public List<JenkinsJobResponse> listJobs(ExternalSystem externalSystem, String viewName) {
// 只查询必要的字段确保包含lastBuild的timestamp字段 // 只查询必要的字段确保包含lastBuild的timestamp字段
String treeQuery = "jobs[name,url,description,buildable,nextBuildNumber," + String treeQuery = "jobs[name,url,description,buildable,nextBuildNumber," +
"lastBuild[number,timestamp,result],color]"; "lastBuild[number,timestamp,result],color]";
List<JenkinsJobResponse> jobs = callJenkinsApi( List<JenkinsJobResponse> jobs = callJenkinsApi(
externalSystem, externalSystem,
"/view/" + viewName + "/api/json", "/view/" + viewName + "/api/json",
@ -362,16 +394,16 @@ public class JenkinsServiceIntegration implements IJenkinsServiceIntegration {
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
JenkinsJobResponse jobResponse = mapper.readValue(response.getBody(), JenkinsJobResponse.class); JenkinsJobResponse jobResponse = mapper.readValue(response.getBody(), JenkinsJobResponse.class);
// 4. 清理URL移除基础URL部分 // 4. 清理URL移除基础URL部分
if (jobResponse.getUrl() != null) { if (jobResponse.getUrl() != null) {
String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/"); String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/");
jobResponse.setUrl(jobResponse.getUrl().replace(baseUrl, "")); jobResponse.setUrl(jobResponse.getUrl().replace(baseUrl, ""));
} }
return jobResponse; return jobResponse;
} }
return null; return null;
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to get Jenkins job: jobName={}, error={}", jobName, e.getMessage(), e); log.error("Failed to get Jenkins job: jobName={}, error={}", jobName, e.getMessage(), e);

View File

@ -18,6 +18,7 @@ import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -61,7 +62,9 @@ public class DeployNodeDelegate extends BaseNodeDelegate<DeployNodePanelVariable
protected void executeInternal(DelegateExecution execution, DeployNodePanelVariables panelVariables, DeployNodeLocalVariables localVariables) { protected void executeInternal(DelegateExecution execution, DeployNodePanelVariables panelVariables, DeployNodeLocalVariables localVariables) {
ExternalSystem externalSystem = externalSystemRepository.findById(localVariables.getExternalSystemId()).orElseThrow(() -> new RuntimeException("ExternalSystem not found!!!")); ExternalSystem externalSystem = externalSystemRepository.findById(localVariables.getExternalSystemId()).orElseThrow(() -> new RuntimeException("ExternalSystem not found!!!"));
JenkinsJob jenkinsJob = jenkinsJobRepository.findById(localVariables.getJobId()).orElseThrow(() -> new RuntimeException("Jenkins job not found!!!")); JenkinsJob jenkinsJob = jenkinsJobRepository.findById(localVariables.getJobId()).orElseThrow(() -> new RuntimeException("Jenkins job not found!!!"));
String queueId = jenkinsServiceIntegration.buildWithParameters(externalSystem, jenkinsJob.getJobName()); Map<String, String> parameters = new HashMap<>();
parameters.put("PIPELINE_SCRIPT", localVariables.getScript());
String queueId = jenkinsServiceIntegration.buildWithParameters(externalSystem, jenkinsJob.getJobName(), parameters);
JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(queueId); JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(queueId);
// 3. 轮询构建状态 // 3. 轮询构建状态
pollBuildStatus(externalSystem, jenkinsJob.getJobName(), buildInfo.getBuildNumber()); pollBuildStatus(externalSystem, jenkinsJob.getJobName(), buildInfo.getBuildNumber());

View File

@ -1,6 +1,7 @@
package com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables; package com.qqchen.deploy.backend.workflow.dto.definition.node.localVariables;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.qqchen.deploy.backend.workflow.annotation.CodeEditorConfig;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty; import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -36,5 +37,26 @@ public class DeployNodeLocalVariables extends BaseNodeLocalVariables {
) )
private Long jobId; private Long jobId;
@SchemaProperty(
title = "Pipeline script",
description = "流水线脚本",
required = true,
format = "monaco-editor", // 使用 Monaco Editor
defaultValue = "#!/bin/bash\n\necho \"Hello World\"",
codeEditor = @CodeEditorConfig(
language = "shell",
theme = "vs-dark",
minimap = false,
lineNumbers = true,
wordWrap = true,
fontSize = 14,
tabSize = 2,
autoComplete = true,
folding = true
),
order = 6
)
private String script;
} }