SpringBoot 应用自动部署脚本
本文档详细说明了 SpringBoot 应用的部署脚本和执行脚本的使用方法,包括在不同环境下的部署策略。
每次改完代码,你是不是都要经历这样一套流程:手动 mvn package、打开 FTP 工具上传 JAR 包、SSH 登上服务器、停掉旧服务、启动新服务、再看看日志有没有报错?整套操作下来少说五分钟,一天部署个三四次就是二十分钟纯机械劳动。
更要命的是,手动操作总会犯错——忘了停旧进程导致端口冲突、传错了 JAR 包版本、配置文件没跟着更新。这些”低级失误”往往发生在你最忙、最没耐心的时候。
所以,把部署流程脚本化是必要的。下面分享两个我在实际项目中使用的脚本:一个管理应用生命周期的 start.sh,一个实现一键部署的 deploy.sh。
应用启动脚本 (start.sh)
这个脚本负责应用在服务器上的启动、停止、重启和状态查看。它部署在远程服务器上,是整个自动化流程的基础。
脚本内容
start.sh
#!/bin/bash
# 应用名称
APP_NAME="yg-admin"
# JAR包路径
JAR_FILE="yg-admin.jar"
# 配置文件路径
CONFIG_FILE="application-prod.yml"
# 激活的配置文件
PROFILE="prod"
# JVM参数
JVM_OPTS="-Dfile.encoding=UTF-8"
# 获取应用PID
get_pid() {
PID=$(pgrep -f "$JAR_FILE")
echo $PID
}
# 检查应用是否运行
is_running() {
PID=$(get_pid)
if [ -n "$PID" ]; then
return 0 # 正在运行
else
return 1 # 未运行
fi
}
# 启动应用
start() {
echo "正在启动 $APP_NAME..."
# 检查应用是否已经在运行
if is_running; then
PID=$(get_pid)
echo "$APP_NAME 已经在运行中,PID: $PID"
return 1
fi
# 检查JAR文件是否存在
if [ ! -f "$JAR_FILE" ]; then
echo "错误: $JAR_FILE 文件不存在!"
return 1
fi
# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
echo "警告: $CONFIG_FILE 配置文件不存在!"
read -p "是否继续启动?(y/n): " CONTINUE
if [ "$CONTINUE" != "y" ]; then
echo "启动已取消"
return 1
fi
fi
# 启动应用
nohup java $JVM_OPTS -jar $JAR_FILE --spring.config.additional-location=file:$CONFIG_FILE --spring.profiles.active=$PROFILE >/dev/null 2>&1 &
# 记录PID
PID=$!
echo "应用正在启动,PID: $PID"
# 等待几秒,确认应用是否成功启动
sleep 5
if ps -p $PID > /dev/null; then
echo "$APP_NAME 启动成功!"
return 0
else
echo "$APP_NAME 启动失败,请检查日志!"
return 1
fi
}
# 停止应用
stop() {
echo "正在停止 $APP_NAME..."
# 检查应用是否在运行
if ! is_running; then
echo "$APP_NAME 没有运行!"
return 0
fi
# 获取PID
PID=$(get_pid)
# 尝试优雅停止
echo "正在停止进程 PID: $PID..."
kill -15 $PID
# 等待应用停止
TIMEOUT=30
for ((i=0; i<TIMEOUT; i++)); do
if ! ps -p $PID > /dev/null; then
echo "$APP_NAME 已成功停止!"
return 0
fi
sleep 1
echo -n "."
done
# 如果超时,强制终止
echo ""
echo "无法优雅停止应用,正在强制终止..."
kill -9 $PID
# 再次检查
sleep 2
if ! ps -p $PID > /dev/null; then
echo "$APP_NAME 已被强制终止!"
return 0
else
echo "错误:无法终止 $APP_NAME!"
return 1
fi
}
# 重启应用
restart() {
echo "正在重启 $APP_NAME..."
# 先停止应用
if is_running; then
stop
# 等待一段时间确保完全停止
sleep 5
fi
# 然后启动应用
start
}
# 显示应用状态
status() {
if is_running; then
PID=$(get_pid)
echo "$APP_NAME 正在运行,PID: $PID"
# 显示运行时间
RUNTIME=$(ps -o etime= -p $PID)
echo "运行时间: $RUNTIME"
else
echo "$APP_NAME 没有运行"
fi
}
# 帮助信息
show_help() {
echo "使用方法: $0 {start|stop|restart|status}"
echo " start - 启动应用"
echo " stop - 停止应用"
echo " restart - 重启应用"
echo " status - 显示应用状态"
}
# 主函数
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
*)
show_help
exit 1
;;
esac
exit $?
设计要点
这个脚本有几个值得注意的细节:
优雅停止优先:stop 函数先发 kill -15(SIGTERM),给应用 30 秒时间处理正在进行的请求和释放资源。只有超时了才会 kill -9 强制终止。这在生产环境中非常重要——粗暴地杀进程可能导致数据不一致。
启动前的安全检查:启动前会检查应用是否已经在运行(防止重复启动)、JAR 文件是否存在、配置文件是否就位。这些看似简单的检查,能避免大量部署事故。
配置参数集中管理:应用名、JAR 路径、配置文件等全部在脚本顶部声明为变量。换项目时只需要改这几行,脚本的其余部分完全复用。
应用部署脚本 (deploy.sh)
有了 start.sh 管理服务器上的应用生命周期,接下来需要一个在本地执行的脚本,把”构建 - 上传 - 重启”这条链路串起来。
脚本内容
deploy.sh
#!/bin/bash
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
# 定义颜色
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
# 定义服务器信息
SERVER_ALIAS="yg_iot"
SERVER_PATH="/home/admin/backend"
JAR_NAME="yg-admin.jar"
echo -e "${YELLOW}开始构建项目...${NC}"
mvn clean && mvn package
# 检查Maven构建是否成功
if [ $? -ne 0 ]; then
echo -e "${RED}Maven构建失败!部署终止。${NC}"
exit 1
fi
echo -e "${YELLOW}构建成功,准备部署到服务器...${NC}"
# 删除服务器上的旧JAR包
echo -e "${YELLOW}删除服务器上的旧JAR包...${NC}"
ssh $SERVER_ALIAS "cd $SERVER_PATH && rm -f $JAR_NAME"
# 上传新的JAR包
echo -e "${YELLOW}上传新的JAR包到服务器...${NC}"
cd yg-admin/target && scp $JAR_NAME $SERVER_ALIAS:$SERVER_PATH/
# 检查上传是否成功
if [ $? -ne 0 ]; then
echo -e "${RED}JAR包上传失败!部署终止。${NC}"
exit 1
fi
# 重启应用
echo -e "${YELLOW}重启应用...${NC}"
ssh $SERVER_ALIAS "cd $SERVER_PATH && sh start.sh restart"
# 检查重启是否成功
if [ $? -ne 0 ]; then
echo -e "${RED}应用重启失败!请检查服务器日志。${NC}"
exit 1
else
echo -e "${GREEN}部署完成!应用已成功重启。${NC}"
fi
# 显示应用状态
echo -e "${YELLOW}获取应用状态...${NC}"
ssh $SERVER_ALIAS "cd $SERVER_PATH && sh start.sh status"
echo -e "${GREEN}部署流程全部完成!${NC}"
工作流程
整个部署流程是一条直线:Maven 构建 -> 删除旧包 -> 上传新包 -> 重启服务 -> 确认状态。每一步都有错误检查,任何一环失败都会立即终止并给出红色提示,不会带着错误继续往下走。
这里有个前提条件:你需要在 ~/.ssh/config 中配置好服务器别名(如 yg_iot),并且设置了免密登录。否则每次 SSH 和 SCP 都会弹出密码输入框,自动化也就无从谈起了。
Windows 环境怎么办?
不是所有团队都用 Mac 或 Linux 开发。如果你在 Windows 上工作,有几种方案可以选择。
1. 使用 Git Bash 或 WSL
最省事的方案。Git Bash 或 WSL 提供了 Linux 兼容的 Shell 环境,上面的脚本几乎不用改,只需要调整 Java 路径:
#!/bin/bash
# Windows下设置Java环境变量(使用具体路径)
export JAVA_HOME="C:/Program Files/Java/jdk1.8.0_xxx"
export PATH=$JAVA_HOME/bin:$PATH
# 其余脚本保持不变
...
2. 使用 PowerShell 脚本
如果团队更习惯 Windows 原生工具,PowerShell 是更好的选择。语法和 Bash 不同,但逻辑完全一致:
deploy.ps1
# PowerShell脚本 - deploy.ps1
# 设置Java环境
$env:JAVA_HOME = "C:\Program Files\Java\jdk1.8.0_xxx"
$env:Path = "$env:JAVA_HOME\bin;$env:Path"
# 定义颜色
$GREEN = @{ForegroundColor = "Green"}
$RED = @{ForegroundColor = "Red"}
$YELLOW = @{ForegroundColor = "Yellow"}
# 定义服务器信息
$SERVER_ALIAS = "yg_iot"
$SERVER_PATH = "/home/admin/backend"
$JAR_NAME = "yg-admin.jar"
Write-Host "开始构建项目..." @YELLOW
# 清理并打包项目
mvn clean
if ($LASTEXITCODE -ne 0) {
Write-Host "Maven清理失败!" @RED
exit 1
}
mvn package
if ($LASTEXITCODE -ne 0) {
Write-Host "Maven构建失败!部署终止。" @RED
exit 1
}
Write-Host "构建成功,准备部署到服务器..." @YELLOW
# 使用SSH命令(需要安装OpenSSH客户端)
# 删除服务器上的旧JAR包
Write-Host "删除服务器上的旧JAR包..." @YELLOW
ssh $SERVER_ALIAS "cd $SERVER_PATH && rm -f $JAR_NAME"
# 上传新的JAR包
Write-Host "上传新的JAR包到服务器..." @YELLOW
Set-Location -Path "yg-admin\target"
scp $JAR_NAME "${SERVER_ALIAS}:${SERVER_PATH}/"
if ($LASTEXITCODE -ne 0) {
Write-Host "JAR包上传失败!部署终止。" @RED
exit 1
}
# 重启应用
Write-Host "重启应用..." @YELLOW
ssh $SERVER_ALIAS "cd $SERVER_PATH && sh start.sh restart"
if ($LASTEXITCODE -ne 0) {
Write-Host "应用重启失败!请检查服务器日志。" @RED
exit 1
} else {
Write-Host "部署完成!应用已成功重启。" @GREEN
}
# 显示应用状态
Write-Host "获取应用状态..." @YELLOW
ssh $SERVER_ALIAS "cd $SERVER_PATH && sh start.sh status"
Write-Host "部署流程全部完成!" @GREEN
3. 使用批处理脚本
对于一些老旧环境或者不方便安装 PowerShell 的场景,传统的 batch 脚本也能胜任。不过需要用 PuTTY 工具集来处理 SSH 连接:
deploy.batch
@echo off
setlocal enabledelayedexpansion
:: 设置Java环境
set "JAVA_HOME=C:\Program Files\Java\jdk1.8.0_xxx"
set "PATH=%JAVA_HOME%\bin;%PATH%"
:: 定义服务器信息
set "SERVER_ALIAS=yg_iot"
set "SERVER_PATH=/home/admin/backend"
set "JAR_NAME=yg-admin.jar"
echo 开始构建项目...
:: 清理并打包项目
call mvn clean
if %ERRORLEVEL% neq 0 (
echo Maven清理失败!
exit /b 1
)
call mvn package
if %ERRORLEVEL% neq 0 (
echo Maven构建失败!部署终止。
exit /b 1
)
echo 构建成功,准备部署到服务器...
:: 使用PuTTY的pscp和plink工具进行SSH操作
echo 删除服务器上的旧JAR包...
plink %SERVER_ALIAS% "cd %SERVER_PATH% && rm -f %JAR_NAME%"
:: 上传新的JAR包
echo 上传新的JAR包到服务器...
cd yg-admin\target
pscp %JAR_NAME% %SERVER_ALIAS%:%SERVER_PATH%/
if %ERRORLEVEL% neq 0 (
echo JAR包上传失败!部署终止。
exit /b 1
)
:: 重启应用
echo 重启应用...
plink %SERVER_ALIAS% "cd %SERVER_PATH% && sh start.sh restart"
if %ERRORLEVEL% neq 0 (
echo 应用重启失败!请检查服务器日志。
exit /b 1
) else (
echo 部署完成!应用已成功重启。
)
:: 显示应用状态
echo 获取应用状态...
plink %SERVER_ALIAS% "cd %SERVER_PATH% && sh start.sh status"
echo 部署流程全部完成!
备注: 批处理脚本需要安装 PuTTY 工具集(包含 plink 和 pscp)并配置好 SSH 连接。
生产环境还需要考虑什么?
上面的脚本覆盖了基本的部署场景,但在真正的生产环境中,你可能还需要处理以下问题。
1. 端口占用问题
偶尔会遇到旧进程没有完全退出,端口还被占着的情况。加一个端口检查函数能提前发现问题:
check_port() {
PORT=$1
if netstat -tuln | grep -q ":$PORT "; then
echo "端口 $PORT 已被占用!"
return 1
fi
return 0
}
# 使用示例
if ! check_port 8080; then
echo "尝试使用备用端口 8081..."
PORT=8081
if ! check_port $PORT; then
echo "备用端口也被占用,启动失败!"
exit 1
fi
fi
2. JVM 内存设置
默认的 JVM 参数在生产中几乎肯定不够用。根据服务器资源和应用特性,合理配置内存参数:
# JVM内存设置示例
JVM_OPTS="-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m"
3. 进程管理
脚本方案适合中小项目。如果应用需要开机自启、崩溃自动重启等能力,建议引入专业的进程管理工具:
# Supervisor配置示例 (/etc/supervisor/conf.d/myapp.conf)
[program:myapp]
command=java -jar /path/to/app.jar
directory=/path/to
user=appuser
autostart=true
autorestart=true
startsecs=10
startretries=3
stdout_logfile=/var/log/myapp/stdout.log
stderr_logfile=/var/log/myapp/stderr.log
4. 备份与回滚
没有回滚能力的部署流程是不完整的。万一新版本有 Bug,你需要能迅速切回上一个版本:
# 备份当前版本
backup() {
echo "备份当前版本..."
BACKUP_DIR="backups"
mkdir -p $BACKUP_DIR
if [ -f "$JAR_FILE" ]; then
TIMESTAMP=$(date +"%Y%m%d%H%M%S")
cp "$JAR_FILE" "$BACKUP_DIR/$APP_NAME-$TIMESTAMP.jar"
echo "备份完成: $BACKUP_DIR/$APP_NAME-$TIMESTAMP.jar"
# 记录当前备份版本
echo "$BACKUP_DIR/$APP_NAME-$TIMESTAMP.jar" > "$BACKUP_DIR/latest_backup.txt"
else
echo "没有找到当前版本,跳过备份"
fi
}
# 回滚到上一版本
rollback() {
echo "正在回滚到上一版本..."
BACKUP_DIR="backups"
LATEST_BACKUP_FILE=$(cat "$BACKUP_DIR/latest_backup.txt" 2>/dev/null)
if [ -f "$LATEST_BACKUP_FILE" ]; then
echo "找到备份版本: $LATEST_BACKUP_FILE"
cp "$LATEST_BACKUP_FILE" "$JAR_FILE"
echo "回滚完成,准备重启应用..."
restart
else
echo "没有找到可用的备份版本,无法回滚!"
return 1
fi
}
写在最后
重要: 有些脚本未经验证,请自行修改!
这些脚本的核心价值不在于脚本本身,而在于”把可重复的操作自动化”这个思路。在实际使用前,有几点需要注意:
- 根据你的实际环境修改脚本中的路径、应用名称和服务器配置
- 确保服务器上已安装所需的依赖(如 Java、Maven)
- 在生产环境使用前,先在测试环境充分验证
- 针对不同操作系统可能需要调整部分命令(如 Linux 与 macOS 的差异)
- 对于敏感操作(如停止服务、删除文件),考虑添加确认步骤
- 添加日志记录功能,便于追踪部署过程中的问题
- 根据实际需求调整 JVM 参数和内存设置
- 使用前请确认脚本具有执行权限(
chmod +x *.sh)
当然,如果项目规模进一步扩大,你可能需要考虑 Jenkins、GitLab CI/CD 或 GitHub Actions 这样的 CI/CD 工具。但对于很多中小团队来说,一组维护良好的 Shell 脚本已经足够解决问题了——简单、透明、可控。