topfans/backend/dev.sh
zerosaturation 2382ae880c feat: add signal trap and graceful shutdown to dev.sh
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 12:46:49 +08:00

313 lines
10 KiB
Bash
Executable File
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.

#!/bin/bash
# Hot-Reload Dev Script for TopFans Backend
# Usage: ./dev.sh
#
# Requires: fswatch (Mac: brew install fswatch) or inotifywait (Linux)
set -e
# 信号捕获:优雅退出
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
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"
# 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
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")
# 启动一个服务
# 用法: 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=$!
# 保存 PID 到文件
echo $pid > "/tmp/dev_sh_${name}.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
}
# 构建一个服务(在服务目录下执行 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
}
# 重建并重启单个服务(构建成功后才杀旧进程)
# 用法: 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
}
# 启动文件监听器
# 用法: 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
# 时间戳防抖:每次事件更新标记文件
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
}
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