// ================================================================= // 🚀 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:自定义镜像标签 // // =================================================================