from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import HTMLResponse, FileResponse from fastapi.exceptions import RequestValidationError from contextlib import asynccontextmanager from apps.urls import api_router from apps.settings.urls import UPLOAD_DIR from database import engine, get_db from models import Base from init_data import init_all_data from pathlib import Path from starlette.requests import Request from starlette.responses import JSONResponse @asynccontextmanager async def lifespan(app: FastAPI): # 启动时初始化数据库 async with engine.begin() as conn: # 创建所有表 await conn.run_sync(Base.metadata.create_all) # 初始化数据 async for db in get_db(): await init_all_data(db) break yield # 关闭时的清理工作 await engine.dispose() # 实例化FastAPI app = FastAPI( title="图书系统授权服务API", description="""图书系统授权服务API是一套用于用户认证和授权的服务,提供用户注册、登录、权限校验等功能。 同时实现了基于角色的访问控制(RBAC),支持自定义角色和权限。还增加了日志记录功能,方便监控和调试。""", version="1.0.0", lifespan=lifespan, docs_url=None, redoc_url="/redoc", ) # 开发环境前后端分离时的跨域(生产环境请收窄 allow_origins) app.add_middleware( CORSMiddleware, allow_origins=[ "http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:3000", "http://127.0.0.1:3000", ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], expose_headers=["X-Captcha-ID"], ) # 挂载swagger-ui静态文件目录 swagger_ui_path = Path(__file__).parent.parent / "swagger-ui" app.mount("/static", StaticFiles(directory=str(swagger_ui_path)), name="static") # 上传图片通过自定义端点提供(和 settings/urls.py 中的 UPLOAD_DIR 保持一致) @app.get("/uploads/{filename}") async def serve_upload(filename: str): """提供上传的图片文件""" file_path = UPLOAD_DIR / filename if not file_path.exists(): raise HTTPException(status_code=404, detail="文件不存在") return FileResponse(str(file_path)) # 自定义Swagger UI页面 @app.get("/docs", include_in_schema=False) async def custom_swagger_ui_html(): html_content = """ Book System API - Swagger UI
""" return HTMLResponse(content=html_content) # 包含路由 app.include_router(api_router) # 声明装饰器方法和路径 @app.get("/") # 声明装饰器函数 async def home(): return {"message": "Hello World!!!"} # 自定义验证错误处理器 @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): errors = [] for err in exc.errors(): field = err.get("loc", ["unknown"])[-1] msg = err.get("msg", "验证失败") if "min_length" in msg.lower(): if field == "username": msg = "用户名长度至少为3个字符" elif field == "password": msg = "密码长度至少为6个字符" elif "max_length" in msg.lower(): if field == "username": msg = "用户名长度不能超过50个字符" elif field == "password": msg = "密码长度不能超过50个字符" errors.append({"field": field, "message": msg}) return JSONResponse( status_code=422, content={"code": 422, "message": "验证失败", "errors": errors} ) # 如果使用命令行启动,使用uvicorn 文件名:app --reload启动即可,下面命令就不用写 # 如果写下面的命令,就不需要命令行启动了,直接用IDE运行即可 if __name__ == "__main__": import uvicorn import os name = f"{os.path.splitext(os.path.basename(os.path.abspath(__file__)))[0]}:app" uvicorn.run(name, host="0.0.0.0", port=8000, reload=True, reload_dirs=["_"])