67、练习 、 全局异常处理 、接口文档 、jwt介绍和原理 、 base64编码和解码

108 阅读9分钟

练习

基于APIView写过滤排序分页

分析

根据ListModelMixin分析

class ListModelMixin:
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())  # 过滤-->GenericAPIView里的filter_queryset

        page = self.paginate_queryset(queryset)  # 分页--GenericAPIView里的paginate_queryset
        if page is not None:
            serializer = self.get_serializer(page, many=True)  # 如果page有值,序列化
            return self.get_paginated_response(serializer.data) # 返回了GenericAPIView--》self.paginator.get_paginated_response(data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)
        

鼠标点进GenericAPIView里的paginate_queryset

def paginate_queryset(self, queryset):   # GenericAPIView里的paginate_queryset
    if self.paginator is None:   
        return None  # 如果没有,返回none
    return self.paginator.paginate_queryset(queryset, self.request, view=self)  # 返回:分页类对象.paginate_queryset()

鼠标点进GenericAPIView里的paginator

@property
def paginator(self):  # # GenericAPIView里的paginator

    if not hasattr(self, '_paginator'):    # 没有_paginator就执行
        if self.pagination_class is None:  # 视图里面写的列表
            self._paginator = None
        else:
            self._paginator = self.pagination_class()   
    return self._paginator      # 返回none或分页类对象

鼠标点进GenericAPIView的get_paginated_response()

def get_paginated_response(self, data):  # GenericAPIView的get_paginated_response()

    assert self.paginator is not None  # 断言自己有分页类对象
    return self.paginator.get_paginated_response(data)   # 分页类对象调用get_paginated_response()--->PageNumberPagination的get_paginated_response

鼠标点进PageNumberPagination的get_paginated_response


def get_paginated_response(self, data):  # PageNumberPagination的get_paginated_response
    return Response(OrderedDict([
        ('count', self.page.paginator.count),
        ('next', self.get_next_link()),
        ('previous', self.get_previous_link()),
        ('results', data)
    ]))

实现

from rest_framework.views import APIView
from app01.models import Publish
from .serializer import PublishSerializer
from rest_framework.response import Response
from .page import MyPage

class PublishView(APIView):
    def get(self,request):
        qs = Publish.objects.all()
        name = request.query_params.get('name')
        addr = request.query_params.get('addr')
        order = request.query_params.get('ordering')
        print(order)
        # 排序
        if order:
            publish_list = qs.order_by(order).all()
        # 过滤  ?name=红&addr=四川
        if name:
            qs = qs.filter(name__contains=name).all()
        if addr:
            qs = qs.filter(name__contains=addr).all()
        # 分页

        # page = MyPage().paginate_queryset(qs,request,self)
        pagination = MyPage()
        page = pagination.paginate_queryset(qs,request,self)

        ser = PublishSerializer(instance=page,many=True)
        # return Response({'code':100,'msg':'查询成功','data':ser.data})
        return Response({'code':100,
                         'msg':'查询成功',
                         'count':pagination.page.paginator.count,
                         'next':pagination.get_next_link(),
                         'previous':pagination.get_previous_link(),
                         'results':ser.data})

全局异常处理

1.对于前端来讲,后端即便报错,也要返回统一的格式,前端便于处理:{code:999,msg:'系统异常,请联系系统管理员'}
2.drf只要出了异常,就会执行drf配置文件中的:'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
3.只要三大认证,视图类的方法出了异常,都会执行一个函数:rest_framework.views import exception_handler

APIView执行流程

1.有了drf,后期都写CBV,都是继承APIView及其子类

2.执行流程
	1.入口:path('books/', BookView.as_view())  -->请求来了执行:BookView.as_view()(request,)
  2.自己写的BookView类里没有as_view绑定的方法--->父类APIView里有as_view绑定的方法
  3.请求来了,执行 :(View类的as_view中的view,只是去掉了csrf的认证)(request)
  4.执行父类APIView中有dispatch方法
                                                
3.总结
  	1.以后只要继承APIView的所有视图类的方法,都没有csrf的校验了
    2.以后只要继承APIView的所有视图类的方法 中的request是新的request了
    3.在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
    4.期间除了各种错误,都会被异常捕获,统一处理 

分析

