Flask需用@app.errorhandler(HTTPException)捕获HTTP异常、@app.errorhandler(Exception)兜底未处理异常;Django需自定义中间件在DEBUG=False时重写process_exception;FastAPI需显式注册各类exception_handler并注意依赖异常捕获范围。

Flask里怎么统一捕获所有HTTP异常并返回JSON
直接用 @app.errorhandler 注册通用异常处理器,但要注意:它只捕获由 Flask 主动抛出的 HTTPException(比如 404、500),不抓未处理的 Python 异常(如 KeyError、ValueError)。
所以必须额外注册 @app.errorhandler(Exception) 来兜底,但这里要小心——它会吃掉所有未被捕获的异常,包括你本想让开发环境报错中断的逻辑错误。
- 生产环境才开启
@app.errorhandler(Exception),开发时建议保留原始 traceback - 在兜底 handler 里手动区分是否为
HTTPException,避免重复包装:用isinstance(e, HTTPException) - 返回前务必设响应状态码,否则默认是
200,前端可能误判成功
from flask import jsonify
from werkzeug.exceptions import HTTPException
@app.errorhandler(HTTPException)
def handle_http_exception(e):
return jsonify({"code": e.code, "msg": e.description}), e.code
@app.errorhandler(Exception)
def handle_unexpected_exception(e):
if isinstance(e, HTTPException):
return handle_http_exception(e)
app.logger.exception("Unhandled exception")
return jsonify({"code": 500, "msg": "Internal error"}), 500
Django中如何替换默认的500页面并返回JSON
Django 的全局异常处理靠中间件和 DEFAULT_EXCEPTION_REPORTER_FILTER 配合,但真正拦截 500 并改返回格式,得靠自定义中间件 + 覆盖 process_exception 方法。
关键点在于:Django 默认在 DEBUG=True 时返回 HTML 调试页,这个行为无法用中间件绕过;只有 DEBUG=False 才走 process_exception,且此时必须确保 ALLOWED_HOSTS 配置正确,否则连 500 都不会触发你的逻辑。
立即学习“Python免费学习笔记(深入)”;
- 中间件必须放在
MIDDLEWARE列表靠前位置(比如第二位),否则可能被后续中间件提前终止 - 不要在
process_exception里 raise 新异常,否则会进 Django 默认错误页 - 如果用了 DRF,优先用
EXCEPTION_HANDLER,它比中间件更精准,且天然支持Response对象
class JsonExceptionHandler:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
if settings.DEBUG:
return
return JsonResponse({"code": 500, "msg": "Server error"}, status=500)
FastAPI里为什么exception_handler不生效
FastAPI 的异常处理器默认只对显式 raise 的异常生效,比如 HTTPException 或你自定义的继承自 HTTPException 的类。它不捕获路径函数内部未处理的普通异常(如 ZeroDivisionError),除非你手动加了 @app.exception_handler(Exception)。
更大的坑是:如果你用了 Depends,而依赖函数里抛了异常,默认不会被路由层的 exception handler 捕获——得单独给依赖加 @app.exception_handler,或者把依赖异常转成 HTTPException 再抛。
- 全局 handler 必须在
app = FastAPI()实例化之后注册,不能写在 import 阶段 -
RequestValidationError是 Pydantic 错误,需单独注册@app.exception_handler(RequestValidationError) - handler 函数参数签名必须严格匹配,比如带
request: Request就不能漏掉
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
return JSONResponse({"code": exc.status_code, "msg": exc.detail}, status_code=exc.status_code)
@app.exception_handler(Exception)
async def generic_exception_handler(request, exc):
return JSONResponse({"code": 500, "msg": "Internal error"}, status_code=500)
统一JSON结构里要不要包含data字段
要不要加 data 字段,本质是接口契约问题,不是技术限制。但一旦选了,就得全项目一致,否则前端要写两套解析逻辑。
常见做法是:成功响应放 data,失败放 msg 和 code;但注意 data 类型必须可预测——比如列表接口返回 {"data": []},空对象接口返回 {"data": {}},别混用 None 或省略字段。
- 分页接口容易踩坑:是把
data设为列表,还是包一层{"list": [], "total": 100}?推荐后者,避免前端每次都要判断data类型 - 如果后端有多个服务共用同一套前端,
data字段名最好配置化,别硬编码在每个 handler 里 - 别为了“统一”强行塞
data: null,空响应直接返回{"code": 0, "msg": "ok"}更干净
最麻烦的是历史接口迁移:旧接口没 data,新接口加了,中间网关或 BFF 层就得做字段透传或转换——这种兼容性成本,往往比一开始定规范还高。










