/**
* 宇擎后端 yudao-server 自动化部署 Pipeline
*
* 功能:
* - 从 Git 仓库拉取代码
* - Maven 构建生成 JAR 包
* - 通过 SSH 传输 JAR 和 Dockerfile 到目标服务器
* - 在目标服务器构建 Docker 镜像并启动容器
*
* 前置条件:
* - Jenkins 已安装 Maven Integration Plugin
* - Jenkins 已安装 SSH Agent Plugin 或 Publish Over SSH Plugin
* - Jenkins 已配置 JDK 17+ 和 Maven 3.8+
* - 目标服务器已安装 Docker
* - 目标服务器已部署 MySQL 和 Redis
*/
pipeline {
agent any
// 工具配置
tools {
jdk 'jdk17' // 对应 Jenkins 全局工具配置中的 JDK 名称
maven 'maven3' // 对应 Jenkins 全局工具配置中的 Maven 名称
}
// 环境变量
environment {
// 目标服务器配置
REMOTE_HOST = '192.168.30.130'
REMOTE_USER = 'root'
REMOTE_PATH = '/data/app/yudao-server'
// SSH 凭据 ID(对应 Jenkins 凭据配置)
SSH_CRED = 'server-130-ssh'
// Docker 镜像配置
IMAGE_NAME = 'yudao-server'
IMAGE_TAG = "${BUILD_NUMBER}"
CONTAINER_NAME = 'yudao-server'
// 应用配置
APP_PORT = '48080'
SPRING_PROFILE = 'prod'
// JVM 配置(可根据服务器内存调整)
JAVA_OPTS = '-Xms1024m -Xmx4096m -Djava.security.egd=file:/dev/./urandom'
// 项目路径
PROJECT_DIR = 'yudao-server'
}
// 构建参数
parameters {
choice(
name: 'DEPLOY_ENV',
choices: ['prod', 'test'],
description: '选择部署环境'
)
string(
name: 'JAVA_OPTS_OVERRIDE',
defaultValue: '',
description: '覆盖默认的 JVM 参数(可选)'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: true,
description: '是否跳过单元测试'
)
booleanParam(
name: 'CLEAN_OLD_IMAGES',
defaultValue: true,
description: '是否清理旧的 Docker 镜像'
)
}
// 构建触发器
// 如果需要自动触发,可以取消注释以下配置:
// triggers {
// githubPush() // GitHub webhook 触发
// // 或使用 cron 定时触发
// // cron('H 2 * * *') // 每天凌晨2点构建
// }
stages {
/**
* 阶段1: 拉取代码
*/
stage('1. 拉取代码') {
steps {
echo '📥 开始拉取代码...'
checkout scm
script {
// 获取 Git 提交信息
env.GIT_COMMIT_SHORT = sh(
script: 'git rev-parse --short HEAD',
returnStdout: true
).trim()
env.GIT_COMMIT_MSG = sh(
script: 'git log -1 --pretty=%B',
returnStdout: true
).trim()
env.GIT_AUTHOR = sh(
script: 'git log -1 --pretty=%an',
returnStdout: true
).trim()
}
echo "✓ 代码拉取完成"
echo " 提交: ${env.GIT_COMMIT_SHORT}"
echo " 作者: ${env.GIT_AUTHOR}"
echo " 信息: ${env.GIT_COMMIT_MSG}"
}
}
/**
* 阶段2: 环境检查(可选,可跳过以加快构建)
*/
stage('2. 环境检查') {
when {
expression { env.BUILD_NUMBER == '1' || params.DEPLOY_ENV == 'test' }
}
steps {
echo '🔍 检查构建环境...'
sh '''
echo "Java 版本:"
java -version
echo ""
echo "Maven 版本:"
mvn -version
'''
echo "✓ 环境检查完成"
}
}
/**
* 阶段3: Maven 构建(优化:并行构建、增量编译)
*/
stage('3. Maven 构建') {
steps {
echo '🔨 开始 Maven 构建...'
script {
def skipTestsFlag = params.SKIP_TESTS ? '-DskipTests' : ''
// 优化:使用并行构建(-T 1C 表示使用所有可用 CPU 核心)
// 优化:跳过不必要的清理(增量构建更快)
// 优化:使用批处理模式减少输出
def mavenOpts = '-T 1C -B'
// 在项目根目录执行 Maven 构建
sh """
# 清理并构建
mvn clean package '-Dmaven.test.skip=true' '-Dskip.repackage=true' -pl ${PROJECT_DIR} -am -Pprod
# 单体模式 mvn clean package -am -pl yudao-server -Dmaven.test.skip=true -Dskip.repackage=true
# 微服务模式 mvn clean package -am -pl yudao-server -Dmaven.test.skip=true
# 验证构建产物
if [ -f "${PROJECT_DIR}/target/yudao-server.jar" ]; then
echo "✓ 构建成功"
echo " JAR 文件大小:"
ls -lh ${PROJECT_DIR}/target/yudao-server.jar
else
echo "✗ 构建失败:JAR 文件不存在"
exit 1
fi
"""
}
}
}
/**
* 阶段4: 准备部署文件
*/
stage('4. 准备部署文件') {
steps {
echo '📦 准备部署文件...'
sh """
# 创建部署目录
mkdir -p deploy
# 复制 JAR 文件
cp ${PROJECT_DIR}/target/yudao-server.jar deploy/
# 复制 Dockerfile
cp ${PROJECT_DIR}/Dockerfile deploy/
# 显示部署文件
echo "部署文件列表:"
ls -lh deploy/
"""
echo "✓ 部署文件准备完成"
}
}
/**
* 阶段5: 传输文件到目标服务器
*/
stage('5. 传输文件') {
steps {
echo '🚚 开始传输文件到目标服务器...'
sshagent(credentials: [env.SSH_CRED]) {
sh """
# 确保远程目录存在
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
${REMOTE_USER}@${REMOTE_HOST} \
"mkdir -p ${REMOTE_PATH}"
# 传输部署文件
echo "传输 JAR 文件..."
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
deploy/yudao-server.jar \
${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/
echo "传输 Dockerfile..."
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
deploy/Dockerfile \
${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/
echo "✓ 文件传输完成"
"""
}
}
}
/**
* 阶段6: 构建 Docker 镜像
*/
stage('6. 构建 Docker 镜像') {
steps {
echo '🐳 开始构建 Docker 镜像...'
sshagent(credentials: [env.SSH_CRED]) {
sh """
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
${REMOTE_USER}@${REMOTE_HOST} << 'ENDSSH'
cd ${REMOTE_PATH}
# 准备 Dockerfile 目录结构
mkdir -p target
mv yudao-server.jar target/
# 构建 Docker 镜像
echo "构建镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
# 同时打上 latest 标签
docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest
echo "✓ Docker 镜像构建完成"
docker images | grep ${IMAGE_NAME}
ENDSSH
"""
}
}
}
/**
* 阶段7: 部署容器
*/
stage('7. 部署容器') {
steps {
echo '🚀 开始部署容器...'
script {
def javaOpts = params.JAVA_OPTS_OVERRIDE ?: env.JAVA_OPTS
sshagent(credentials: [env.SSH_CRED]) {
sh """
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
${REMOTE_USER}@${REMOTE_HOST} << 'ENDSSH'
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 always \\
-p ${APP_PORT}:48080 \\
-e "SPRING_PROFILES_ACTIVE=${SPRING_PROFILE}" \\
-e "JAVA_OPTS=${javaOpts}" \\
-e "TZ=Asia/Shanghai" \\
-v /data/app/logs:/yudao-server/logs \\
${IMAGE_NAME}:${IMAGE_TAG}
echo "容器启动完成"
docker ps | grep ${CONTAINER_NAME}
ENDSSH
"""
}
}
}
}
/**
* 阶段8: 健康检查
*/
stage('8. 健康检查') {
steps {
echo '🏥 执行健康检查...'
sshagent(credentials: [env.SSH_CRED]) {
sh """
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
${REMOTE_USER}@${REMOTE_HOST} << 'ENDSSH'
echo "等待应用启动..."
# 优化:减少等待时间(最多等待 3 分钟,每 5 秒检查一次)
MAX_RETRIES=60
RETRY_INTERVAL=5
HEALTH_URL="http://127.0.0.1:${APP_PORT}/actuator/health"
# 先等待 10 秒,让容器有时间启动
sleep 15
for i in \$(seq 1 \$MAX_RETRIES); do
echo "健康检查尝试 \$i/\$MAX_RETRIES..."
# 检查容器是否在运行
if ! docker ps | grep -q ${CONTAINER_NAME}; then
echo "✗ 容器未运行!"
echo "容器日志:"
docker logs ${CONTAINER_NAME} --tail 50
exit 1
fi
# 检查健康端点
HTTP_CODE=\$(curl -s -o /dev/null -w "%{http_code}" \$HEALTH_URL 2>/dev/null || echo "000")
if [ "\$HTTP_CODE" = "200" ]; then
echo "✓ 健康检查通过!"
curl -s \$HEALTH_URL
echo ""
exit 0
fi
echo " HTTP 状态码: \$HTTP_CODE,等待 \$RETRY_INTERVAL 秒..."
sleep \$RETRY_INTERVAL
done
echo "✗ 健康检查失败!应用启动超时。"
echo "容器日志:"
docker logs ${CONTAINER_NAME} --tail 100
exit 1
ENDSSH
"""
}
}
}
/**
* 阶段9: 清理旧镜像(可选)
*/
stage('9. 清理旧镜像') {
when {
expression { params.CLEAN_OLD_IMAGES == true }
}
steps {
echo '🧹 清理旧的 Docker 镜像...'
sshagent(credentials: [env.SSH_CRED]) {
sh """
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
${REMOTE_USER}@${REMOTE_HOST} << 'ENDSSH'
# 保留最近 5 个版本的镜像
echo "清理旧镜像(保留最近 5 个版本)..."
docker images ${IMAGE_NAME} --format "{{.Tag}}" | \\
grep -v latest | \\
sort -rn | \\
tail -n +6 | \\
xargs -I {} docker rmi ${IMAGE_NAME}:{} 2>/dev/null || true
# 清理悬空镜像
docker image prune -f 2>/dev/null || true
echo "✓ 清理完成"
echo "当前镜像列表:"
docker images ${IMAGE_NAME}
ENDSSH
"""
}
}
}
}
// 构建后操作
post {
success {
echo '''
╔══════════════════════════════════════════════════════════════╗
║ ✅ 部署成功! ║
╠══════════════════════════════════════════════════════════════╣
║ 应用地址: http://192.168.30.130:48080 ║
║ Swagger: http://192.168.30.130:48080/swagger-ui/index.html ║
║ Actuator: http://192.168.30.130:48080/actuator ║
╚══════════════════════════════════════════════════════════════╝
'''
}
failure {
echo '''
╔══════════════════════════════════════════════════════════════╗
║ ❌ 部署失败! ║
╠══════════════════════════════════════════════════════════════╣
║ 请检查构建日志排查问题 ║
║ 常见问题: ║
║ 1. Maven 构建失败 - 检查代码编译错误 ║
║ 2. SSH 连接失败 - 检查凭据和网络 ║
║ 3. 容器启动失败 - 检查 Docker 日志 ║
║ 4. 健康检查失败 - 检查数据库/Redis 连接 ║
╚══════════════════════════════════════════════════════════════╝
'''
}
always {
// 清理工作空间中的临时文件
sh 'rm -rf deploy 2>/dev/null || true'
// 归档构建产物(可选)
// archiveArtifacts artifacts: 'yudao-server/target/*.jar', fingerprint: true
}
}
}