DRF学习之全局异常处理、接口文档书写

一、全局异常处理

1、drf默认异常处理源码分析

在DRF中,继承APIView后,它的执行流程是首先去除了所有请求的csrf认证,然后把视图类的request对象变成了新的request对象,新的reqeust对象是DRF的,但是以前Django的request对象用起来是一样的,同时把新的reqeust对象放到了视图类的对象中,然后在执行视图类的方法之前,又执行了三大认证。最后就是在执行三大认证或视图类方法的过程中只要报错了,就会被全局异常捕获处理。而在这里我们要说的就是这个DRF执行流程中最后一个环节,全局异常处理。

  • 只要三大认证,视图类的方法出了异常,都会执行一个函数:rest_framework.views import exception_handler
  • 查看dispatch源码:如下
    def dispatch(self, request, *args, **kwargs):
 
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            # 三大认证执行
            self.initial(request, *args, **kwargs)

            # 视图类的方法开始执行
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
            response = handler(request, *args, **kwargs)
            # 视图类方法结束执行
        # 如果三大认证或视图类方法报错,执行全局异常处理捕获    
        except Exception as exc:
            response = self.handle_exception(exc)
			'''
        	这里self是视图类的对象,现在在它的父类APIView里面,所以我们从APIView中找找有没有
        	这个handle_exception方法,另外这个括号里面的exc就是异常对象
        	这个handle_exception方法也是我们主要分析的方法
        	''' 
    	self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response  
  • 执行handle_exception 方法时就会执行方法中exception_handler = self.get_exception_handler() 这一行代码,但
  • 是这一行代码就是前面 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler’中的rest_framework.views.exception_handle函数
   # 在APIView里面找到了handle_exception方法
   def handle_exception(self, exc):
	  # 如果没有认证通过,http响应状态码变成403,并且往响应头中放入了数据
       if isinstance(exc, (exceptions.NotAuthenticated,
                           exceptions.AuthenticationFailed)):
           # WWW-Authenticate header for 401 responses, else coerce to 403
           # 如果异常是 NotAuthenticated 或 AuthenticationFailed,处理授权相关的逻辑
           auth_header = self.get_authenticate_header(self.request)
			
			# 如果存在认证头,将其添加到异常中
           if auth_header:
               exc.auth_header = auth_header
           else:
           	   # 如果不存在认证头,将状态码更改为 403 FORBIDDEN
               exc.status_code = status.HTTP_403_FORBIDDEN
               
               
	 # 这里的exception_handler就是我们自定义异常处理重写的函数
	 '''
	self是视图类对象,已知视图类对象中没有get_excpetion_handler这个方法,然后去父类APIView中查找到了
	def get_exception_handler(self):
       return self.settings.EXCEPTION_HANDLER
    可以看到,它是去配置文件中拿这个EXCEPTION_HANDLER了,这也是为什么我们自定义的异常处理需要配置,如果在配置文件中没有拿到,就会去DRF的配置文件中拿到它
        'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
	'''
       exception_handler = self.get_exception_handler()
      
	  # 获取异常处理上下文
       context = self.get_exception_handler_context()

	  '''
	 这里的exception_handler就是我们自定义异常处理重写的函数,这也就是为什么我们需要传两个参数的原因	
	  exc就是错误对象,context就是上下文,它是一个对象,并且里面存着当次请求的request和视图类
	  这个response就是最后返回的异常结果,这就是为什么我们重写后最后需要返回一个响应对象
	  '''
       response = exception_handler(exc, context)
		
	  '''
	  如果响应对象是None就是抛出异常,就不会捕获,直接返回给前端,所以这个全局异常只能捕获DRF的异常
	  这也是为什么我们需要重写,就是为了返回全局异常无法捕获的异常
	  '''
       if response is None:
           self.raise_uncaught_exception(exc)
           '''
			也可以看一下这个方法,还是一样self是视图类对象,然后在父类APIView中找到了这个方法
			def raise_uncaught_exception(self, exc):
		        if settings.DEBUG: 在调试模式下处理未捕获的异常
		            request = self.request  获取当次请求
		            请求接受的渲染格式
		            renderer_format = getattr(request.accepted_renderer, 'format')
		            然后根据渲染格式渲染到页面
		            use_plaintext_traceback = renderer_format not in ('html', 'api', 'admin')
		            request.force_plaintext_errors(use_plaintext_traceback)
		            
		        最后将未捕获的异常直接渲染给页面
		        raise exc
			'''
		
	  # 这里将响应对象的exception属性设置为True,表示异常已被处理
       response.exception = True
       # 最后返回处理后的响应对象
       return response
  • 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'

  • 接下来我们单独去读读DRF默认配置的异常处理

