352 lines
12 KiB
Bash
Executable File
352 lines
12 KiB
Bash
Executable File
#!/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 gateway activityService galleryService socialService assetService userService; do
|
||
pkill -9 -f "$service" 2>/dev/null || true
|
||
rm -f "/tmp/dev_sh_${service}.pid" "/tmp/dev_sh_${service}_restart" "/tmp/dev_sh_${service}.lock"
|
||
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"
|
||
local lock_file="/tmp/dev_sh_${name}.lock"
|
||
|
||
# 加锁防止并发重启
|
||
if [ -f "$lock_file" ]; then
|
||
return 0
|
||
fi
|
||
touch "$lock_file"
|
||
|
||
# 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
|
||
|
||
# 解锁
|
||
rm -f "$lock_file"
|
||
}
|
||
|
||
# 启动文件监听器
|
||
# 用法: 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
|
||
# 排除: .git目录, 测试文件, 二进制文件(无扩展名), .exe, bin/目录
|
||
fswatch -r "$watch_path" \
|
||
--exclude='\.git' \
|
||
--exclude='_test\.go$' \
|
||
--exclude='\.exe$' \
|
||
--exclude='gateway$' \
|
||
--exclude='userService$' \
|
||
--exclude='assetService$' \
|
||
--exclude='socialService$' \
|
||
--exclude='galleryService$' \
|
||
--exclude='activityService$'
|
||
else
|
||
inotifywait -r -m -e modify,create,write "$watch_path" \
|
||
--exclude='\.git' \
|
||
--exclude='_test\.go$' \
|
||
--exclude='\.exe$' \
|
||
--exclude='gateway$' \
|
||
--exclude='userService$' \
|
||
--exclude='assetService$' \
|
||
--exclude='socialService$' \
|
||
--exclude='galleryService$' \
|
||
--exclude='activityService$'
|
||
fi | while read event; do
|
||
# 时间戳防抖:每次事件更新标记文件
|
||
# Darwin 不支持 date +%s%N,使用 python 获取纳秒时间戳
|
||
if [[ "$(uname)" == "Darwin" ]]; then
|
||
python3 -c 'import time; print(int(time.time()*1e9))' > "$restart_marker"
|
||
else
|
||
date +%s%N > "$restart_marker"
|
||
fi
|
||
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
|
||
if [[ "$(uname)" == "Darwin" ]]; then
|
||
now=$(python3 -c 'import time; print(int(time.time()*1e9))')
|
||
else
|
||
now=$(date +%s%N)
|
||
fi
|
||
local last_time=$(cat "$restart_marker" 2>/dev/null || echo 0)
|
||
local elapsed=$((now - last_time))
|
||
if (( elapsed >= 500000000 )); 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 |