diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/JenkinsBuildStatus.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/JenkinsBuildStatus.java index 6c6f484b..8c1da6e7 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/JenkinsBuildStatus.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/JenkinsBuildStatus.java @@ -1,12 +1,23 @@ package com.qqchen.deploy.backend.deploy.enums; -// Jenkins构建状态枚举 +import lombok.Getter; + +/** + * Jenkins构建状态枚举 + */ +@Getter public enum JenkinsBuildStatus { - SUCCESS, // 构建成功 + SUCCESS("构建成功"), + FAILURE("构建失败"), + IN_PROGRESS("构建中"), + ABORTED("构建已取消"), + NOT_FOUND("构建记录不存在"), + UNSTABLE("构建不稳定"); - FAILURE, // 构建失败 - IN_PROGRESS,// 构建中 - ABORTED, // 构建被取消 - NOT_FOUND // 构建不存在 + private final String description; + + JenkinsBuildStatus(String description) { + this.description = description; + } } \ No newline at end of file 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 d244e691..0973a798 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 @@ -3,6 +3,8 @@ package com.qqchen.deploy.backend.deploy.integration.impl; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.utils.DateUtils; import com.qqchen.deploy.backend.deploy.entity.ExternalSystem; import com.qqchen.deploy.backend.deploy.enums.JenkinsBuildStatus; @@ -202,7 +204,7 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes()); headers.set("Authorization", "Basic " + new String(encodedAuth)); } - default -> throw new RuntimeException("Unsupported authentication type: " + decryptedSystem.getAuthType()); + default -> throw new BusinessException(ResponseCode.JENKINS_AUTH_FAILED, new Object[] {decryptedSystem.getAuthType()}); } // 设置接受JSON响应 @@ -247,12 +249,14 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration // 6. 获取队列ID String location = response.getHeaders().getFirst("Location"); if (location == null) { - throw new RuntimeException("未获取到构建队列信息"); + throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {201, "未获取到构建队列信息"}); } return extractQueueId(location); + } catch (BusinessException e) { + throw e; } catch (Exception e) { log.error("Failed to trigger Jenkins build: job={}, error={}", jobName, e.getMessage(), e); - throw new RuntimeException("触发Jenkins构建失败: " + jobName, e); + throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {"POST", e.getMessage()}); } } @@ -283,7 +287,7 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration ); if (response.getBody() == null) { - throw new RuntimeException("无法获取Jenkins Job配置"); + throw new BusinessException(ResponseCode.JENKINS_JOB_NOT_FOUND, new Object[] {jobName}); } // 解析XML @@ -396,9 +400,11 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration ); log.info("Successfully added PIPELINE_SCRIPT parameter to Jenkins job: {}", jobName); + } catch (BusinessException e) { + throw e; } 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); + throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {"UPDATE_CONFIG", e.getMessage()}); } } @@ -443,13 +449,13 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration Map queueInfo = response.getBody(); if (queueInfo == null) { - throw new RuntimeException("Failed to get queue information"); + throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {200, "队列信息为空"}); } // 检查是否被取消 Boolean cancelled = (Boolean) queueInfo.get("cancelled"); if (Boolean.TRUE.equals(cancelled)) { - throw new RuntimeException("Build was cancelled in queue"); + throw new BusinessException(ResponseCode.JENKINS_BUILD_CANCELLED_IN_QUEUE); } // 检查是否已开始执行 @@ -495,9 +501,11 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration case "ABORTED" -> JenkinsBuildStatus.ABORTED; default -> JenkinsBuildStatus.IN_PROGRESS; }; + } catch (BusinessException e) { + throw e; } catch (Exception e) { log.error("Failed to get build status: job={}, buildNumber={}", jobName, buildNumber, e); - throw new RuntimeException("Failed to get build status", e); + throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {"GET_STATUS", e.getMessage()}); } } @@ -615,11 +623,13 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration return buildResponse; } - throw new RuntimeException("Failed to get build details: empty response"); + throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {200, "构建详情响应为空"}); + } catch (BusinessException e) { + throw e; } catch (Exception e) { log.error("Failed to get build details: job={}, buildNumber={}, error={}", jobName, buildNumber, e.getMessage(), e); - throw new RuntimeException("获取Jenkins构建详情失败: " + jobName + "#" + buildNumber, e); + throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {"GET_BUILD_DETAILS", e.getMessage()}); } } @@ -635,7 +645,7 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration try { jsonNode = objectMapper.readTree(response.getBody()); } catch (JsonProcessingException e) { - throw new RuntimeException("解析Jenkins响应失败", e); + throw new BusinessException(ResponseCode.JENKINS_RESPONSE_PARSE_ERROR, new Object[] {e.getMessage()}); } // 2. 从响应头中获取cookie @@ -688,10 +698,12 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration mapper.getTypeFactory().constructCollectionType(List.class, responseType)); } return Collections.emptyList(); + } catch (BusinessException e) { + throw e; } catch (Exception e) { log.error("Failed to call Jenkins API: path={}, error={}, responseType={}", path, e.getMessage(), responseType.getSimpleName(), e); - throw new RuntimeException("调用Jenkins API失败: " + path, e); + throw new BusinessException(ResponseCode.JENKINS_API_ERROR, new Object[] {path, e.getMessage()}); } } @@ -803,9 +815,11 @@ public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration } return null; + } catch (BusinessException e) { + throw e; } catch (Exception e) { log.error("Failed to get Jenkins job: jobName={}, error={}", jobName, e.getMessage(), e); - throw new RuntimeException("获取Jenkins任务失败: " + jobName, e); + throw new BusinessException(ResponseCode.JENKINS_JOB_NOT_FOUND, new Object[] {jobName}); } } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java index 3542e187..5816cb8b 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java @@ -241,7 +241,19 @@ public enum ResponseCode { NOTIFICATION_CHANNEL_NOT_FOUND(3120, "notification.channel.not.found"), NOTIFICATION_CHANNEL_DISABLED(3121, "notification.channel.disabled"), NOTIFICATION_CHANNEL_CONFIG_ERROR(3122, "notification.channel.config.error"), - NOTIFICATION_SEND_FAILED(3123, "notification.send.failed"); + NOTIFICATION_SEND_FAILED(3123, "notification.send.failed"), + + // Jenkins集成错误码 (3200-3219) + JENKINS_SERVER_NOT_FOUND(3200, "jenkins.server.not.found"), + JENKINS_CONNECTION_FAILED(3201, "jenkins.connection.failed"), + JENKINS_AUTH_FAILED(3202, "jenkins.auth.failed"), + JENKINS_JOB_NOT_FOUND(3203, "jenkins.job.not.found"), + JENKINS_SERVER_ERROR(3204, "jenkins.server.error"), + JENKINS_QUEUE_TIMEOUT(3205, "jenkins.queue.timeout"), + JENKINS_BUILD_TIMEOUT(3206, "jenkins.build.timeout"), + JENKINS_API_ERROR(3207, "jenkins.api.error"), + JENKINS_RESPONSE_PARSE_ERROR(3208, "jenkins.response.parse.error"), + JENKINS_BUILD_CANCELLED_IN_QUEUE(3209, "jenkins.build.cancelled.in.queue"); private final int code; private final String messageKey; // 国际化消息key diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsBuildDelegate.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsBuildDelegate.java index 043a6bc2..11e21310 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsBuildDelegate.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/delegate/JenkinsBuildDelegate.java @@ -7,6 +7,9 @@ import com.qqchen.deploy.backend.deploy.integration.response.JenkinsConsoleOutpu import com.qqchen.deploy.backend.deploy.integration.response.JenkinsBuildResponse; import com.qqchen.deploy.backend.deploy.integration.response.JenkinsQueueBuildInfoResponse; import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository; +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import com.qqchen.deploy.backend.framework.utils.DateUtils; import com.qqchen.deploy.backend.workflow.dto.inputmapping.JenkinsBuildInputMapping; import com.qqchen.deploy.backend.workflow.dto.outputs.JenkinsBuildOutputs; import com.qqchen.deploy.backend.workflow.enums.LogLevel; @@ -17,8 +20,9 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; - -import com.qqchen.deploy.backend.framework.utils.DateUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.ResourceAccessException; import java.util.HashMap; import java.util.Map; @@ -53,50 +57,75 @@ public class JenkinsBuildDelegate extends BaseNodeDelegate configs, JenkinsBuildInputMapping input) { - log.info("Jenkins Build - serverId: {}, jobName: {}", input.getServerId(), input.getJobName()); + try { + log.info("Jenkins Build - serverId: {}, jobName: {}", input.getServerId(), input.getJobName()); - // 1. 获取外部系统(不存在会抛出异常,由基类处理) - ExternalSystem externalSystem = externalSystemRepository.findById(input.getServerId()) - .orElseThrow(() -> new RuntimeException("Jenkins服务器不存在: " + input.getServerId())); + // 1. 获取外部系统(配置错误 - 技术异常) + ExternalSystem externalSystem = externalSystemRepository.findById(input.getServerId()) + .orElseThrow(() -> new BusinessException(ResponseCode.JENKINS_SERVER_NOT_FOUND, new Object[]{input.getServerId()})); - String jobName = input.getJobName(); + String jobName = input.getJobName(); - // 2. 触发构建 - Map parameters = new HashMap<>(); - // 可以根据需要添加构建参数 - // parameters.put("BRANCH", "main"); + // 2. 触发构建 + Map parameters = new HashMap<>(); + // 可以根据需要添加构建参数 + // parameters.put("BRANCH", "main"); - String queueId = jenkinsServiceIntegration.buildWithParameters(externalSystem, jobName, parameters); + String queueId = jenkinsServiceIntegration.buildWithParameters(externalSystem, jobName, parameters); - log.info("Jenkins build queued: queueId={}", queueId); + log.info("Jenkins build queued: queueId={}", queueId); - // 3. 等待构建从队列中开始 - JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(externalSystem, queueId); + // 3. 等待构建从队列中开始 + JenkinsQueueBuildInfoResponse buildInfo = waitForBuildToStart(externalSystem, queueId); - log.info("Jenkins build started: buildNumber={}", buildInfo.getBuildNumber()); + log.info("Jenkins build started: buildNumber={}", buildInfo.getBuildNumber()); - // 4. 记录构建启动日志 - workflowNodeLogService.info(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, String.format("Jenkins 构建已启动: job=%s, buildNumber=%d", jobName, buildInfo.getBuildNumber())); + // 4. 记录构建启动日志 + workflowNodeLogService.info(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, + String.format("Jenkins 构建已启动: job=%s, buildNumber=%d", jobName, buildInfo.getBuildNumber())); - // 5. 轮询构建状态直到完成 - // 只有成功时才会返回到这里 - JenkinsBuildStatus buildStatus = pollBuildStatus(execution, externalSystem, jobName, buildInfo.getBuildNumber()); + // 5. 轮询构建状态直到完成 + JenkinsBuildStatus buildStatus = pollBuildStatus(execution, externalSystem, jobName, buildInfo.getBuildNumber()); - // 5. 获取构建详细信息(包括 duration, changeSets, artifacts) - JenkinsBuildResponse buildDetails = jenkinsServiceIntegration.getBuildDetails(externalSystem, jobName, buildInfo.getBuildNumber()); + // 6. 获取构建详细信息(包括 duration, changeSets, artifacts) + JenkinsBuildResponse buildDetails = jenkinsServiceIntegration.getBuildDetails(externalSystem, jobName, buildInfo.getBuildNumber()); - // 打印调试信息(仅数量,避免大对象噪音) - int changeSetsCount = (buildDetails.getChangeSets() != null) ? buildDetails.getChangeSets().size() : 0; - int artifactsCount = (buildDetails.getArtifacts() != null) ? buildDetails.getArtifacts().size() : 0; - log.info("Build details - changeSetsCount={}, artifactsCount={}", changeSetsCount, artifactsCount); + // 打印调试信息(仅数量,避免大对象噪音) + int changeSetsCount = (buildDetails.getChangeSets() != null) ? buildDetails.getChangeSets().size() : 0; + int artifactsCount = (buildDetails.getArtifacts() != null) ? buildDetails.getArtifacts().size() : 0; + log.info("Build details - changeSetsCount={}, artifactsCount={}", changeSetsCount, artifactsCount); - // 6. 设置输出结果(执行到这里说明构建成功) - fillOutputsFrom(buildInfo, buildDetails, buildStatus, output); + // 7. 设置输出结果(包含节点状态) + fillOutputsFrom(buildInfo, buildDetails, buildStatus, output); - // 记录完成日志 - workflowNodeLogService.info(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, "Jenkins 构建任务执行完成"); + // 8. 记录完成日志 + String logMessage = buildStatus == JenkinsBuildStatus.SUCCESS + ? "Jenkins 构建任务执行完成:构建成功" + : String.format("Jenkins 构建任务执行完成:%s", buildStatus.getDescription()); + workflowNodeLogService.info(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, logMessage); - // ✅ 不需要 return,status 已经是 SUCCESS + // ✅ 正常返回,基类会根据 output.status + continueOnFailure 决定是否抛 BpmnError + + } catch (ResourceAccessException e) { + // 网络连接异常 - 技术异常,强制失败 + log.error("Jenkins 连接失败: serverId={}, error={}", input.getServerId(), e.getMessage(), e); + throw new BusinessException(ResponseCode.JENKINS_CONNECTION_FAILED, new Object[]{input.getServerId()}); + + } catch (HttpClientErrorException.Unauthorized | HttpClientErrorException.Forbidden e) { + // 认证失败 - 技术异常,强制失败 + log.error("Jenkins 认证失败: serverId={}, status={}", input.getServerId(), e.getStatusCode()); + throw new BusinessException(ResponseCode.JENKINS_AUTH_FAILED, new Object[]{input.getServerId()}); + + } catch (HttpClientErrorException.NotFound e) { + // Job不存在 - 技术异常,强制失败 + log.error("Jenkins Job 不存在: jobName={}", input.getJobName()); + throw new BusinessException(ResponseCode.JENKINS_JOB_NOT_FOUND, new Object[]{input.getJobName()}); + + } catch (HttpServerErrorException e) { + // Jenkins服务器错误 - 技术异常,强制失败 + log.error("Jenkins 服务器错误: serverId={}, status={}", input.getServerId(), e.getStatusCode()); + throw new BusinessException(ResponseCode.JENKINS_SERVER_ERROR, new Object[]{e.getStatusCode().value()}); + } } private JenkinsQueueBuildInfoResponse waitForBuildToStart(ExternalSystem externalSystem, String queueId) { @@ -119,7 +148,7 @@ public class JenkinsBuildDelegate extends BaseNodeDelegate