Files
loan-pricing/docs/superpowers/specs/2026-04-01-production-one-click-deploy-design.md

9.5 KiB
Raw Blame History

生产一键部署脚本设计文档

1. 背景

当前仓库已经存在生产环境部署脚本 bin/prod/deploy_release.sh 和 Java 管理脚本 bin/prod/restart_java.sh。但这套脚本依赖固定的 /home/webapp 目录结构、独立启停脚本以及安装好的 Nginx不符合本次“在脚本同目录直接完成发布”的目标。

本次需要设计一份新的生产一键部署脚本,直接放在发布目录内执行。该目录中同时存在:

  • 部署脚本本身
  • 1 个发布 zip
  • backend/ 目录
  • frontend/ 目录

发布 zip 内固定包含 1 个后端 jar 和 1 个前端 dist.zip。脚本执行时需要完成旧版本备份、新版本替换、后端重启和前端解压部署。

2. 已确认约束

  • 交付形态必须是单脚本,自包含,不拆分独立重启脚本
  • Java 可执行路径直接写在脚本内常量中,不通过命令行参数或外部配置文件传入
  • 发布包从脚本同目录读取
  • 发布包内必须且只能包含 1 个后端 jar 和 1 个前端 dist.zip
  • 旧版后端 jar 与旧版前端 dist 目录必须通过“原地重命名 + 时间戳”的方式保留
  • 后端启动参数固定沿用当前生产约束:--spring.profiles.active=pro --server.port=63310
  • 不增加兼容性、补丁性、兜底性或回滚性方案
  • 方案必须保持最短路径实现,并可完成全链路逻辑验证

3. 当前资产与现状

3.1 现有生产脚本

bin/prod/deploy_release.sh 已具备以下能力:

  • 解压发布包
  • 校验包内 jardist.zip
  • 备份旧版后端与前端
  • 替换产物
  • 调用独立 Java 管理脚本完成重启

但该脚本默认依赖 /home/webapp 固定目录、restart_java.sh 外部脚本和 Nginx 管理流程,超出了本次需求边界。

3.2 现有 Java 管理逻辑

bin/prod/restart_java.sh 已实现:

  • 使用 PID 文件记录后端进程
  • 通过 -Dloan.pricing.home 标记识别托管进程
  • 停止时先 TERM 再按需 KILL
  • 启动后等待端口 63310 监听成功

这部分逻辑可以作为新单脚本中的后端启停参考,但最终不再拆成第二个脚本文件。

4. 方案对比

方案一:单脚本自包含部署

做法:

  • 新增一份单独的部署脚本
  • 在脚本顶部写死 JAVA_BIN、端口、日志路径和目录约定
  • 执行时自动读取脚本同目录发布包
  • 将解包、备份、替换、启停全部内联到同一文件

优点:

  • 与本次“一起写在一个脚本里”的目标完全一致
  • 发布目录内即可直接执行,路径最短
  • 不依赖独立重启脚本或外部配置文件
  • 用户只需维护一个脚本文件

缺点:

  • 后续如果要单独管理 startstopstatus,需要再扩展脚本

方案二:保留现有双脚本结构,只调整入口

做法:

  • 保留部署脚本和重启脚本两份文件
  • 让部署脚本在同目录发布模式下调用重启脚本

优点:

  • 可复用现有结构,改动相对少

缺点:

  • 不满足“写在一起”的明确要求
  • 交付物仍有两个脚本,使用路径不够直接

方案三:极简覆盖式脚本

做法:

  • 不维护 PID 文件
  • 直接按文件名覆盖
  • 使用宽泛的 Java 进程匹配方式停止旧进程

优点:

  • 脚本最短

缺点:

  • 容易误伤其他 Java 进程
  • 状态不可控
  • 不满足全链路可验证要求

5. 设计结论

采用方案一:单脚本自包含部署。

最终交付为 1 份新的生产一键部署脚本,负责在脚本同目录完成完整发布链路。脚本以当前仓库已有生产脚本为参考,但不保留 Nginx 管理、根目录安装和双脚本调用等超出本次范围的设计。

6. 脚本结构设计

6.1 目录约定

脚本执行目录固定为脚本所在目录。目录内约定如下:

  • deploy_release.sh 或本次确定的新脚本文件
  • 1 个发布 zip
  • backend/
  • frontend/

脚本不依赖仓库根目录运行,也不依赖外部工作目录传参。

6.2 脚本内固定配置

脚本顶部固定声明以下配置项:

  • JAVA_BIN
  • BACKEND_PORT=63310
  • SPRING_PROFILE=pro
  • JAVA_OPTS
  • BACKEND_PID_FILE=backend/backend.pid
  • BACKEND_LOG_FILE=backend/backend-console.log
  • BACKEND_JAR_TARGET=backend/ruoyi-admin.jar
  • FRONTEND_DIST_DIR=frontend/dist
  • FRONTEND_DIST_ARCHIVE=frontend/dist.zip

其中 JAVA_BIN 由维护者直接修改脚本内常量,不再设计其他配置入口。

6.3 执行流程

