增加构建通知

This commit is contained in:
dengqichen 2025-11-14 16:37:22 +08:00
parent c81006177f
commit 22ad888c9f
4 changed files with 117 additions and 55 deletions

View File

@ -2,7 +2,9 @@ package com.qqchen.deploy.backend.deploy.enums;
// Jenkins构建状态枚举
public enum JenkinsBuildStatus {
SUCCESS, // 构建成功
FAILURE, // 构建失败
IN_PROGRESS,// 构建中
ABORTED, // 构建被取消

View File

@ -21,4 +21,28 @@ public class DateUtils {
public static LocalDateTime toLocalDateTime(long millis) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault());
}
/**
* Format a duration in milliseconds to HH:mm:ss (zero-padded).
* Example: 159000 -> 00:02:39
*/
public static String formatDurationHMS(long millis) {
if (millis < 0) millis = 0;
long totalSeconds = millis / 1000L;
long hours = totalSeconds / 3600L;
long minutes = (totalSeconds % 3600L) / 60L;
long seconds = totalSeconds % 60L;
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
/**
* Format a duration in seconds to HH:mm:ss (zero-padded).
*/
public static String formatDurationHMSBySeconds(long seconds) {
if (seconds < 0) seconds = 0;
long hours = seconds / 3600L;
long minutes = (seconds % 3600L) / 60L;
long sec = seconds % 60L;
return String.format("%02d:%02d:%02d", hours, minutes, sec);
}
}

View File

