from typing import Any, Callable, Dict, Optional from fastapi import FastAPI, Request, Response from fastapi.responses import JSONResponse import json from starlette.middleware.base import BaseHTTPMiddleware from starlette.types import ASGIApp from app.schemas.response import StandardResponse, ErrorResponse class ResponseMiddleware(BaseHTTPMiddleware): """ 中间件:统一处理API响应格式 请求正确:{code:200, data:Any} 业务错误:{code:500, message:""} """ async def dispatch(self, request: Request, call_next: Callable) -> Response: # 不需要处理的路径 exclude_paths = ["/docs", "/redoc", "/openapi.json"] if any(request.url.path.startswith(path) for path in exclude_paths): return await call_next(request) response = await call_next(request) # 如果是HTTPException,直接返回,不进行封装 if response.status_code >= 400: return response # 正常响应进行封装 if response.status_code < 300 and response.headers.get("content-type") == "application/json": response_body = [chunk async for chunk in response.body_iterator] response_body = b"".join(response_body) if response_body: try: data = json.loads(response_body) # 已经是统一格式的响应,不再封装 if isinstance(data, dict) and "code" in data and ("data" in data or "message" in data): return Response( content=response_body, status_code=response.status_code, headers=dict(response.headers), media_type=response.media_type ) # 封装为标准响应格式 result = StandardResponse(code=200, data=data).model_dump() return JSONResponse( content=result, status_code=response.status_code, headers=dict(response.headers) ) except json.JSONDecodeError: # 非JSON响应,直接返回 return Response( content=response_body, status_code=response.status_code, headers=dict(response.headers), media_type=response.media_type ) return response def add_response_middleware(app: FastAPI) -> None: """添加响应处理中间件到FastAPI应用""" app.add_middleware(ResponseMiddleware)