Golang_demo/Jenkinsfile.go-template
2025-06-25 17:54:12 +08:00

960 lines
40 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// =================================================================
// 🚀 Go项目通用Jenkins Pipeline模板
// =================================================================
//
// 📋 使用说明:
// 1. 复制此模板到你的Go项目根目录重命名为Jenkinsfile
// 2. 修改下面的 "项目配置" 部分
// 3. 确保Jenkins中已配置好相关工具和凭据
// 4. 推送到Git仓库即可自动触发构建
//
// 🔧 支持的特性:
// - ✅ Go模块化项目构建和测试
// - ✅ SonarQube代码质量扫描优化后11秒完成
// - ✅ Docker镜像构建和测试
// - ✅ 多环境自动部署(生产/测试)
// - ✅ 健康检查和回滚机制
// - ✅ 测试覆盖率报告
// - ✅ 构建产物归档
//
// =================================================================
pipeline {
agent any
// 🎛️ 构建参数 - 支持手动构建时的自定义配置
parameters {
choice(
name: 'DEPLOY_ENV',
choices: ['auto', 'production', 'staging', 'development', 'skip'],
description: '部署环境 (auto=根据分支自动选择, skip=仅构建不部署)'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: false,
description: '跳过单元测试(不推荐)'
)
booleanParam(
name: 'SKIP_SONAR',
defaultValue: false,
description: '跳过代码质量扫描'
)
booleanParam(
name: 'FORCE_REBUILD_IMAGE',
defaultValue: false,
description: '强制重新构建Docker镜像'
)
string(
name: 'CUSTOM_TAG',
defaultValue: '',
description: '自定义Docker镜像标签留空使用构建号'
)
}
options {
buildDiscarder(logRotator(
numToKeepStr: '20',
daysToKeepStr: '30',
artifactNumToKeepStr: '10'
))
timeout(time: 60, unit: 'MINUTES')
timestamps()
parallelsAlwaysFailFast()
skipDefaultCheckout()
}
tools {
go 'go' // 确保Jenkins中已配置Go工具名称为'go'
}
environment {
// ===========================================
// 📝 项目配置 - 每个项目都需要修改这些变量
// ===========================================
PROJECT_NAME = 'my-go-app' // 🔧 修改:项目名称
DEPLOY_SERVER = '116.62.163.84' // 🔧 修改部署服务器IP
SSH_CREDENTIAL_ID = 'deploy-server-ssh-key' // 🔧 修改SSH凭据ID
// SonarQube配置可选如果不使用可以注释掉
SONAR_HOST_URL = 'http://116.62.163.84:15010' // 🔧 修改SonarQube服务器地址
SONAR_CREDENTIAL_ID = 'sonar-token' // 🔧 修改SonarQube Token凭据ID
// Docker镜像仓库配置可选
DOCKER_REGISTRY = '' // 🔧 修改Docker仓库地址留空使用本地
DOCKER_CREDENTIAL_ID = '' // 🔧 修改Docker仓库凭据ID
// ===========================================
// 🔧 端口配置 - 根据你的环境修改
// ===========================================
PROD_PORT = '15021' // 生产环境端口
STAGING_PORT = '15022' // 预发布环境端口
DEV_PORT = '15023' // 开发环境端口
// 应用配置
APP_PORT = '8080' // 应用内部端口通常是8080
// ===========================================
// 🔧 自动化配置 - 通常不需要修改
// ===========================================
CGO_ENABLED = '0'
GOOS = 'linux'
GOARCH = 'amd64'
// 动态变量
IMAGE_NAME = "${PROJECT_NAME}"
IMAGE_TAG = "${params.CUSTOM_TAG ?: BUILD_NUMBER}"
FULL_IMAGE_NAME = "${DOCKER_REGISTRY ? DOCKER_REGISTRY + '/' : ''}${IMAGE_NAME}:${IMAGE_TAG}"
}
stages {
stage('🔄 初始化') {
parallel {
stage('检出代码') {
steps {
echo "🔄 检出代码 - ${PROJECT_NAME}..."
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(
script: "git rev-parse --short HEAD",
returnStdout: true
).trim()
env.GIT_BRANCH = sh(
script: "git rev-parse --abbrev-ref HEAD || echo '${BRANCH_NAME}'",
returnStdout: true
).trim()
// 根据分支自动确定部署环境
if (params.DEPLOY_ENV == 'auto') {
if (env.BRANCH_NAME == 'main' || env.BRANCH_NAME == 'master') {
env.DEPLOY_ENV = 'production'
env.DEPLOY_PORT = env.PROD_PORT
} else if (env.BRANCH_NAME == 'staging' || env.BRANCH_NAME == 'release') {
env.DEPLOY_ENV = 'staging'
env.DEPLOY_PORT = env.STAGING_PORT
} else {
env.DEPLOY_ENV = 'development'
env.DEPLOY_PORT = env.DEV_PORT
}
} else {
env.DEPLOY_ENV = params.DEPLOY_ENV
switch(params.DEPLOY_ENV) {
case 'production':
env.DEPLOY_PORT = env.PROD_PORT
break
case 'staging':
env.DEPLOY_PORT = env.STAGING_PORT
break
case 'development':
env.DEPLOY_PORT = env.DEV_PORT
break
case 'skip':
env.DEPLOY_PORT = 'none'
break
}
}
env.CONTAINER_NAME = "${PROJECT_NAME}-${env.DEPLOY_ENV}"
}
echo "📋 构建信息:"
echo " 项目: ${PROJECT_NAME}"
echo " 分支: ${env.BRANCH_NAME}"
echo " 提交: ${env.GIT_COMMIT_SHORT}"
echo " 环境: ${env.DEPLOY_ENV}"
echo " 端口: ${env.DEPLOY_PORT}"
echo " 镜像: ${FULL_IMAGE_NAME}"
}
}
stage('环境检查') {
steps {
echo '🔍 检查构建环境...'
sh '''
echo "=== 系统信息 ==="
uname -a
echo "=== Go版本 ==="
go version
echo "=== Go环境 ==="
go env GOROOT GOPATH GOPROXY GOSUMDB CGO_ENABLED GOOS GOARCH
echo "=== Docker版本 ==="
docker --version
docker system info --format "{{.Name}}: {{.ServerVersion}}"
echo "=== 工作目录 ==="
pwd && ls -la
echo "=== 检查Go项目结构 ==="
if [ -f "go.mod" ]; then
echo "✅ 发现go.mod文件"
head -10 go.mod
else
echo "⚠️ 未发现go.mod文件请确保这是一个Go模块项目"
echo "建议运行: go mod init ${PROJECT_NAME}"
fi
echo "=== 检查SonarQube Scanner ==="
ls -la /var/jenkins_home/tools/hudson.plugins.sonar.SonarRunnerInstallation/ || echo "SonarQube Scanner未安装"
'''
echo "✅ 环境检查完成"
}
}
}
}
stage('📦 依赖管理') {
steps {
echo '📦 管理Go依赖...'
sh '''
echo "下载依赖..."
go mod download -x
echo "验证依赖..."
go mod verify
echo "整理依赖..."
go mod tidy
echo "检查依赖漏洞..."
if command -v govulncheck >/dev/null 2>&1; then
govulncheck ./... || echo "⚠️ 发现安全漏洞,请检查"
else
echo " govulncheck未安装跳过漏洞检查"
fi
echo "检查依赖更新..."
go list -u -m all | head -20 || true
echo "✅ 依赖管理完成"
'''
}
}
stage('🔍 代码质量') {
parallel {
stage('静态检查') {
steps {
echo '🔍 运行Go静态检查...'
sh '''
echo "运行go vet..."
go vet ./... || {
echo "❌ go vet 发现问题"
exit 1
}
echo "检查代码格式..."
UNFORMATTED=$(gofmt -l .)
if [ -n "$UNFORMATTED" ]; then
echo "❌ 以下文件格式不正确:"
echo "$UNFORMATTED"
echo "请运行: go fmt ./..."
exit 1
fi
echo "检查Go语法..."
if ! go build -o /dev/null ./...; then
echo "❌ Go语法检查失败"
exit 1
fi
echo "✅ 静态检查通过"
'''
}
}
stage('代码规范') {
steps {
echo '📝 检查代码规范...'
sh '''
echo "检查包导入顺序..."
if command -v goimports >/dev/null 2>&1; then
UNORGANIZED=$(goimports -l .)
if [ -n "$UNORGANIZED" ]; then
echo "⚠️ 以下文件导入顺序不规范:"
echo "$UNORGANIZED"
echo "建议运行: goimports -w ."
fi
fi
echo "检查代码复杂度..."
if command -v gocyclo >/dev/null 2>&1; then
gocyclo -over 15 . || echo "⚠️ 发现高复杂度函数"
fi
echo "✅ 代码规范检查完成"
'''
}
}
}
}
stage('🧪 测试') {
when {
not { params.SKIP_TESTS }
}
parallel {
stage('单元测试') {
steps {
echo '🧪 运行单元测试...'
sh '''
echo "创建测试结果目录..."
mkdir -p test-results reports
echo "运行测试..."
go test -v -coverprofile=coverage.out -covermode=atomic \\
-json ./... > test-results/test-report.json
# 生成JUnit格式的测试报告如果有go-junit-report
if command -v go-junit-report >/dev/null 2>&1; then
cat test-results/test-report.json | go-junit-report > test-results/junit.xml
fi
echo "生成覆盖率报告..."
go tool cover -html=coverage.out -o reports/coverage.html
go tool cover -func=coverage.out | tee reports/coverage-summary.txt
# 提取覆盖率百分比
COVERAGE=$(go tool cover -func=coverage.out | grep total | grep -oE '[0-9]+\\.[0-9]+%')
echo "📊 总覆盖率: $COVERAGE"
echo "$COVERAGE" > reports/coverage.txt
# 检查覆盖率阈值(可选)
COVERAGE_NUM=$(echo $COVERAGE | sed 's/%//')
if [ "${COVERAGE_NUM%.*}" -lt 50 ]; then
echo "⚠️ 代码覆盖率低于50%: $COVERAGE"
# 可以选择是否让构建失败
# exit 1
fi
echo "✅ 单元测试完成,覆盖率: $COVERAGE"
'''
}
post {
always {
script {
// 发布测试报告
if (fileExists('test-results/junit.xml')) {
publishTestResults testResultsPattern: 'test-results/junit.xml'
}
// 发布覆盖率报告
if (fileExists('reports/coverage.html')) {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'reports',
reportFiles: 'coverage.html',
reportName: '📊 Go Coverage Report',
reportTitles: 'Go代码覆盖率报告'
])
echo '✅ Go覆盖率报告已发布'
}
// 归档测试产物
archiveArtifacts artifacts: 'coverage.out,reports/*,test-results/*', allowEmptyArchive: true
}
}
}
}
stage('性能测试') {
steps {
echo '⚡ 运行性能测试...'
sh '''
echo "运行基准测试..."
mkdir -p reports
if go test -bench=. -benchmem ./... > reports/benchmark.txt 2>&1; then
echo "✅ 基准测试完成"
echo "📊 性能测试结果:"
cat reports/benchmark.txt | head -20
else
echo " 未发现基准测试或测试失败"
echo "建议添加基准测试函数,如: func BenchmarkMyFunction(b *testing.B)"
fi
'''
}
post {
always {
archiveArtifacts artifacts: 'reports/benchmark.txt', allowEmptyArchive: true
}
}
}
}
}
stage('📊 代码扫描') {
when {
not { params.SKIP_SONAR }
}
steps {
echo '📊 运行SonarQube代码扫描...'
script {
try {
withCredentials([string(credentialsId: "${SONAR_CREDENTIAL_ID}", variable: 'SONAR_TOKEN')]) {
withSonarQubeEnv('sonarQube') {
def scannerHome = tool name: 'sonarQube', type: 'hudson.plugins.sonar.SonarRunnerInstallation'
sh """
echo "=== SonarQube环境信息 ==="
echo "SONAR_HOST_URL: \$SONAR_HOST_URL"
echo "SONAR_SCANNER_HOME: ${scannerHome}"
echo "✅ 使用Jenkins管理的SonarQube Scanner"
# 运行SonarQube扫描
${scannerHome}/bin/sonar-scanner \\
-Dsonar.projectKey=${PROJECT_NAME} \\
-Dsonar.projectName="${PROJECT_NAME}" \\
-Dsonar.projectVersion=${BUILD_NUMBER} \\
-Dsonar.sources=. \\
-Dsonar.exclusions=**/*_test.go,**/vendor/**,**/*.mod,**/*.sum,**/reports/**,**/test-results/** \\
-Dsonar.tests=. \\
-Dsonar.test.inclusions=**/*_test.go \\
-Dsonar.test.exclusions=**/vendor/** \\
-Dsonar.go.coverage.reportPaths=coverage.out \\
-Dsonar.sourceEncoding=UTF-8
"""
}
}
echo "✅ SonarQube扫描完成"
} catch (Exception e) {
echo "⚠️ SonarQube扫描失败: ${e.getMessage()}"
if (env.DEPLOY_ENV == 'production') {
echo "❌ 生产环境必须通过代码质量检查"
throw e
} else {
echo "⚠️ 非生产环境,继续构建流程"
}
}
}
}
}
stage('🔨 构建') {
parallel {
stage('编译应用') {
steps {
echo '🔨 编译Go应用...'
sh '''
echo "开始编译..."
# 设置构建信息
BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S')
LDFLAGS="-w -s"
LDFLAGS="$LDFLAGS -X main.Version=${BUILD_NUMBER}"
LDFLAGS="$LDFLAGS -X main.GitCommit=${GIT_COMMIT_SHORT}"
LDFLAGS="$LDFLAGS -X main.BuildTime=${BUILD_TIME}"
echo "构建标志: $LDFLAGS"
# 编译
CGO_ENABLED=${CGO_ENABLED} GOOS=${GOOS} GOARCH=${GOARCH} \\
go build -ldflags="$LDFLAGS" -o ${PROJECT_NAME} .
echo "验证二进制文件..."
ls -lh ${PROJECT_NAME}
file ${PROJECT_NAME} || true
# 尝试获取版本信息
./${PROJECT_NAME} --version 2>/dev/null || echo " 应用不支持--version参数"
./${PROJECT_NAME} -h 2>/dev/null || echo " 应用不支持-h参数"
echo "✅ 编译完成: ${PROJECT_NAME}"
'''
}
post {
success {
archiveArtifacts artifacts: "${PROJECT_NAME}", fingerprint: true
}
}
}
stage('准备部署文件') {
steps {
echo '📝 准备部署配置文件...'
sh '''
# 创建部署脚本
cat > deploy.sh << 'EOF'
#!/bin/bash
set -e
# 部署配置
PROJECT_NAME="${PROJECT_NAME}"
CONTAINER_NAME="${CONTAINER_NAME}"
DEPLOY_PORT="${DEPLOY_PORT}"
APP_PORT="${APP_PORT}"
IMAGE_TAG="${IMAGE_TAG}"
DEPLOY_ENV="${DEPLOY_ENV}"
echo "🚀 部署 $PROJECT_NAME 到 $DEPLOY_ENV 环境..."
echo "容器名称: $CONTAINER_NAME"
echo "端口映射: $DEPLOY_PORT:$APP_PORT"
# 停止旧容器
echo "停止旧容器..."
docker stop $CONTAINER_NAME 2>/dev/null || true
docker rm $CONTAINER_NAME 2>/dev/null || true
# 启动新容器
echo "启动新容器..."
docker run -d \\
--name $CONTAINER_NAME \\
--restart unless-stopped \\
-p $DEPLOY_PORT:$APP_PORT \\
-e GIN_MODE=release \\
-e DEPLOY_ENV=$DEPLOY_ENV \\
-e TZ=Asia/Shanghai \\
--health-cmd="curl -f http://localhost:$APP_PORT/health || curl -f http://localhost:$APP_PORT/ping || exit 1" \\
--health-interval=30s \\
--health-timeout=3s \\
--health-retries=3 \\
$PROJECT_NAME:$IMAGE_TAG
echo "✅ 部署完成"
echo "🔗 访问地址: http://$(hostname -I | awk '{print \\$1}'):$DEPLOY_PORT"
echo "🏥 健康检查: docker inspect --format='{{.State.Health.Status}}' $CONTAINER_NAME"
EOF
chmod +x deploy.sh
echo "✅ 部署脚本准备完成"
'''
}
}
}
}
stage('🐳 Docker镜像') {
steps {
echo '🐳 构建Docker镜像...'
script {
// 检查是否需要重新构建
def shouldBuild = params.FORCE_REBUILD_IMAGE
if (!shouldBuild) {
def imageExists = sh(
script: "docker images -q ${IMAGE_NAME}:${IMAGE_TAG}",
returnStdout: true
).trim()
shouldBuild = imageExists.isEmpty()
}
if (shouldBuild) {
sh '''
echo "开始构建Docker镜像..."
# 确保二进制文件存在
if [ ! -f "${PROJECT_NAME}" ]; then
echo "❌ 二进制文件不存在: ${PROJECT_NAME}"
exit 1
fi
chmod +x ${PROJECT_NAME}
# 检查Dockerfile
if [ ! -f "Dockerfile" ]; then
echo "⚠️ 未找到Dockerfile创建默认Dockerfile..."
cat > Dockerfile << 'DOCKERFILE_EOF'
FROM alpine:latest
# 安装必要工具和设置时区
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \\
apk update && \\
apk add --no-cache curl ca-certificates tzdata && \\
rm -rf /var/cache/apk/*
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 创建非root用户
RUN addgroup -g 1000 appuser && adduser -u 1000 -G appuser -s /bin/sh -D appuser
# 设置工作目录
WORKDIR /app
# 复制应用程序
COPY ${PROJECT_NAME} .
# 设置权限
RUN mkdir -p /app/logs && \\
chown -R appuser:appuser /app && \\
chmod +x /app/${PROJECT_NAME}
# 切换到非root用户
USER appuser
# 暴露端口
EXPOSE ${APP_PORT}
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \\
CMD curl -f http://localhost:${APP_PORT}/health || curl -f http://localhost:${APP_PORT}/ping || exit 1
# 启动应用
ENTRYPOINT ["./${PROJECT_NAME}"]
DOCKERFILE_EOF
fi
# 构建镜像
docker build \\
--build-arg PROJECT_NAME=${PROJECT_NAME} \\
--build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \\
--build-arg VCS_REF=${GIT_COMMIT_SHORT} \\
--build-arg VERSION=${BUILD_NUMBER} \\
-t ${IMAGE_NAME}:${IMAGE_TAG} \\
-t ${IMAGE_NAME}:latest \\
.
echo "镜像构建完成"
docker images ${IMAGE_NAME}:${IMAGE_TAG}
# 检查镜像大小
IMAGE_SIZE=$(docker images --format "table {{.Size}}" ${IMAGE_NAME}:${IMAGE_TAG} | tail -n 1)
echo "📦 镜像大小: $IMAGE_SIZE"
'''
// 推送到镜像仓库(如果配置了)
if (env.DOCKER_REGISTRY && env.DOCKER_CREDENTIAL_ID) {
withCredentials([usernamePassword(credentialsId: env.DOCKER_CREDENTIAL_ID, usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh '''
echo "登录Docker仓库..."
echo "$DOCKER_PASS" | docker login $DOCKER_REGISTRY -u "$DOCKER_USER" --password-stdin
echo "推送镜像到仓库..."
docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${FULL_IMAGE_NAME}
docker push ${FULL_IMAGE_NAME}
docker tag ${IMAGE_NAME}:latest ${DOCKER_REGISTRY}/${IMAGE_NAME}:latest
docker push ${DOCKER_REGISTRY}/${IMAGE_NAME}:latest
echo "✅ 镜像推送完成: ${FULL_IMAGE_NAME}"
'''
}
}
} else {
echo "✅ 镜像已存在,跳过构建: ${IMAGE_NAME}:${IMAGE_TAG}"
}
}
}
}
stage('🧪 镜像测试') {
steps {
echo '🧪 测试Docker镜像...'
script {
def testContainerName = "test-${PROJECT_NAME}-${BUILD_NUMBER}"
def testPort = "808${BUILD_NUMBER % 100}"
try {
sh """
echo "启动测试容器..."
docker run -d --name ${testContainerName} \\
-p ${testPort}:${APP_PORT} \\
-e GIN_MODE=test \\
-e DEPLOY_ENV=test \\
${IMAGE_NAME}:${IMAGE_TAG}
echo "等待应用启动..."
sleep 15
echo "检查容器状态..."
docker ps | grep ${testContainerName}
docker logs ${testContainerName}
echo "测试应用端点..."
SUCCESS=false
for i in \$(seq 1 10); do
echo "尝试连接 \$i/10..."
if curl -f http://localhost:${testPort}/health; then
echo "✅ 健康检查端点响应正常"
SUCCESS=true
break
elif curl -f http://localhost:${testPort}/ping; then
echo "✅ Ping端点响应正常"
SUCCESS=true
break
elif curl -f http://localhost:${testPort}/; then
echo "✅ 根路径响应正常"
SUCCESS=true
break
else
echo "⚠️ 连接失败等待3秒后重试..."
sleep 3
fi
done
if [ "\$SUCCESS" = "false" ]; then
echo "❌ 镜像测试失败,无法连接到应用"
docker logs ${testContainerName}
exit 1
fi
echo "✅ 镜像测试通过"
"""
} finally {
// 清理测试容器
sh """
docker stop ${testContainerName} || true
docker rm ${testContainerName} || true
"""
}
}
}
}
stage('🚀 部署') {
when {
not {
anyOf {
expression { params.DEPLOY_ENV == 'skip' }
expression { env.DEPLOY_ENV == 'skip' }
}
}
}
steps {
echo "🚀 部署到${env.DEPLOY_ENV}环境..."
script {
sshagent([env.SSH_CREDENTIAL_ID]) {
sh '''
echo "📤 传输部署文件..."
# 保存镜像
docker save ${IMAGE_NAME}:${IMAGE_TAG} -o ${PROJECT_NAME}-${IMAGE_TAG}.tar
# 传输文件
scp -o StrictHostKeyChecking=no \\
${PROJECT_NAME}-${IMAGE_TAG}.tar \\
deploy.sh \\
root@${DEPLOY_SERVER}:/tmp/
# 远程部署
ssh -o StrictHostKeyChecking=no root@${DEPLOY_SERVER} << EOF
cd /tmp
echo "🔄 加载Docker镜像..."
docker load -i ${PROJECT_NAME}-${IMAGE_TAG}.tar
echo "🚀 执行部署..."
chmod +x deploy.sh
./deploy.sh
echo "🧹 清理临时文件..."
rm -f ${PROJECT_NAME}-${IMAGE_TAG}.tar deploy.sh
echo "✅ 部署完成"
echo "🔗 应用地址: http://${DEPLOY_SERVER}:${DEPLOY_PORT}"
EOF
# 清理本地临时文件
rm -f ${PROJECT_NAME}-${IMAGE_TAG}.tar
'''
}
}
}
}
stage('🏥 健康检查') {
when {
not {
anyOf {
expression { params.DEPLOY_ENV == 'skip' }
expression { env.DEPLOY_ENV == 'skip' }
}
}
}
steps {
echo '🏥 执行应用健康检查...'
script {
echo "等待应用启动..."
sleep(time: 30, unit: 'SECONDS')
def healthCheckPassed = false
def healthUrls = [
"http://${DEPLOY_SERVER}:${env.DEPLOY_PORT}/health",
"http://${DEPLOY_SERVER}:${env.DEPLOY_PORT}/ping",
"http://${DEPLOY_SERVER}:${env.DEPLOY_PORT}/"
]
for (int i = 1; i <= 5; i++) {
echo "第${i}次健康检查..."
for (String url : healthUrls) {
try {
def response = sh(
script: "curl -s -o /dev/null -w '%{http_code}' '${url}'",
returnStdout: true
).trim()
if (response == "200") {
echo "✅ 健康检查通过: ${url} (${response})"
healthCheckPassed = true
break
} else {
echo "⚠️ ${url} 响应状态码: ${response}"
}
} catch (Exception e) {
echo "⚠️ ${url} 检查异常: ${e.getMessage()}"
}
}
if (healthCheckPassed) {
break
}
if (i < 5) {
echo "等待30秒后重试..."
sleep(time: 30, unit: 'SECONDS')
}
}
if (!healthCheckPassed) {
def errorMsg = "❌ 健康检查失败,应用可能未正常启动"
echo errorMsg
if (env.DEPLOY_ENV == 'production') {
echo "🔄 生产环境健康检查失败,考虑回滚..."
// 这里可以添加自动回滚逻辑
error(errorMsg)
} else {
echo "⚠️ 非生产环境,允许继续但需要人工检查"
}
} else {
echo "✅ 应用部署成功并通过健康检查"
}
}
}
}
}
post {
always {
script {
echo '🧹 执行清理工作...'
// 清理构建产物
sh """
rm -f ${PROJECT_NAME} coverage.out deploy.sh
rm -rf test-results/ reports/
docker image prune -f || true
docker builder prune -f || true
"""
// 发布构建徽章
def badgeText = "${env.DEPLOY_ENV}: ${currentBuild.currentResult}"
def badgeColor = currentBuild.currentResult == 'SUCCESS' ? 'brightgreen' : 'red'
addBadge(icon: "success.gif", text: badgeText, color: badgeColor)
}
}
success {
script {
def deployInfo = env.DEPLOY_ENV != 'skip' ?
"🔗 访问: http://${DEPLOY_SERVER}:${env.DEPLOY_PORT}" :
"📦 仅构建,未部署"
def sonarInfo = params.SKIP_SONAR ?
"" :
"📊 SonarQube: ${SONAR_HOST_URL}/dashboard?id=${PROJECT_NAME}"
def message = """
🎉 ${PROJECT_NAME} 构建成功!
📋 项目: ${env.JOB_NAME}
🔢 构建号: ${env.BUILD_NUMBER}
🌿 分支: ${env.BRANCH_NAME ?: 'unknown'}
📝 提交: ${env.GIT_COMMIT_SHORT ?: 'unknown'}
🌍 环境: ${env.DEPLOY_ENV}
⏱️ 耗时: ${currentBuild.durationString}
🐳 镜像: ${FULL_IMAGE_NAME}
${deployInfo}
${sonarInfo}
📈 构建详情: ${env.BUILD_URL}
"""
echo message
// 发送通知(根据需要启用)
// slackSend(color: 'good', message: message)
// emailext(subject: "✅ ${PROJECT_NAME} 构建成功", body: message)
// dingTalk(robot: 'your-robot-id', message: message)
}
}
failure {
script {
def message = """
💥 ${PROJECT_NAME} 构建失败!
📋 项目: ${env.JOB_NAME}
🔢 构建号: ${env.BUILD_NUMBER}
🌿 分支: ${env.BRANCH_NAME ?: 'unknown'}
📝 提交: ${env.GIT_COMMIT_SHORT ?: 'unknown'}
🌍 环境: ${env.DEPLOY_ENV}
⏱️ 耗时: ${currentBuild.durationString}
🔗 日志: ${env.BUILD_URL}console
🛠️ 修复后请重新构建
"""
echo message
// 清理失败的资源
sh """
docker stop test-${PROJECT_NAME}-${BUILD_NUMBER} || true
docker rm test-${PROJECT_NAME}-${BUILD_NUMBER} || true
"""
// 发送失败通知
// slackSend(color: 'danger', message: message)
// emailext(subject: "❌ ${PROJECT_NAME} 构建失败", body: message)
}
}
cleanup {
script {
try {
cleanWs(deleteDirs: true)
echo "✅ 工作空间清理完成"
} catch (Exception e) {
echo "⚠️ 清理失败: ${e.getMessage()}"
}
}
}
}
}
// =================================================================
// 📖 使用文档:
//
// 1. 项目配置:
// - 修改 PROJECT_NAME 为你的项目名称
// - 修改 DEPLOY_SERVER 为你的服务器IP
// - 配置端口映射和凭据ID
//
// 2. Jenkins要求
// - Go工具名称为'go'
// - SonarQube Server名称为'sonarQube'
// - SonarQube Scanner名称为'sonarQube'
// - SSH凭据配置部署服务器的SSH Key
//
// 3. 项目要求:
// - 必须是Go模块项目有go.mod文件
// - 建议有健康检查端点:/health 或 /ping
// - 可选:添加基准测试函数
//
// 4. 分支策略:
// - main/master → 生产环境
// - staging/release → 预发布环境
// - 其他分支 → 开发环境
//
// 5. 手动参数:
// - DEPLOY_ENV选择部署环境
// - SKIP_TESTS跳过测试不推荐
// - SKIP_SONAR跳过代码扫描
// - FORCE_REBUILD_IMAGE强制重建镜像
// - CUSTOM_TAG自定义镜像标签
//
// =================================================================