| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- """PaperGraph 主应用入口 —— FastAPI 实例、中间件、路由挂载与生命周期管理."""
- import asyncio
- import logging
- from contextlib import asynccontextmanager
- from fastapi import FastAPI, HTTPException, Request
- from fastapi.middleware.cors import CORSMiddleware
- from fastapi.responses import JSONResponse
- from starlette.middleware.base import BaseHTTPMiddleware
- from ..settings import configure_logging, get_settings, print_config, validate_config
- from ..services.graph.kg_relations import get_kg_metrics
- from .routes import paper_routes, paper_reader_routes, search_routes
- settings = get_settings()
- logger = logging.getLogger(__name__)
- class _MeaningfulActivityMiddleware(BaseHTTPMiddleware):
- async def dispatch(self, request: Request, call_next):
- response = await call_next(request)
- try:
- from ..services.daily.daily_auto_refresh import touch_meaningful_activity_if_needed
- touch_meaningful_activity_if_needed(request.app, request.method, request.url.path)
- except Exception:
- pass
- return response
- @asynccontextmanager
- async def lifespan(app: FastAPI):
- configure_logging(settings.log_level)
- logger.info("%s", "=" * 60)
- logger.info("📚 %s v%s", settings.app_name, settings.app_version)
- logger.info("%s", "=" * 60)
- app.state.last_meaningful_activity_monotonic = None
- daily_refresh_task: asyncio.Task | None = None
- print_config()
- try:
- validate_config()
- logger.info("✅ 配置验证通过")
- except ValueError as e:
- logger.error("❌ 配置验证失败: %s", e)
- raise
- try:
- from ..services.daily.daily_auto_refresh import spawn_daily_auto_refresh
- daily_refresh_task = spawn_daily_auto_refresh(app)
- except Exception as exc:
- logger.warning("每日论文后台自动刷新任务未启动: %s", exc)
- logger.info("%s", "=" * 60)
- yield
- logger.info("%s", "=" * 60)
- logger.info("👋 应用正在关闭...")
- if daily_refresh_task is not None:
- daily_refresh_task.cancel()
- try:
- await daily_refresh_task
- except asyncio.CancelledError:
- pass
- except Exception:
- logger.debug("每日论文后台任务结束异常", exc_info=True)
- logger.info("%s", "=" * 60)
- app = FastAPI(
- title=settings.app_name,
- version=settings.app_version,
- description=settings.description,
- docs_url=None,
- redoc_url=None,
- lifespan=lifespan,
- )
- app.add_middleware(
- CORSMiddleware,
- allow_origins=settings.get_cors_origins_list(),
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
- app.add_middleware(_MeaningfulActivityMiddleware)
- app.include_router(paper_routes.router, prefix="/api")
- app.include_router(paper_reader_routes.router, prefix="/api")
- app.include_router(search_routes.router, prefix="/api")
- @app.get("/")
- async def root():
- return {
- "name": settings.app_name,
- "version": settings.app_version,
- "status": "running",
- "docs_enabled": False,
- }
- @app.get("/health")
- async def health():
- return {
- "status": "healthy",
- "service": settings.app_name,
- "version": settings.app_version,
- "kg_metrics": get_kg_metrics(),
- }
- @app.exception_handler(Exception)
- async def global_exception_handler(request: Request, exc: Exception):
- if isinstance(exc, HTTPException):
- return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail})
- logger.exception(
- "未处理异常: %s %s",
- request.method,
- request.url.path,
- exc_info=exc,
- )
- return JSONResponse(
- status_code=500,
- content={"success": False, "message": str(exc)},
- )
- if __name__ == "__main__":
- import uvicorn
- uvicorn.run(
- "app.api.main:app",
- host=settings.host,
- port=settings.port,
- reload=settings.debug,
- )
|