#!/usr/bin/env bash # 一键启动本地开发环境:迁移 → 初始化管理员 → 构建前端 → 启动 API+worker → 打开页面。 # 默认使用 SQLite 轻量开发库(var/db.sqlite3);设置 DATABASE_URL 则使用 PostgreSQL。 # Ctrl-C 或 --stop 停止本脚本启动的进程。 set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" PORT="${PORT:-8000}" SERVER_PID_FILE="var/dev-server.pid" WORKER_PID_FILE="var/dev-worker.pid" SERVER_LOG="var/dev-server.log" WORKER_LOG="var/dev-worker.log" die() { echo "错误:$*" >&2; exit 1; } detect_python() { if [ -x ".venv/bin/python" ]; then PY=".venv/bin/python" elif command -v uv >/dev/null 2>&1; then PY="uv run python" else die "未找到 .venv/bin/python 或 uv,请先安装依赖(uv sync)" fi } read_pidfile() { [ -f "$1" ] && cat "$1" || true; } stop_all() { local stopped=0 pid for file in "$SERVER_PID_FILE" "$WORKER_PID_FILE"; do pid="$(read_pidfile "$file")" if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then kill "$pid" 2>/dev/null || true stopped=1 fi rm -f "$file" done [ "$stopped" = 1 ] && echo "已停止本地开发服务" || echo "没有正在运行的开发服务" } health_ok() { curl -fsS --max-time 2 "http://127.0.0.1:$PORT/api/health" >/dev/null 2>&1; } open_page() { local url="http://127.0.0.1:$PORT/" if command -v open >/dev/null 2>&1; then open "$url" elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$url" >/dev/null 2>&1 || true fi } case "${1:-}" in --stop) stop_all exit 0 ;; "" | --no-open) ;; *) die "未知参数:$1(支持 --stop / --no-open)" ;; esac detect_python mkdir -p var if health_ok; then echo "已在 $PORT 端口运行,直接打开页面" [ "${1:-}" != "--no-open" ] && open_page exit 0 fi if [ -z "${DATABASE_URL:-}" ]; then echo "未设置 DATABASE_URL,使用 SQLite 轻量开发库 var/db.sqlite3(仅限页面测试与开发回归)" fi echo "执行数据库迁移…" $PY manage.py migrate --no-input >/dev/null # init_local.py 以数据库为准幂等执行:缺 admin 时创建并写凭据文件,已存在则跳过 echo "初始化本地管理员(已存在则跳过)…" PYTHONPATH="$ROOT" $PY scripts/init_local.py if [ ! -d frontend/dist ]; then echo "构建前端(首次)…" npm run build >/dev/null fi echo "启动 worker(日志 ${WORKER_LOG})…" nohup $PY manage.py import_worker >"$WORKER_LOG" 2>&1 & echo $! >"$WORKER_PID_FILE" echo "启动 API 服务…" nohup $PY manage.py runserver "127.0.0.1:$PORT" --noreload >"$SERVER_LOG" 2>&1 & echo $! >"$SERVER_PID_FILE" cleanup() { stop_all >/dev/null 2>&1 || true } trap cleanup INT TERM for _ in $(seq 1 30); do if health_ok; then break fi if [ -n "$(read_pidfile "$SERVER_PID_FILE")" ] && ! kill -0 "$(read_pidfile "$SERVER_PID_FILE")" 2>/dev/null; then tail -20 "$SERVER_LOG" >&2 die "API 服务启动失败(完整日志:${SERVER_LOG})" fi sleep 1 done health_ok || { tail -20 "$SERVER_LOG" >&2; die "健康检查超时(日志:${SERVER_LOG})"; } echo echo "本地开发环境已就绪:http://127.0.0.1:$PORT" [ -f var/local-access.txt ] && echo "登录凭据:var/local-access.txt(用户名 admin,权限 0600)" if [ -z "${DATABASE_URL:-}" ]; then echo "提示:当前为 SQLite 轻量模式;高精度金额聚合与生产验收请按 README 配置 PostgreSQL。" fi echo "日志:${SERVER_LOG} / ${WORKER_LOG} · Ctrl-C 或 scripts/dev_up.sh --stop 停止" echo [ "${1:-}" != "--no-open" ] && open_page wait "$(read_pidfile "$SERVER_PID_FILE")" || true cleanup