drf - 过滤、排序、异常源码剖析、jwt

发布时间 2023-09-10 10:32:53作者: Way*yy

过滤类的源码剖析

1、为什么在视图类中配置了一个过滤类,就可以走?
	  -filter_backends = [SearchFilter,MyFilter]
2、前提条件是必须继承在视图类中继承GenericAPIView:
	  因为filter_backends是GenericAPIView的类属性。
3、如果光继承了GenericAPIView还是不行,还需要再继承ListModelMixin,因为过滤是在ListModelMixin中做的。
当我的程序来到:
            def list(self, request, *args, **kwargs):
                queryset = self.filter_queryset(self.get_queryset())
-我list方法中没有这个方法,最终就会找到我GenericAPIView中的filter_queryset方法:
    def filter_queryset(self, queryset):
        # 将我视图类中的filter_backends先强转成一个列表,然后for循环
        for backend in list(self.filter_backends):
            # 再实例化我这个过滤类,调用里面的filter_queryset方法得到一个对象,再将对象返回出去
            queryset = backend().filter_queryset(self.request, queryset, self)
            return queryset

分页类的源码剖析

1.1、视图类中配置了分页类,为什么就有分页了?
	 - pagination_class = CommonCursorPagination  # GenericAPIView类属性
如果光继承了GenericAPIView还是不行,还需要再继承ListModelMixin,因为分页是在ListModelMixin中做的。
当我的程序来到:
    def list(self, request, *args, **kwargs):
        # 过滤开始
        queryset = self.filter_queryset(self.get_queryset())
        
#######################分页开始#######################
		# 当程序走到这里就会调用paginate_queryset方法,但是这个方法List类中没有,最终会找到GenericAPIView中
        page = self.paginate_queryset(queryset)
        if page is not None: # 如果return None就不做分页
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
#######################分页结束#######################
        
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)
    
GenericAPIView中的paginate_queryset方法:
    def paginate_queryset(self, queryset):
        # 调用下面的paginator的方法,得到一个None或者一个分页类
        if self.paginator is None:
            return None
        # 得到一个分页类就会调用该类里面的paginate_queryset方法,完成分页
        return self.paginator.paginate_queryset(queryset, self.request, view=self)
 
    -self.paginator 方法包装成了属性,就是分页类的对象  CommonCursorPagination()
    @property
    def paginator(self):
        self._paginator = self.pagination_class() # 咱们配置的分页类  CommonCursorPagination
        return self._paginator
    - 调用了 分页类对象的paginate_queryset--->完成真正的分页
    	分页类对象.paginate_queryset(queryset, self.request, view=self)

异常的源码剖析

为什么在配置文件中配置了自己写的全局异常处理函数,只要出了异常,它就会走?
	-APIView执行流程---》dispatch的 
    	try:
        except Exception as exc:
            # 执行handle_exception方法
            response = self.handle_exception(exc) # exc看这里,exc是错误对象
            
            -self.handle_exception(exc):# 执行我视图类中的handle_exception
            
        def handle_exception(self, exc):
            exception_handler = self.get_exception_handler() # 拿到的就是配置文件中配的那个函数
            response = exception_handler(exc, context) #执行这个函数,传了俩参数
            return response
        
        -self.get_exception_handler()是如何从配置文件中拿出来的
        -self.settings.EXCEPTION_HANDLER  # 先从项目配置文件中找:key为它的EXCEPTION_HANDLER,如果项目配置文件没有,拿drf内置的配置文件


        # 如果你写了全局异常处理函数,配置好了,但是前端还没有返回固定格式,可能的原因是什么?
        -1 这个错误不是在三大认证和视图类的方法中产生的,之前产生的--》中间件,包装新的request
        -2 你写的common_exception执行出错了

接口文档

作用:
	让前端知道我每个接口的用途,所以我们需要写一个接口文档来供前端使用
    
接口文档的展现形式:
    1、word、.md
    2、自动生成接口文档------>后端通过配置----->把所有接口都自动生成----->地址---->访问这个地址就可以看到所有接口
    3、公司内部搭建接口文档平台:
        - 开源:Yapi--->同学搭建一个,给搭建用
            -https://zhuanlan.zhihu.com/p/366025001
        - 自己开发(自研)
        
    4 使用第三方平台(花钱)-->showdoc
    
接口文档该如何写:
	以用户注册为例:
        1、接口描述
        2、请求地址
        3、请求方式
        4、编码格式:json、urlencoded、form-data
        5 请求参数:参数详解
            -请求地址参数
            -请求体参数
        6 返回格式示例--》返回参数说明
        7 备注(可有可无)--》错误码
        
自定生成接口文档:
    1、pip install coreapi
    2、设置接口文档访问路径:
    from rest_framework.documentation import include_docs_urls
    urlpatterns = [
        path('docs/', include_docs_urls(title='站点页面标题'))
    ]
    3、在视图中加注释
    4、配置文件中配置:
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'

jwt介绍和构成(理论)

# 做会话保持的发展历史
	-https://www.cnblogs.com/liuqingzheng/p/8990027.html

# jwt:Json Web Token:web方向的token认证方案
# 在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证(token串)。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制

# Json web token (JWT), JWT用在咱们前后端做登录认证的,如果登录了,就携带token过来,如果没登录,就不携带---》后端通过验证token的准确性,确定是谁访问我们


#JWT的构成--三部分
	eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    
    # 头:header
    	-一般放公司信息,加密方式(没放秘钥)
    # 荷载:payload
    	-当前用户的信息:用户名,用户id,token过期时间。。。
    # 签名:signature
    	-第一部分和第二部分通过加密得到的字符串

jwt的签发与认证

