Files
backend/backend/app/main.py
T
34047007@qq.com a6cd99a4ca
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
feat: initial commit - oncology literature search platform
OncoLit: a multi-tenant oncology literature search, feed, and
collaboration platform. Built with FastAPI + Vue 3 + PostgreSQL.
Includes PubMed pipeline, drug approvals, AI summaries, and
systematic review tools.
2026-07-27 07:59:18 +08:00

107 lines
3.8 KiB
Python

from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from app.api.v1.router import api_router
from app.config import settings
from app.core.csrf import CSRFProtectionMiddleware
from app.core.error_handlers import SecurityHeadersMiddleware, global_exception_handler, http_exception_handler
from app.core.logging_config import setup_logging
from app.core.middleware import TraceMiddleware
from app.core.monitoring import PerformanceMiddleware, RequestLoggingMiddleware, get_metrics
from app.core.monitoring import init_sentry
from app.core.rate_limiter import RateLimitMiddleware
from app.db import engine
from app.core.logging_config import get_logger
setup_logging()
init_sentry(settings.SENTRY_DSN or None)
logger = get_logger("startup")
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动检查:非 DEBUG 模式不能使用默认 JWT_SECRET
if not settings.DEBUG and settings.JWT_SECRET == "dev-secret-change-in-production":
import sys
print("\n" + "=" * 60, file=sys.stderr)
print(" FATAL: JWT_SECRET is set to the default dev value!", file=sys.stderr)
print(" Set a secure JWT_SECRET in .env or environment variables.", file=sys.stderr)
print("=" * 60 + "\n", file=sys.stderr)
sys.exit(1)
# SMTP 配置状态检查
if settings.SMTP_HOST:
logger.info("SMTP configured: %s:%s%s", settings.SMTP_HOST, settings.SMTP_PORT, settings.SMTP_FROM)
else:
logger.warning("SMTP not configured — emails will be logged, not sent")
yield
await engine.dispose()
app = FastAPI(
title="SciLit Oncology",
description="肿瘤科文献管理平台",
version="0.1.0",
lifespan=lifespan,
docs_url="/docs" if settings.DEBUG else None,
redoc_url="/redoc" if settings.DEBUG else None,
openapi_url="/openapi.json" if settings.DEBUG else None,
)
# 全局异常处理
app.add_exception_handler(Exception, global_exception_handler)
app.add_exception_handler(HTTPException, http_exception_handler)
# 中间件(从内到外)
# CORS: allow_credentials 不能与 allow_origins="*" 同时用,必须明确列出允许的源
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
allow_headers=["Authorization", "Content-Type", "X-CSRF-Token", "X-Tenant-Id"],
expose_headers=["X-Response-Time-Ms", "X-RateLimit-Remaining", "X-RateLimit-Limit"],
)
app.add_middleware(RateLimitMiddleware)
app.add_middleware(PerformanceMiddleware)
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware(CSRFProtectionMiddleware)
app.add_middleware(RequestLoggingMiddleware)
app.add_middleware(TraceMiddleware)
app.include_router(api_router, prefix=settings.API_V1_PREFIX)
@app.get("/health")
async def health():
db_ok = False
try:
from sqlalchemy import text
async with engine.begin() as conn:
await conn.execute(text("SELECT 1"))
db_ok = True
except Exception:
import logging
logging.getLogger(__name__).warning("Health check DB connection failed")
return {"status": "ok" if db_ok else "degraded", "specialty": settings.SPECIALTY, "db": "ok" if db_ok else "error", "version": "0.1.0"}
@app.get("/health/metrics")
async def health_metrics():
"""监控指标端点"""
db_ok = False
try:
from sqlalchemy import text
async with engine.begin() as conn:
await conn.execute(text("SELECT 1"))
db_ok = True
except Exception:
import logging
logging.getLogger(__name__).warning("Health metrics DB connection failed")
return {
"db": "ok" if db_ok else "error",
"alerts": get_metrics(),
"ws": {"online_users": 0, "online_connections": 0},
}