@ -1,30 +1,24 @@
package com.qqchen.deploy.backend.workflow.delegate;
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
import com.qqchen.deploy.backend.deploy.entity.JenkinsJob;
import com.qqchen.deploy.backend.deploy.enums.JenkinsBuildStatus;
import com.qqchen.deploy.backend.deploy.integration.IJenkinsServiceIntegration;
import com.qqchen.deploy.backend.deploy.integration.response.JenkinsConsoleOutputResponse;
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.deploy.repository.IJenkinsJobRepository;
import com.qqchen.deploy.backend.workflow.constants.WorkFlowConstants;
import com.qqchen.deploy.backend.workflow.dto.inputmapping.JenkinsBuildInputMapping;
import com.qqchen.deploy.backend.workflow.dto.outputs.JenkinsBuildOutputs;
import com.qqchen.deploy.backend.workflow.entity.WorkflowNodeInstance;
import com.qqchen.deploy.backend.workflow.enums.LogLevel;
import com.qqchen.deploy.backend.workflow.enums.LogSource;
import com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum;
import com.qqchen.deploy.backend.workflow.service.IWorkflowNodeInstanceService;
import com.qqchen.deploy.backend.workflow.service.IWorkflowNodeLogService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.BpmnError;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import com.qqchen.deploy.backend.framework.utils.DateUtils;
import java.util.HashMap;
import java.util.Map;
@ -90,29 +84,13 @@ public class JenkinsBuildDelegate extends BaseNodeDelegate<JenkinsBuildInputMapp
// 5. 获取构建详细信息包括 duration, changeSets, artifacts
JenkinsBuildResponse buildDetails = jenkinsServiceIntegration.getBuildDetails(externalSystem, jobName, buildInfo.getBuildNumber());
// 打印调试信息
log.info("Build details - changeSets: {}, artifacts: {}", buildDetails.getChangeSets(), buildDetails.getArtifacts());
// 打印调试信息仅数量避免大对象噪音
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. 设置输出结果执行到这里说明构建成功
// 直接修改预初始化的 output 对象
// 设置 Jenkins 特有字段
output.setBuildStatus(buildStatus.name());
output.setBuildNumber(buildInfo.getBuildNumber());
output.setBuildUrl(buildInfo.getBuildUrl());
// 从构建详情中提取信息
output.setBuildDuration(buildDetails.getDuration() != null ? buildDetails.getDuration().intValue() : 0);
// 直接使用服务层计算与提取的结束时间与提交ID
output.setDeployEndTimeMillis(buildDetails.getEndTimeMillis());
output.setDeployEndTime(buildDetails.getEndTime());
// 提取 Git Commit ID由服务层兜底填充
output.setGitCommitId(buildDetails.getGitCommitId() != null ? buildDetails.getGitCommitId() : "");
// 直接使用服务层拼接的制品URL串
output.setArtifactUrl(buildDetails.getArtifactUrl() != null ? buildDetails.getArtifactUrl() : "");
fillOutputsFrom(buildInfo, buildDetails, buildStatus, output);
// 记录完成日志
workflowNodeLogService.info(execution.getProcessInstanceId(), execution.getCurrentActivityId(), LogSource.JENKINS, "Jenkins 构建任务执行完成");
@ -173,29 +151,18 @@ public class JenkinsBuildDelegate extends BaseNodeDelegate<JenkinsBuildInputMapp
JenkinsBuildStatus status = jenkinsServiceIntegration.getBuildStatus(externalSystem, jobName, buildNumber);
log.info("Jenkins build status: job={}, buildNumber={}, status={}", jobName, buildNumber, status);
switch (status) {
case SUCCESS:
// 构建成功拉取剩余日志后返回状态
log.info("Jenkins build succeeded: job={}, buildNumber={}", jobName, buildNumber);
fetchRemainingLogs(execution, externalSystem, jobName, buildNumber, logOffset);
logInfo(String.format("✅ Jenkins 构建成功: buildNumber=%d", buildNumber));
return status;
case FAILURE:
// 构建失败拉取剩余日志后抛出异常
fetchRemainingLogs(execution, externalSystem, jobName, buildNumber, logOffset);
throw new RuntimeException(String.format("Jenkins build failed: job=%s, buildNumber=%d", jobName, buildNumber));
case ABORTED:
// 构建被取消拉取剩余日志后抛出异常
fetchRemainingLogs(execution, externalSystem, jobName, buildNumber, logOffset);
throw new RuntimeException(String.format("Jenkins build was aborted: job=%s, buildNumber=%d", jobName, buildNumber));
case IN_PROGRESS:
// 继续轮询
attempts++;
break;
case NOT_FOUND:
// 构建记录丢失抛出异常
throw new RuntimeException(String.format("Jenkins build not found: job=%s, buildNumber=%d", jobName, buildNumber));
if (status == JenkinsBuildStatus.IN_PROGRESS) {
// 继续轮询
attempts++;
continue;
}
// 统一处理终止态SUCCESS/FAILURE/ABORTED/NOT_FOUND
if (needsFetchLogs(status)) {
fetchRemainingLogs(execution, externalSystem, jobName, buildNumber, logOffset);
}
logTerminalStatus(status, jobName, buildNumber);
return status;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Build status polling was interrupted", e);
@ -225,4 +192,63 @@ public class JenkinsBuildDelegate extends BaseNodeDelegate<JenkinsBuildInputMapp
}
}
/**
* 终止状态是否需要抓取剩余日志
*/
private boolean needsFetchLogs(JenkinsBuildStatus status) {
return status == JenkinsBuildStatus.SUCCESS
|| status == JenkinsBuildStatus.FAILURE
|| status == JenkinsBuildStatus.ABORTED;
}
/**
* 统一终止态日志输出
*/
private void logTerminalStatus(JenkinsBuildStatus status, String jobName, Integer buildNumber) {
switch (status) {
case SUCCESS -> {
log.info("Jenkins build succeeded: job={}, buildNumber={}", jobName, buildNumber);
logInfo(String.format("✅ Jenkins 构建成功: buildNumber=%d", buildNumber));
}
case FAILURE -> {
log.info("Jenkins build failed: job={}, buildNumber={}", jobName, buildNumber);
logInfo(String.format("❌ Jenkins 构建失败: buildNumber=%d", buildNumber));
}
case ABORTED -> {
log.info("Jenkins build aborted: job={}, buildNumber={}", jobName, buildNumber);
logInfo(String.format("⏹ Jenkins 构建被取消: buildNumber=%d", buildNumber));
}
case NOT_FOUND -> {
log.warn("Jenkins build not found: job={}, buildNumber={}", jobName, buildNumber);
logInfo(String.format("⚠ Jenkins 构建记录不存在: buildNumber=%d", buildNumber));
}
}
}
/**
* 将服务层返回的构建详情填充到输出对象
*/
private void fillOutputsFrom(JenkinsQueueBuildInfoResponse buildInfo, JenkinsBuildResponse buildDetails, JenkinsBuildStatus buildStatus, JenkinsBuildOutputs output) {
// Jenkins 基本信息
output.setBuildStatus(buildStatus);
output.setBuildNumber(buildInfo.getBuildNumber());
output.setBuildUrl(buildInfo.getBuildUrl());
// 构建时长毫秒与秒
long durationMs = buildDetails.getDuration() != null ? Math.max(0L, buildDetails.getDuration()) : 0L;
int durationSeconds = (int) Math.min(Integer.MAX_VALUE, durationMs / 1000L);
output.setBuildDuration(durationSeconds);
output.setBuildDurationMillis(durationMs);
output.setBuildDurationFormatted(DateUtils.formatDurationHMS(durationMs));
// 结束时间服务层已计算
output.setBuildEndTimeMillis(buildDetails.getEndTimeMillis());
output.setBuildEndTime(buildDetails.getEndTime());
// Git 提交ID服务层已兜底
output.setGitCommitId(buildDetails.getGitCommitId() != null ? buildDetails.getGitCommitId() : "");
// 制品URL服务层已拼接
output.setArtifactUrl(buildDetails.getArtifactUrl() != null ? buildDetails.getArtifactUrl() : "");
}
}

View File

@ -1,6 +1,6 @@
package com.qqchen.deploy.backend.workflow.dto.outputs;
import lombok.Builder;
import com.qqchen.deploy.backend.deploy.enums.JenkinsBuildStatus;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -22,7 +22,7 @@ public class JenkinsBuildOutputs extends BaseNodeOutputs {
/**
* 构建状态
*/
private String buildStatus;
private JenkinsBuildStatus buildStatus;
/**
* 构建URL
@ -44,8 +44,18 @@ public class JenkinsBuildOutputs extends BaseNodeOutputs {
*/
private Integer buildDuration;
private Long deployEndTimeMillis;
/**
* 构建时长毫秒
*/
private Long buildDurationMillis;
private String deployEndTime;
/**
* 构建时长格式HH:mm:ss例如 00:02:39
*/
private String buildDurationFormatted;
private Long buildEndTimeMillis;
private String buildEndTime;
}