101 lines
3.7 KiB
Python
101 lines
3.7 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 集成)。
|
||
- docgen(P2):routers/docgen.py 对 services/docgen/*(dev1 归属)按契约 import,
|
||
模块缺失时自动回落到内置最小接口桩(生成/编辑返回 503),不阻断 BFF 启动。
|
||
- 生产形态(M6):ui/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 capabilities, docgen, extraction, figures, health, kg, legacy, qa, sessions
|
||
from .routers import bioflow_proxy
|
||
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(capabilities.router)
|
||
app.include_router(kg.router)
|
||
app.include_router(extraction.router)
|
||
app.include_router(figures.router)
|
||
app.include_router(legacy.router)
|
||
app.include_router(docgen.router) # docgen(P2)
|
||
if get_settings().bioflow_enabled:
|
||
app.include_router(bioflow_proxy.router) # 生信反代(BIOFLOW_ENABLED 开关,FR-BIO-028)
|
||
|
||
@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
|
||
|
||
# 生产形态:静态托管 ui/dist(存在时挂载;本地开发期可无)。
|
||
# 2026-08-25 PM 修复:dist 路径必须基于项目根解析(BFF 工作目录是 sdk/,
|
||
# 相对路径会解析成 sdk/ui/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()
|