def exception_handler(exc, context):
   		# 看到DRF默认配置的全局异常处理,就可以知道为什么我们要那么配置自定义的了,其实就是照着这个写的
   		# 这里就是自己的异常
	    if isinstance(exc, Http404):
	        exc = exceptions.NotFound()
	    elif isinstance(exc, PermissionDenied):
	        exc = exceptions.PermissionDenied()
		
		# 这里是DRF的所有异常,只要是DRF的异常都会走这里,因为DRF的异常都继承了APIException
	    if isinstance(exc, exceptions.APIException):
	        # 如果异常的详细信息是列表或字典,直接使用该信息
	        if isinstance(exc.detail, (list, dict)):
	            data = exc.detail
	        else:
	           # 否则,将详细信息包装在 detail 键下
	            data = {'detail': exc.detail}
			# 如果想要验证drf出现异常是否会执行上面exception_handler函数的话,可以在这打印一句话
            # 然后再视图类中抛出raise APIException('我出错误了')
	        
            # 返回处理后的响应
	        '''
	        所以这里有两个情况
	        (1) Response(data:{'detail': exc.detail})
	        (2) Response(data:错误信息(字典或列表))
	        所以这种情况,这也是为什么我们自定义的时候,取detail时,怕取不到设置了自定义的一个异常信息或者在or一个data这样会		  更好一些,例如:
	        detail = res.data.get('detail') or res.data or "drf异常,请联系系统管理员"
        	return Response({'code':666,'message':detail})
	        '''
	        return Response(data, status=exc.status_code, headers=headers)
		
		# 对于其他类型的异常,返回 None,让 DRF 使用默认的异常处理机制,就是不处理直接返回给前端页面
	    return None

2、自定义异常处理

针对上面的源码,我们也可以自定义异常处理,DRF默认的异常处理,只处理APIException及其子类的异常,处理不了的会返回None,我们可以判断异常是否属于APIException及其子类的异常,如果是则返回错误信息,如果不是,那么我们则返回服务器错误。

  • 自定义异常处理
from rest_framework.views import exception_handler
from rest_framework.response import Response
'''
需求:
0 执行原来的exception_handler
    判断返回值是否为 None:
        如果是None,说明是非drf异常,自己包装一个Response
        如果不是None,说明是drf异常,包装一个Response
1 错误状态码可能会有很多类型: 通过exc 错误对象,判断具体是哪个类的对象(是什么错误)--》更细粒度区分不同的错误
    991
    992
    993
    996
    998
    999
2 只要程序走到common_exception_handler ,就说明有异常,通常做法
    -返回给前端固定错误描述: 服务器异常,请稍后再试
    -使用日志,记录错误:越详细越好
        -时间,请求方式,请求的地址,客户端ip,用户id。。。。
'''

class PasswordException(Exception):
    def __init__(self,msg):
        self.msg=msg


def common_exception_handler(exc, context):
    # 日志记录放这里即可:请求方式,请求的地址,客户端ip,用户id
    request=context.get('request')
    view=context.get('view') # 哪个视图类出的错
    user_id=request.user.pk or '匿名用户'
    print(f'请求方式是:{request.method},请求地址是:{request.get_full_path()},客户端ip:{request.META.get("REMOTE_ADDR")},用户id:{user_id}, 错误的视图类是{view.__class__.__name__}')

    # 1 返回 Response:说明是drf的异常      2 返回None:说明不是drf的异常
    response = exception_handler(exc, context)
    if response:
        if isinstance(response.data,dict):
            err=response.data.get('detail','系统错误,请联系系统管理员')
        elif isinstance(response.data,list):
            err = response.data[0]
        else:
            err ='系统错误,请联系系统管理员'
        response = Response({'code': 998, 'msg': f'drf的异常:{err}'})
        pass
    else:
        if isinstance(exc,ZeroDivisionError):
            response = Response({'code': 991, 'msg': '不能除以0'})
        elif isinstance(exc,Exception):
            response = Response({'code': 992, 'msg': 'Exception错误'})
        else:
            # 表明是 非drf的异常
            err=str(exc)
            response = Response({'code': 999, 'msg': f'django的异常:{err}'})
    return response
  • 视图类
from rest_framework.views import APIView
from utils.exceptions import PasswordException
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError, AuthenticationFailed, APIException


class UserView(APIView):
    def get(self, request, *args, **kwargs):
        '''
        这个接口用来查询xx
        '''
        # 1 drf异常,主动抛
        # raise ValidationError('小伙子,出错了')
        # raise AuthenticationFailed('小伙子,出错了,没认证通过吖')
        raise APIException('错误了!!!')

        # 2 django的异常或python的异常
        # res = 9 / 0
        # raise PasswordException('ccccc')

        # l=[2,3,4]
        # print(l[9])

        # raise Exception('错误')

        # return Response('正常返回xx')
  • 配置文件
