#!/bin/zsh # 双击本文件,或在终端执行:./start.command cd "$(dirname "$0")" BACKEND_HOST="127.0.0.1" BACKEND_PORT="8800" BACKEND_URL="http://${BACKEND_HOST}:${BACKEND_PORT}" STUDIO_URL="http://localhost:8900" if [[ ! -d .venv ]]; then echo "未找到 .venv,正在创建…" python3 -m venv .venv || exit 1 fi source .venv/bin/activate # 每次启动都同步依赖,requirements.txt 有更新时自动补装(已装满时耗时极短) pip install -q -r server/requirements.txt || exit 1 if [[ ! -f .env ]]; then echo "未找到 .env,已从 .env.example 复制,请填入 DEEPSEEK_API_KEY 后再启动。" cp .env.example .env echo "按回车关闭…" read -r exit 1 fi # 数据结构变更由 Alembic 管理;必须先迁移,再启动会读取新字段的服务端。 if ! alembic upgrade head; then echo "⚠️ 数据库迁移失败,停止启动以避免新旧代码与表结构不一致" exit 1 fi # ---- 采集插件(extensions/collector):启动时构建一次,之后监听代码改动自动重建 ---- # 产物 .output/chrome-mv3 由 Chrome 以解压方式加载;重建后仍需去 chrome://extensions 点刷新 COLLECTOR_WATCH_PID="" COLLECTOR_STAMP="$(mktemp -u /tmp/osk-collector-stamp.XXXXXX)" watch_collector() { touch "$COLLECTOR_STAMP" while true; do sleep 3 if [[ -n "$(find extensions/collector/src extensions/collector/wxt.config.ts -newer "$COLLECTOR_STAMP" -print 2>/dev/null)" ]]; then touch "$COLLECTOR_STAMP" echo "🔧 检测到插件代码改动,重新构建…" if pnpm -C extensions/collector build >/dev/null 2>&1; then echo "✅ 采集插件已重建($(date +%H:%M:%S)),去 chrome://extensions 点刷新生效" else echo "⚠️ 采集插件重建失败($(date +%H:%M:%S)):可手动跑 pnpm -C extensions/collector build 看报错" fi fi done } if [[ ! -d extensions/collector ]]; then echo "⚠️ 未找到 extensions/collector,跳过采集插件构建" elif ! command -v pnpm >/dev/null 2>&1; then echo "⚠️ 未找到 pnpm,本次跳过采集插件构建(可手动:pnpm -C extensions/collector build)" elif ! CI=true pnpm -C extensions/collector install --silent; then echo "⚠️ 采集插件依赖安装失败,本次跳过构建" else echo "正在构建采集插件…" if pnpm -C extensions/collector build; then echo "采集插件已构建:extensions/collector/.output/chrome-mv3" else echo "⚠️ 采集插件构建失败,继续启动服务(可手动重试:pnpm -C extensions/collector build)" fi watch_collector & COLLECTOR_WATCH_PID=$! fi echo # ---- 同步并启动前端(studio:Vite 开发服务器,/api 代理到后端 8800)---- STUDIO_PID="" WORKER_SUPERVISOR_PID="" cleanup() { if [[ -n "$WORKER_SUPERVISOR_PID" ]] && kill -0 "$WORKER_SUPERVISOR_PID" 2>/dev/null; then # 先终止 Worker 子进程,再结束守护循环;避免退出 start.command 后残留处理器。 pkill -P "$WORKER_SUPERVISOR_PID" 2>/dev/null kill "$WORKER_SUPERVISOR_PID" 2>/dev/null fi if [[ -n "$STUDIO_PID" ]] && kill -0 "$STUDIO_PID" 2>/dev/null; then kill "$STUDIO_PID" 2>/dev/null sleep 0.2 # 兜底:确保 vite 及其子进程退出 lsof -ti :8900 | xargs kill 2>/dev/null fi if [[ -n "$COLLECTOR_WATCH_PID" ]] && kill -0 "$COLLECTOR_WATCH_PID" 2>/dev/null; then pkill -P "$COLLECTOR_WATCH_PID" 2>/dev/null # 可能正在跑的 build 子进程 kill "$COLLECTOR_WATCH_PID" 2>/dev/null fi rm -f "$COLLECTOR_STAMP" 2>/dev/null } trap cleanup EXIT # ---- 持久化后台任务 Worker(与 uvicorn 独立,服务重启不会中断正在轮询的模型任务)---- # Worker 因配置/网络等未捕获错误退出时,2 秒后自动拉起;否则任务会长期停在「排队中」。 supervise_worker() { while true; do python server/worker.py WORKER_EXIT_CODE=$? echo "⚠️ 后台任务 Worker 已退出(code: $WORKER_EXIT_CODE),2 秒后自动重启…" sleep 2 done } supervise_worker & WORKER_SUPERVISOR_PID=$! echo "后台任务 Worker 守护进程已启动(PID: $WORKER_SUPERVISOR_PID)" if ! command -v pnpm >/dev/null 2>&1; then echo "⚠️ 未找到 pnpm,本次跳过 studio 前端(可手动:cd studio && pnpm dev)" # CI=true:node_modules 与 lockfile 不一致时自动重建,不弹确认 elif CI=true pnpm -C studio install --silent; then pnpm -C studio dev & # vite:8900,启动后会自动打开浏览器 STUDIO_PID=$! else echo "⚠️ studio 依赖安装失败,本次只启动后端" fi echo echo "前端访问地址:" echo " 工具台 v1(web/) $BACKEND_URL/ozonSeller.html" echo " 发布工作台(studio/) $STUDIO_URL" echo " 后端 API 文档 $BACKEND_URL/docs" echo echo "按 Ctrl+C 可停止全部服务" echo # --app-dir server 让 uvicorn 在 server/ 下找 main:app,工作目录仍是仓库根(.env 在此) uvicorn main:app --app-dir server --reload --reload-include '*.yaml' --host "$BACKEND_HOST" --port "$BACKEND_PORT" echo echo "服务已停止。按回车关闭窗口…" read -r