以后使用jwt,最核心的就两个地方
	-签发:(登录接口)
        -登录接口、登录成功、签发token(三段式)
        -header,用base64编码,暂放
        {"company": "公司信息",}
        -payload,用base64编码,暂放
        {用户名,用户权限,过期时间}
        -使用加密方式:md5,把header和payload 都update进md5中--->生成前面--->base64编码
        -三段拼接起来---》用 .  分割 
        
    -认证:(认证类)
        -用户携带token过来,认证
        -取出第一部分的token
        -取出第二段的payload
        -使用之前同样的加密算法(密码),得到前面的
        -跟token的第三部分比较,如果一样的话,表示没有被修改,顺利执行下去,返回两个值
        -如果被修改了抛出异常

base64编码

作用:
    1、token串使用的就是base64编码格式
    2、互联网中前后端数据交互,可以使用base64编码
    3、图片二进制可以使用base64编码传递
    
介绍:
	Base64编码是一种将二进制数据转换为可打印ASCII字符的编码方式。它常用于在网络传输中传递二进制数据,或者在文本协议中嵌入二进制数据。

注意事项:
	需要注意的是,base64.b64encode和base64.b64decode函数的参数和返回值都是字节串(bytes类型),而不是字符串(str类型)

使用方法

import json
import base64

d = {'name': "杨赋华", 'age': 18, "gender": "男"}

#######编码
d_json = json.dumps(d)
# 注意使用base64加密必须是字节类型,所以一定要encode一下
d_base = base64.b64encode(d_json.encode("utf-8"))
print(d_base)
# b'eyJuYW1lIjogIlx1Njc2OFx1OGQ0Ylx1NTM0ZSIsICJhZ2UiOiAxOCwgImdlbmRlciI6ICJcdTc1MzcifQ==' 这就是一个标准的base64编码

#######解码
d_base_decode = base64.b64decode(d_base)
print(json.loads(d_base_decode))
# {'name': '杨赋华', 'age': 18, 'gender': '男'}

drf-jwt的介绍

# django 中使用jwt

# 可以自己写,使用第三方
	-django-rest-framework-jwt:有点老
    -djangorestframework-simplejwt:新的
    -自己写:								https://gitee.com/liuqingzheng/rbac_manager/blob/master/libs/lqz_jwt/token.py
    
# 下载:
pip install djangorestframework-jwt

django-rest-framework-jwt快速使用

签发:
	默认是在auth_user表中签发 ------>登录接口------>内部已经封装好了
    
路由层:
    rom rest_framework_jwt.views import obtain_jwt_token
    urlpatterns = [
        path('login/', obtain_jwt_token),
    ] # obtain_jwt_token登录接口
    
# 在auth_user中添加一条数据,post访问改地址,就会得到一个token串

局部认证:
	from rest_framework_jwt.authentication import JSONWebTokenAuthentication
    class Publish(ViewSetMixin, ListAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializers
        authentication_classes = [JSONWebTokenAuthentication]
        # 问题:配置了登录才可以访问该接口,但是不起作用,还是可以直接访问该接口,所以还需要配合权限类使用
权限:
	from rest_framework.permissions import IsAuthenticated
	permission_classes = [IsAuthenticated]
	-IsAuthenticated源码解析:
    class IsAuthenticated(BasePermission):
        def has_permission(self, request, view):
            # 如果都为True权限通过
            return bool(request.user and request.user.is_authenticated)
默认认证类:带了token过来,它会校验,不带,就不校验------>不校验,request.user就没有值------>使用IsAuthenticated类做限制
前端固定写法
Authorization:jwt 中间要空一格eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InlhbmciLCJleHAiOjE2OTQyMzczNjMsImVtYWlsIjoiMTIzQHFxLmNvbSJ9.tsBOxloMlKtA6kDKANxZemDX2BzTRTYZHCa6Gey_vGM

定制签发返回格式

# 自定义认证返回结果

# 写个函数
def jwt_response_payload_handler(token, user=None, request=None):
    return {
        'status': 100,
        'msg': '登录成功',
        'token': token,
        'username': user.username
    }

# 配置文件配置
JWT_AUTH = {
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.response.jwt_response_payload_handler',
}

源码分析

为什么 路由这样配了,就会有个登录接口?
	path('login/', obtain_jwt_token), # obtain_jwt_token  
    # obtain_jwt_token = ObtainJSONWebToken.as_view()
ObtainJSONWebToken源码:
	class ObtainJSONWebToken(JSONWebTokenAPIView):
        # 这里面就写了一个序列化类
        serializer_class = JSONWebTokenSerializer
        
JSONWebTokenAPIView源码:# 这里面写了一个登录接口
class JSONWebTokenAPIView(APIView):
    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)

        if serializer.is_valid():
            user = serializer.object.get('user') or request.user
            token = serializer.object.get('token')
            response_data = jwt_response_payload_handler(token, user, request)
            response = Response(response_data)
            if api_settings.JWT_AUTH_COOKIE:
                expiration = (datetime.utcnow() +
                              api_settings.JWT_EXPIRATION_DELTA)
                response.set_cookie(api_settings.JWT_AUTH_COOKIE,
                                    token,
                                    expires=expiration,
                                    httponly=True)
                return response
    # 在父类中:JSONWebTokenAPIView  post
	-登录走的是JSONWebTokenAPIView的post,签发token 是在序列化类中
    -签发完token执行了,咱们写的jwt_response_payload_handler,所以才能定制返回格式

作业

# 1 自己封装一个jwt的签发和认证
# 2 用一下 yapi,showdoc

------
# 3 自定义用户表,使用djagno-jwt去签发(登录接口)和认证(认证类)