# 自定义异常配置文件中要加入
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler',
}
  • 总结使用步骤
#1  定义一个函数
def common_exception_handler(exc, context):
    # 加入日志的逻辑,错误中记录日志---> 越详细越好
    ...日志逻辑...
    response = exception_handler(exc, context)
    if response:
        return Response(data={'code': 9998, 'msg': response.data})
    else:
        return Response(data={'code': 9999, 'msg': '服务器异常,请联系系统管理员'})
    
 # 2 在配置文件中配置
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler', 
}

3、总结

  • 对于前端来讲,无论后端什么情况,前端收到的都是统一的格式

    • 正常响应:{code:100,msg:成功}

    • 不正常响应:{code:101,msg:登录失败}

    • 出了错:{code:999,msg:错误信息} {code:998,msg:错误信息}

  • 只要三大认证,视图类的方法出了异常,都会执行一个函数:rest_framework.views import exception_handler

  • 注意:exception_handler

    • 如果异常对象是drf的APIException对象,就会返回Response
    • exception_handler只处理了drf的异常,其它的异常需要我们自己处理
    • 如果异常对象不是drf的APIException对象,就会返回None
  • 补充:

    • isinstance() 判断一个对象是不是某个类的对象 isinstance(对象,类)
    • ssubclass() 判断一个类,是不是另一个类的子类

二、接口文档

1、为什么要写接口文档

在我们后端写完接口之后,应该清楚

  • 地址
  • 携带参数
  • 请求方式
  • 编码格式
  • 返回数据格式

并将这些内容必须写成接口文档,方便前端参照,而前端的话在拿到接口文档,应该参考接口文档进行前端的相关工作开发。

2、接口文档展示形式

  • md、word等共享文档写接口文档

  • 接口文档开放平台参考----> 微博开放平台

  • 第三方平台

    • 例如:showDoc 但是需要花钱
    • https://www.showdoc.com.cn/item/index
  • 公司自研

  • 开源的接口文档平台

    • Yapi:百度开源的
  • 通过项目,自动生成接口文档平台

    • coreapi
    • drf-yasg:可以看着文档自己集成到项目里

3、接口文档规范

这要取决你所在的公司有着什么样的规范,毕竟是自己公司内部员工看的,所以需要统一

4、coreapi使用

(1)安装

pip3 install coreapi

(2)路由中配置

from rest_framework.documentation import include_docs_urls
urlpatterns = [
    path('docs/', include_docs_urls(title='coreapi自动生成接口文档'))
]

(3)本地配置文件中配置

REST_FRAMEWORK = {
    	'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
	}

(4)正常写视图类

  • 注意:在方法中加的注释,会在接口文档中体现。在类里面但是在方法外的注释是不会体现的

  • 在序列化类中写required,help_text等参数也会在接口文档中体现

Django REST Framework (DRF) 是一个用于构建 Web API 的强大工具,它是基于 Django 框架的。DRF 提供了方便的方式来处理 HTTP 请求,定义视图、序列化器、路由以及认证等组件,使得开发RESTful API变得更加高效和易于维护。 接口文档DRF中非常关键的一部分,它帮助开发者、团队成员和外部用户提供对API的清晰理解。在DRF中,你可以使用`rest_framework.authtoken`或`drf_yasg`库(如Swagger UI)来生成详细的API文档。以下是生成和使用DRF接口文档的一些步骤: 1. 安装必要的库(如`drf_yasg`): ```bash pip install djangorestframework drf-yasg ``` 2. 配置settings.py中的SWAGGER设置: ```python INSTALLED_APPS = [ # ... 'drf_yasg', 'rest_framework_swagger', ] ``` 3. 在`urls.py`中包含API和文档的URL路径: ```python from drf_yasg.views import get_schema_view schema_view = get_schema_view(title='Your API Name', public=True) urlpatterns = [ # Your API routes... path('openapi/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), ] ``` 4. 创建视图和定义序列化器: 使用DRF的ViewSet和Serializer为你的API资源定义行为。 5. 使用`@api_view`装饰器或`@action`方法来定义API操作。 6. 运行服务器并访问`http://yourserver.com/openapi/`,通常会显示一个交互式的Swagger UI页面,其中包含了你的API的所有公开接口和描述。 相关问题: 1. Swagger UI在DRF中起到什么作用? 2. 如何在DRF中使用`@api_view`装饰器定义API? 3. 如何自定义接口文档的主题和样式?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值