【12.0】DRF之全局异常处理
【一】引入
- 在前端开发中,为了便于处理后端报错,通常需要后端返回统一的格式。
- 通过统一的格式,前端可以更方便地处理后端返回的错误信息
- 比如根据错误码展示不同的提示信息给用户。
{code:999,msg:'系统异常,请联系系统管理员'}
// 其中code表示错误码,msg表示错误信息。
- 只要三大认证,视图类的方法出了异常,都会执行一个函数:
# 全局异常处理
# 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
- 在后端开发中,无论是视图类的方法出现异常,还是其他地方出现异常,都可以通过全局异常处理来进行统一的处理。
- 例如,给定的Python代码片段展示了一个全局异常处理函数,它会处理所有视图类方法出现的异常。
- 通过配置
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
- 当发生异常时,系统会调用该函数进行异常处理。
- 在该函数中,可以执行一些自定义的操作来处理异常
- 比如记录日志、返回统一的错误响应等。
- 但是不能在源码中修改处理异常的函数
- 不能直接修改源码中的异常处理函数。
- 这是因为源码是框架或库的核心部分,更改源码可能导致意想不到的后果,并且对于源码的修改在升级或维护时可能会产生冲突。
- 因此,在开发过程中应尽量避免修改源码,而是通过配置或继承等方式来实现自定义的异常处理逻辑。
【二】源码分析
from rest_framework.views import exception_handler
exception_handler
def exception_handler(exc, context):
"""
Returns the response that should be used for any given exception.
By default we handle the REST framework `APIException`, and also
Django's built-in `Http404` and `PermissionDenied` exceptions.
Any unhandled exceptions may return `None`, which will cause a 500 error
to be raised.
"""
if isinstance(exc, Http404):
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied()
if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
if isinstance(exc.detail, (list, dict)):
data = exc.detail
else:
data = {'detail': exc.detail}
set_rollback()
return Response(data, status=exc.status_code, headers=headers)
return None
rest_framework
是一个用于构建Web API的框架
- 而
views
模块提供了与视图相关的功能。
exception_handler
函数用于处理异常并返回相应的响应。
- 该函数接受两个参数:
exc
表示捕获到的异常对象context
表示异常上下文信息。函数首先会检查捕获到的异常类型
- 如果是
Http404
类型的异常,则将其转换为NotFound
异常。- 同样地,如果是
PermissionDenied
类型的异常,则转换为PermissionDenied
异常。- 这些转换操作是为了将Django内置的异常转换为REST框架中定义的异常类型。
如果捕获到的异常是继承自
APIException
的自定义异常
- 那么函数会根据异常对象的属性来构造响应。
- 具体来说,函数会考虑异常对象的
auth_header
属性和wait
属性,并在响应头中设置相应的值- 。然后,函数会判断异常对象的
detail
属性的类型
- 如果是
list
或dict
类型,直接将其作为响应数据- 否则将
detail
属性封装为字典数据。最后
- 函数会调用
set_rollback()
函数来设置回滚标志,并返回一个带有相应数据、状态码和响应头的Response
对象。- 如果无法匹配到合适的异常类型,函数将返回
None
,导致抛出500错误。
【三】自定义处理异常函数
# 仿写 exception_handler
from rest_framework.views import exception_handler
from rest_framework.response import Response
def common_exception_handler(exc, context):
'''
exception_handler:只处理了 DRF 的异常,其他的异常不会被捕获
:param exc:
:param context:
:return:
'''
# 一般出现异常,都会进行日志的记录
# 定义返回异常格式
back_dict = {"code": 999, "message": ""}
# 如果异常对象是 DRF 的 APIException对象,则会返回 Response
# 如果异常对象不是 DRF 的 APIException对象,则会返回 None
# 执行原来的 exception_handler 函数会捕获到异常 并且返回 Response 或 None
result = exception_handler(exc, context)
if result:
# 有值 Response 则说明是DRF的异常,已经被捕获和处理了
back_dict['code'] = 996
# 异常返回有两种格式,一种是字典,一种是直接的数据
if isinstance(result.data, dict):
# 异常是字典格式
back_dict["detail"] = result.data.get('detail')
else:
# 异常是直接的数据
print(result.data)
back_dict["detail"] = result.data
back_dict["message"] = "程序异常,请联系管理员处理异常!"
# back_dict["message"] = str(exc) # 这种错误展示会将具体的错误信息展示出来,一般给客户做东西,客户可以提交这个信息便于排查问题
return Response(back_dict)
else:
# 无值 None 则说明是 其他的异常,没有被处理
back_dict["message"] = "系统异常,请联系管理员处理异常!"
return Response(back_dict)
- 定义返回异常格式:
- 创建一个字典
back_dict
,用于存储异常相关的信息。- 调用
exception_handler
函数:
- 首先尝试调用DRF内置的
exception_handler
函数,它会捕获DRF的APIException对象并返回Response- 如果传入的异常不是DRF的APIException对象,则返回None。
- 处理DRF的异常:
- 如果
result
有值(即存在返回的Response)
- 说明是DRF的异常被捕获和处理了。
- 此时将
back_dict
的code
设置为996,表示程序异常,将result.data
进行判断- 如果是字典格式
- 则取其中的
detail
作为异常的具体信息- 否则直接使用
result.data
作为异常的具体信息。- 将
back_dict
的message
设置为"程序异常,请联系管理员处理异常!",最后返回处理后的Response。- 处理其他异常:
- 如果
result
没有值(None),则说明是其他未被处理的异常。- 将
back_dict
的message
设置为"系统异常,请联系管理员处理异常!"- 然后返回处理后的Response。
- 配置文件中修改
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.excepiton.my_excepiton.common_exception_handler',
}
- 为了使用该自定义异常处理函数,需要在配置文件中进行相应的修改。
- 在配置文件的
REST_FRAMEWORK
中
- 将
EXCEPTION_HANDLER
的值设置为自定义函数的路径,- 即'app01.excepiton.my_excepiton.common_exception_handler'。
-
异常展示
-
DRF的异常(APIException)
{ "code": 996, "message": "程序异常,请联系管理员处理异常!", "detail": "这是DRF的异常" }
-
原生的异常(Exception)
{ "code": 999, "message": "系统异常,请联系管理员处理异常!" }
-
【四】异常处理模板
from rest_framework.views import exception_handler
from rest_framework.response import Response
def common_exception_handler(exc, context):
# 定义返回异常格式
back_dict = {"code": 999, "message": ""}
result = exception_handler(exc, context)
if result:
back_dict['code'] = 996
if isinstance(result.data, dict):
back_dict["detail"] = result.data.get('detail')
else:
print(result.data)
back_dict["detail"] = result.data
back_dict["message"] = "程序异常,请联系管理员处理异常!"
# back_dict["message"] = str(exc)
return Response(back_dict)
else:
back_dict["message"] = "系统异常,请联系管理员处理异常!"
return Response(back_dict)
rest_framework.views.exception_handler
是 Django REST framework 中的一个内置函数,用于处理视图函数中发生的异常。rest_framework.response.Response
是 Django REST framework 提供的用于创建 HTTP 响应的类。common_exception_handler
函数是自定义的异常处理函数,它会被 Django REST framework 在发生异常时自动调用。exc
是捕获到的异常对象。context
是一个包含有关发生异常的上下文信息的字典,可以包含有关请求、视图和其他相关数据的信息。response_data
是用于构建最终响应的字典,包含了错误码(code)和错误消息(message)等字段。- 当
result
不为空时,表示有发生异常,并通过result
获取到异常的详细信息,将其添加到response_data
中,并将错误码设置为 996。- 当
result
为空时,表示没有发生异常,直接将错误码设置为 999,并设置错误消息为系统异常。- 返回最终响应,其中包含了处理后的异常信息。
【补充】函数和方法的区别
- 在编程中,函数和方法是两个相关但又有区别的概念。
【1】函数
- 函数是一段可以重复使用的代码块,它接受输入参数并产生输出结果。
- 函数通常独立于任何特定的对象或类,并可以在程序的不同位置被调用和执行。
- 函数可以有返回值,也可以没有返回值。
【2】方法
- 方法是与特定对象或类关联的函数。
- 它是一个属于某个类或对象的函数,用于描述该类或对象的行为。
- 方法可以读取和修改类或对象的属性,也可以执行与对象相关的操作。
- 方法与对象或类之间存在着紧密的耦合关系,必须通过对象或类来调用和执行。
【3】函数和方法之间区别
-
所属关系:
-
函数可以独立存在
-
而方法必须依赖于对象或类存在。
-
-
调用方式:
-
函数可以直接调用
-
而方法必须通过对象或类来调用。
-
-
参数传递:
-
函数的参数是显式传递的
-
方法的第一个参数通常是隐式传递的,表示调用该方法的对象(通常命名为self)。
-
-
数据访问权限:
-
函数不能直接访问对象的属性
-
而方法可以通过self参数访问对象的属性。
-
-
命名约定:
- 函数的名称通常用小写字母和下划线
- 方法的名称通常用驼峰命名法。
-
下面是一个示例代码,展示了函数和方法的区别:
# 函数示例
def add(a, b):
return a + b
result = add(2, 3)
print(result) # 输出:5
# 方法示例
class Calculator:
def add(self, a, b):
return a + b
calculator = Calculator()
result = calculator.add(2, 3)
print(result) # 输出:5
- 在上述示例中
- add函数是一个独立的函数,可以直接调用。
- 而Calculator类中的add方法是一个属于Calculator类的函数,必须通过Calculator对象来调用。
【4】总结起来
- 函数是一段独立的可重复使用的代码
- 而方法是与对象或类紧密关联的函数,用于描述对象或类的行为。
- 它们在调用方式、所属关系、参数传递和数据访问权限等方面存在明显的区别。
【补充】isinstance()/issubclass()
- isinstance()函数和issubclass()函数是Python中的两个内置函数
- 用于判断对象与类之间的关系。
【1】isinstance()
- isinstance()函数用于检查一个对象是否是一个类的实例。
- 它接受两个参数
- 第一个参数是待检查的对象
- 第二个参数是类或类型。
- 如果对象是第二个参数指定的类或类型的实例
- 则返回True;
- 否则返回False。
- 例如:
class Person:
pass
person = Person()
print(isinstance(person, Person)) # 输出:True
print(isinstance(person, object)) # 输出:True
print(isinstance(person, str)) # 输出:False
- 在上面的例子中,通过isinstance()函数可以判断person对象是否是Person类的实例。
- 同时,由于所有的类都继承自object类,所以person对象也被认为是object类的实例。
【2】issubclass()
- issubclass()函数用于检查一个类是否是另一个类的子类。
- 它接受两个参数
- 第一个参数是待检查的类
- 第二个参数是父类或超类。
- 如果第一个参数是第二个参数指定的类或类型的子类
- 则返回True;
- 否则返回False。
- 例如:
class Animal:
pass
class Dog(Animal):
pass
print(issubclass(Dog, Animal)) # 输出:True
print(issubclass(Dog, object)) # 输出:True
print(issubclass(Dog, str)) # 输出:False
- 在上述示例中,通过issubclass()函数可以判断Dog类是否是Animal类的子类。
- 同时由于所有的类都是object类的子类,所以Dog类也被认为是object类的子类。
- 这就是isinstance()函数和issubclass()函数的详解。
- 通过使用这两个函数,我们可以判断对象与类之间的关系,从而进行相应的处理。
本文来自博客园,作者:Chimengmeng,转载请注明原文链接:https://www.cnblogs.com/dream-ze/p/17594595.html