260 lines
7.6 KiB
Bash
Executable File
260 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ===== 本地调试启动脚本:后端 reload + 前端热更新 =====
|
|
|
|
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
cd "$ROOT_DIR"
|
|
|
|
if [ -f "$ROOT_DIR/.env" ]; then
|
|
set -a
|
|
# 调试模式复用 Docker 部署的环境变量,尤其是 JWT 和敏感字段加密密钥。
|
|
# shellcheck disable=SC1091
|
|
source "$ROOT_DIR/.env"
|
|
set +a
|
|
fi
|
|
|
|
BACKEND_HOST="${BACKEND_HOST:-0.0.0.0}"
|
|
BACKEND_PORT="${BACKEND_PORT:-8800}"
|
|
FRONTEND_HOST="${FRONTEND_HOST:-0.0.0.0}"
|
|
FRONTEND_PORT="${FRONTEND_PORT:-5174}"
|
|
LOG_LEVEL="${LOG_LEVEL:-DEBUG}"
|
|
MYSQL_IMAGE="${MYSQL_IMAGE:-mysql:8.4}"
|
|
MYSQL_BIND_HOST="${MYSQL_BIND_HOST:-127.0.0.1}"
|
|
MYSQL_HOST_PORT="${MYSQL_HOST_PORT:-3307}"
|
|
MYSQL_DATABASE="${MYSQL_DATABASE:-douyu_login}"
|
|
MYSQL_USER="${MYSQL_USER:-douyu_login}"
|
|
DEV_DB_HOST="${DEV_DB_HOST:-127.0.0.1}"
|
|
DEV_DB_PORT="${DEV_DB_PORT:-$MYSQL_HOST_PORT}"
|
|
DB_HOST="$DEV_DB_HOST"
|
|
DB_PORT="$DEV_DB_PORT"
|
|
DB_NAME="${DB_NAME:-$MYSQL_DATABASE}"
|
|
DB_USER="${DB_USER:-$MYSQL_USER}"
|
|
DB_PASSWORD="${DB_PASSWORD:-${MYSQL_PASSWORD:-}}"
|
|
DB_POOL_SIZE="${DB_POOL_SIZE:-20}"
|
|
DB_MAX_OVERFLOW="${DB_MAX_OVERFLOW:-20}"
|
|
DB_POOL_TIMEOUT="${DB_POOL_TIMEOUT:-30}"
|
|
DB_POOL_RECYCLE="${DB_POOL_RECYCLE:-1800}"
|
|
YYB_WORKER_PORT="${YYB_WORKER_PORT:-8810}"
|
|
YYB_WORKER_IMAGE="${YYB_WORKER_IMAGE:-douyu-yyb-worker:local}"
|
|
DEV_YYB_WORKER_URL="${DEV_YYB_WORKER_URL:-http://127.0.0.1:${YYB_WORKER_PORT}}"
|
|
YYB_WORKER_CONTAINER="${YYB_WORKER_CONTAINER:-douyu-yyb-worker-dev}"
|
|
YYB_WORKER_DATA_DIR="${YYB_WORKER_DATA_DIR:-$ROOT_DIR/data/yyb-worker-dev}"
|
|
LOG_DIR="$ROOT_DIR/logs"
|
|
LOG_DATE="$(date +%Y-%m-%d)"
|
|
BACKEND_LOG="$LOG_DIR/backend-$LOG_DATE.log"
|
|
FRONTEND_LOG="$LOG_DIR/frontend-$LOG_DATE.log"
|
|
WORKER_LOG="$LOG_DIR/yyb-worker-$LOG_DATE.log"
|
|
|
|
BACKEND_PID=""
|
|
FRONTEND_PID=""
|
|
WORKER_LOG_PID=""
|
|
WORKER_STARTED_BY_DEV=false
|
|
|
|
stop_tree() {
|
|
local pid="$1"
|
|
local children
|
|
if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
|
|
return
|
|
fi
|
|
children="$(pgrep -P "$pid" 2>/dev/null || true)"
|
|
for child in $children; do
|
|
stop_tree "$child"
|
|
done
|
|
kill "$pid" 2>/dev/null || true
|
|
}
|
|
|
|
cleanup() {
|
|
trap - EXIT INT TERM
|
|
stop_tree "$FRONTEND_PID"
|
|
stop_tree "$BACKEND_PID"
|
|
stop_tree "$WORKER_LOG_PID"
|
|
if [ "$WORKER_STARTED_BY_DEV" = true ]; then
|
|
docker stop "$YYB_WORKER_CONTAINER" >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
require_cmd() {
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
echo "缺少命令: $1"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
require_cmd uv
|
|
require_cmd npm
|
|
require_cmd docker
|
|
|
|
detect_compose() {
|
|
if docker compose version >/dev/null 2>&1; then
|
|
echo "docker compose"
|
|
elif command -v docker-compose >/dev/null 2>&1; then
|
|
echo "docker-compose"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
detect_backend_proxy_host() {
|
|
if [ "$BACKEND_HOST" != "0.0.0.0" ] && [ "$BACKEND_HOST" != "::" ]; then
|
|
echo "$BACKEND_HOST"
|
|
return
|
|
fi
|
|
# 绑定 0.0.0.0 时,Vite 代理不要固定连 127.0.0.1;某些本地工具会抢占 loopback 端口。
|
|
local ip
|
|
ip="$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || true)"
|
|
if [ -z "$ip" ] && command -v hostname >/dev/null 2>&1; then
|
|
ip="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
|
|
fi
|
|
echo "${ip:-127.0.0.1}"
|
|
}
|
|
|
|
if [ -n "${DATABASE_URL:-}" ] && [[ "$DATABASE_URL" != mysql+* ]]; then
|
|
echo "dev.sh 只支持 MySQL,请移除 SQLite DATABASE_URL: $DATABASE_URL"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "${MYSQL_PASSWORD:-}" ] || [ -z "${MYSQL_ROOT_PASSWORD:-}" ]; then
|
|
echo "请先在 .env 中设置 MYSQL_PASSWORD 和 MYSQL_ROOT_PASSWORD"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$DB_PASSWORD" ]; then
|
|
echo "请先在 .env 中设置 DB_PASSWORD 或 MYSQL_PASSWORD"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "${YYB_WORKER_KEY:-}" ]; then
|
|
echo "请先在 .env 中设置 YYB_WORKER_KEY"
|
|
exit 1
|
|
fi
|
|
|
|
COMPOSE=$(detect_compose)
|
|
if [ -z "$COMPOSE" ]; then
|
|
echo "缺少 docker compose"
|
|
exit 1
|
|
fi
|
|
BACKEND_PROXY_HOST="${BACKEND_PROXY_HOST:-$(detect_backend_proxy_host)}"
|
|
BACKEND_PROXY_TARGET="${VITE_BACKEND_TARGET:-http://${BACKEND_PROXY_HOST}:${BACKEND_PORT}}"
|
|
|
|
mkdir -p data "$LOG_DIR"
|
|
mkdir -p "$YYB_WORKER_DATA_DIR"
|
|
|
|
trap cleanup EXIT INT TERM
|
|
|
|
start_yyb_worker() {
|
|
if docker ps --format '{{.Names}}' | grep -qx "$YYB_WORKER_CONTAINER"; then
|
|
echo "本地 YYB Worker 已运行: ${DEV_YYB_WORKER_URL}"
|
|
return
|
|
fi
|
|
if docker ps -a --format '{{.Names}}' | grep -qx "$YYB_WORKER_CONTAINER"; then
|
|
echo "发现已停止的本地 YYB Worker,正在清理旧容器..."
|
|
docker rm "$YYB_WORKER_CONTAINER" >/dev/null
|
|
fi
|
|
|
|
echo "正在构建本地 YYB Worker..."
|
|
YYB_WORKER_KEY="$YYB_WORKER_KEY" $COMPOSE build yyb-worker
|
|
echo "正在启动本地 YYB Worker..."
|
|
docker run -d --rm \
|
|
--name "$YYB_WORKER_CONTAINER" \
|
|
-p "127.0.0.1:${YYB_WORKER_PORT}:8810" \
|
|
-e TZ="${TZ:-Asia/Shanghai}" \
|
|
-e YYB_WORKER_KEY="$YYB_WORKER_KEY" \
|
|
-v "$YYB_WORKER_DATA_DIR:/app/config/worker-jobs" \
|
|
"$YYB_WORKER_IMAGE" >/dev/null
|
|
WORKER_STARTED_BY_DEV=true
|
|
|
|
echo "等待本地 YYB Worker 就绪..."
|
|
for i in $(seq 1 15); do
|
|
if curl -fsS "${DEV_YYB_WORKER_URL}/health" >/dev/null 2>&1; then
|
|
echo "YYB Worker 已就绪"
|
|
return
|
|
fi
|
|
if [ "$i" = "15" ]; then
|
|
echo "YYB Worker 启动超时,查看日志:"
|
|
docker logs "$YYB_WORKER_CONTAINER" || true
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
}
|
|
|
|
stream_yyb_worker_logs() {
|
|
# docker logs 默认只输出到终端;持续转存后保留 Worker 的异常和协议脚本输出。
|
|
(docker logs --tail 100 --follow "$YYB_WORKER_CONTAINER" 2>&1 | tee -a "$WORKER_LOG") &
|
|
WORKER_LOG_PID=$!
|
|
}
|
|
|
|
start_yyb_worker
|
|
stream_yyb_worker_logs
|
|
|
|
echo "正在启动本地 MySQL..."
|
|
MYSQL_IMAGE="$MYSQL_IMAGE" \
|
|
MYSQL_BIND_HOST="$MYSQL_BIND_HOST" \
|
|
MYSQL_HOST_PORT="$MYSQL_HOST_PORT" \
|
|
$COMPOSE up -d mysql
|
|
|
|
echo "等待 MySQL 就绪..."
|
|
for i in $(seq 1 30); do
|
|
if $COMPOSE exec -T mysql sh -c 'mysqladmin ping -h localhost -uroot -p"$MYSQL_ROOT_PASSWORD" --silent' >/dev/null 2>&1; then
|
|
echo "MySQL 已就绪"
|
|
break
|
|
fi
|
|
if [ "$i" = "30" ]; then
|
|
echo "MySQL 启动超时,查看日志:"
|
|
$COMPOSE logs --tail 50 mysql
|
|
exit 1
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
echo ""
|
|
echo "=============================="
|
|
echo " 本地调试模式"
|
|
echo "=============================="
|
|
echo " 后端: http://${BACKEND_HOST}:${BACKEND_PORT}"
|
|
echo " 前端: http://localhost:${FRONTEND_PORT}"
|
|
echo " API 代理: ${BACKEND_PROXY_TARGET}"
|
|
echo " MySQL: ${DB_HOST}:${DB_PORT}/${DB_NAME}"
|
|
echo " YYB Worker: ${DEV_YYB_WORKER_URL}"
|
|
echo " 后端日志: ${BACKEND_LOG}"
|
|
echo " 前端日志: ${FRONTEND_LOG}"
|
|
echo " Worker 日志: ${WORKER_LOG}"
|
|
echo " 退出: Ctrl+C"
|
|
echo "=============================="
|
|
echo ""
|
|
|
|
(
|
|
export DB_HOST DB_PORT DB_NAME DB_USER DB_PASSWORD
|
|
export DB_POOL_SIZE DB_MAX_OVERFLOW DB_POOL_TIMEOUT DB_POOL_RECYCLE
|
|
export YYB_WORKER_KEY LOG_LEVEL
|
|
export YYB_WORKER_URL="$DEV_YYB_WORKER_URL"
|
|
uv run uvicorn web.backend.main:app \
|
|
--host "$BACKEND_HOST" \
|
|
--port "$BACKEND_PORT" \
|
|
--reload \
|
|
--reload-dir core \
|
|
--reload-dir utils \
|
|
--reload-dir web/backend 2>&1 | tee -a "$BACKEND_LOG"
|
|
) &
|
|
BACKEND_PID=$!
|
|
|
|
(
|
|
cd web/frontend
|
|
VITE_BACKEND_TARGET="$BACKEND_PROXY_TARGET" \
|
|
npm run dev -- --host "$FRONTEND_HOST" --port "$FRONTEND_PORT" 2>&1 | tee -a "$FRONTEND_LOG"
|
|
) &
|
|
FRONTEND_PID=$!
|
|
|
|
while true; do
|
|
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
|
|
wait "$BACKEND_PID"
|
|
exit $?
|
|
fi
|
|
if ! kill -0 "$FRONTEND_PID" 2>/dev/null; then
|
|
wait "$FRONTEND_PID"
|
|
exit $?
|
|
fi
|
|
sleep 1
|
|
done
|