import logging import os import time from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse, JSONResponse from app.config import settings from app.database import init_db from app.routers import ( auth, user, report, analysis, chat, health_records, calendar, doctor, share, ) # 日志配置 logging.basicConfig( level=logging.DEBUG if settings.debug else logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger("孕育无忧") @asynccontextmanager async def lifespan(app: FastAPI): logger.info("服务启动中...") await init_db() os.makedirs(settings.upload_dir, exist_ok=True) logger.info("服务启动完成 http://%s:%s", settings.host, settings.port) yield logger.info("服务关闭") app = FastAPI( title="孕育无忧 API", description="孕期健康检查报告解读后端服务", version="1.0.0", lifespan=lifespan, ) api_prefix = settings.api_prefix.rstrip("/") or "" if api_prefix and not api_prefix.startswith("/"): api_prefix = f"/{api_prefix}" uploads_prefix = settings.uploads_prefix.rstrip("/") or "" if uploads_prefix and not uploads_prefix.startswith("/"): uploads_prefix = f"/{uploads_prefix}" # CORS 配置 cors_origins = ( ["*"] if settings.cors_origins == "*" else [o.strip() for o in settings.cors_origins.split(",") if o.strip()] ) app.add_middleware( CORSMiddleware, allow_origins=cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 请求日志中间件 @app.middleware("http") async def log_requests(request: Request, call_next): start = time.time() response = await call_next(request) duration = round((time.time() - start) * 1000) logger.info( "%s %s → %s (%dms)", request.method, request.url.path, response.status_code, duration, ) return response # 全局异常处理 @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): logger.error("未处理异常 %s %s: %s", request.method, request.url.path, exc, exc_info=True) return JSONResponse( status_code=500, content={"detail": "服务器内部错误,请稍后重试"}, ) app.mount(uploads_prefix or "/uploads", StaticFiles(directory=settings.upload_dir), name="uploads") app.include_router(auth.router, prefix=f"{api_prefix}/auth", tags=["认证"]) app.include_router(user.router, prefix=f"{api_prefix}/user", tags=["用户"]) app.include_router(report.router, prefix=f"{api_prefix}/report", tags=["报告"]) app.include_router(analysis.router, prefix=f"{api_prefix}/analysis", tags=["AI解读"]) app.include_router(chat.router, prefix=f"{api_prefix}/chat", tags=["AI对话"]) app.include_router(health_records.router, prefix=f"{api_prefix}/health-records", tags=["健康档案"]) app.include_router(calendar.router, prefix=f"{api_prefix}/calendar", tags=["产检日历"]) app.include_router(doctor.router, prefix=f"{api_prefix}/doctor", tags=["专家咨询"]) app.include_router(share.router, prefix=f"{api_prefix}/share", tags=["分享"]) @app.get(f"{api_prefix}/health") async def health_check(): return {"status": "ok", "service": "孕育无忧 API"} @app.get("/") async def web_demo(): """Web 体验页面""" return FileResponse( os.path.join(os.path.dirname(__file__), "static", "index.html") )