import os from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request as StarletteRequest from starlette.responses import JSONResponse from slowapi import _rate_limit_exceeded_handler from slowapi.errors import RateLimitExceeded from services.limiter import limiter from fastapi.staticfiles import StaticFiles from config import get_settings from database import engine, Base import models # noqa: F401 from routers import auth, courseware, animation, exercise, lesson_plan, essay, ai, resource, community, exam, classroom, material, search, admin, mindmap, notification, chat settings = get_settings() @asynccontextmanager async def lifespan(app: FastAPI): Base.metadata.create_all(bind=engine) os.makedirs(settings.UPLOAD_DIR, exist_ok=True) yield app = FastAPI( title=settings.APP_NAME, version=settings.APP_VERSION, lifespan=lifespan, ) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class BodySizeLimitMiddleware(BaseHTTPMiddleware): """Reject request bodies exceeding MAX_REQUEST_BODY_SIZE (default 60MB).""" async def dispatch(self, request: StarletteRequest, call_next): max_size = getattr(settings, "MAX_REQUEST_BODY_SIZE", 60 * 1024 * 1024) cl = request.headers.get("content-length") if cl and int(cl) > max_size: return JSONResponse( status_code=413, content={"detail": f"请求体过大,最大允许 {max_size // (1024*1024)}MB"}, ) return await call_next(request) app.add_middleware(BodySizeLimitMiddleware) app.include_router(auth.router) app.include_router(courseware.router) app.include_router(animation.router) app.include_router(exercise.router) app.include_router(lesson_plan.router) app.include_router(essay.router) app.include_router(exam.router) app.include_router(ai.router) app.include_router(resource.router) app.include_router(material.router) app.include_router(community.router) app.include_router(classroom.router) app.include_router(search.router) app.include_router(admin.router) app.include_router(mindmap.router) app.include_router(notification.router) app.include_router(chat.router) if os.path.exists(settings.UPLOAD_DIR): app.mount("/uploads", StaticFiles(directory=settings.UPLOAD_DIR), name="uploads") @app.get("/api/health") def health_check(): return {"status": "ok", "version": settings.APP_VERSION} if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=settings.DEBUG)