"""FastAPI 应用入口。 负责组装数据库、进程内节点注册表、调度器与静态前端,并在应用生命周期内 管理后台线程的启动与清理。单体版所有节点在同一进程内直接调用。 """ from __future__ import annotations # 先加载 .env(含 LLM API Key 等本地配置),再导入读取环境变量的 config。 from dotenv import load_dotenv load_dotenv() import os # noqa: E402 from contextlib import asynccontextmanager # noqa: E402 from datetime import datetime, timezone # noqa: E402 from pathlib import Path # noqa: E402 from fastapi import FastAPI # noqa: E402 from fastapi.middleware.cors import CORSMiddleware # noqa: E402 from fastapi.staticfiles import StaticFiles # noqa: E402 from wov_app import registry from wov_app.batch import BatchWorker from wov_app.config import BATCH_ENABLED, DB_PATH, STORAGE_DIR, WORKSPACE_ROOT from wov_app.db import Database from wov_app.maintenance import OrphanCleaner from wov_app.routers import apps, batch, workflows from wov_app.scheduler import WorkflowScheduler from wov_app.seed import seed_default_workflows @asynccontextmanager async def lifespan(app: FastAPI): """应用生命周期:启动时初始化数据、注册节点和后台服务,退出时回收资源。""" # 确保数据与存储目录存在,避免首次启动写文件失败。 db = Database(DB_PATH) STORAGE_DIR.mkdir(parents=True, exist_ok=True) # 静态注册全部内置节点到进程内注册表(内存态,不落库)。 registry.register_all() # 默认创建演示工作流,可关闭便于测试。 if os.getenv("WOV_AUTO_SEED", "1") == "1": seed_default_workflows(db) # 重启恢复:上次进程被杀时遗留的 RUNNING 任务恢复为 QUEUED, # 调度器会从产物表断点续跑(不重复已完成节点);PAUSED 保持等待显式 resume。 db.recover_interrupted_runs(datetime.now(timezone.utc).isoformat()) scheduler = WorkflowScheduler(db, STORAGE_DIR) # 调度器默认开启,处理排队中的任务;测试可关闭后手动执行。 if os.getenv("WOV_SCHEDULER_ENABLED", "1") == "1": scheduler.start() cleaner = OrphanCleaner(db, STORAGE_DIR) # 孤儿数据清理默认开启,定时清除死数据;测试可关闭。 if os.getenv("WOV_CLEANUP_ENABLED", "1") == "1": cleaner.start() # 批量处理引擎:处理用户所选文件夹的批量任务(source=batch 的运行), # 与主调度器互不抢占;默认开启,测试可关闭。 batch_worker = BatchWorker(db) if os.getenv("WOV_BATCH_ENABLED", "1") == "1": batch_worker.start() # 共享对象挂到 app.state,路由通过 Depends 延迟获取。 app.state.db = db app.state.scheduler = scheduler app.state.cleaner = cleaner app.state.batch = batch_worker yield # 退出时先停批量引擎、调度器与清理器,避免残留后台线程。 batch_worker.stop() cleaner.stop() scheduler.stop() app = FastAPI(title="VRSub API(单体版)", version="0.1.0", lifespan=lifespan) # MVP 阶段不做鉴权,允许跨域便于本地调试与静态页面访问。 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) app.include_router(workflows.router) app.include_router(apps.router) app.include_router(batch.router) @app.get("/health") def health() -> dict: """进程存活探针,供部署环境与前端检测后端可用性。""" return {"status": "ok", "service": "wov-api", "mode": "monolith"} # 静态前端目录位于仓库根下的 web,由 FastAPI 直接挂载。 FRONTEND_DIR = WORKSPACE_ROOT / "web" app.mount("/", StaticFiles(directory=FRONTEND_DIR, html=True), name="frontend")