找回密码
 立即注册
首页 业界区 业界 Java 部署脚本,支持回滚

Java 部署脚本,支持回滚

阎一禾 5 天前
Java 部署脚本
Java Sprintboot jar 项目启动、停止脚本:https://www.cnblogs.com/vipsoft/p/15952112.html
SpringBoot 不同的环境,打不同的包名: https://www.cnblogs.com/vipsoft/p/18577679
deploy.sh
  1. #!/bin/bash
  2. # 应用配置
  3. RUN_JAR_NAME="vipsoft-gateway-1.0.1.jar"
  4. NEW_JAR_NAME="vipsoft-gateway-1.0.2.jar"
  5. BACK_JAR_NAME="vipsoft-gateway-1.0.1.jar1229"
  6. # 使用说明
  7. usage() {
  8.     echo "使用说明:"
  9.     echo "  ./deploy.sh deploy <新jar包>      # 部署新版本,默认使用文件里写好的"
  10.     echo "  ./deploy.sh rollback <版本号>     # 回滚到指定版本(格式: 20241229_1430),默认使用文件里写好的"
  11.     echo "  ./deploy.sh stop                  # 停止应用"
  12.     echo "  ./deploy.sh start                 # 启动应用"
  13.     echo "  ./deploy.sh restart               # 重启应用"
  14.     echo "  ./deploy.sh status                # 查看状态"
  15.     echo "  ./deploy.sh logs [行数]           # 查看日志(默认50行),默认为 tail -f"
  16.     echo "  ./deploy.sh backup                # 备份当前版本"
  17.     echo ""
  18.     echo "示例:"
  19.     echo "  ./deploy.sh deploy app-1.27.1.jar1229"
  20.     echo "  ./deploy.sh rollback vipsoft-gateway-1.0.1.jar1229"
  21. }
  22. JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -Dlogging.config=./logback.xml"
  23. SPRING_OPTS="--spring.profiles.active=test"
  24. LOG_DIR="./logs"
  25. PID_FILE="./app.pid"
  26. TIMESTAMP=$(date +%Y%m%d_%H%M%S)
  27. # 创建日志目录
  28. mkdir -p $LOG_DIR
  29. # 颜色输出
  30. RED='\033[0;31m'
  31. GREEN='\033[0;32m'
  32. YELLOW='\033[1;33m'
  33. NC='\033[0m' # No Color
  34. # 打印带颜色的消息
  35. print_info() {
  36.     echo -e "${GREEN}[INFO]${NC} $1"
  37. }
  38. print_warn() {
  39.     echo -e "${YELLOW}[WARN]${NC} $1"
  40. }
  41. print_error() {
  42.     echo -e "${RED}[ERROR]${NC} $1"
  43. }
  44. # 部署新版本
  45. deploy_jar() {
  46.     local new_jar="$1"
  47.     if [ ! -z "$1" ]; then
  48.         NEW_JAR_NAME=$new_jar
  49.     fi
  50.     # 检查文件是否以 .jar 结尾
  51.     if [[ ! "$NEW_JAR_NAME" =~ \.jar$ ]]; then
  52.         print_error "文件名必须以 .jar 结尾: ${NEW_JAR_NAME}"
  53.         exit 1
  54.     fi
  55.     if [ ! -f "$NEW_JAR_NAME" ]; then
  56.         print_error "部署版本不存在: ${NEW_JAR_NAME}"
  57.         exit 1
  58.     fi
  59.     print_info "部署: ${NEW_JAR_NAME}"
  60.     # 停止应用
  61.     stop
  62.    
  63.     # 替换jar包
  64.     if [ -f "$RUN_JAR_NAME" ]; then
  65.         #mv "$RUN_JAR_NAME" "${RUN_JAR_NAME}.${TIMESTAMP}"
  66.         mv "$RUN_JAR_NAME" "${BACK_JAR_NAME}"        
  67.         print_info "备份: ${RUN_JAR_NAME} => ${BACK_JAR_NAME}"
  68.     fi
  69.    
  70.     #mv "$new_jar" "$RUN_JAR_NAME"
  71.     RUN_JAR_NAME=$NEW_JAR_NAME
  72.     # 启动应用
  73.     start
  74. }
  75. # 回滚到指定版本
  76. rollback_version() {
  77.     local backup_jar="$1"
  78.     if [ ! -z "$1" ]; then
  79.         BACK_JAR_NAME=$backup_jar
  80.     fi
  81.     if [ ! -f "$BACK_JAR_NAME" ]; then
  82.         print_error "备份版本不存在: ${BACK_JAR_NAME}"
  83.         echo "可用的备份版本:"
  84.         ls -l ${NEW_JAR_NAME}.* 2>/dev/null || echo "暂无备份"
  85.         exit 1
  86.     fi
  87.     # 停止应用
  88.     stop
  89.      
  90.     if [ -f "$RUN_JAR_NAME" ]; then
  91.         #mv "$RUN_JAR_NAME" "${RUN_JAR_NAME}.${TIMESTAMP}"
  92.         mv "$RUN_JAR_NAME" "${BACK_JAR_NAME}"        
  93.         print_info "备份: ${RUN_JAR_NAME}"
  94.     fi
  95.     mv "$BACK_JAR_NAME" "$RUN_JAR_NAME"
  96.     print_info "回滚到版本: ${BACK_JAR_NAME}"
  97.     #deploy_jar "$backup_jar"
  98.     #RUN_JAR_NAME=$NEW_JAR_NAME
  99.     # 启动应用
  100.     start
  101. }
  102. # 备份当前jar包
  103. backup_current_jar() {
  104.     if [ -f "$JAR_NAME" ]; then
  105.         # 创建备份目录
  106.         mkdir -p $BACKUP_DIR
  107.         
  108.         local backup_name="${JAR_NAME}.${TIMESTAMP}"
  109.         print_info "备份当前版本: $backup_name"
  110.         cp "$JAR_NAME" "$BACKUP_DIR/$backup_name"
  111.         
  112.         # 清理旧备份,保留最近5个
  113.         (cd $BACKUP_DIR && ls -t ${JAR_NAME}.* 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null || true)
  114.     else
  115.         print_warn "当前jar包不存在,无需备份"
  116.     fi
  117. }
  118. start() {
  119.     # 1. 创建日志目录
  120.     print_info "检查日志目录: ${LOG_DIR}"
  121.     if [ ! -d "$LOG_DIR" ]; then
  122.         print_info "创建目录: ${LOG_DIR}"
  123.         mkdir -p "$LOG_DIR"
  124.         
  125.         if [ $? -ne 0 ]; then
  126.             print_error "创建目录失败: ${LOG_DIR}"
  127.             print_error "请检查权限: sudo mkdir -p ${LOG_DIR}"
  128.             print_error "或者修改 LOG_DIR 配置"
  129.             exit 1
  130.         fi
  131.         print_info "目录创建成功"
  132.     else
  133.         print_info "目录已存在"
  134.     fi
  135.    
  136.     # 2. 检查目录权限
  137.     if [ ! -w "$LOG_DIR" ]; then
  138.         print_error "目录不可写: ${LOG_DIR}"
  139.         print_error "请设置权限: sudo chmod 755 ${LOG_DIR}"
  140.         print_error "或者设置所有者: sudo chown \$USER ${LOG_DIR}"
  141.         exit 1
  142.     fi
  143.        
  144.     if [ -f $PID_FILE ] && kill -0 $(cat $PID_FILE) 2>/dev/null; then
  145.         echo "${RUN_JAR_NAME} 应用已经在运行,PID: $(cat $PID_FILE)"
  146.         exit 1
  147.     fi
  148.    
  149.     echo "正在启动应用 ${RUN_JAR_NAME}..."
  150.     nohup java $JAVA_OPTS -jar $RUN_JAR_NAME $SPRING_OPTS > $LOG_DIR/app.out 2>&1 &
  151.     echo $! > $PID_FILE
  152.     echo "应用启动成功 ${RUN_JAR_NAME},PID: $(cat $PID_FILE)"
  153.     show_logs
  154. }
  155. stop() {
  156.     if [ ! -f $PID_FILE ]; then
  157.         echo "PID文件不存在,应用可能未运行"
  158.         return 1
  159.     fi
  160.    
  161.     PID=$(cat $PID_FILE)
  162.     echo "正在停止应用 ${RUN_JAR_NAME},PID: ${PID}"
  163.    
  164.     # 优雅停止
  165.     kill $PID 2>/dev/null
  166.    
  167.     # 等待最多30秒
  168.     for i in {1..30}; do
  169.         if kill -0 $PID 2>/dev/null; then
  170.             sleep 1
  171.         else
  172.             echo "应用已停止 ${RUN_JAR_NAME}"
  173.             rm -f $PID_FILE
  174.             return 0
  175.         fi
  176.     done
  177.    
  178.     # 强制停止
  179.     echo "强制停止应用 ${RUN_JAR_NAME}..."
  180.     kill -9 $PID 2>/dev/null
  181.     rm -f $PID_FILE
  182.     echo "应用已强制停止 ${RUN_JAR_NAME}"
  183. }
  184. restart() {
  185.     stop
  186.     sleep 2
  187.     start
  188. }
  189. status() {
  190.     if [ -f $PID_FILE ] && kill -0 $(cat $PID_FILE) 2>/dev/null; then
  191.         echo "应用正在运行,PID: $(cat $PID_FILE)"
  192.         ps -p $(cat $PID_FILE) -o pid,cmd,lstart
  193.     else
  194.         echo "应用未运行"
  195.     fi
  196. }
  197. # 查看日志
  198. show_logs() {
  199.     local logFile=$LOG_DIR/debug.log
  200.     if [ -z "$1" ]; then
  201.         tail -f $logFile
  202.     elif [ -f "$logFile" ]; then
  203.         local lines=${1:-50}
  204.         tail -n $lines $logFile
  205.     else
  206.         print_warn "日志文件不存在:${logFile}"
  207.     fi
  208. }
  209. # 主函数
  210. main() {
  211.     case "$1" in
  212.         deploy)
  213.             # if [ -z "$2" ]; then
  214.             #     print_error "请指定要部署的jar包"
  215.             #     usage
  216.             #     exit 1
  217.             # fi
  218.             deploy_jar "$2"
  219.             ;;
  220.         rollback)
  221.             # if [ -z "$2" ]; then
  222.             #     print_error "请指定要回滚的版本号"
  223.             #     usage
  224.             #     exit 1
  225.             # fi
  226.             rollback_version "$2"
  227.             ;;
  228.         start)
  229.             start
  230.             ;;
  231.         stop)
  232.             stop
  233.             ;;
  234.         restart)
  235.             restart
  236.             ;;
  237.         status)
  238.             status
  239.             ;;
  240.         logs)
  241.             show_logs "$2"
  242.             ;;
  243.         *)
  244.             echo "使用说明: $0 {start|stop|restart|status}"
  245.             exit 1
  246.             ;;
  247.     esac
  248. }   
  249. # 脚本入口
  250. if [ $# -eq 0 ]; then
  251.     usage
  252.     exit 1
  253. fi
  254. main "$@"
复制代码
说明
  1. if [[ ! "$NEW_JAR_NAME" =~ \.jar$ ]]; then
  2.     print_error "文件名必须以 .jar 结尾: ${NEW_JAR_NAME}"
  3.     exit 1
  4. fi
  5. # =~ 是bash的正则匹配操作符
  6. # \.jar 中的 \. 表示匹配实际的点号(正则中点是特殊字符)
  7. # $ 表示字符串结尾
  8. # 双中括号 [[ ]] 支持正则匹配,单中括号 [ ] 不支持
复制代码
为什么有时候 $后面加 {} 有时候不加
这是Shell中变量引用的不同方式,主要区别在于明确变量边界和使用数组
1. 基本规则:什么时候需要 {}

需要 {} 的情况:
  1. # 1. 变量名后面紧跟其他字符(没有空格分隔)
  2. name="world"
  3. echo "Hello, ${name}!"      # ✅ 正确: Hello, world!
  4. echo "Hello, $name!"        # ⚠️ 有风险: 会被解释为 $name! 变量
  5. # 2. 数组元素访问
  6. files=("a.txt" "b.txt")
  7. echo "${files[0]}"          # ✅ 正确: a.txt
  8. echo "$files[0]"            # ❌ 错误: 只会输出 a.txt[0]
  9. # 3. 特殊参数扩展
  10. echo "${#name}"             # ✅ 输出字符串长度: 5
  11. echo "$#name"               # ❌ 错误: 输出参数个数+name
  12. # 4. 变量默认值
  13. echo "${JAR_NAME:-default.jar}"  # ✅ 如果JAR_NAME为空,使用默认值
复制代码
不需要 {} 的情况:
  1. # 1. 变量名后面是空格或换行
  2. name="world"
  3. echo "Hello, $name"         # ✅ 正确: Hello, world
  4. # 2. 变量名后面是特殊字符(会被shell识别为终止)
  5. echo "Value: $name."        # ✅ 正确: Value: world.
  6. echo "File: $name.txt"      # ✅ 正确(但如果有变量$nametxt会出错)
  7. # 3. 简单变量引用
  8. JAR_NAME="app.jar"
  9. echo "检测到的Jar包: $JAR_NAME"  # ✅ 正确
复制代码
2. 数组的特殊性

这是你问题中的关键区别:
  1. # 数组变量必须使用 ${}
  2. missing=("java" "git" "mvn")
  3. # ✅ 正确:使用 ${missing[*]} 或 ${missing[@]}
  4. echo "缺少命令: ${missing[*]}"      # 输出: 缺少命令: java git mvn
  5. echo "缺少命令: ${missing[@]}"      # 同上(但在循环中有区别)
  6. # ❌ 错误:不加 {} 会被当成普通变量
  7. echo "缺少命令: $missing[*]"        # 输出: 缺少命令: java[*]
  8. echo "缺少命令: $missing[@]"        # 输出: 缺少命令: java[@]
复制代码
3. 具体对比分析

你的例子1:数组必须用 ${}
  1. missing=("java" "git")
  2. # 正确示例
  3. echo "缺少命令: ${missing[*]}"  # ✅ 输出: 缺少命令: java git
  4. echo "缺少命令: ${missing[@]}"  # ✅ 输出: 缺少命令: java git
  5. # 错误示例
  6. echo "缺少命令: $missing[*]"    # ❌ 输出: 缺少命令: java[*]
  7. echo "缺少命令: $missing"       # ❌ 输出: 缺少命令: java(只显示第一个元素)
复制代码
你的例子2:普通变量可以不用 {}
  1. JAR_NAME="app-1.27.jar"
  2. # 都可以,但第一种更安全
  3. echo "检测到的Jar包: ${JAR_NAME}"  # ✅ 推荐
  4. echo "检测到的Jar包: $JAR_NAME"    # ✅ 可接受
复制代码
4. 完整示例展示区别
  1. #!/bin/bash
  2. # 场景1:普通变量
  3. APP_NAME="myapp"
  4. VERSION="1.27"
  5. # 正确但可能有问题
  6. echo "应用: $APP_NAME$VERSION"      # ✅ 输出: myapp1.27
  7. echo "应用: ${APP_NAME}${VERSION}"  # ✅ 输出: myapp1.27(更明确)
  8. # 有问题的情况
  9. echo "应用: $APP_NAME_$VERSION"    # ❌ 尝试访问 $APP_NAME_ 变量
  10. echo "应用: ${APP_NAME}_$VERSION"  # ✅ 正确: myapp_1.27
  11. # 场景2:数组变量
  12. commands=("start" "stop" "restart")
  13. echo "命令: ${commands[*]}"         # ✅ 正确: start stop restart
  14. echo "命令: $commands[*]"           # ❌ 错误: start[*]
  15. echo "命令: $commands"              # ❌ 错误: start(只取第一个)
  16. # 场景3:带特殊字符
  17. file="data"
  18. echo "文件: $file.txt"              # ⚠️ 可能出错:如果存在 $filetxt 变量
  19. echo "文件: ${file}.txt"            # ✅ 安全: data.txt
复制代码
5. 最佳实践建议

总是使用 {}(推荐)
  1. # 始终使用 ${},这样最安全
  2. echo "${variable}"
  3. echo "${array[@]}"
  4. echo "${JAR_NAME}"
复制代码
安全引用表格

场景推荐写法风险写法说明普通变量${VAR} | $VAR加{}总是安全的变量拼接${VAR}text | $VARtext避免歧义数组所有元素${ARRAY[@]} | $ARRAY普通引用只取第一个数组特定元素${ARRAY[0]} | $ARRAY[0]必须加{}带特殊字符${VAR}_suffix | $VAR_suffix避免错误解析6. 高级用法:变量扩展

{} 还能实现强大的变量扩展功能:
  1. # 1. 默认值
  2. echo "${NAME:-无名氏}"      # 如果NAME为空,显示"无名氏"
  3. echo "$NAME"                # 如果NAME为空,显示空
  4. # 2. 变量长度
  5. name="hello"
  6. echo "长度: ${#name}"       # 输出: 5
  7. echo "长度: $#name"         # 错误: 输出参数个数
  8. # 3. 字符串切片
  9. str="abcdefg"
  10. echo "${str:0:3}"          # 输出: abc(从0开始取3个)
  11. echo "${str: -3}"          # 输出: efg(最后3个)
  12. # 4. 模式替换
  13. path="/home/user/file.txt"
  14. echo "${path/file/doc}"    # 输出: /home/user/doc.txt
  15. echo "${path//\// }"       # 输出:  home user file.txt(替换所有/)
复制代码
7. 你的具体问题解答
  1. # 你的代码中:
  2. echo "缺少命令: ${missing[*]}"  # ✅ 必须加{},因为missing是数组
  3. echo "检测到的Jar包: $JAR_NAME"  # ✅ 可以不加,但加了更安全
  4. # 更好的写法(总是加{}):
  5. echo "缺少命令: ${missing[*]}"     # 明确表示是数组
  6. echo "检测到的Jar包: ${JAR_NAME}"   # 安全,避免后续修改带来的问题
复制代码
总结


  • 数组变量必须用 ${}:${array[@]} 或 ${array
  • }
  • 普通变量建议用 ${}:更安全,更明确
  • 变量名边界模糊时用 ${}:${var}_suffix
  • 使用高级功能时用 ${}:默认值、字符串操作等
黄金规则:当有疑问时,总是使用 ${},这样永远不会出错。多两个字符能避免很多难以调试的问题。
在Shell脚本中,return 1 和 exit 1 有重要的区别:
核心区别

特性return 1exit 1作用范围函数内部整个脚本执行后返回调用处继续执行立即终止脚本可捕获会被函数调用者接收整个进程退出退出状态设置 $? 为1脚本以状态码1退出详细解释

1. return 1 - 函数返回值
  1. #!/bin/bash
  2. test_return() {
  3.     echo "函数开始"
  4.     if [ $1 -eq 0 ]; then
  5.         return 0  # 成功
  6.     else
  7.         return 1  # 失败
  8.     fi
  9. }
  10. test_return 0
  11. echo "返回值: $?"  # 输出: 0
  12. test_return 1
  13. echo "返回值: $?"  # 输出: 1
  14. echo "脚本继续执行..."  # 这行会执行
复制代码
2. exit 1 - 退出脚本
  1. #!/bin/bash
  2. test_exit() {
  3.     echo "函数开始"
  4.     if [ $1 -eq 0 ]; then
  5.         return 0
  6.     else
  7.         echo "发生错误,退出脚本"
  8.         exit 1  # 整个脚本立即终止!
  9.     fi
  10. }
  11. test_exit 0
  12. echo "这行会执行"  # 会执行
  13. test_exit 1
  14. echo "这行永远不会执行"  # 不会执行
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册