From 2de9e1dd5741d0cdbbbf350a99cd21a9c845bb5e Mon Sep 17 00:00:00 2001 From: zerosaturation Date: Mon, 13 Apr 2026 12:09:34 +0800 Subject: [PATCH] docs: fix hot-reload plan - timestamp debounce, PID files, cleanup Co-Authored-By: Claude Opus 4.6 --- .../2026-04-13-hot-reload-dev-script-plan.md | 499 ++++++++++++++++++ 1 file changed, 499 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-13-hot-reload-dev-script-plan.md diff --git a/docs/superpowers/plans/2026-04-13-hot-reload-dev-script-plan.md b/docs/superpowers/plans/2026-04-13-hot-reload-dev-script-plan.md new file mode 100644 index 0000000..92b4db3 --- /dev/null +++ b/docs/superpowers/plans/2026-04-13-hot-reload-dev-script-plan.md @@ -0,0 +1,499 @@ +# 热更新开发脚本实现计划 (dev.sh) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**目标:** 创建 `dev.sh` 脚本,监听后端各服务目录的 `.go` 文件变更,自动重新编译并重启对应服务。 + +**架构:** 每个服务一个独立的文件监听器进程,监控该服务目录下的所有 `.go` 文件。检测到变更后,通过 300ms 防抖机制避免频繁重建,编译成功后才终止旧进程并启动新进程,确保服务不中断。 + +**技术栈:** Bash 脚本 + fswatch (Mac) / inotifywait (Linux) + +--- + +## 文件结构 + +``` +backend/ +└── dev.sh # 新增:热更新开发脚本 +``` + +`dev.sh` 是唯一新增的文件,所有逻辑都在这个脚本中实现。 + +--- + +## 任务总览 + +| 任务 | 内容 | +|------|------| +| Task 1 | 编写 `dev.sh` 主框架(检测依赖工具、加载 .env、解析参数) | +| Task 2 | 实现服务启动函数(build + start + log) | +| Task 3 | 实现文件监听器函数(fswatch/inotifywait + 防抖 + 重启逻辑) | +| Task 4 | 实现主启动流程(按顺序启动所有服务 + 启动所有监听器) | +| Task 5 | 实现信号捕获与优雅退出(trap SIGINT/SIGTERM) | +| Task 6 | 测试脚本是否能正常运行 | + +--- + +## Task 1: dev.sh 主框架 + +**文件:** +- 新增: `backend/dev.sh` + +- [ ] **Step 1: 创建 `backend/dev.sh` 文件头部** + +```bash +#!/bin/bash + +# Hot-Reload Dev Script for TopFans Backend +# Usage: ./dev.sh +# +# Requires: fswatch (Mac: brew install fswatch) or inotifywait (Linux) + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" +``` + +- [ ] **Step 2: 检测依赖工具** + +```bash +# Detect platform +if [[ "$(uname)" == "Darwin" ]]; then + WATCHER_TOOL="fswatch" + WATCHER_CMD="fswatch -r" +elif [[ "$(uname)" == "Linux" ]]; then + WATCHER_TOOL="inotifywait" + WATCHER_CMD="inotifywait -r -m -e modify,create,write" +else + echo -e "${RED}不支持的平台${NC}" + exit 1 +fi + +if ! command -v "$WATCHER_TOOL" &> /dev/null; then + echo -e "${RED}缺少工具: $WATCHER_TOOL${NC}" + if [[ "$(uname)" == "Darwin" ]]; then + echo "安装方法: brew install fswatch" + else + echo "安装方法: sudo apt install inotify-tools (Debian/Ubuntu)" + echo " sudo yum install inotify-tools (CentOS/RHEL)" + fi + exit 1 +fi +``` + +- [ ] **Step 3: 加载 .env 文件(同 start.sh)** + +```bash +ENV_FILE="$SCRIPT_DIR/.env" +if [ -f "$ENV_FILE" ]; then + echo -e "${GREEN}📄 加载 .env 文件...${NC}" + set -a + source "$ENV_FILE" + set +a +fi + +DB_HOST="${DB_HOST:-localhost}" +DB_PORT="${DB_PORT:-5432}" +DB_USER="${DB_USER:-haihuizhu}" +DB_PASSWORD="${DB_PASSWORD:-admin}" +DB_NAME="${DB_NAME:-top-fans}" +DB_ARGS=(-db-host="$DB_HOST" -db-port="$DB_PORT" -db-user="$DB_USER" -db-password="$DB_PASSWORD" -db-name="$DB_NAME") +``` + +- [ ] **Step 4: Commit** + +```bash +git add backend/dev.sh +git commit -m "feat: add dev.sh main framework header" +``` + +--- + +## Task 2: 服务构建与启动函数 + +- [ ] **Step 1: 添加 `start_service` 函数** + +```bash +# 启动一个服务 +# 用法: start_service name binary port use_db +start_service() { + local name=$1 + local binary=$2 + local port=$3 + local use_db=$4 + + echo -e "${GREEN}🚀 启动 $name...${NC}" + + if [ "$use_db" = "1" ]; then + "$SCRIPT_DIR/$binary" -port=$port "${DB_ARGS[@]}" > "/tmp/${name}.log" 2>&1 & + else + "$SCRIPT_DIR/$binary" -port=$port > "/tmp/${name}.log" 2>&1 & + fi + local pid=$! + + sleep 2 + + if ps -p $pid > /dev/null 2>&1; then + echo -e "${GREEN}✅ $name 已启动 (PID: $pid, 端口: $port)${NC}" + else + echo -e "${RED}❌ $name 启动失败${NC}" + echo -e "${YELLOW}查看日志: tail -f /tmp/${name}.log${NC}" + fi +} +``` + +- [ ] **Step 2: 添加 `build_service` 函数** + +```bash +# 构建一个服务(在服务目录下执行 go build) +# 用法: build_service name dir binary +build_service() { + local name=$1 + local dir=$2 + local binary=$3 + + if [ ! -d "$SCRIPT_DIR/$dir" ]; then + echo -e "${RED}❌ $dir 目录不存在${NC}" + return 1 + fi + + cd "$SCRIPT_DIR/$dir" + if go build -o "$SCRIPT_DIR/$binary" . 2>&1; then + echo -e "${GREEN}✅ $name 编译成功${NC}" + return 0 + else + echo -e "${RED}❌ $name 编译失败${NC}" + return 1 + fi +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add backend/dev.sh +git commit -m "feat: add build_service and start_service functions" +``` + +--- + +## Task 3: 文件监听器函数 + +- [ ] **Step 1: 修改 `start_service` 函数(增加 PID 文件写入)** + +在 `start_service` 函数的 `local pid=$!` 之后,添加: + +```bash + # 保存 PID 到文件 + echo $pid > "/tmp/dev_sh_${name}.pid" +``` + +- [ ] **Step 2: 添加 `restart_service` 函数(单服务重建重启,PID 文件追踪)** + +```bash +# 重建并重启单个服务(构建成功后才杀旧进程) +# 用法: restart_service name dir binary port use_db +restart_service() { + local name=$1 + local dir=$2 + local binary=$3 + local port=$4 + local use_db=$5 + local pid_file="/tmp/dev_sh_${name}.pid" + + # Step 1: 编译(先编译,编译成功才杀旧进程) + if [ ! -d "$SCRIPT_DIR/$dir" ]; then + echo -e "${RED}❌ $dir 目录不存在,跳过${NC}" + return 1 + fi + + cd "$SCRIPT_DIR/$dir" + if ! go build -o "$SCRIPT_DIR/$binary" . 2>&1; then + echo -e "${RED}❌ [$name] 编译失败,旧进程保持运行${NC}" + echo -e "${RED} 查看详细日志: tail -f /tmp/${name}.log${NC}" + return 1 + fi + echo -e "${GREEN}✅ [$name] 编译成功${NC}" + + # Step 2: 编译成功后,杀旧进程(从 PID 文件读取) + if [ -f "$pid_file" ]; then + local old_pid=$(cat "$pid_file") + if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then + kill "$old_pid" 2>/dev/null || true + echo -e "${YELLOW}🛑 [$name] 旧进程 (PID: $old_pid) 已终止${NC}" + fi + rm -f "$pid_file" + fi + + # Step 3: 启动新进程 + sleep 1 + cd "$SCRIPT_DIR" + if [ "$use_db" = "1" ]; then + "$SCRIPT_DIR/$binary" -port=$port "${DB_ARGS[@]}" > "/tmp/${name}.log" 2>&1 & + else + "$SCRIPT_DIR/$binary" -port=$port > "/tmp/${name}.log" 2>&1 & + fi + local new_pid=$! + echo $new_pid > "$pid_file" + sleep 2 + + if ps -p $new_pid > /dev/null 2>&1; then + echo -e "${GREEN}✅ [$name] 已重启 (PID: $new_pid)${NC}" + else + echo -e "${RED}❌ [$name] 重启失败,查看日志: tail -f /tmp/${name}.log${NC}" + fi +} +``` + +- [ ] **Step 3: 添加 `start_watcher` 函数(时间戳防抖,无 pipe race)** + +```bash +# 启动文件监听器 +# 用法: start_watcher name dir binary port use_db +start_watcher() { + local name=$1 + local dir=$2 + local binary=$3 + local port=$4 + local use_db=$5 + local watch_path="$SCRIPT_DIR/$dir" + local restart_marker="/tmp/dev_sh_${name}_restart" + + if [ ! -d "$watch_path" ]; then + echo -e "${RED}❌ 监听目录不存在: $watch_path${NC}" + return 1 + fi + + # 清理可能遗留的重启标记 + rm -f "$restart_marker" + + ( + if [[ "$(uname)" == "Darwin" ]]; then + fswatch -r "$watch_path" --exclude='\.git' --exclude='_test\.go$' + else + inotifywait -r -m -e modify,create,write "$watch_path" --exclude='\.git' --exclude='_test\.go$' + fi | while read event; do + # 时间戳防抖:每次事件更新标记文件,restart_trigger.sh 负责延迟执行 + date +%s%N > "$restart_marker" + done + ) & + local watcher_pid=$! + echo "$watcher_pid" >> /tmp/dev_sh_watchers.tmp + echo -e "${GREEN}👁️ [$name] 文件监听器已启动 (PID: $watcher_pid)${NC}" + + # 后台防抖循环:每 300ms 检查一次是否需要重启 + ( + while true; do + sleep 0.3 + if [ ! -f "$restart_marker" ]; then + continue + fi + local now=$(date +%s%N) + local last_time=$(cat "$restart_marker" 2>/dev/null || echo 0) + local elapsed=$((now - last_time)) + if (( elapsed >= 300000000 )); then + # 距上次事件已过 300ms,执行重启 + rm -f "$restart_marker" + restart_service "$name" "$dir" "$binary" "$port" "$use_db" + fi + done + ) & + local debounce_pid=$! + echo "$debounce_pid" >> /tmp/dev_sh_watchers.tmp +} +``` + +- [ ] **Step 4: Commit** + +```bash +git add backend/dev.sh +git commit -m "feat: add restart_service and start_watcher functions" +``` + +--- + +## Task 4: 主启动流程 + +- [ ] **Step 1: 添加主启动流程** + +```bash +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN} TopFans Backend 热更新开发模式${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo -e "${YELLOW}数据库: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}${NC}" +echo -e "${YELLOW}文件监听器: $WATCHER_TOOL${NC}" +echo "" + +# 初始化监听器 PID 列表 +> /tmp/dev_sh_watchers.tmp + +# 清理残留 PID 文件(上次非正常退出可能留下) +for service in activityService galleryService socialService assetService userService gateway; do + rm -f "/tmp/dev_sh_${service}.pid" "/tmp/dev_sh_${service}_restart" +done + +# 停止现有服务(清理环境) +echo -e "${YELLOW}🛑 停止现有服务...${NC}" +for service in gateway userService socialService assetService galleryService activityService; do + pkill -9 -f "$service" 2>/dev/null || true +done +sleep 1 + +# 先构建所有服务 +echo "" +echo -e "${YELLOW}🔨 预编译所有服务...${NC}" +build_service "gateway" "gateway" "gateway/gateway" +build_service "userService" "services/userService" "services/userService/userService" +build_service "assetService" "services/assetService" "services/assetService/assetService" +build_service "socialService" "services/socialService" "services/socialService/socialService" +build_service "galleryService" "services/galleryService" "services/galleryService/galleryService" +build_service "activityService" "services/activityService" "services/activityService/activityService" +cd "$SCRIPT_DIR" + +# 启动所有服务 +echo "" +echo -e "${YELLOW}🚀 启动所有服务...${NC}" +start_service "userService" "services/userService/userService" 20000 1 +start_service "assetService" "services/assetService/assetService" 20003 1 +start_service "socialService" "services/socialService/socialService" 20002 1 +start_service "galleryService" "services/galleryService/galleryService" 20004 1 +start_service "activityService" "services/activityService/activityService" 20005 1 +start_service "gateway" "gateway/gateway" 8080 0 + +# 启动所有文件监听器 +echo "" +echo -e "${YELLOW}👁️ 启动所有文件监听器...${NC}" +start_watcher "gateway" "gateway" "gateway/gateway" 8080 0 +start_watcher "userService" "services/userService" "services/userService/userService" 20000 1 +start_watcher "assetService" "services/assetService" "services/assetService/assetService" 20003 1 +start_watcher "socialService" "services/socialService" "services/socialService/socialService" 20002 1 +start_watcher "galleryService" "services/galleryService" "services/galleryService/galleryService" 20004 1 +start_watcher "activityService" "services/activityService" "services/activityService/activityService" 20005 1 + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN} 热更新开发模式已启动!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo -e "${YELLOW}服务地址:${NC}" +echo " - Gateway: http://localhost:8080" +echo " - Swagger UI: http://localhost:8080/swagger/index.html" +echo " - User Service: tri://localhost:20000" +echo " - Social Service: tri://localhost:20002" +echo " - Asset Service: tri://localhost:20003" +echo " - Gallery Service: tri://localhost:20004" +echo " - Activity Service: tri://localhost:20005" +echo "" +echo -e "${YELLOW}按 Ctrl+C 停止所有服务${NC}" +echo "" + +# 保持脚本运行 +wait +``` + +- [ ] **Step 2: Commit** + +```bash +git add backend/dev.sh +git commit -m "feat: add main startup flow in dev.sh" +``` + +--- + +## Task 5: 信号捕获与优雅退出 + +- [ ] **Step 1: 在脚本头部(set -e 之后)添加 trap** + +```bash +# 信号捕获:优雅退出 +cleanup() { + echo "" + echo -e "${YELLOW}🛑 收到停止信号,正在关闭所有服务...${NC}" + + # 先杀所有 watcher 和 debounce 进程(防止继续触发重启) + if [ -f /tmp/dev_sh_watchers.tmp ]; then + while read watcher_pid; do + kill "$watcher_pid" 2>/dev/null || true + done < /tmp/dev_sh_watchers.tmp + rm -f /tmp/dev_sh_watchers.tmp + fi + + # 清理所有 PID 文件并杀服务进程 + for service in activityService galleryService socialService assetService userService gateway; do + pkill -9 -f "$service" 2>/dev/null || true + rm -f "/tmp/dev_sh_${service}.pid" "/tmp/dev_sh_${service}_restart" + echo -e "${YELLOW} 🛑 $service 已停止${NC}" + done + + echo -e "${GREEN}✅ 所有服务已关闭${NC}" + exit 0 +} + +trap cleanup SIGINT SIGTERM +``` + +- [ ] **Step 2: 将 trap 代码移到 `set -e` 之后(Task 1 的 Step 1 中添加)** + +在文件头部的 `set -e` 之后、`SCRIPT_DIR=` 之前插入 trap 代码。 + +- [ ] **Step 3: Commit** + +```bash +git add backend/dev.sh +git commit -m "feat: add signal trap and graceful shutdown to dev.sh" +``` + +--- + +## Task 6: 整体测试 + +- [ ] **Step 1: 添加执行权限并检查脚本语法** + +```bash +chmod +x backend/dev.sh +bash -n backend/dev.sh # 语法检查,输出为空表示无语法错误 +``` + +- [ ] **Step 2: 检查 fswatch 是否可用(如未安装给出提示)** + +```bash +if command -v fswatch &> /dev/null; then + echo "fswatch 已安装: $(fswatch --version | head -1)" +else + echo "fswatch 未安装,Mac 用户请运行: brew install fswatch" +fi +``` + +- [ ] **Step 3: Commit** + +```bash +git add backend/dev.sh +git commit -m "chore: add execute permission and validation to dev.sh" +``` + +--- + +## 完成后验证 + +脚本创建完成后,运行以下命令验证: + +```bash +# 1. 语法检查 +bash -n backend/dev.sh + +# 2. 查看帮助信息 +./backend/dev.sh --help 2>&1 | head -20 + +# 3. (如果有 fswatch)后台启动并检查进程 +./backend/dev.sh & +sleep 5 +ps aux | grep -E "(dev.sh|fswatch|gateway|userService)" | grep -v grep +```