mirror of
https://github.com/handsomezhuzhu/QQuiz.git
synced 2026-02-20 12:00:14 +00:00
133 lines
3.9 KiB
Python
133 lines
3.9 KiB
Python
"""
|
||
QQuiz FastAPI Application - 单容器模式(前后端整合)
|
||
"""
|
||
from fastapi import FastAPI, Request
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.staticfiles import StaticFiles
|
||
from fastapi.responses import HTMLResponse, FileResponse, JSONResponse
|
||
from contextlib import asynccontextmanager
|
||
import os
|
||
from pathlib import Path
|
||
from dotenv import load_dotenv
|
||
from slowapi.errors import RateLimitExceeded
|
||
from slowapi.middleware import SlowAPIMiddleware
|
||
|
||
from database import init_db, init_default_config, get_db_context
|
||
from rate_limit import limiter
|
||
|
||
# Load environment variables
|
||
load_dotenv()
|
||
|
||
|
||
async def rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded):
|
||
return JSONResponse(
|
||
status_code=429,
|
||
content={"detail": "Rate limit exceeded. Please try again later."}
|
||
)
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
"""Application lifespan events"""
|
||
# Startup
|
||
print("🚀 Starting QQuiz Application...")
|
||
|
||
# Initialize database
|
||
await init_db()
|
||
|
||
# Initialize default configurations
|
||
async with get_db_context() as db:
|
||
await init_default_config(db)
|
||
|
||
# Create uploads directory
|
||
upload_dir = os.getenv("UPLOAD_DIR", "./uploads")
|
||
os.makedirs(upload_dir, exist_ok=True)
|
||
print(f"📁 Upload directory: {upload_dir}")
|
||
|
||
print("✅ Application started successfully!")
|
||
|
||
yield
|
||
|
||
# Shutdown
|
||
print("👋 Shutting down QQuiz Application...")
|
||
|
||
|
||
# Create FastAPI app
|
||
app = FastAPI(
|
||
title="QQuiz API",
|
||
description="智能刷题与题库管理平台",
|
||
version="1.0.0",
|
||
lifespan=lifespan
|
||
)
|
||
|
||
app.state.limiter = limiter
|
||
app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
|
||
app.add_middleware(SlowAPIMiddleware)
|
||
|
||
# Configure CORS
|
||
cors_origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",")
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=cors_origins,
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
|
||
# Import and include routers
|
||
from routers import auth, exam, question, mistake, admin
|
||
|
||
app.include_router(auth.router, prefix="/api/auth", tags=["Authentication"])
|
||
app.include_router(exam.router, prefix="/api/exams", tags=["Exams"])
|
||
app.include_router(question.router, prefix="/api/questions", tags=["Questions"])
|
||
app.include_router(mistake.router, prefix="/api/mistakes", tags=["Mistakes"])
|
||
app.include_router(admin.router, prefix="/api/admin", tags=["Admin"])
|
||
|
||
|
||
# API 健康检查
|
||
@app.get("/health")
|
||
async def health_check():
|
||
"""Health check endpoint"""
|
||
return {"status": "healthy"}
|
||
|
||
|
||
# ============ 静态文件服务(前后端整合) ============
|
||
|
||
# 检查静态文件目录是否存在
|
||
STATIC_DIR = Path(__file__).parent / "static"
|
||
if STATIC_DIR.exists():
|
||
# 挂载静态资源(JS、CSS、图片等)
|
||
app.mount("/assets", StaticFiles(directory=str(STATIC_DIR / "assets")), name="static_assets")
|
||
|
||
# 前端应用的所有路由(SPA路由)
|
||
@app.get("/{full_path:path}")
|
||
async def serve_frontend(full_path: str):
|
||
"""
|
||
服务前端应用
|
||
- API 路由已在上面定义,优先匹配
|
||
- 其他所有路由返回 index.html(SPA 单页应用)
|
||
"""
|
||
index_file = STATIC_DIR / "index.html"
|
||
if index_file.exists():
|
||
return FileResponse(index_file)
|
||
else:
|
||
return {
|
||
"message": "Frontend not built yet",
|
||
"hint": "Run 'cd frontend && npm run build' to build the frontend"
|
||
}
|
||
else:
|
||
print("⚠️ 静态文件目录不存在,前端功能不可用")
|
||
print("提示:请先构建前端应用或使用开发模式")
|
||
|
||
# 如果没有静态文件,显示 API 信息
|
||
@app.get("/")
|
||
async def root():
|
||
"""Root endpoint"""
|
||
return {
|
||
"message": "Welcome to QQuiz API",
|
||
"version": "1.0.0",
|
||
"docs": "/docs",
|
||
"note": "Frontend not built. Please build frontend or use docker-compose."
|
||
}
|