直接在视图中用try/except不够用,因其无法捕获中间件抛出的异常、难以统一处理404/403等状态码、导致JSON响应结构不一致,且无法覆盖整个请求生命周期;而process_exception中间件可全局拦截所有视图层异常,实现集中日志、结构化响应与状态码精准映射。

为什么直接在视图里 try/except 不够用
Django 的视图函数或类方法中手动捕获异常,看似简单,但会快速失控:重复代码多、漏捕中间件抛出的异常、无法统一处理 404/500 等 HTTP 状态码、JSON 响应结构不一致。更关键的是,Http404、PermissionDenied、未捕获的 ValueError 或数据库 IntegrityError 会直接触发 Django 默认错误页或 500 响应,前端拿不到结构化错误信息。
真正需要的是一个入口级拦截点,覆盖所有请求生命周期中抛出的异常,且能区分业务异常与系统异常。
用 Middleware 实现全局异常拦截最可靠
Django 中唯一能稳定捕获所有视图层异常(包括 CBV 的 dispatch、FBV 执行过程、甚至部分中间件后续抛出的异常)的位置,是自定义异常中间件。它必须放在 MIDDLEWARE 配置的**末尾**(即靠近视图一侧),否则会被前面的中间件吞掉异常。
实操建议:
- 新建
middleware.py,定义类继承BaseHandler不推荐;直接实现__call__方法并调用self.get_response(request),再用try/except包裹 - 捕获
Exception时,**务必排除SystemExit和KeyboardInterrupt**,否则导致开发服务器无法 Ctrl+C 退出 - 对
Http404、PermissionDenied、ValidationError等 Django 内置异常,应映射为对应 HTTP 状态码(如 404、403、400),而非全归为 500 - 返回响应必须是
HttpResponse子类,推荐用JsonResponse并设置status参数,避免手动写HttpResponse(content=json.dumps(...), status=400, content_type='application/json')
# middleware.py
from django.http import JsonResponse
from django.core.exceptions import ValidationError, PermissionDenied
from django.http import Http404
from django.db import IntegrityError
<p>class ExceptionMiddleware:
def <strong>init</strong>(self, get_response):
self.get_response = get_response</p><pre class="brush:python;toolbar:false;">def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
if isinstance(exception, Http404):
return JsonResponse({'error': 'Not found'}, status=404)
elif isinstance(exception, PermissionDenied):
return JsonResponse({'error': 'Permission denied'}, status=403)
elif isinstance(exception, ValidationError):
return JsonResponse({'error': 'Validation failed', 'details': exception.message_dict}, status=400)
elif isinstance(exception, IntegrityError):
return JsonResponse({'error': 'Data conflict'}, status=409)
else:
# 生产环境此处应记录日志,而不是暴露 traceback
return JsonResponse({'error': 'Internal server error'}, status=500)
settings.py 中注册顺序很关键
中间件注册位置决定执行顺序。process_exception 只有在该中间件之后的中间件(或视图)抛出异常时才会被调用。所以必须确保你的异常中间件**在安全类中间件(如 SecurityMiddleware)之后、但在 CommonMiddleware 之前或之后均可,只要不在最前**。
常见错误配置:
- 把异常中间件写在
django.middleware.security.SecurityMiddleware之前 → 安全中间件抛出的异常捕获不到 - 写成
'myapp.middleware.ExceptionMiddleware'但没加引号 → 启动报NameError - 忘记在
INSTALLED_APPS中添加应用 →ModuleNotFoundError
正确示例(片段):
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'myapp.middleware.ExceptionMiddleware', # ← 放这里,生效范围最大
'django.contrib.auth.middleware.AuthenticationMiddleware',
]
别忽略 DRF 场景下的冲突
如果你项目用了 Django REST Framework,DRF 自带了 APIView 和异常处理器(exception_handler),它会在自己的 dispatch 流程中提前捕获异常并返回响应。此时你写的中间件里的 process_exception **根本不会被调用**。
解决方式分情况:
- 纯 DRF 项目:直接覆写
settings.py中的REST_FRAMEWORK['EXCEPTION_HANDLER'],指向自定义函数,统一处理所有 API 异常 - 混合项目(既有普通视图又有 APIView):中间件 + DRF 异常处理器**都要保留**,但需保证响应格式一致(例如都返回
{'error': ..., 'code': ...}) - 特别注意:DRF 的
ValidationError和 Django 表单的ValidationError是两个类,需分别判断
容易被忽略的一点:中间件无法捕获异步视图(async def)抛出的异常,Django 4.1+ 要求使用 AsyncExceptionMiddleware 并重写 __acall__ —— 同步和异步异常处理逻辑得分开维护。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











