feat(dev): scripts/dev_up.sh 一键启动本地后端与页面测试
Verify / verify (push) Failing after 48s

- 迁移 → 管理员初始化 → 前端构建 → API+worker 后台启动 → 健康检查 → 自动打开页面
- 支持 --stop 停止、--no-open 跳过打开浏览器;端口已在服务时直接复用
- 未设 DATABASE_URL 时使用 SQLite 轻量库并给出验收提示
- 修复 bash 3.2(macOS 自带)将紧贴 $VAR 的多字节字符并入变量名的问题:
  全角括号内的变量一律用 ${VAR} 显式界定
- README 本地运行章节补充快速启动说明
This commit is contained in:
Jayway Liu
2026-09-26 07:32:27 +08:00
parent 0ac0c4eb56
commit 0739bcc4fb
2 changed files with 132 additions and 0 deletions
+8
View File
@@ -33,6 +33,14 @@
需要 Node.js 22、uv、Python 3.12、PostgreSQL 17。所有示例从仓库根目录执行。 需要 Node.js 22、uv、Python 3.12、PostgreSQL 17。所有示例从仓库根目录执行。
快速启动本地页面(迁移、管理员初始化、前端构建、API 与导入 worker 一条命令完成,健康检查通过后自动打开 `http://127.0.0.1:8000`;`Ctrl-C` 或 `scripts/dev_up.sh --stop` 停止,`--no-open` 跳过自动打开浏览器):
```bash
scripts/dev_up.sh
```
未设置 `DATABASE_URL` 时该脚本使用 SQLite 轻量开发库 `var/db.sqlite3`,仅限页面测试与开发回归;金额聚合与财务验收需按下方配置 PostgreSQL。手动分步流程如下:
```bash ```bash
uv sync --frozen --python 3.12 uv sync --frozen --python 3.12
npm ci --registry=https://registry.npmjs.org npm ci --registry=https://registry.npmjs.org
+124
View File
@@ -0,0 +1,124 @@
#!/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
if [ ! -f var/local-access.txt ]; then
echo "初始化本地管理员…"
PYTHONPATH="$ROOT" $PY scripts/init_local.py
fi
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