92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
"""main.py —— BFF 应用入口(ARCHITECTURE.md §2/§3)。
|
||||
|
|
|
|||
|
|
- 挂全部 5 个 router(qa/kg/extraction 为 A2/A3 实现,import 路径不变)。
|
|||
|
|
- CORS:允许 http://localhost:5180 与 http://127.0.0.1:5180(Vite dev)。
|
|||
|
|
- lifespan:init_db(幂等迁移);关闭回收 httpx 客户端与 Neo4j 连接。
|
|||
|
|
- 全局 RequestValidationError → VALIDATION 信封(API.md §1,2026-08-25 PM 集成)。
|
|||
|
|
- 生产形态(M6):frontend/dist 存在时静态托管(ARCHITECTURE.md §3)。
|
|||
|
|
"""
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
from contextlib import asynccontextmanager
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
from fastapi import FastAPI, Request
|
|||
|
|
from fastapi.exceptions import RequestValidationError
|
|||
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|||
|
|
from fastapi.responses import JSONResponse
|
|||
|
|
from fastapi.staticfiles import StaticFiles
|
|||
|
|
|
|||
|
|
from .config import get_settings
|
|||
|
|
from .db import close_db, init_db
|
|||
|
|
from .routers import extraction, health, kg, qa, sessions
|
|||
|
|
from .upstream import close_client
|
|||
|
|
|
|||
|
|
logging.basicConfig(level=logging.INFO)
|
|||
|
|
|
|||
|
|
_ALLOWED_ORIGINS = [
|
|||
|
|
"http://localhost:5180",
|
|||
|
|
"http://127.0.0.1:5180",
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
|
|||
|
|
def create_app() -> FastAPI:
|
|||
|
|
settings = get_settings()
|
|||
|
|
app = FastAPI(title=settings.app_name, version=settings.version)
|
|||
|
|
|
|||
|
|
app.add_middleware(
|
|||
|
|
CORSMiddleware,
|
|||
|
|
allow_origins=_ALLOWED_ORIGINS,
|
|||
|
|
allow_credentials=True,
|
|||
|
|
allow_methods=["*"],
|
|||
|
|
allow_headers=["*"],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
app.include_router(health.router)
|
|||
|
|
app.include_router(sessions.router)
|
|||
|
|
app.include_router(qa.router)
|
|||
|
|
app.include_router(kg.router)
|
|||
|
|
app.include_router(extraction.router)
|
|||
|
|
|
|||
|
|
@app.exception_handler(RequestValidationError)
|
|||
|
|
async def _validation_handler(_: Request, exc: RequestValidationError) -> JSONResponse:
|
|||
|
|
# API.md §1 已裁决:参数校验错误收敛为统一 VALIDATION 信封(422)
|
|||
|
|
return JSONResponse(
|
|||
|
|
status_code=422,
|
|||
|
|
content={
|
|||
|
|
"ok": False,
|
|||
|
|
"error": {"code": "VALIDATION", "message": "请求参数不合法", "degraded": False},
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
@asynccontextmanager
|
|||
|
|
async def _lifespan(_app: FastAPI):
|
|||
|
|
await init_db()
|
|||
|
|
try:
|
|||
|
|
yield
|
|||
|
|
finally:
|
|||
|
|
await close_client()
|
|||
|
|
try:
|
|||
|
|
from .services.neo4j import close_neo4j # A3 提供;缺失则跳过
|
|||
|
|
|
|||
|
|
await close_neo4j()
|
|||
|
|
except ImportError:
|
|||
|
|
pass
|
|||
|
|
await close_db()
|
|||
|
|
|
|||
|
|
app.router.lifespan_context = _lifespan
|
|||
|
|
|
|||
|
|
# 生产形态:静态托管 frontend/dist(存在时挂载;本地开发期可无)。
|
|||
|
|
# 2026-08-25 PM 修复:dist 路径必须基于项目根解析(BFF 工作目录是 backend/,
|
|||
|
|
# 相对路径会解析成 backend/frontend/dist);sync.sh 排除 dist,需单独同步。
|
|||
|
|
project_root = Path(__file__).resolve().parent.parent.parent
|
|||
|
|
dist = project_root / settings.frontend_dist_dir if not Path(settings.frontend_dist_dir).is_absolute() else Path(settings.frontend_dist_dir)
|
|||
|
|
if dist.is_dir():
|
|||
|
|
app.mount("/", StaticFiles(directory=str(dist), html=True), name="frontend")
|
|||
|
|
|
|||
|
|
return app
|
|||
|
|
|
|||
|
|
|
|||
|
|
app = create_app()
|