Files
loan-pricing/docs/superpowers/plans/2026-04-01-production-one-click-deploy-backend-plan.md

386 lines
11 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.
# Production One-Click Deploy Backend Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:executing-plans to implement this plan in this repository. Do not use subagents. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 新增一份生产一键部署脚本,在脚本同目录完成发布包校验、旧版后端备份、后端进程停止、新版 `jar` 替换、后端重启和部署结果验证。
**Architecture:** 以现有 [bin/prod/deploy_release.sh](/Users/wkc/Desktop/loan-pricing/loan-pricing/bin/prod/deploy_release.sh) 和 [bin/prod/restart_java.sh](/Users/wkc/Desktop/loan-pricing/loan-pricing/bin/prod/restart_java.sh) 为参考但将解包、备份、PID 管理、进程识别、端口等待全部内联到新的单脚本 [bin/prod/deploy_from_package.sh](/Users/wkc/Desktop/loan-pricing/loan-pricing/bin/prod/deploy_from_package.sh)。实现只覆盖单实例发布链路,不接入 Nginx、不拆独立启停脚本、不引入额外配置文件。
**Tech Stack:** POSIX shell、`unzip``find``pgrep``nohup``ss``sh -n`
---
### Task 1: 锁定脚本接口与参考实现边界
**Files:**
- Inspect: `docs/superpowers/specs/2026-04-01-production-one-click-deploy-design.md`
- Inspect: `bin/prod/deploy_release.sh`
- Inspect: `bin/prod/restart_java.sh`
- [ ] **Step 1: 核对设计文档中的后端职责边界**
Run: `rg -n "单脚本|JAVA_BIN|backend/backend.pid|63310|loan.pricing.home|TERM|KILL" docs/superpowers/specs/2026-04-01-production-one-click-deploy-design.md`
Expected: 能定位单脚本、自定义 `JAVA_BIN`、PID 文件、进程标记和端口等待等核心约束。
- [ ] **Step 2: 抽取现有生产脚本中可复用的后端逻辑**
Run: `rg -n "assert_single_jar|extract_release_package|cleanup|BACKEND_JAR|collect_backend_pids|stop_backend|start_backend|BACKEND_MARKER|BACKEND_PORT" bin/prod/deploy_release.sh bin/prod/restart_java.sh`
Expected: 能定位现有发布包解压校验、PID 管理和端口等待逻辑,作为新脚本参考来源。
- [ ] **Step 3: 确认新脚本目标文件尚不存在**
Run: `test ! -f bin/prod/deploy_from_package.sh && echo "missing"`
Expected: 输出 `missing`,说明可以新增独立脚本而不覆盖现有 `/home/webapp` 发布脚本。
- [ ] **Step 4: 提交本任务**
```bash
git add docs/superpowers/plans/2026-04-01-production-one-click-deploy-backend-plan.md
git commit -m "新增生产一键部署后端计划"
```
### Task 2: 创建单脚本基础骨架与公共函数
**Files:**
- Create: `bin/prod/deploy_from_package.sh`
- [ ] **Step 1: 写入脚本头部配置和使用说明**
在 [bin/prod/deploy_from_package.sh](/Users/wkc/Desktop/loan-pricing/loan-pricing/bin/prod/deploy_from_package.sh) 顶部写入最小骨架:
```sh
#!/bin/sh
set -eu
JAVA_BIN="/home/webapp/env/java/bin/java"
BACKEND_PORT=63310
SPRING_PROFILE="pro"
JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError"
```
并补齐以下说明函数:
```sh
usage() {
cat <<'EOF'
用法:
./bin/prod/deploy_from_package.sh
EOF
}
```
- [ ] **Step 2: 实现脚本目录定位和路径常量**
至少补齐以下路径变量:
```sh
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
BACKEND_DIR="$SCRIPT_DIR/backend"
FRONTEND_DIR="$SCRIPT_DIR/frontend"
BACKEND_JAR_TARGET="$BACKEND_DIR/ruoyi-admin.jar"
BACKEND_PID_FILE="$BACKEND_DIR/backend.pid"
BACKEND_LOG_FILE="$BACKEND_DIR/backend-console.log"
FRONTEND_DIST_ARCHIVE="$FRONTEND_DIR/dist.zip"
FRONTEND_DIST_DIR="$FRONTEND_DIR/dist"
BACKEND_MARKER="-Dloan.pricing.home=$SCRIPT_DIR"
```
- [ ] **Step 3: 实现日志、时间戳和清理函数**
至少补齐:
```sh
timestamp() {
date "+%Y%m%d%H%M%S"
}
log_info() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1"
}
log_error() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >&2
}
```
以及:
```sh
cleanup() {
if [ -n "${WORK_DIR:-}" ] && [ -d "$WORK_DIR" ]; then
rm -rf "$WORK_DIR"
fi
}
```
- [ ] **Step 4: 做语法校验**
Run: `sh -n bin/prod/deploy_from_package.sh`
Expected: 无输出,返回码为 0。
- [ ] **Step 5: 提交本任务**
```bash
git add bin/prod/deploy_from_package.sh
git commit -m "新增生产一键部署脚本骨架"
```
### Task 3: 实现发布包发现、解压和后端备份
**Files:**
- Modify: `bin/prod/deploy_from_package.sh`
- [ ] **Step 1: 实现目录与命令前置校验**
补齐以下校验:
```sh
require_dir() {
if [ ! -d "$1" ]; then
log_error "缺少目录: $1"
exit 1
fi
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
log_error "缺少命令: $1"
exit 1
fi
}
```
在主流程中校验:
```sh
require_dir "$BACKEND_DIR"
require_dir "$FRONTEND_DIR"
require_command unzip
require_command find
require_command pgrep
require_command ss
```
- [ ] **Step 2: 实现同目录唯一发布 zip 查找**
新增函数:
```sh
find_release_archive() {
archives=$(find "$SCRIPT_DIR" -maxdepth 1 -type f -name '*.zip' ! -name 'dist.zip')
count=$(printf '%s\n' "$archives" | sed '/^$/d' | wc -l | tr -d ' ')
if [ "$count" -ne 1 ]; then
log_error "脚本同目录发布 zip 数量不正确,期望 1 个,实际 $count"
exit 1
fi
printf '%s\n' "$archives"
}
```
- [ ] **Step 3: 实现解压与唯一 `jar` 校验**
补齐:
```sh
extract_release_package() {
release_archive="$1"
release_extract_dir="$2"
mkdir -p "$release_extract_dir"
unzip -oq "$release_archive" -d "$release_extract_dir"
}
assert_single_jar() {
search_dir="$1"
count=$(find "$search_dir" -type f -name '*.jar' | wc -l | tr -d ' ')
if [ "$count" -ne 1 ]; then
log_error "后端 jar 数量不正确,期望 1 个,实际 $count"
exit 1
fi
find "$search_dir" -type f -name '*.jar' | head -n 1
}
```
- [ ] **Step 4: 实现旧版后端备份和新 `jar` 替换**
补齐:
```sh
backup_backend_jar() {
if [ -f "$BACKEND_JAR_TARGET" ]; then
mv "$BACKEND_JAR_TARGET" "$BACKEND_JAR_TARGET.$(timestamp).bak"
fi
}
deploy_backend_jar() {
source_jar="$1"
mv "$source_jar" "$BACKEND_JAR_TARGET"
}
```
- [ ] **Step 5: 用临时目录构造后端备份场景做静态验证**
Run:
```bash
tmpdir=$(mktemp -d)
mkdir -p "$tmpdir/backend" "$tmpdir/frontend/package"
touch "$tmpdir/backend/ruoyi-admin.jar"
touch "$tmpdir/frontend/dist.zip"
touch "$tmpdir/package/app.jar"
(cd "$tmpdir/package" && zip -q release.zip app.jar >/dev/null)
test -f "$tmpdir/backend/ruoyi-admin.jar"
rm -rf "$tmpdir"
```
Expected: 命令执行成功,用于确认计划中的文件命名和目录约定可被临时目录复现。
- [ ] **Step 6: 做语法校验**
Run: `sh -n bin/prod/deploy_from_package.sh`
Expected: 无输出,返回码为 0。
- [ ] **Step 7: 提交本任务**
```bash
git add bin/prod/deploy_from_package.sh
git commit -m "实现生产部署脚本后端发布包处理"
```
### Task 4: 实现后端进程停止、启动与端口等待
**Files:**
- Modify: `bin/prod/deploy_from_package.sh`
- [ ] **Step 1: 实现托管进程识别函数**
补齐以下函数:
```sh
is_managed_backend_pid() {
pid="$1"
if [ -z "${pid:-}" ] || ! kill -0 "$pid" 2>/dev/null; then
return 1
fi
args=$(ps -o args= -p "$pid" 2>/dev/null || true)
case "$args" in
*"$BACKEND_MARKER"*"$BACKEND_JAR_TARGET"*|*"$BACKEND_JAR_TARGET"*"$BACKEND_MARKER"*)
return 0
;;
esac
return 1
}
```
- [ ] **Step 2: 实现 PID 收集与停止流程**
补齐:
```sh
collect_backend_pids() {
pids=""
if [ -f "$BACKEND_PID_FILE" ]; then
file_pid=$(cat "$BACKEND_PID_FILE" 2>/dev/null || true)
if [ -n "${file_pid:-}" ] && is_managed_backend_pid "$file_pid"; then
pids="$pids $file_pid"
fi
fi
marker_pids=$(pgrep -f "$BACKEND_MARKER" 2>/dev/null || true)
for pid in $marker_pids; do
if is_managed_backend_pid "$pid"; then
pids="$pids $pid"
fi
done
printf '%s\n' "$(echo "$pids" | xargs 2>/dev/null || true)"
}
```
以及 `stop_backend()`,要求:
- 先发 `TERM`
- 等待最多 30 秒
- 超时后发 `KILL`
- 最后删除 `backend.pid`
- [ ] **Step 3: 实现启动流程**
补齐 `start_backend()`,要求:
```sh
nohup "$JAVA_BIN" $JAVA_OPTS "$BACKEND_MARKER" -jar "$BACKEND_JAR_TARGET" \
--spring.profiles.active="$SPRING_PROFILE" \
--server.port="$BACKEND_PORT" >> "$BACKEND_LOG_FILE" 2>&1 &
```
并在启动后:
- 写入 `backend/backend.pid`
- 轮询 `ss -lnt | grep ":63310 "` 最长 30 秒
- 若未监听成功则报错退出
- [ ] **Step 4: 实现主流程调用顺序**
主流程必须按以下顺序调用:
```sh
backup_backend_jar
stop_backend
deploy_backend_jar "$backend_jar_source"
start_backend
```
不得把 `stop_backend` 放到备份后面之外的位置,避免旧进程继续占用将被替换的 `jar`
- [ ] **Step 5: 做语法校验**
Run: `sh -n bin/prod/deploy_from_package.sh`
Expected: 无输出,返回码为 0。
- [ ] **Step 6: 提交本任务**
```bash
git add bin/prod/deploy_from_package.sh
git commit -m "实现生产部署脚本后端启停逻辑"
```
### Task 5: 补后端实施记录并验证关键链路
**Files:**
- Modify: `bin/prod/deploy_from_package.sh`
- Create: `doc/implementation-report-2026-04-01-production-one-click-deploy-backend.md`
- [ ] **Step 1: 做脚本静态语法校验**
Run: `sh -n bin/prod/deploy_from_package.sh`
Expected: 无输出,返回码为 0。
- [ ] **Step 2: 检查脚本中的关键配置与标记是否落位**
Run: `rg -n "JAVA_BIN=|BACKEND_PORT=63310|SPRING_PROFILE=|BACKEND_MARKER=|backend.pid|backend-console.log|pgrep -f|ss -lnt" bin/prod/deploy_from_package.sh`
Expected: 能看到 Java 路径、端口、进程标记、PID 文件、日志文件和端口等待逻辑都已写入脚本。
- [ ] **Step 3: 编写后端实施记录**
在 [doc/implementation-report-2026-04-01-production-one-click-deploy-backend.md](/Users/wkc/Desktop/loan-pricing/loan-pricing/doc/implementation-report-2026-04-01-production-one-click-deploy-backend.md) 至少记录:
```markdown
- 新增脚本 `bin/prod/deploy_from_package.sh`
- 脚本内固定 `JAVA_BIN`
- 后端发布包解压与唯一 jar 校验规则
- 旧版 jar 时间戳备份规则
- PID 文件、进程标记、端口 63310 等待逻辑
- 执行的验证命令与结果
```
- [ ] **Step 4: 核对实施记录路径**
Run: `ls doc/implementation-report-2026-04-01-production-one-click-deploy-backend.md`
Expected: 文件存在于仓库 `doc/` 目录。
- [ ] **Step 5: 提交本任务**
```bash
git add bin/prod/deploy_from_package.sh doc/implementation-report-2026-04-01-production-one-click-deploy-backend.md
git commit -m "完成生产一键部署后端实现"
```