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

291 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 生产一键部署脚本设计文档
## 1. 背景
当前仓库已经存在生产环境部署脚本 [bin/prod/deploy_release.sh](/Users/wkc/Desktop/loan-pricing/loan-pricing/bin/prod/deploy_release.sh) 和 Java 管理脚本 [bin/prod/restart_java.sh](/Users/wkc/Desktop/loan-pricing/loan-pricing/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](/Users/wkc/Desktop/loan-pricing/loan-pricing/bin/prod/deploy_release.sh) 已具备以下能力:
- 解压发布包
- 校验包内 `jar``dist.zip`
- 备份旧版后端与前端
- 替换产物
- 调用独立 Java 管理脚本完成重启
但该脚本默认依赖 `/home/webapp` 固定目录、`restart_java.sh` 外部脚本和 Nginx 管理流程,超出了本次需求边界。
### 3.2 现有 Java 管理逻辑
[bin/prod/restart_java.sh](/Users/wkc/Desktop/loan-pricing/loan-pricing/bin/prod/restart_java.sh) 已实现:
- 使用 PID 文件记录后端进程
- 通过 `-Dloan.pricing.home` 标记识别托管进程
- 停止时先 `TERM` 再按需 `KILL`
- 启动后等待端口 `63310` 监听成功
这部分逻辑可以作为新单脚本中的后端启停参考,但最终不再拆成第二个脚本文件。
## 4. 方案对比
### 方案一:单脚本自包含部署
做法:
- 新增一份单独的部署脚本
- 在脚本顶部写死 `JAVA_BIN`、端口、日志路径和目录约定
- 执行时自动读取脚本同目录发布包
- 将解包、备份、替换、启停全部内联到同一文件
优点:
- 与本次“一起写在一个脚本里”的目标完全一致
- 发布目录内即可直接执行,路径最短
- 不依赖独立重启脚本或外部配置文件
- 用户只需维护一个脚本文件
缺点:
- 后续如果要单独管理 `start``stop``status`,需要再扩展脚本
### 方案二:保留现有双脚本结构,只调整入口
做法:
- 保留部署脚本和重启脚本两份文件
- 让部署脚本在同目录发布模式下调用重启脚本
优点:
- 可复用现有结构,改动相对少
缺点:
- 不满足“写在一起”的明确要求
- 交付物仍有两个脚本,使用路径不够直接
### 方案三:极简覆盖式脚本
做法:
- 不维护 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.zip``frontend/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_from_package.sh](/Users/wkc/Desktop/loan-pricing/loan-pricing/bin/prod/deploy_from_package.sh)
保留 [bin/prod/deploy_release.sh](/Users/wkc/Desktop/loan-pricing/loan-pricing/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.pid``backend/backend-console.log` 被正确生成
## 12. 非目标
本次明确不包含以下内容:
- 不接入 Nginx 启停或重载
- 不处理 Java 安装
- 不生成独立的后端重启脚本
- 不增加回滚脚本
- 不新增命令行参数模式
- 不新增外部配置文件
- 不兼容多实例或多应用部署场景