java_demo/Jenkinsfile
2025-06-27 11:23:18 +08:00

581 lines
23 KiB
Groovy
Raw Blame History

This file contains ambiguous Unicode characters

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.

pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 60, unit: 'MINUTES')
timestamps()
}
environment {
// 目标服务器配置
DEPLOY_SERVER = '116.62.163.84'
// Docker相关环境变量
IMAGE_NAME = 'jenkins-demo'
IMAGE_TAG = "${BUILD_NUMBER}"
// SonarQube配置
SONAR_HOST_URL = 'http://116.62.163.84:15010'
SONAR_PROJECT_KEY = 'jenkins-demo'
SONAR_TOKEN = 'squ_7e4217cabd0faae6f3b8ee359b3b8e2ac52eb69a'
// 构建结果通知配置
WEBHOOK_URL = 'http://116.62.163.84:15030/api/build-result' // 替换为你的接收服务器URL
WEBHOOK_SECRET = 'your-webhook-secret' // 可选的webhook密钥
}
tools {
maven 'Maven-3.9.6'
}
stages {
stage('Checkout') {
steps {
echo '🔄 开始检出代码...'
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(
script: "git rev-parse --short HEAD",
returnStdout: true
).trim()
// 获取仓库URL
env.REPO_URL = sh(
script: "git config --get remote.origin.url",
returnStdout: true
).trim()
}
echo "📋 Git提交ID: ${env.GIT_COMMIT_SHORT}"
echo "📦 仓库URL: ${env.REPO_URL}"
}
}
stage('环境检查') {
steps {
echo '🔍 检查构建环境...'
script {
sh '''
echo "=== Java版本 ==="
java -version
echo "=== Maven版本 ==="
mvn -version
echo "=== Docker版本 ==="
docker --version
echo "=== 工作目录 ==="
pwd && ls -la
'''
env.MVN_CMD = 'mvn'
echo "✅ 构建环境检查完成"
}
}
}
stage('编译') {
steps {
echo '🔨 开始编译项目...'
sh "mvn clean compile -DskipTests=true"
}
}
stage('单元测试') {
steps {
echo '🧪 运行单元测试...'
sh "mvn test"
}
post {
always {
script {
if (fileExists('target/surefire-reports/*.xml')) {
publishTestResults testResultsPattern: 'target/surefire-reports/*.xml'
}
if (fileExists('target/site/jacoco/jacoco.xml')) {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target/site/jacoco',
reportFiles: 'index.html',
reportName: 'JaCoCo Coverage Report'
])
echo '✅ JaCoCo覆盖率报告已发布'
}
}
}
}
}
stage('代码质量扫描') {
steps {
echo '🔍 运行SonarQube代码扫描...'
script {
try {
sh """
mvn sonar:sonar \
-Dsonar.projectKey=${SONAR_PROJECT_KEY} \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.login=${SONAR_TOKEN} \
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
"""
echo "✅ SonarQube代码扫描完成"
} catch (Exception e) {
echo "⚠️ SonarQube扫描失败继续构建流程: ${e.getMessage()}"
}
}
}
}
stage('打包') {
steps {
echo '📦 开始打包应用程序...'
sh "mvn package -DskipTests=true"
}
post {
success {
script {
if (fileExists('target/*.jar')) {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
}
}
}
stage('构建Docker镜像') {
steps {
echo '🐳 构建Docker镜像...'
script {
try {
// 清理旧镜像
sh 'docker image prune -f || true'
echo "开始构建Docker镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
// 使用传统Docker构建避免buildx的复杂性
timeout(time: 20, unit: 'MINUTES') {
sh """
# 使用传统Docker构建
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} -t ${IMAGE_NAME}:latest .
echo "✅ Docker镜像构建完成"
# 验证镜像
docker images ${IMAGE_NAME}:${IMAGE_TAG}
"""
}
} catch (Exception e) {
echo "❌ Docker构建失败: ${e.getMessage()}"
// 显示更多调试信息
sh '''
echo "=== Docker系统信息 ==="
docker system info
echo "=== Docker磁盘使用情况 ==="
docker system df
echo "=== 当前镜像列表 ==="
docker images
'''
throw e
}
}
}
}
stage('镜像测试') {
steps {
echo '🧪 测试Docker镜像...'
script {
try {
// 启动测试容器
sh """
echo "启动测试容器..."
docker run -d --name test-${BUILD_NUMBER} -p 8081:8080 ${IMAGE_NAME}:${IMAGE_TAG}
echo "等待应用启动..."
sleep 30
echo "检查容器状态..."
docker ps | grep test-${BUILD_NUMBER} || true
docker logs test-${BUILD_NUMBER} || true
echo "测试健康检查端点..."
for i in \$(seq 1 5); do
echo "尝试连接 \$i/5..."
if curl -f http://localhost:8081/actuator/health; then
echo "✅ 健康检查通过"
break
else
echo "⚠️ 连接失败等待5秒后重试..."
sleep 5
fi
if [ \$i -eq 5 ]; then
echo "❌ 镜像测试失败,但不中断构建"
docker logs test-${BUILD_NUMBER}
exit 1
fi
done
"""
echo "✅ 镜像测试完成"
} catch (Exception e) {
echo "⚠️ 镜像测试失败: ${e.getMessage()}"
echo "📋 这可能是网络或启动时间问题,继续构建流程"
} finally {
// 清理测试容器
sh """
docker stop test-${BUILD_NUMBER} || true
docker rm test-${BUILD_NUMBER} || true
"""
}
}
}
}
stage('部署应用') {
steps {
echo '🚀 部署应用到服务器...'
script {
// 根据分支决定部署端口和配置
def deployPort = '15020'
def containerName = 'jenkins-demo'
def springProfile = 'prod'
if (env.BRANCH_NAME == 'develop' || env.BRANCH_NAME?.startsWith('feature/')) {
deployPort = '15021' // 测试环境使用不同端口
containerName = 'jenkins-demo-test'
springProfile = 'test'
}
// 传输镜像并部署
sshagent(['deploy-server-ssh-key']) {
sh """
# 保存Docker镜像为tar文件
echo "📤 保存镜像为文件..."
docker save ${IMAGE_NAME}:${IMAGE_TAG} -o ${IMAGE_NAME}-${IMAGE_TAG}.tar
# 传输镜像文件到目标服务器
echo "📤 传输镜像到服务器..."
scp -o StrictHostKeyChecking=no ${IMAGE_NAME}-${IMAGE_TAG}.tar root@${DEPLOY_SERVER}:/tmp/
# 在目标服务器上部署
ssh -o StrictHostKeyChecking=no root@${DEPLOY_SERVER} << 'EOF'
echo "🔄 在服务器上部署应用..."
# 加载Docker镜像
docker load -i /tmp/${IMAGE_NAME}-${IMAGE_TAG}.tar
docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest
# 停止并删除现有容器
docker stop ${containerName} || true
docker rm ${containerName} || true
# 运行新容器
docker run -d --name ${containerName} \\
-p ${deployPort}:8080 \\
--restart unless-stopped \\
-e SPRING_PROFILES_ACTIVE=${springProfile} \\
-e JAVA_OPTS="-Xms256m -Xmx512m" \\
${IMAGE_NAME}:${IMAGE_TAG}
# 清理临时文件
rm -f /tmp/${IMAGE_NAME}-${IMAGE_TAG}.tar
echo "✅ 应用部署完成,端口: ${deployPort}"
echo "🔗 访问地址: http://${DEPLOY_SERVER}:${deployPort}"
EOF
# 清理本地临时文件
rm -f ${IMAGE_NAME}-${IMAGE_TAG}.tar
"""
}
echo "✅ 应用部署完成!"
}
}
}
stage('健康检查') {
steps {
echo '🏥 执行应用健康检查...'
script {
// 等待应用启动
sleep(time: 30, unit: 'SECONDS')
def deployPort = '15020'
if (env.BRANCH_NAME == 'develop' || env.BRANCH_NAME?.startsWith('feature/')) {
deployPort = '15021'
}
try {
def healthCheckUrl = "http://${DEPLOY_SERVER}:${deployPort}/actuator/health"
def response = sh(
script: "curl -s -o /dev/null -w '%{http_code}' ${healthCheckUrl} || echo '000'",
returnStdout: true
).trim()
if (response == "200") {
echo "✅ 应用健康检查通过"
} else {
echo "⚠️ 应用健康检查失败HTTP状态码: ${response}"
echo "🔄 应用可能还在启动中..."
// 再等待一段时间重试
sleep(time: 30, unit: 'SECONDS')
response = sh(
script: "curl -s -o /dev/null -w '%{http_code}' ${healthCheckUrl} || echo '000'",
returnStdout: true
).trim()
if (response == "200") {
echo "✅ 重试后健康检查通过"
} else {
echo "⚠️ 健康检查仍未通过,但不阻止构建"
}
}
} catch (Exception e) {
echo "⚠️ 健康检查异常: ${e.getMessage()}"
}
}
}
}
}
post {
always {
script {
echo '🧹 清理工作空间...'
// 发送构建结果通知
sendBuildResult()
try {
// 清理Docker资源
sh '''
# 清理未使用的镜像
docker image prune -f || true
# 清理构建缓存
docker builder prune -f || true
# 清理测试容器
docker stop test-${BUILD_NUMBER} || true
docker rm test-${BUILD_NUMBER} || true
'''
} catch (Exception e) {
echo "⚠️ 清理失败: ${e.getMessage()}"
}
}
}
success {
script {
echo '✅ 流水线执行成功!'
def deployPort = '15020'
def environment = 'production'
if (env.BRANCH_NAME == 'develop' || env.BRANCH_NAME?.startsWith('feature/')) {
deployPort = '15021'
environment = 'test'
}
def message = """
🎉 Jenkins构建成功
📋 项目: ${env.JOB_NAME}
🔢 构建号: ${env.BUILD_NUMBER}
🌿 分支: ${env.BRANCH_NAME ?: 'unknown'}
📝 提交: ${env.GIT_COMMIT_SHORT ?: 'unknown'}
⏱️ 持续时间: ${currentBuild.durationString}
🔗 构建链接: ${env.BUILD_URL}
🌐 应用地址: http://${DEPLOY_SERVER}:${deployPort}
📦 仓库链接: ${env.REPO_URL ?: 'unknown'}
🏷️ 环境: ${environment}
"""
echo message
// 发送成功通知
sendBuildResult('SUCCESS')
}
}
failure {
script {
echo '❌ 流水线执行失败!'
def message = """
💥 Jenkins构建失败
📋 项目: ${env.JOB_NAME}
🔢 构建号: ${env.BUILD_NUMBER}
🌿 分支: ${env.BRANCH_NAME ?: 'unknown'}
📝 提交: ${env.GIT_COMMIT_SHORT ?: 'unknown'}
⏱️ 持续时间: ${currentBuild.durationString}
🔗 构建链接: ${env.BUILD_URL}
📄 查看日志: ${env.BUILD_URL}console
📦 仓库链接: ${env.REPO_URL ?: 'unknown'}
"""
echo message
// 发送失败通知
sendBuildResult('FAILURE')
}
}
cleanup {
script {
try {
// 清理工作空间
cleanWs()
echo "✅ 清理完成"
} catch (Exception e) {
echo "⚠️ 清理失败: ${e.getMessage()}"
}
}
}
}
}
// 发送构建结果的函数
def sendBuildResult(String overrideResult = null) {
script {
try {
def buildResult = overrideResult ?: currentBuild.result ?: 'SUCCESS'
def deployPort = '15020'
def environment = 'production'
def applicationUrl = "http://${DEPLOY_SERVER}:${deployPort}"
if (env.BRANCH_NAME == 'develop' || env.BRANCH_NAME?.startsWith('feature/')) {
deployPort = '15021'
environment = 'test'
applicationUrl = "http://${DEPLOY_SERVER}:${deployPort}"
}
def payload = [
// 基本信息
jobName: env.JOB_NAME,
buildNumber: env.BUILD_NUMBER,
result: buildResult,
status: buildResult.toLowerCase(),
// Git信息
branch: env.BRANCH_NAME ?: 'unknown',
gitCommit: env.GIT_COMMIT_SHORT ?: 'unknown',
gitCommitFull: env.GIT_COMMIT ?: 'unknown',
repoUrl: env.REPO_URL ?: 'unknown', // 新增仓库链接字段
// 时间信息
startTime: new Date(currentBuild.startTimeInMillis).format("yyyy-MM-dd HH:mm:ss"),
duration: currentBuild.durationString ?: 'unknown',
durationMs: currentBuild.duration ?: 0,
timestamp: new Date().format("yyyy-MM-dd HH:mm:ss"),
// URL信息
buildUrl: env.BUILD_URL,
consoleUrl: "${env.BUILD_URL}console",
applicationUrl: applicationUrl,
// 部署信息
environment: environment,
deployServer: DEPLOY_SERVER,
deployPort: deployPort,
imageName: IMAGE_NAME,
imageTag: IMAGE_TAG,
// 扩展信息
jenkinsUrl: env.JENKINS_URL,
workspace: env.WORKSPACE,
buildUser: env.BUILD_USER ?: 'system',
nodeLabel: env.NODE_LABEL ?: 'unknown',
// Java项目特有信息
projectType: 'java',
mavenVersion: sh(script: 'mvn -version | head -1', returnStdout: true).trim(),
javaVersion: sh(script: 'java -version 2>&1 | head -1', returnStdout: true).trim()
]
echo "📤 发送构建结果到: ${WEBHOOK_URL}"
echo "📋 构建结果: ${buildResult}"
// 方式1: 使用HTTP Request Plugin推荐
try {
def headers = [
[name: 'Content-Type', value: 'application/json'],
[name: 'User-Agent', value: 'Jenkins-Pipeline'],
[name: 'X-Jenkins-Build', value: env.BUILD_NUMBER]
]
// 如果设置了webhook密钥添加签名头
if (WEBHOOK_SECRET && WEBHOOK_SECRET != 'your-webhook-secret') {
headers.add([name: 'X-Webhook-Secret', value: WEBHOOK_SECRET])
}
def response = httpRequest(
httpMode: 'POST',
url: WEBHOOK_URL,
contentType: 'APPLICATION_JSON',
requestBody: groovy.json.JsonOutput.toJson(payload),
customHeaders: headers,
validResponseCodes: '200:299',
timeout: 30,
ignoreSslErrors: true
)
echo "✅ HTTP Request成功响应状态: ${response.status}"
if (response.content) {
echo "📝 响应内容: ${response.content}"
}
} catch (Exception httpException) {
echo "⚠️ HTTP Request Plugin失败: ${httpException.getMessage()}"
echo "🔄 尝试使用curl备用方案..."
// 方式2: 使用curl作为备用方案
try {
def jsonPayload = groovy.json.JsonOutput.toJson(payload)
def curlHeaders = "-H 'Content-Type: application/json' -H 'User-Agent: Jenkins-Pipeline' -H 'X-Jenkins-Build: ${env.BUILD_NUMBER}'"
if (WEBHOOK_SECRET && WEBHOOK_SECRET != 'your-webhook-secret') {
curlHeaders += " -H 'X-Webhook-Secret: ${WEBHOOK_SECRET}'"
}
def curlResponse = sh(
script: """
curl -X POST \\
${curlHeaders} \\
--data '${jsonPayload}' \\
--connect-timeout 30 \\
--max-time 60 \\
--silent \\
--show-error \\
--write-out "HTTP_STATUS:%{http_code}" \\
"${WEBHOOK_URL}"
""",
returnStdout: true
).trim()
if (curlResponse.contains('HTTP_STATUS:2')) {
echo "✅ curl请求成功"
echo "📝 响应: ${curlResponse}"
} else {
echo "⚠️ curl请求可能失败: ${curlResponse}"
}
} catch (Exception curlException) {
echo "❌ curl备用方案也失败: ${curlException.getMessage()}"
echo "⚠️ 无法发送构建结果通知,但不影响构建流程"
}
}
} catch (Exception e) {
echo "❌ 发送构建结果失败: ${e.getMessage()}"
echo "⚠️ 构建结果通知失败,但不影响构建流程"
}
}
}