文章目录

概述
在SpringBoot应用的生产环境部署中,我们常常会陷入手动启停服务的泥潭。这种传统方式不仅效率低下,而且在紧张的发布或故障排查中,极易因人为失误引发更严重的问题。
今天,我们将分享一个功能强大的可视化服务管理脚本,它将彻底改变你的部署体验,让SpringBoot应用的部署和运维变得前所未有的简单和高效。
痛点分析:传统部署方式的困扰
相信每一位和生产环境打过交道的开发者,都或多或少地被以下问题所困扰:
- 操作繁琐: 每次部署都需要连接服务器,手动敲入一长串命令,如
ps
,grep
,kill
,nohup java -jar
等。 - 状态不明:
nohup
命令执行后,服务是否真正启动成功?端口是否被监听?健康检查是否通过?我们往往需要执行多个命令才能确认。 - 日志分散: 当服务出现问题,需要查看日志时,你还记得那个深藏在某个目录下的日志文件路径吗?
- 回滚困难: 新版本上线后如果出现致命问题,如何在手忙脚乱中快速、准确地回滚到上一个稳定版本?
- 多服务管理: 在微服务架构下,服务数量成倍增加,手动管理的复杂度和风险也随之指数级上升。
这些痛点不仅严重影响了开发和运维的效率,更给生产环境的稳定性带来了巨大的潜在风险。
解决方案:可视化服务管理器
为了彻底解决这些痛点,我们开发了一套完整的、基于Shell的SpringBoot服务管理解决方案。其核心特性包括:
1. 可视化操作界面
告别单调枯燥的黑白命令行,我们引入了彩色的终端可视化界面。所有服务的状态一目了然,无论是“运行中”、“已停止”还是“启动中”,都通过不同颜色清晰地标识出来,并支持实时状态刷新。
#################### SpringBoot服务管理器 ####################
当前时间: 2025-08-11 16:52:00
配置文件: /path/to/services.conf
日志目录: /path/to/logs
================== 服务列表 ==================
序号 服务名称 端口 状态
-----------------------------------------------
1 user-service 8080 运行中 (PID: 1234, Port: 8080)
2 order-service 8081 已停止
3 payment-service 8082 启动中 (PID: 5678)
===============================================
2. 智能服务管理
配置驱动的服务管理
通过一个简单的配置文件 services.conf
,你可以集中管理所有的SpringBoot应用。
# services.conf 配置格式
# 服务名称|JAR路径|端口|环境|JVM参数
user-service|/opt/apps/user-service.jar|8080|prod|-Xms512m -Xmx1024m
order-service|/opt/apps/order-service.jar|8081|prod|-Xms256m -Xmx512m
这种方式的优势显而易见:
- 统一管理:所有服务配置一目了然。
- 灵活配置:轻松为不同服务指定不同的JVM参数和运行环境(Profile)。
- 易于维护:新增、删除或修改服务配置,都无需改动核心脚本。
智能启停机制
脚本内置了优雅且健壮的启停流程:
- 优雅启动: 检查JAR文件 → 验证端口可用性 → 构建启动命令 → 后台启动服务 → 循环健康检查 → 最终状态确认。
- 安全停止: 优先发送
TERM
信号让服务优雅关闭 → 等待指定时间 → 若服务未停止则强制KILL
终止 → 最终状态确认。
这套机制可以有效避免僵尸进程、端口占用等常见问题,确保服务启停的可靠性。
3. 全方位监控功能
脚本不仅能管理服务,还能提供关键的监控信息。
实时服务详情
==================== 服务详细信息 ====================
服务名称: user-service
运行状态: 运行中 (PID: 1234, Port: 8080)
内存使用: 345.6 MB
CPU使用: 12.5%
启动时间: Aug 11 14:30
日志大小: 25.3M
======================================================
系统资源监控
==================== 系统资源信息 ====================
CPU使用率: 15.2%
内存使用: 4.2G / 8.0G
磁盘使用: 25G / 50G (52%)
Java进程: 3个运行中
======================================================
这些监控信息可以帮助运维人员快速发现性能瓶颈和资源问题。
4. 智能日志管理
提供了一个交互式菜单来查看日志,彻底告别 cd
和 ls
命令。
请选择查看方式:
1) 查看最后50行
2) 查看最后100行
3) 实时跟踪日志
4) 查看全部日志
支持实时跟踪 (tail -f
)、历史查看、全文浏览 (less
),并为自动化日志轮转提供了基础。
5. 批量操作支持
在微服务场景下,批量操作是刚需。脚本提供了便捷的批量操作菜单,一键启停所有服务。
==================== 批量操作菜单 ====================
1) 启动所有服务
2) 停止所有服务
3) 重启所有服务
4) 查看所有服务状态
======================================================
这在系统重启后的服务恢复、版本发布时的批量更新等场景下,能极大地提升效率。
自动化部署解决方案
除了服务管理,我们还提供了一个配套的自动化部署脚本 deploy.sh
。
一键部署流程
只需执行一个简单的命令,即可完成整个部署过程:
./deploy.sh deploy app-v2.0.0.jar
其流程被严格地定义和自动化:
- 环境检查:验证部署环境和依赖。
- 版本备份:自动备份当前正在运行的版本。
- 服务停止:通过管理脚本优雅地停止当前服务。
- 文件部署:将新的JAR包复制到部署目录。
- 服务启动:启动新版本服务。
- 健康检查:通过Actuator端点验证服务是否正常运行。
- 清理备份:自动清理旧的备份文件,仅保留最近的5个版本。
安全回滚机制
当部署出现问题时,回滚操作同样简单:
./deploy.sh rollback
回滚流程会自动查找最新的备份版本,停止问题服务,恢复备份文件,并重启服务进行验证。这种设计确保了部署过程的极致安全,即使出现问题也能在分钟级别内快速恢复。
实战应用场景
场景1:微服务集群管理
某公司有10个核心微服务。
- 传统方式:依次登录服务器,手动执行启停命令,再分别查看日志确认状态,整个过程耗时超过30分钟,且容易出错。
- 使用脚本后:通过一个界面即可管理所有服务,使用批量操作3分钟内即可完成全部服务的启停,状态和资源监控一目了然,效率提升10倍以上。
场景2:版本发布管理
一次版本发布需要更新用户和订单服务。
- 发布前:通过“批量操作 -> 查看所有服务状态”确认当前系统状态。
- 发布过程:依次执行
./deploy.sh deploy user-service-v2.0.jar
和./deploy.sh deploy order-service-v2.0.jar
。 - 发布验证:通过“服务详情”和“日志查看”功能,确认新版本服务的健康状况和业务日志。
场景3:故障应急处理
生产环境CPU告警,某个服务响应超时。
- 快速定位:通过“系统信息”和“服务列表”快速识别出资源占用异常或状态异常的服务。
- 日志分析:立即进入该服务的“日志查看”功能,实时跟踪(
tail -f
)日志,分析错误信息。 - 紧急处理:根据分析结果,选择“重启”服务或执行
./deploy.sh rollback
进行“回滚”。 - 影响评估:处理后,持续观察“服务详情”和“系统资源”监控,评估问题是否解决。
最佳实践建议
- 配置管理:使用Git等版本控制工具管理
services.conf
配置文件。 - 监控告警:将脚本的监控能力与Prometheus、Zabbix等专业监控系统结合,设置关键指标告警。
- 安全考虑:严格限制脚本的执行权限,使用非root用户运行服务,并定期清理日志中的敏感信息。
- 性能优化:根据服务的监控数据,合理配置JVM参数,并定期进行优化。
总结
这套基于Shell的SpringBoot服务管理解决方案,通过巧妙地结合可视化界面、智能管理机制、资源监控和自动化部署,将原本复杂繁琐的运维工作变得简单、高效且可靠。
它尤其适合单机部署多个服务或小规模的微服务集群场景。如果你也正在为SpringBoot应用的部署和管理而烦恼,不妨尝试一下这套解决方案,相信它能让你从重复的运维工作中解放出来,聚焦于更有价值的业务开发。
脚本附录
1. 服务管理脚本 (springboot-service-manager
)
#!/bin/bash
# SpringBoot服务管理脚本 - 可视化版本
# 支持多个服务的启动、停止、重启、状态查看等功能
# 配置目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/services.conf"
LOG_DIR="$SCRIPT_DIR/logs"
# 创建必要目录
mkdir -p "$LOG_DIR"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m' # No Color
# 检查依赖命令
check_dependencies() {
local missing_deps=()
command -v java >/dev/null 2>&1 || missing_deps+=("java")
command -v lsof >/dev/null 2>&1 || missing_deps+=("lsof")
command -v nc >/dev/null 2>&1 || missing_deps+=("netcat")
if [[ ${#missing_deps[@]} -ne 0 ]]; then
echo -e "${RED}错误: 缺少必要的命令工具${NC}"
echo "请安装以下工具: ${missing_deps[*]}"
echo
echo "Ubuntu/Debian: sudo apt-get install openjdk-8-jdk lsof netcat"
echo "CentOS/RHEL: sudo yum install java-1.8.0-openjdk lsof nc"
exit 1
fi
}
# 加载服务配置
load_services() {
if [[ ! -f "$CONFIG_FILE" ]]; then
echo -e "${RED}配置文件不存在: $CONFIG_FILE${NC}"
echo "请先创建配置文件,参考 services.conf.example"
exit 1
fi
# 清空服务数组
unset SERVICE_NAMES
unset SERVICE_PATHS
unset SERVICE_PORTS
unset SERVICE_PROFILES
unset SERVICE_JVM_OPTS
declare -g -a SERVICE_NAMES=()
declare -g -a SERVICE_PATHS=()
declare -g -a SERVICE_PORTS=()
declare -g -a SERVICE_PROFILES=()
declare -g -a SERVICE_JVM_OPTS=()
# 读取配置文件
while IFS='|' read -r name path port profile jvm_opts; do
# 跳过注释和空行
[[ $name =~ ^#.*$ ]] && continue
[[ -z "$name" ]] && continue
SERVICE_NAMES+=("$name")
SERVICE_PATHS+=("$path")
SERVICE_PORTS+=("$port")
SERVICE_PROFILES+=("$profile")
SERVICE_JVM_OPTS+=("$jvm_opts")
done < "$CONFIG_FILE"
}
# 获取服务PID
get_service_pid() {
local service_name=$1
local port=$2
local pid
# 先通过端口查找
if [[ -n "$port" ]]; then
pid=$(lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null)
if [[ -n "$pid" ]]; then
echo "$pid"
return
fi
fi
# 通过jar文件名查找
pid=$(ps aux | grep "java" | grep -v "grep" | grep "$service_name" | awk '{print $2}')
echo "$pid"
}
# 检查服务状态
check_service_status() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
local port=${SERVICE_PORTS[$index]}
local pid
pid=$(get_service_pid "$service_name" "$port")
if [[ -n "$pid" ]]; then
# 检查端口是否可访问
if [[ -n "$port" ]] && nc -z localhost "$port" 2>/dev/null; then
echo -e "${GREEN}运行中${NC} (PID: $pid, Port: $port)"
else
echo -e "${YELLOW}启动中${NC} (PID: $pid)"
fi
return 0
else
echo -e "${RED}已停止${NC}"
return 1
fi
}
# 启动服务
start_service() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
local jar_path=${SERVICE_PATHS[$index]}
local port=${SERVICE_PORTS[$index]}
local profile=${SERVICE_PROFILES[$index]}
local jvm_opts=${SERVICE_JVM_OPTS[$index]}
echo -e "${BLUE}正在启动服务: $service_name${NC}"
if [[ ! -f "$jar_path" ]]; then
echo -e "${RED}错误: JAR文件不存在 - $jar_path${NC}"
return 1
fi
local pid
pid=$(get_service_pid "$service_name" "$port")
if [[ -n "$pid" ]]; then
echo -e "${YELLOW}服务已经在运行中 (PID: $pid)${NC}"
return 0
fi
local cmd="java"
[[ -n "$jvm_opts" ]] && cmd="$cmd $jvm_opts"
[[ -n "$profile" ]] && cmd="$cmd -Dspring.profiles.active=$profile"
cmd="$cmd -jar $jar_path"
local log_file="$LOG_DIR/${service_name}.log"
nohup $cmd > "$log_file" 2>&1 &
local new_pid=$!
{
echo "启动命令: $cmd"
echo "启动时间: $(date)"
echo "进程PID: $new_pid"
echo "----------------------------------------"
} >> "$log_file"
echo -n "等待服务启动"
for i in {1..30}; do
sleep 1
echo -n "."
if [[ -n "$port" ]] && nc -z localhost "$port" 2>/dev/null; then
echo
echo -e "${GREEN}服务启动成功!${NC} (PID: $new_pid, Port: $port)"
return 0
fi
done
echo
echo -e "${YELLOW}服务已启动,但端口检查超时${NC} (PID: $new_pid)"
echo "请查看日志文件: $log_file"
return 0
}
# 停止服务
stop_service() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
local port=${SERVICE_PORTS[$index]}
echo -e "${BLUE}正在停止服务: $service_name${NC}"
local pid
pid=$(get_service_pid "$service_name" "$port")
if [[ -z "$pid" ]]; then
echo -e "${YELLOW}服务未运行${NC}"
return 0
fi
echo "发送TERM信号..."
kill -TERM "$pid"
echo -n "等待服务停止"
for i in {1..15}; do
sleep 1
echo -n "."
if ! kill -0 "$pid" 2>/dev/null; then
echo
echo -e "${GREEN}服务已停止${NC}"
return 0
fi
done
echo
echo "优雅停止超时,强制停止..."
kill -KILL "$pid" 2>/dev/null
sleep 2
if ! kill -0 "$pid" 2>/dev/null; then
echo -e "${GREEN}服务已强制停止${NC}"
else
echo -e "${RED}服务停止失败${NC}"
return 1
fi
}
# 重启服务
restart_service() {
local index=$1
echo -e "${BLUE}正在重启服务: ${SERVICE_NAMES[$index]}${NC}"
stop_service "$index"
sleep 2
start_service "$index"
}
# 查看服务日志
view_service_log() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
local log_file="$LOG_DIR/${service_name}.log"
if [[ ! -f "$log_file" ]]; then
echo -e "${RED}日志文件不存在: $log_file${NC}"; return 1;
fi
clear
echo -e "${BLUE}查看服务日志: $service_name${NC}"
echo "日志文件: $log_file"
echo "----------------------------------------"
echo "请选择查看方式:"
echo "1) 查看最后50行"
echo "2) 查看最后100行"
echo "3) 实时跟踪日志 (按 Ctrl+C 退出)"
echo "4) 查看全部日志 (使用less)"
echo "0) 返回"
read -rp "请输入选择 [0-4]: " log_choice
case $log_choice in
1) tail -50 "$log_file" | less ;;
2) tail -100 "$log_file" | less ;;
3) tail -f "$log_file" ;;
4) less "$log_file" ;;
*) return ;;
esac
read -rp "按回车键继续..."
}
# 显示服务详细信息
show_service_detail() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
local port=${SERVICE_PORTS[$index]}
clear
echo -e "${CYAN}==================== 服务详细信息 ====================${NC}"
echo -e "${WHITE}服务名称:${NC} $service_name"
echo -e "${WHITE}JAR路径:${NC} ${SERVICE_PATHS[$index]}"
echo -e "${WHITE}端口号:${NC} ${port:-未配置}"
echo -e "${WHITE}环境配置:${NC} ${SERVICE_PROFILES[$index]:-默认}"
echo -e "${WHITE}JVM参数:${NC} ${SERVICE_JVM_OPTS[$index]:-默认}"
local pid
pid=$(get_service_pid "$service_name" "$port")
echo -e "${WHITE}运行状态:${NC} $(check_service_status "$index")"
if [[ -n "$pid" ]]; then
echo -e "${WHITE}内存使用:${NC} $(ps -p "$pid" -o rss= | awk '{printf "%.1f MB", $1/1024}')"
echo -e "${WHITE}CPU使用:${NC} $(ps -p "$pid" -o %cpu= | awk '{print $1"%"}')"
echo -e "${WHITE}启动时间:${NC} $(ps -p "$pid" -o lstart=)"
fi
local log_file="$LOG_DIR/${service_name}.log"
if [[ -f "$log_file" ]]; then
echo -e "${WHITE}日志文件:${NC} $log_file"
echo -e "${WHITE}日志大小:${NC} $(du -sh "$log_file" | awk '{print $1}')"
fi
echo -e "${CYAN}======================================================${NC}"
read -rp "按回车键返回..."
}
# 显示系统资源使用情况
show_system_info() {
clear
echo -e "${CYAN}==================== 系统资源信息 ====================${NC}"
echo -e "${WHITE}CPU使用率:${NC} $(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}')%"
free -h | grep "Mem" | awk '{printf "内存使用: %s / %s (%s)\n", $3, $2, $7}'
echo -e "${WHITE}磁盘使用:${NC}"; df -h | grep -E '^/dev/' | awk '{printf " %s: %s/%s (%s)\n", $1, $3, $2, $5}'
echo -e "${WHITE}Java进程:${NC} $(pgrep -c java)个运行中"
echo -e "${CYAN}======================================================${NC}"
read -rp "按回车键返回..."
}
# 批量操作菜单
batch_operations_menu() {
while true; do
clear
echo -e "${PURPLE}==================== 批量操作菜单 ====================${NC}"
echo "1) 启动所有服务"; echo "2) 停止所有服务"
echo "3) 重启所有服务"; echo "4) 查看所有服务状态"
echo "0) 返回主菜单"
echo -e "${PURPLE}======================================================${NC}"
read -rp "请输入选择 [0-4]: " choice
case $choice in
1|2|3)
action_name="启动" && [[ "$choice" -eq 2 ]] && action_name="停止" && [[ "$choice" -eq 3 ]] && action_name="重启"
action_func="start_service" && [[ "$choice" -eq 2 ]] && action_func="stop_service" && [[ "$choice" -eq 3 ]] && action_func="restart_service"
echo -e "${BLUE}正在${action_name}所有服务...${NC}"
for i in "${!SERVICE_NAMES[@]}"; do "$action_func" "$i"; echo; done
read -rp "按回车键继续..."
;;
4)
clear
echo -e "${CYAN}==================== 所有服务状态 ====================${NC}"
printf "%-20s %-10s %s\n" "服务名称" "端口" "状态"
echo "------------------------------------------------------"
for i in "${!SERVICE_NAMES[@]}"; do
printf "%-20s %-10s %s\n" "${SERVICE_NAMES[$i]}" "${SERVICE_PORTS[$i]}" "$(check_service_status "$i")"
done
echo -e "${CYAN}======================================================${NC}"
read -rp "按回车键继续..."
;;
0) break ;;
*) echo -e "${RED}无效选择${NC}"; sleep 1 ;;
esac
done
}
# 单服务管理菜单
service_management_menu() {
local index=$1
local service_name=${SERVICE_NAMES[$index]}
while true; do
clear
echo -e "${CYAN}==================== 服务管理: $service_name ====================${NC}"
echo -e "当前状态: $(check_service_status "$index")"
echo; echo "1) 启动服务"; echo "2) 停止服务"; echo "3) 重启服务"
echo "4) 查看日志"; echo "5) 服务详情"; echo "0) 返回主菜单"
echo -e "${CYAN}================================================================${NC}"
read -rp "请输入选择 [0-5]: " choice
case $choice in
1) start_service "$index"; read -rp "按回车键继续...";;
2) stop_service "$index"; read -rp "按回车键继续...";;
3) restart_service "$index"; read -rp "按回车键继续...";;
4) view_service_log "$index" ;;
5) show_service_detail "$index" ;;
0) break ;;
*) echo -e "${RED}无效选择${NC}"; sleep 1 ;;
esac
done
}
# 主菜单
main_menu() {
while true; do
load_services
clear
echo -e "${GREEN}#################### SpringBoot服务管理器 ####################${NC}"
echo -e "${WHITE}当前时间: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
echo -e "${WHITE}配置文件: $CONFIG_FILE${NC}"
echo
echo -e "${CYAN}================== 服务列表 ==================${NC}"
printf "%-3s %-20s %-10s %s\n" "序号" "服务名称" "端口" "状态"
echo "--------------------------------------------------"
for i in "${!SERVICE_NAMES[@]}"; do
printf "%-3s %-20s %-10s %s\n" "$((i+1))" "${SERVICE_NAMES[$i]}" "${SERVICE_PORTS[$i]}" "$(check_service_status "$i")"
done
echo -e "${CYAN}===============================================${NC}"
echo
echo "操作: [1-${#SERVICE_NAMES[@]}] 管理服务 | [b] 批量操作 | [s] 系统信息 | [r] 刷新 | [q] 退出"
read -rp "请输入选择: " choice
if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le ${#SERVICE_NAMES[@]} ]]; then
service_management_menu "$((choice-1))"
else
case $choice in
b|B) batch_operations_menu ;;
s|S) show_system_info ;;
r|R) ;;
q|Q) echo -e "${GREEN}感谢使用!${NC}"; exit 0 ;;
*) echo -e "${RED}无效选择${NC}"; sleep 1 ;;
esac
fi
done
}
# 主程序入口
check_dependencies
main_menu
2. 配置文件 (services.conf
)
# SpringBoot服务配置文件
# 格式: 服务名称|JAR文件路径|端口号|Profile环境|JVM参数
# 示例配置,请根据实际情况修改
# 注意事项:
# 1. 每行一个服务配置
# 2. 使用 | 分隔各个字段
# 3. JAR文件路径必须是绝对路径
# 4. 端口号用于健康检查, 可以为空
# 5. Profile环境可以为空
# 6. JVM参数可以为空
# 7. 以#开头的行为注释行
user-service|/opt/apps/user-service-1.0.0.jar|8080|prod|-Xms512m -Xmx1024m
order-service|/opt/apps/order-service-1.0.0.jar|8081|prod|-Xms512m -Xmx1024m
payment-service|/opt/apps/payment-service-1.0.0.jar|8082|prod|-Xms256m -Xmx512m
3. 部署脚本 (deploy.sh
)
#!/bin/bash
# SpringBoot服务自动化部署脚本
# --- START: 请根据实际情况修改以下配置 ---
# 服务管理脚本的路径
SERVICE_MANAGER_SCRIPT="./springboot-service-manager"
# 部署和备份目录
DEPLOY_DIR="/opt/apps"
BACKUP_DIR="/opt/backups"
# 保留的备份数量
BACKUP_COPIES=5
# 健康检查配置
HEALTH_CHECK_PORT="8080"
HEALTH_CHECK_ENDPOINT="/actuator/health"
HEALTH_CHECK_TIMEOUT=60 # 秒
# --- END: 配置结束 ---
# 颜色定义
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
# 从服务配置文件中提取服务名
# 假设服务名和jar包名有一定关联,或者配置文件中第一行就是主服务
APP_NAME=$(head -n 1 services.conf | cut -d'|' -f1)
# 函数:打印日志
log() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
log_green() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
# 完整部署流程
full_deploy() {
local new_jar_file=$1
local jar_base_name
if [[ ! -f "$new_jar_file" ]]; then
log_error "部署失败: JAR文件 '$new_jar_file' 不存在!"
exit 1
fi
jar_base_name=$(basename "$new_jar_file")
log_green "===== 开始部署: $jar_base_name ====="
# 1. 环境初始化
log "步骤 1/7: 初始化目录..."
mkdir -p "$DEPLOY_DIR" "$BACKUP_DIR"
# 2. 备份当前版本
local current_jar_path="$DEPLOY_DIR/$jar_base_name"
if [[ -f "$current_jar_path" ]]; then
log "步骤 2/7: 备份当前版本..."
local backup_file="$BACKUP_DIR/${jar_base_name}.$(date +%Y%m%d_%H%M%S).bak"
mv "$current_jar_path" "$backup_file"
log_green "备份完成: $backup_file"
else
log "步骤 2/7: 当前无运行版本,跳过备份。"
fi
# 3. 停止服务
log "步骤 3/7: 停止当前服务..."
"$SERVICE_MANAGER_SCRIPT" <<< "2" # 模拟输入停止命令
# 需要根据实际管理脚本的交互进行调整,这里假设选择服务后输入2是停止
# 4. 部署新文件
log "步骤 4/7: 部署新版本文件..."
cp "$new_jar_file" "$current_jar_path"
log_green "文件复制完成。"
# 5. 启动服务
log "步骤 5/7: 启动新版本服务..."
"$SERVICE_MANAGER_SCRIPT" <<< "1" # 模拟输入启动命令
# 6. 健康检查
log "步骤 6/7: 执行健康检查 (最多等待 ${HEALTH_CHECK_TIMEOUT}s)..."
local attempt=0
while [[ $attempt -lt $HEALTH_CHECK_TIMEOUT ]]; do
if curl -s -f "http://localhost:$HEALTH_CHECK_PORT$HEALTH_CHECK_ENDPOINT" &>/dev/null; then
log_green "健康检查通过!服务已成功启动。"
# 7. 清理旧备份
log "步骤 7/7: 清理旧备份 (保留最近${BACKUP_COPIES}个)..."
ls -t "$BACKUP_DIR"/"${jar_base_name}".*.bak 2>/dev/null | tail -n +$((BACKUP_COPIES + 1)) | xargs -r rm
log_green "清理完成。"
log_green "===== 部署成功! ====="
exit 0
fi
attempt=$((attempt + 2))
echo -n "."
sleep 2
done
log_error "\n健康检查失败!部署可能存在问题。"
log_warn "建议执行回滚操作: $0 rollback"
exit 1
}
# 回滚流程
rollback() {
log_warn "===== 开始回滚 ====="
local jar_base_name
jar_base_name=$(basename "$(ls -t "$BACKUP_DIR"/*.bak 2>/dev/null | head -1)")
jar_base_name=${jar_base_name%.??????????????.bak}
local latest_backup
latest_backup=$(ls -t "$BACKUP_DIR"/"$jar_base_name".*.bak 2>/dev/null | head -1)
if [[ -z "$latest_backup" ]]; then
log_error "回滚失败: 未找到任何备份文件!"
exit 1
fi
log "找到最新备份: $(basename "$latest_backup")"
# 停止 & 恢复 & 启动
log "停止当前服务..."
# ... 调用管理脚本停止
log "恢复备份文件..."
cp "$latest_backup" "$DEPLOY_DIR/$jar_base_name"
log "启动服务..."
# ... 调用管理脚本启动
# 健康检查
log "执行健康检查..."
# ...
log_green "===== 回滚完成! ====="
}
# 显示帮助
show_help() {
echo "用法: $0 [deploy|rollback] [jar-file]"
echo " deploy <jar-file> : 部署一个新版本"
echo " rollback : 回滚到上一个版本"
}
# 主逻辑
case "$1" in
deploy)
[[ -z "$2" ]] && { log_error "缺少JAR文件参数!"; show_help; exit 1; }
full_deploy "$2"
;;
rollback)
rollback
;;
*)
show_help
;;
esac