APIView中的dispatch

    def dispatch(self, request, *args, **kwargs):
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        try:
            self.initial(request, *args, **kwargs)  # 执行了认证,权限和频率
            # 在执行视图类方法之前,去掉了csrf认证,包装了新的request,执行了认证频率和权限
            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.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

鼠标点进:self.handle_exception(exc)-->APIView中的handle_exception()方法

    def handle_exception(self, exc):
        if isinstance(exc, (exceptions.NotAuthenticated,
                            exceptions.AuthenticationFailed)):
            
            auth_header = self.get_authenticate_header(self.request)

            if auth_header:
                exc.auth_header = auth_header
            else:
                exc.status_code = status.HTTP_403_FORBIDDEN

        exception_handler = self.get_exception_handler()  

        context = self.get_exception_handler_context()   
        response = exception_handler(exc, context) # 处理异常

        if response is None:
            self.raise_uncaught_exception(exc)

        response.exception = True
        return response

鼠标点进:from rest_framework.views import exception_handler

def exception_handler(exc, context):
    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
ps:
  1.异常对象是drf的APIException对象,就会返回Response(data, status=exc.status_code, headers=headers),不是就返回None 
  2.exception_handler只处理了drf的异常,其它的异常需要我们自己处理
  3.如果异常对象不是drf的APIException对象,就会返回None
  
注意:源码是不能改的,我们可以自定义一个函数,配置一下,以后出了异常,执行我们自己的函数

自定义报错函数


from rest_framework.response import Response
from rest_framework.exceptions import APIException
from rest_framework.views import exception_handler
def common_exception_handler(exc, context):
    res = exception_handler(exc, context)
    if res:
        # 说明drf异常
        if isinstance(res.data, dict):
            detail = res.data.get('detail')
        else:
            print(11)
            detail = res.data
        return Response({'code': 102, 'msg': detail})

    else:
        # 说明其他异常
        # return Response({'code': 101, 'msg': str(exc)})
        return Response({'code': 999, 'msg': '系统异常,请联系系统管理员'})
      
      
ps:需要在项目的配置文件注册
	REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app02.exception.common_exception_handler',
}
  
  

补充

1.isinstance()   判断一个对象是不是某个类的对象  isinstance(对象,类)
2.issubclass()   判断一个类,是不是另一个类的子类

接口文档

概要

1.后端把接口写好后,如登录接口、登录接口、查询所有图书带过滤接口
2.前端人员需要根据接口文档,进行前端开发
3.前后端需要做对接---->对接第一个东西就是这个接口文档--->前端照着接口文档开发
4.ps:公司3个人,每个人开发了10个接口,3个人都要同时写接口文档

接口文档的编写形式

1.world、md编写--->写完放在git,公司的文档管理平台上
2.第三方的接口文档平台(收费)
   eg:https://www.showdoc.com.cn/
3.公司自己开发接口文档平台
4.公司使用开源的接口文档平台,搭建
	1.YAPI:百度开源的
		ps:docker-compose部署Yapi-->https://zhuanlan.zhihu.com/p/366025001
        
5.项目自动生成接口文档
	coreapi
	swagger  --> django推荐的

使用coreapi自动生成接口文档

步骤:
	1.安装:pip3 install coreapi
	2.加路由
      from rest_framework.documentation import include_docs_urls	
      urlpatterns = [
        path('docs/', include_docs_urls(title='站点页面标题'))
      ]
	3.在视图类上加注释
			1.单一方法的视图,可直接使用类视图的文档字符串,如
      class BookListView(generics.ListAPIView):
          """
          返回所有图书信息.
          """
			2.包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如
      class BookListCreateView(generics.ListCreateAPIView):
          """
          get:
          返回所有图书信息.

          post:
          新建图书.
          """
			3.对于视图集ViewSet,仍在类视图的文档字符串中封开定义,但是应使用action名称区分,如

      class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
          """
          list:
          返回图书列表数据

          retrieve:
          返回图书详情数据

          latest:
          返回最新的图书数据

          read:
          修改图书的阅读量
          """

	4.配置文件中配置:openapi--->coreapi
       原来的: 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',
       改为:'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',

	5.访问地址:
		http://127.0.0.1:8000/docs/
  
  