脚本执行顺序固定如下:

  1. 定位脚本所在目录
  2. 校验 backend/frontend/ 目录存在,不存在则直接失败
  3. 在脚本同目录查找唯一一个发布 zip
  4. 解压发布 zip 到临时目录
  5. 校验临时目录内必须且只能找到 1 个 jar 和 1 个 dist.zip
  6. backend/ 目录下现有 jar 重命名为带时间戳的备份文件
  7. frontend/dist 重命名为 dist-时间戳
  8. 停止当前脚本托管的旧后端进程
  9. 将新 jar 移入 backend/ruoyi-admin.jar
  10. 将新 dist.zip 移入 frontend/dist.zip
  11. 删除当前 frontend/dist
  12. 解压新 frontend/dist.zipfrontend/dist/
  13. 启动新的后端 jar
  14. 等待端口 63310 监听成功
  15. 输出部署完成信息

7. 后端启停设计

7.1 进程识别规则

为避免误杀机器上的其他 Java 进程,脚本只管理自己启动的后端实例。

识别规则如下:

  • 启动时附加标记参数:-Dloan.pricing.home=<脚本目录>
  • 优先读取 backend/backend.pid
  • 如果 PID 文件失效,则使用 pgrep -f 按以下组合特征识别:
    • -Dloan.pricing.home=<脚本目录>
    • backend/ruoyi-admin.jar

只有同时满足托管标记和目标 jar 特征的进程,才允许被停止。

7.2 停止流程

停止逻辑如下:

  1. 收集当前托管进程 PID
  2. 如无托管进程,则清理失效 PID 文件并直接继续部署
  3. 对托管进程发送 TERM
  4. 等待最多 30 秒
  5. 若仍存在残留进程,则发送 KILL
  6. 删除 backend/backend.pid

7.3 启动流程

启动逻辑如下:

  1. 校验 JAVA_BIN 可执行
  2. 校验 backend/ruoyi-admin.jar 存在
  3. 使用 nohup 后台启动
  4. 写入 backend/backend.pid
  5. 日志输出到 backend/backend-console.log
  6. 使用固定参数启动:
    • -Dloan.pricing.home=<脚本目录>
    • --spring.profiles.active=pro
    • --server.port=63310
  7. 轮询端口监听状态,最长等待 30 秒

8. 备份与替换设计

8.1 后端备份

如果 backend/ 目录中存在旧版 jar,则将其原地重命名为:

  • 原文件名.YYYYMMDDHHMMSS.bak

不新增独立备份目录,避免引入额外结构。

8.2 前端备份

如果 frontend/dist 目录存在,则将其原地重命名为:

  • dist-YYYYMMDDHHMMSS

如果 frontend/dist.zip 已存在,则允许被新版本覆盖,不再为历史 dist.zip 单独追加备份规则。本次备份目标仅聚焦于用户明确提出的旧 jar 和旧前端 dist

8.3 新产物替换

替换规则如下:

  • 后端统一落到 backend/ruoyi-admin.jar
  • 前端压缩包统一落到 frontend/dist.zip
  • 前端静态资源统一解压到 frontend/dist/

该规则确保部署目录在每次发布后保持稳定结构,便于后续维护。

9. 失败处理设计

本次失败处理仅保留必要的强校验,不增加回滚或兼容分支。

以下场景直接失败退出:

  • 脚本同目录下没有发布 zip
  • 脚本同目录下存在多个发布 zip
  • 解压后 jar 数量不是 1
  • 解压后 dist.zip 数量不是 1
  • JAVA_BIN 不可执行
  • jar 无法写入目标位置
  • dist.zip 解压失败
  • 后端 30 秒内未监听 63310

脚本退出时需要清理临时解压目录,避免残留中间文件。

10. 交付文件设计

本次设计对应的交付物限定为以下内容:

  • 1 个生产一键部署脚本
  • 1 份设计文档
  • 1 份实施记录

建议脚本路径为:

保留 bin/prod/deploy_release.sh 不动,可以避免影响现有 /home/webapp 部署方式;新需求使用独立脚本承载,边界更清晰。

11. 验证设计

本次验证只覆盖该单脚本的一键部署链路,必须包含以下检查:

  1. 执行 sh -n 校验脚本语法
  2. 校验同目录只有 1 个发布 zip 时可继续执行
  3. 校验没有 zip 或存在多个 zip 时直接失败
  4. 校验发布包内必须正好有 1 个 jar 和 1 个 dist.zip
  5. 校验旧 jar 被改名为带时间戳备份文件
  6. 校验旧 frontend/dist 被改名为带时间戳目录
  7. 校验新 jar 最终位于 backend/ruoyi-admin.jar
  8. 校验新 dist.zip 最终位于 frontend/dist.zip
  9. 校验前端资源成功解压到 frontend/dist/
  10. 校验后端进程成功启动并监听 63310
  11. 校验 backend/backend.pidbackend/backend-console.log 被正确生成

12. 非目标

本次明确不包含以下内容:

  • 不接入 Nginx 启停或重载
  • 不处理 Java 安装
  • 不生成独立的后端重启脚本
  • 不增加回滚脚本
  • 不新增命令行参数模式
  • 不新增外部配置文件
  • 不兼容多实例或多应用部署场景