#!/usr/bin/env bash # 一键启动前后端开发服务 set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PIDS=() log() { echo ">>> $*"; } # 加载仓库根目录 .env(source 后可供 PORT / FRONTEND_PORT 使用) if [[ -f "$ROOT/.env" ]]; then set -a # shellcheck disable=SC1091 source "$ROOT/.env" set +a log "已加载 $ROOT/.env" elif [[ -f "$ROOT/.env.example" ]]; then log "未找到 .env,可执行: cp .env.example .env" fi BACKEND_PORT="${PORT:-8080}" FRONTEND_PORT="${FRONTEND_PORT:-5173}" POSTGRES_USER="${POSTGRES_USER:-affiliate}" POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-affiliate_dev_password}" POSTGRES_DB="${POSTGRES_DB:-affiliate_dash}" POSTGRES_PORT="${POSTGRES_PORT:-5432}" POSTGRES_IMAGE="${POSTGRES_IMAGE:-docker.m.daocloud.io/library/postgres:16-alpine}" DEV_DB_CONTAINER="${DEV_DB_CONTAINER:-affiliate_dash_postgres_dev}" DATABASE_URL="${DATABASE_URL:-postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:${POSTGRES_PORT}/${POSTGRES_DB}?sslmode=disable}" export DATABASE_URL # 结束指定端口上的进程(避免残留占用) free_port() { local port=$1 local pids pids=$(lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true) if [[ -n "${pids}" ]]; then log "端口 ${port} 已被占用,正在释放: ${pids}" # shellcheck disable=SC2086 kill ${pids} 2>/dev/null || true sleep 0.5 pids=$(lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true) if [[ -n "${pids}" ]]; then # shellcheck disable=SC2086 kill -9 ${pids} 2>/dev/null || true fi fi } ensure_postgres() { if (echo >"/dev/tcp/127.0.0.1/${POSTGRES_PORT}") >/dev/null 2>&1; then log "检测到本机 PostgreSQL 端口已可访问: ${POSTGRES_PORT}" return 0 fi if ! command -v docker >/dev/null 2>&1; then log "未找到 Docker,请确认本机 PostgreSQL 已可通过 DATABASE_URL 访问" return 0 fi if docker ps --format '{{.Names}}' | grep -qx "$DEV_DB_CONTAINER"; then log "PostgreSQL 开发容器已运行: ${DEV_DB_CONTAINER}" elif docker ps -a --format '{{.Names}}' | grep -qx "$DEV_DB_CONTAINER"; then log "启动 PostgreSQL 开发容器: ${DEV_DB_CONTAINER}" docker start "$DEV_DB_CONTAINER" >/dev/null else log "创建 PostgreSQL 开发容器: ${DEV_DB_CONTAINER}" docker run -d \ --name "$DEV_DB_CONTAINER" \ -p "${POSTGRES_PORT}:5432" \ -e POSTGRES_USER="$POSTGRES_USER" \ -e POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \ -e POSTGRES_DB="$POSTGRES_DB" \ -v "${DEV_DB_CONTAINER}-data:/var/lib/postgresql/data" \ "$POSTGRES_IMAGE" >/dev/null fi for i in $(seq 1 40); do if docker exec "$DEV_DB_CONTAINER" pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null 2>&1; then log "PostgreSQL 已就绪" return 0 fi sleep 0.5 done echo "错误: PostgreSQL 启动超时" docker logs "$DEV_DB_CONTAINER" 2>/dev/null || true exit 1 } cleanup() { trap - INT TERM EXIT echo "" log "正在停止前后端..." for pid in "${PIDS[@]:-}"; do if kill -0 "$pid" 2>/dev/null; then # 结束子进程树 pkill -P "$pid" 2>/dev/null || true kill "$pid" 2>/dev/null || true fi done free_port "$BACKEND_PORT" free_port "$FRONTEND_PORT" wait 2>/dev/null || true log "已全部停止" exit 0 } trap cleanup INT TERM # 检查依赖 if ! command -v go >/dev/null 2>&1; then echo "错误: 未找到 go,请先安装 Go" exit 1 fi if ! command -v npm >/dev/null 2>&1; then echo "错误: 未找到 npm,请先安装 Node.js" exit 1 fi # 首次安装前端依赖 if [[ ! -d "$ROOT/frontend/node_modules" ]]; then log "安装前端依赖..." (cd "$ROOT/frontend" && npm install) fi free_port "$BACKEND_PORT" free_port "$FRONTEND_PORT" ensure_postgres log "启动后端 http://localhost:${BACKEND_PORT}" ( cd "$ROOT/backend" export PORT="$BACKEND_PORT" go run ./cmd/server ) & PIDS+=($!) # 等后端健康检查就绪(最多约 15 秒) for i in $(seq 1 30); do if curl -sf "http://127.0.0.1:${BACKEND_PORT}/health" >/dev/null 2>&1; then log "后端已就绪" break fi if ! kill -0 "${PIDS[0]}" 2>/dev/null; then echo "错误: 后端启动失败" cleanup fi sleep 0.5 done log "启动前端 http://localhost:${FRONTEND_PORT}" ( cd "$ROOT/frontend" npm run dev -- --host --port "$FRONTEND_PORT" ) & PIDS+=($!) echo "" log "========================================" log " 游戏皮肤分销系统 已启动" log " 前端: http://localhost:${FRONTEND_PORT}" log " 后端: http://localhost:${BACKEND_PORT}" log " 账号: admin / admin123" log " 按 Ctrl+C 停止全部服务" log "========================================" echo "" # 任一子进程退出则整体清理 wait -n 2>/dev/null || wait cleanup