ps:1.表模型或序列化类的字段上写help_text--->会显示在接口文档的字段介绍上
		2.参数的Description需要在模型类或序列化器类的字段中以help_text选项定义,如:

    class Student(models.Model):
          ...
          age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄')
          ...


      class StudentSerializer(serializers.ModelSerializer):
          class Meta:
              model = Student
              fields = "__all__"
              extra_kwargs = {
                  'age': {
                      'required': True,
                      'help_text': '年龄'
                  }
              }
   3.视图集ViewSet中的retrieve名称,在接口文档网站中叫做read

jwt介绍和原理

Cookie,Session,Token发展史:https://www.cnblogs.com/liuqingzheng/p/16154439.html

1.cookie,session,token发展历史
		1.会话管理
    2.cookie:客户端浏览器的键值对
    3.session:服务端的键值对(djangosession表,内存中,文件,缓存数据库)
    ps:cookie+session:成千上万条的数据对服务器来说是有巨大压力的,严重的限制了服务器扩展能力
    4.token:服务器生成的加密字符串,如果存在客户端浏览器上,就叫cookie
    				不在服务器上存储,保证安全,加密
    	  -三部分:头,荷载,签名
        -签发:登录成功,签发
        -认证:认证类中认证
2. jwt:Json web token (JWT),web方向的token认证

Jwt认证

举例:
		小F已经登录了系统, 我给他发一个令牌(token), 里边包含了小F的 user id, 下一次小F 再次通过Http请求访问的时候, 把这个token通过Http header带过去不就可以了。
		不过这和session id没有本质区别啊, 任何人都可以可以伪造, 所以我得想点儿办法, 让别人伪造不了。
		那就对数据做一个签名吧, 比如说我用HMAC-SHA256 算法,加上一个只有我才知道的密钥, 对数据做一个签名, 把这个签名和数据一起作为token , 由于密钥别人不知道, 就无法伪造token了。

token.png

		这个token不保存, 当小F把这个token 给我发过来的时候,再用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道小F已经登录过了,并且可以直接取到小F的user id , 如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证
  	Token 中的数据是明文保存的(虽然我会用Base64做下编码, 但那不是加密), 还是可以被别人看到的, 所以我不能在其中保存像密码这样的敏感信息。

token2.png

基于session认证和基于token认证区别

基于session认证

基于session认证.jpeg

基于jwt认证

基于jwt认证.jpeg

base64编码和解码

1.base64并不是一种加密方式,只是编码解码方式
2.编码:base64.b64encode(二进制数据)
3.解码:base64.b64decode(二进制数据)
4.例子
    import json
    import base64
    d = {'user_id':1,'username':'nana'}
    d_str = json.dumps(d)
    res = base64.b64encode(bytes(d_str,encoding = 'utf-8'))
    print(res)  # b'eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogIm5hbmEifQ=='

    res = base64.b64decode(res)
    print(res)  # b'{"user_id": 1, "username": "nana"}'
    
5.记住: base64编码,长度一定是4的倍数。如果不够,用 = 补齐
6.base64的用途
    1.互联网中,前后端数据交互,使用base64编码
    2.jwt 字符串使用base64编码
    3.互联网中一些图片,使用base64编码

作业

1.写一个全局异常处理,无论drf或其它异常,都统一返回格式,并写接口测试
2.使用coreapi自动生成接口文档
3.随便选择一个接口文档平台,注册,试用如何写接口文档
4.了解一下什么是mock数据(对测试,前端人来讲)

了解一下什么是mock数据(对测试,前端人来讲)

1.什么是mock数据?
		前后端同时开发的时候,后端接口数据没有出来,前端可以mock假数据,模拟开发;
端知识杂记

2.mock数据的几种方式?
	1.直接在页面写死数据,后期等接口来了,再改成动态的; 小型的项目,不出5个页面的可以解决,或是每个页面的数据很少的可以解决,但是不推荐,后期太麻烦
	2.在js里直接声明变量,并给变量赋值,在逻辑脚本中使用,并渲染到dom;
	3.将模拟数据编辑成json数据或者是零碎的js脚本中,通过请求,取回数据,并进行业务逻辑处理,渲染到dom
	4. 前后台在需求分解之后,一起定义好接口api,包含:请求url(项目前缀+具体的接口名称)、请求方式、请求参数、数据响应;
		前端研发人员根据接口约定,模拟请求返回对应的数据,完成对应的交互;
		后台人员根据接口约定,完成对应的api,并完成对应的自测;
		待后台人员交付接口api后,前端人员直接修改接口项目前缀,切换到对应的环境,即可进入项目提测