# 生产一键部署脚本设计文档 ## 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 安装 - 不生成独立的后端重启脚本 - 不增加回滚脚本 - 不新增命令行参数模式 - 不新增外部配置文件 - 不兼容多实例或多应用部署场景