新增springboot应用自动部署脚本

This commit is contained in:
2026-03-05 11:06:02 +08:00
commit aac94ddf4a
2 changed files with 784 additions and 0 deletions

396
deploy/springboot/deploy.sh Normal file
View File

@@ -0,0 +1,396 @@
#!/usr/bin/env bash
###############################################################################
# Spring Boot 应用部署脚本
#
# 主要功能:
# 1. 初始化部署变量JDK 路径、运行目录、日志目录、外置配置目录、JVM 参数等)
# 2. 自动创建部署相关目录(如不存在)
# 3. 基于 Jar 名称进行进程检查(支持指定 Jar 或自动发现 Jar
# 4. 停止同名应用进程(优先 kill -15超时后 kill -9
# 5. 使用 nohup 启动应用并进行启动结果校验
#
# 使用说明:
# 1. 首次使用请根据实际环境修改“变量初始化区”
# 2. 脚本支持通过命令行覆盖部分变量,示例:
# ./deploy.sh --jar-name app.jar --profile prod --debug true
# 3. 日志输出为中文,便于定位部署阶段和问题
###############################################################################
set -u
############################################
# 一、变量初始化区(请按需修改)
############################################
# JDK 安装目录(必须能找到 bin/java
JDK_HOME="/usr/local/jdk"
# Spring Boot Jar 运行目录(放置 jar 包的位置)
APP_HOME="/opt/apps/springboot"
# 应用日志输出目录(脚本会自动创建)
LOG_DIR="/opt/logs/springboot"
# 外置 Spring Boot 配置文件目录(脚本会自动创建)
# 建议在该目录中放置 application-<profile>.yml 或 application.yml
CONFIG_DIR="/opt/config/springboot"
# 指定 Jar 包名称(例如 demo.jar
# 若留空则自动从 APP_HOME 中寻找第一个 *.jar 作为候选
JAR_NAME=""
# JVM 参数(可按机器资源调整)
JVM_OPTS="-Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOG_DIR}/heapdump.hprof -Dfile.encoding=UTF-8"
# Spring Boot Profiledev/test/prod 等)
SPRING_PROFILE="prod"
# Java 启动附加参数(例如时区等系统属性)
JAVA_SYS_PROPS="-Duser.timezone=Asia/Shanghai"
# Debug 开关true/false
DEBUG_ENABLED="false"
# Debug 端口和监听地址(仅在 DEBUG_ENABLED=true 时生效)
DEBUG_PORT="5005"
DEBUG_SUSPEND="n" # y: 启动时等待调试器连接n: 不等待
DEBUG_ADDRESS="0.0.0.0"
# 停止进程等待超时时间(秒)
STOP_TIMEOUT="30"
# 启动后健康检查等待时间(秒)
START_WAIT="8"
# 启动日志文件
CONSOLE_LOG_FILE="${LOG_DIR}/console.out"
# 运行用户(用于日志提示,不强制切换用户)
RUN_USER="$(whoami)"
# 额外 Spring 参数(可按需增加,例如 --server.port=8080
SPRING_EXTRA_ARGS=""
############################################
# 二、内部变量与通用函数
############################################
SCRIPT_NAME="$(basename "$0")"
CURRENT_TIME="$(date '+%Y-%m-%d %H:%M:%S')"
# 彩色输出(若终端不支持可改为空)
COLOR_RESET="\033[0m"
COLOR_INFO="\033[1;34m"
COLOR_WARN="\033[1;33m"
COLOR_ERROR="\033[1;31m"
COLOR_SUCCESS="\033[1;32m"
log_info() {
echo -e "${COLOR_INFO}[信息][$(date '+%Y-%m-%d %H:%M:%S')]${COLOR_RESET} $*"
}
log_warn() {
echo -e "${COLOR_WARN}[警告][$(date '+%Y-%m-%d %H:%M:%S')]${COLOR_RESET} $*"
}
log_error() {
echo -e "${COLOR_ERROR}[错误][$(date '+%Y-%m-%d %H:%M:%S')]${COLOR_RESET} $*"
}
log_success() {
echo -e "${COLOR_SUCCESS}[成功][$(date '+%Y-%m-%d %H:%M:%S')]${COLOR_RESET} $*"
}
print_header() {
echo "============================================================"
echo "脚本名称 : ${SCRIPT_NAME}"
echo "执行时间 : ${CURRENT_TIME}"
echo "执行用户 : ${RUN_USER}"
echo "============================================================"
}
usage() {
cat <<EOF
用法:
${SCRIPT_NAME} [可选参数]
可选参数:
--jdk-home <path> 指定 JDK_HOME
--app-home <path> 指定 APP_HOME
--log-dir <path> 指定 LOG_DIR
--config-dir <path> 指定 CONFIG_DIR
--jar-name <name> 指定 Jar 包名称(如 demo.jar
--profile <name> 指定 Spring Profile如 dev/test/prod
--debug <true|false> 是否开启远程调试
--debug-port <port> 调试端口(默认 5005
--stop-timeout <sec> 停止超时时间(秒)
--start-wait <sec> 启动后等待检查时间(秒)
--help 显示帮助
示例:
${SCRIPT_NAME} --jar-name demo.jar --profile prod --debug false
EOF
}
# 参数解析:允许在不改脚本文件的情况下按需覆盖变量
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--jdk-home)
JDK_HOME="$2"; shift 2 ;;
--app-home)
APP_HOME="$2"; shift 2 ;;
--log-dir)
LOG_DIR="$2"; shift 2 ;;
--config-dir)
CONFIG_DIR="$2"; shift 2 ;;
--jar-name)
JAR_NAME="$2"; shift 2 ;;
--profile)
SPRING_PROFILE="$2"; shift 2 ;;
--debug)
DEBUG_ENABLED="$2"; shift 2 ;;
--debug-port)
DEBUG_PORT="$2"; shift 2 ;;
--stop-timeout)
STOP_TIMEOUT="$2"; shift 2 ;;
--start-wait)
START_WAIT="$2"; shift 2 ;;
--help)
usage; exit 0 ;;
*)
log_error "未知参数:$1"
usage
exit 1 ;;
esac
done
}
# 基础前置校验,避免关键路径或命令不存在导致后续异常
pre_check() {
log_info "开始执行部署前检查..."
if [[ ! -d "${JDK_HOME}" ]]; then
log_error "JDK_HOME 不存在:${JDK_HOME}"
exit 1
fi
if [[ ! -x "${JDK_HOME}/bin/java" ]]; then
log_error "未找到可执行 Java${JDK_HOME}/bin/java"
exit 1
fi
# APP_HOME 不强制提前存在,后续会自动创建
if ! command -v nohup >/dev/null 2>&1; then
log_error "系统未找到 nohup 命令,请先安装 coreutils 或确认环境。"
exit 1
fi
log_success "部署前检查通过。"
}
# 创建目录:若不存在则创建,存在则提示复用
ensure_dirs() {
log_info "开始检查并创建部署目录..."
for d in "${APP_HOME}" "${LOG_DIR}" "${CONFIG_DIR}"; do
if [[ ! -d "$d" ]]; then
mkdir -p "$d"
log_info "目录不存在,已创建:$d"
else
log_info "目录已存在,跳过创建:$d"
fi
done
log_success "目录检查/创建完成。"
}
# 自动发现 Jar当未手工指定 JAR_NAME 时,从 APP_HOME 取第一个 jar
auto_select_jar() {
if [[ -n "${JAR_NAME}" ]]; then
log_info "已指定 Jar 包名称:${JAR_NAME}"
return 0
fi
log_info "未指定 Jar 包名称,开始从运行目录自动发现:${APP_HOME}"
# 使用 find 获取第一个 jar排序后取第一项可提升稳定性
local found
found="$(find "${APP_HOME}" -maxdepth 1 -type f -name '*.jar' | sort | head -n 1)"
if [[ -z "${found}" ]]; then
log_error "${APP_HOME} 未发现任何 jar 包,请先上传应用 Jar。"
exit 1
fi
JAR_NAME="$(basename "${found}")"
log_success "自动选定 Jar 包:${JAR_NAME}"
}
# 返回匹配 Jar 名称的进程 PID可能多个
get_pids_by_jar() {
local jar="$1"
# 过滤掉 grep 本身,匹配 java 命令行中包含目标 jar 的进程
ps -ef | grep java | grep "${jar}" | grep -v grep | awk '{print $2}'
}
# 优雅停止应用:优先 kill -15超时后 kill -9
stop_app_if_running() {
log_info "开始检查同名进程(基于 Jar 名称):${JAR_NAME}"
local pids
pids="$(get_pids_by_jar "${JAR_NAME}" || true)"
if [[ -z "${pids}" ]]; then
log_info "未发现运行中的同名进程,无需停止。"
return 0
fi
log_warn "发现运行中进程 PID${pids}"
log_info "优先执行 kill -15 进行优雅停止..."
for pid in ${pids}; do
kill -15 "${pid}" 2>/dev/null || true
done
local waited=0
while [[ ${waited} -lt ${STOP_TIMEOUT} ]]; do
sleep 1
waited=$((waited + 1))
local remaining
remaining="$(get_pids_by_jar "${JAR_NAME}" || true)"
if [[ -z "${remaining}" ]]; then
log_success "应用已在 ${waited} 秒内优雅停止。"
return 0
fi
done
log_warn "等待 ${STOP_TIMEOUT} 秒后进程仍存在,执行 kill -9 强制停止..."
local force_pids
force_pids="$(get_pids_by_jar "${JAR_NAME}" || true)"
for pid in ${force_pids}; do
kill -9 "${pid}" 2>/dev/null || true
done
sleep 1
local final_check
final_check="$(get_pids_by_jar "${JAR_NAME}" || true)"
if [[ -n "${final_check}" ]]; then
log_error "强制停止后仍检测到进程:${final_check},请人工排查。"
exit 1
fi
log_success "应用已强制停止。"
}
# 组装 debug 参数
build_debug_opts() {
if [[ "${DEBUG_ENABLED}" == "true" ]]; then
echo "-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND},address=${DEBUG_ADDRESS}:${DEBUG_PORT}"
else
echo ""
fi
}
# 启动应用并校验进程是否存在
start_app() {
local jar_path="${APP_HOME}/${JAR_NAME}"
if [[ ! -f "${jar_path}" ]]; then
log_error "目标 Jar 不存在:${jar_path}"
exit 1
fi
local debug_opts
debug_opts="$(build_debug_opts)"
log_info "开始启动应用..."
log_info "JDK 目录:${JDK_HOME}"
log_info "运行目录:${APP_HOME}"
log_info "日志目录:${LOG_DIR}"
log_info "配置目录:${CONFIG_DIR}"
log_info "Spring Profile${SPRING_PROFILE}"
log_info "Debug 开关:${DEBUG_ENABLED}"
# 启动命令说明:
# 1. 使用 nohup 后台启动,避免会话退出导致进程结束
# 2. --spring.config.location 指向外置配置目录
# 3. 标准输出与错误输出统一重定向到 console.out
nohup "${JDK_HOME}/bin/java" \
${JVM_OPTS} \
${JAVA_SYS_PROPS} \
${debug_opts} \
-jar "${jar_path}" \
--spring.profiles.active="${SPRING_PROFILE}" \
--spring.config.location="${CONFIG_DIR}/" \
${SPRING_EXTRA_ARGS} \
>> "${CONSOLE_LOG_FILE}" 2>&1 &
local new_pid=$!
log_info "启动命令已提交,后台进程 PID提交时: ${new_pid}"
log_info "等待 ${START_WAIT} 秒后进行启动结果检查..."
sleep "${START_WAIT}"
local started_pids
started_pids="$(get_pids_by_jar "${JAR_NAME}" || true)"
if [[ -n "${started_pids}" ]]; then
log_success "应用启动成功,当前 PID${started_pids}"
log_info "启动日志文件:${CONSOLE_LOG_FILE}"
else
log_error "应用启动失败,未检测到进程。请检查日志:${CONSOLE_LOG_FILE}"
exit 1
fi
}
# 打印当前配置,便于部署时核对
print_config() {
echo "---------------- 当前部署配置 ----------------"
echo "JDK_HOME = ${JDK_HOME}"
echo "APP_HOME = ${APP_HOME}"
echo "LOG_DIR = ${LOG_DIR}"
echo "CONFIG_DIR = ${CONFIG_DIR}"
echo "JAR_NAME = ${JAR_NAME:-<自动发现>}"
echo "SPRING_PROFILE = ${SPRING_PROFILE}"
echo "DEBUG_ENABLED = ${DEBUG_ENABLED}"
echo "DEBUG_PORT = ${DEBUG_PORT}"
echo "STOP_TIMEOUT(s) = ${STOP_TIMEOUT}"
echo "START_WAIT(s) = ${START_WAIT}"
echo "CONSOLE_LOG_FILE = ${CONSOLE_LOG_FILE}"
echo "------------------------------------------------"
}
main() {
print_header
# 1) 解析参数
parse_args "$@"
# 2) 打印配置
print_config
# 3) 前置检查
pre_check
# 4) 创建目录
ensure_dirs
# 5) 选择 Jar 包(指定或自动发现)
auto_select_jar
# 6) 每次启动前都进行同名进程检查并执行停止
stop_app_if_running
# 7) 启动应用并检查结果
start_app
log_success "部署流程执行完成。"
}
main "$@"