DRF大回顾

发布时间 2023-09-22 12:46:22作者: Way*yy

drf入门规范

1、前后端开发模式:
    -混合模式:也就是BBS项目
    -前后端分离模式:只负责写接口,不用管前端
    
2、API接口:
    -长得像返回数据的url链接
    -请求方式:get、post、put等等
    -请求参数:地址、请求体
    -返回数据
    -写接口为了给谁用?
    	前端(web、app),或者提供给第三方调用
        
3、接口测试工具------->postman
    -发送http请求工具
    -get请求可以在请求体中携带数据
    -get请求和post请求有什么区别?
    -请求编码格式:
        -urlencoded:key=value&key=value------->从request.POST中取
        -form-data:数据和文件混在一起------->文件从request.FILES中取,数据从request.POST中取
        -json:{"name":"XXX"}------->>数据从request.body中取
    -collections创建,保存导出
    
4、接口规范,restful规范:
    -数据安全的保障
    -接口特征的变现
    -多数据版本共存
    -数据既是资源,均使用名词(可复数)
    -资源操作由请求的方式来决定(method)
    -过滤,通过在url上传参的形式传递搜索条件
    -响应状态码(两层)
    -错误处理,应返回错误信息,error当key
    -返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范
    -需要url请求的资源需要访问资源的请求链接
    
5、序列化和反序列化:
    -序列化:将我的数据类型转换成json格式字符串可供其他语言使用
    -反序列化:将其他语言提供给我们的数据类型,转换成我们自己可以识别的数据类型
    
6、drf:
	Django框架上的一款APP,方便我们快速编写符合restful规范的接口

7、CBV源码分析:
	.....
  
8、APIView比View多出了哪些东西
    -在dispatch中执行了视图类中与请求方式同名的方法
    -去掉了csrf验证
    -包装了全新的request
    -执行了三大认证
    -处理了全局异常
    -可以直接在视图类中的方法中self.request

get请求和post请求的区别

GET请求和POST请求是HTTP协议中常用的两种请求方法,它们在以下几个方面有所区别:

1. 参数传递方式:
   - GET请求:参数通过URL的查询字符串(query string)传递,即参数会附加在URL的末尾,使用`?`进行分隔,多个参数之间使用`&`符号连接。例如:`http://example.com/path?param1=value1&param2=value2`
   - POST请求:参数通过请求体(request body)传递,参数不会附加在URL中,而是在请求体中进行传递。参数的格式可以是表单形式(`application/x-www-form-urlencoded`)或者是JSON等其他格式。

2. 参数长度限制:
   - GET请求:由于参数附加在URL中,URL的长度是有限制的,不同的浏览器和服务器对URL长度的限制不一样,一般在2KB到8KB之间。超过限制的参数可能会被截断或丢失。
   - POST请求:请求体的长度一般没有明确的限制,但是具体的限制取决于服务器的配置和资源。

3. 安全性:
   - GET请求:参数附加在URL中,会被保存在浏览器的历史记录、服务器的日志文件等地方,容易被他人获取。因此,不适合传递敏感信息(如密码)。
   - POST请求:参数在请求体中传递,不会被保存在URL中,相对于GET请求更安全,适合传递敏感信息。

4. 幂等性:
   - GET请求:一般情况下是幂等的,即多次重复请求不会产生副作用。
   - POST请求:一般情况下是非幂等的,即多次重复请求可能会产生副作用,如创建重复的资源。

5. 缓存:
   - GET请求:可以被浏览器缓存,可以通过在请求头中设置`Cache-Control`等字段来控制缓存策略。
   - POST请求:一般不会被浏览器缓存。

根据具体的需求和场景,选择合适的请求方法是很重要的。一般来说,GET请求适合获取资源,而POST请求适合提交数据或执行操作。

http和https协议的区别

HTTP(Hypertext Transfer Protocol)和HTTPS(Hypertext Transfer Protocol Secure)是两种常见的网络传输协议,它们在以下几个方面有所区别:

1. 安全性:
   - HTTP:是明文传输协议,数据传输过程中不进行加密,容易被窃听和篡改。因此,HTTP不适合传输敏感信息。
   - HTTPS:是通过使用SSL(Secure Sockets Layer)或TLS(Transport Layer Security)协议对HTTP进行加密的协议。通过使用公钥加密和私钥解密的方式,HTTPS可以保证数据在传输过程中的安全性,防止数据被窃听和篡改。

2. 默认端口:
   - HTTP:默认使用端口80进行通信。
   - HTTPS:默认使用端口443进行通信。

3. 证书:
   - HTTP:不需要证书。
   - HTTPS:需要使用SSL证书,证书由可信的第三方机构(CA)签发,用于验证服务器的身份。证书包含了服务器的公钥,用于加密数据传输。

4. 性能:
   - HTTP:由于数据传输过程中没有加密和解密的过程,相对于HTTPS来说,性能更高。
   - HTTPS:由于数据传输过程中需要进行加密和解密的过程,相对于HTTP来说,性能略低。

5. SEO(Search Engine Optimization):
   - HTTP:搜索引擎更容易识别和索引HTTP网页。
   - HTTPS:搜索引擎更倾向于将HTTPS网页排名更高,因为HTTPS可以提供更安全的用户体验。

需要注意的是,为了提高网站的安全性和保护用户的隐私,现在越来越多的网站采用了HTTPS协议。在使用HTTPS时,网站需要获得有效的SSL证书,并进行相应的配置。

序列化组件

1、序列化组件的介绍:
    -可以对数据进行序列化------->queryset对象
    -可以对数据进行反序列化------->前端出入的json格式的数据------->保存到数据库中
    -可以对数据进行反序列化检验------->字段自己的校验,局部钩子、全局钩子
    
2、序列化组件的快速使用:
    -先写一个类继承继承Serializer
    -在类中写入字段
    -在视图类中:实例化得到序列化类的对象
    -序列化:序列化类的对象.data retrun Response(ser.data)
    
3、常用字段类
	-跟models里面的没有区别,只是多出来两个:DictField、ListField,这两个参数一个接收字典类型、一个接受列表类型,这两个字段都可以进行序列化和反序列化
    
4、字段参数:
    -read_only = True 只做序列化
    -write_only = False 只做反序列化
    
5、继承Serializer做反序列化新增或修改组需要重写create方法和updata方法
    -新增
        ser=BookSerializer(data=request.data)
        ser.is_valid(raise_exception=True)--->只要校验不通过,直接抛异常
        ser.save()  # 继承Serializer,需要在序列化类中重写create方法,完成新增
        序列化类中重写create方法
        def create(self, validated_data):
            # validated_data 前端传入,校验过后的数据
            # 新增一个对象
            return  新增的对象  # 后续再视图类中只要调用ser.data,这里必须返回

    -修改
        ser=BookSerializer(instance=要修改的对象,data=request.data)
        ser.is_valid(raise_exception=True)--->只要校验不通过,直接抛异常
        ser.save()  # 继承Serializer,需要在序列化类中重写update方法,完成修改
        序列化类中重写update
        def update(self, instance, validated_data):
            # instance 要修改的对象,哪里来的?BookSerializer(instance=要修改的对象的id,data=request.data)
            # validated_data:校验过后数据
            res=Book.objects.filter(pk=instance).update(**validated_data)
            return res   # 返回的res最后干啥了?只要在  视图类中 调用ser.data,他会根据这个返回值做序列化
        
6、反序列化校验:
	字段自己校验----->局部钩子----->全局钩子
    
7、返回定制格式:
    -1、source:
        -1、改写字段名
        -2、跨表查询
        -3、拿到表模型中方法
    -2、SerializerMethodField:在序列化类中写
        -publish = Serializers.SerializerMethodField(read_only = True)
        配合一个get_publish(self,obj)方法就可以定制该字段的返回格式
    -3、在表模型中写方法:
        def publishdict(self):
            return {"name":publish.name}
        在序列化类中:publishdict = Serializers.DictField()

    -只能做序列化用了,反序列化得单独用----->需要使用read_only和write_only控制
    
8、ModelSerializer使用:
	class Auth(Serializers.ModelSerializer):
        或者可以在此处重写字段
        class Meta:
            model = 表名
            fields = "__all__" or ["字段名","字段名"......]
            extra_kwargs={} # 传入字段参数
            
        大部分情况下不需要写create和update了

        局部,全局钩子跟之前一模一样

视图组件

1、两个视图基类:
	APIView:
    	-类属性:
            renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
            parser_classes = api_settings.DEFAULT_PARSER_CLASSES
            authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
            throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
            permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    GenericAPIView:
        	-类属性:
                -queryset = None
                -serializer_class = None
                -lookup_field = 'pk'
                -filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
                -pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
            -类方法:
                -get_queryset:获取所有要序列化的数据
                -get_obj:获取单个对象
                -get_serializer:------>调用了get_serializer_class------>返回了序列化类的对象以及要传入的参数----->instance,data,many
                -filter_queryset----->给ListModelMixin用的,把配置的filter_backends 依次执行完成过滤
                
2、5个视图扩展类需要搭配GenericAPIView使用
    -ListModelMixin,
    -CreateModelMixin,
    -UpdateModelMixin,
    -DestroyModelMixin,
    -RetrieveModelMixi
    -如果一个视图类要写四个接口,必须要借助于ViewSetMixin餐可以完成
    
3、九个视图子类:
	九个视图子类是继承了GenericAPIView以及5个视图扩展类搭配出来的
   	...........略 
4、视图集:
	ViewSetMixin:改变了路由的写法
    诞生出来的子类:
        -ReadOnlyModelViewSet:RetrieveModelMixin + ListModelMixin + GenericViewSet
        -ModelViewSet:GenericViewSet+5个视图扩展类
        -ViewSet:ViewSetMixin + APIView
        -GenericViewSet:ViewSetMixin + GenericAPIView

请求与响应

1、请求:
	-请求源码:request对象
    -request.data:前端传入的数据
    -request.query_params:url中携带的参数
    -request._request:未封装前的request
    -为什么我封装了request以后还可以使用以前的方法?
    	-因为在新的request中有一个__getattr__方法,会将为封装前的方法反射到我封装后的request方法上面
    -请求能解析编码格式:parser_classes
    	-局部使用
        -全局使用
        
2、响应
    -响应Response  源码
    -data:响应体的内容,序列化后的数据给了他
    -headers:响应头    django原生响应头  响应对象['xx']=xxx
    -status:响应状态码
        -响应编码格式-->浏览器,postman,看到的样子
        -局部和全局配置
        
    # 写了个视图类  获取所有接口
    class BookView(ViewSetMixin,GenericAPIView,ListModelMixin):
        queryset = None
        serializer_class = None
        def list(self,request,*args,**kwargs):
            res=super().list(request,*args,**kwargs)
            # 返回字典格式
            res.data  # {code:100,msg:成功,data:[{},{}]}
            return Response({code:100,msg:成功,data:res.data})

路由组件

1 只要继承ViewSetMixin及其子类,路由写法就变了
	-方式一:映射方式
    	视图类.as_view({'get':'lqz'})
    -方式二:自动生成
    	-SimpleRouter  DefaultRouter:区别是DefaultRouter会带根目录
        
        
2 action装饰器:methods:请求方式  detail:为True URL中带数字 ,为False 则不带                    

认证权限频率

1、认证类的使用:
    -1、写一个类,继承BaseAuthentication
    -2、重写 def authenticate(self, request):方法
    -3、取出用户传入的token----->通过token判断登录的用户
    -4、认证成功取出用户,认证失败抛出异常
    -5、return 查到的对象、token,后续通过request.user取出返回的第一个值,request.auth取出第二个值
    
2、权限类:
    -1、写一个类,继承BasePermission
    -2、重写 def has_permission(self, request, view):方法
    -3、判断用户的权限
        acl:权限,取出当前用户所有权限,判断当次请求是否在权限中
        rbac:根据当前用户取出所有角色,通过角色取出所有权限,去个重,判断是否在权限中
    -4、有权限返回True,没有权限返回False
    -5、在视图类中配置或者全局配置
    
3、频率类:
    -1、写一个类,继承SimpleRateThrottle
    -2、重写 def get_cache_key(self, request, view):方法
    -3、类属性配置 scope = "字符串"
    -4、配置文件中配置
        'DEFAULT_THROTTLE_RATES': 
            {
                '字符串': '3/m',
            }
    -5、视图类中配置,全局配置
    
为什么我们写了 权限类,配置行,它就会执行权限控制?
    -APIView 的dispatch中执行了3大认证---》
    def dispatch(self, request, *args, **kwargs):
        self.initial(request, *args, **kwargs) # 三大认证
        
initial源码:
    def initial(self, request, *args, **kwargs):
        self.perform_authentication(request) # 认证类
        self.check_permissions(request) # 权限类
        self.check_throttles(request) # 频率类

权限类源码分析

入口:self.check_permissions(request)

def check_permissions(self, request):
    # for循环我这个对象列表
    for permission in self.get_permissions():
        # 如果has_permission(request, self)返回的是False,注意括号内的这个self是视图类的对象
        if not permission.has_permission(request, self):
            # 如果有任何一个权限对象返回False,则调用permission_denied方法抛出异常。同时,可以通过getattr方法获取权限对象的message和code属性,并作为参数传递给permission_denied方法
            self.permission_denied(
                request,
                message=getattr(permission, 'message', None),
                code=getattr(permission, 'code', None)
            )
            
def get_permissions(self):
    # 使用列表生成式从permission_classes取出一个个自己写的权限类,实例化得到的对象列表返回出去
    return [permission() for permission in self.permission_classes]

认证类源码分析

入口:self.perform_authentication(request)

def perform_authentication(self, request):
    # 点进来发现只有一个request.user,所以我们要去Request里面去找User的方法看看是不是方法为装成了属性
    request.user
    
Request类中:
@property
def user(self):
    # 如果request这个对象中没有_user这个属性就会走下一步
    if not hasattr(self, '_user'):
        # 使用with语句和wrap_attributeerrors()上下文管理器,捕获AttributeError异常。
        with wrap_attributeerrors():

            self._authenticate()
            # 如果有这个属性直接返回出去
    return self._user

_authenticate方法:
def _authenticate(self):
    # for循环我视图类中的认证类的对象列表
    for authenticator in self.authenticators:
        try:
            # 调用我自己写的认证类中的authenticate方法,这个地方的self是request的对象
            user_auth_tuple = authenticator.authenticate(self)
        except exceptions.APIException:
            self._not_authenticated()
            raise
        # 如果user_auth_tuple不为None
        if user_auth_tuple is not None:
            # _authenticator拿到的就是我的认证类
            self._authenticator = authenticator
            # 通过解压赋值将用户给user,token给auth
            self.user, self.auth = user_auth_tuple
            return

    self._not_authenticated()
    
    -认证类可以配置多个,但是如果有一个返回了,后续的就不走了

self.authenticators 是request对象的属性,是在Request实例化的时候传入的,它什么时候实例化的,包装新的Reqeust时传入的--->APIView的dispatch-->request = self.initialize_request(request, *args, **kwargs),initialize_request下的------>authenticators=self.get_authenticators()下的get_authenticators------>
def get_authenticators(self):
    # 通过里边生成式遍历我视图类中的列表,然后实例化得到一个对象
    return [auth() for auth in self.authentication_classes]

频率源码分析

源码入口:self.check_throttles(request)
def check_throttles(self, request): 方法

def check_throttles(self, request):
    throttle_durations = []
    # 遍历我频率类对象的列表
    for throttle in self.get_throttles():
        # 执行频率类中的allow_request,并传入两个参数一个是一个request一个是我试图类的对象,返回True或者False,如果返回True
        if not throttle.allow_request(request, self):
            # 显示还剩多长时间才可以进行访问
            throttle_durations.append(throttle.wait())           
            
# 调用了这个方法           
def get_throttles(self):
     # 使用列表生成式循环我视图类中的频率类列表,并调用放到一个列表中
     return [throttle() for throttle in self.throttle_classes]

频率类要写
    1 写一个类,继承,BaseThrottle
    2 在类中重写:allow_request方法,传入 3个参数
    3 在allow_request写限制逻辑,如果还能访问--》返回True
    4 如果超了次数,就不能访问,返回False
    5 局部配置在视图类上
    6 全局配置在配置文件中

我们自己写频率类的时候,继承了SimpleRateThrottle只重写了 def get_cache_key(self, request, view):方法,猜测可能是SimpleRateThrottle帮我们重写了该方法
    
 SimpleRateThrottle 源码分析:
def allow_request(self, request, view):
    # 咱们没写,以后咱们可以在频率类中直接写
    # rate='3/m'   以后不用写scope了,就会按一分钟访问3次现在
    if self.rate is None:
        return True
    # 调用我的get_cache_key方法返回的值,咱们返回了访问者ip
    self.key = self.get_cache_key(request, view)
    # 判断是否为None,为None返回True
    if self.key is None:
        return True
    # 根据当前访问者ip,取出 这个人的访问时间列表  [访问时间1,访问2,访问3,访问4]
    self.history = self.cache.get(self.key, [])
    # 得到当前时间
    self.now = self.timer()
    # 去除掉访问时间大于60秒的
    while self.history and self.history[-1] <= self.now - self.duration:
        self.history.pop()
    # 判断这个列表长度是否大于3,如果大于等于3返回False
    if len(self.history) >= self.num_requests:
        return self.throttle_failure()
    # 否则调用throttle_success方法
    return self.throttle_success()

# 在SimpleRateThrottle实例化的时候会触发这个方法
def __init__(self):
    # 判断有没有rate这个属性
    if not getattr(self, 'rate', None):
        # 如果没有调用get_rate()方法来获取rate的值并赋给self.rate
        # self.rate拿到的是'3/m'
        self.rate = self.get_rate()
    # 如果有值将会调用parse_rate方法,将self.tate传入,将返回的结果分别给self.num_requests, self.duration
    self.num_requests, self.duration = self.parse_rate(self.rate)
    
# def get_rate(self):方法
def get_rate(self):
    # 判断有没有scope这个属性
    if not getattr(self, 'scope', None):
        # 如果没有直接抛异常
        msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
               self.__class__.__name__)
        raise ImproperlyConfigured(msg)

    try:
        """
        频率类中:
        	scope = xxx
         'DEFAULT_THROTTLE_RATES': {
            'xxx': 3/m,
            'anon': None,
        }
        """
        # 从配置文件中拿到scope这个属性对应的值,返回出去
        return self.THROTTLE_RATES[self.scope]
    except KeyError:
        msg = "No default throttle rate set for '%s' scope" % self.scope
        raise ImproperlyConfigured(msg)
        
        
# def parse_rate(self, rate)
 def parse_rate(self, rate):# rate是3/m
    # 判断rate是否为空,如果为空返回None
    if rate is None:
        return (None, None)
    # 按照/进行切分,把3给num,m给period
    num, period = rate.split('/')
    # 将num转成一个整型
    num_requests = int(num)
    # 判断我period索引为0位置的字符在不在我这个字典中
    duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
    # 返回我的数字3以及m对应的60
    return (num_requests, duration)


# def throttle_success(self):方法
def throttle_success(self):
    # 在self.history列表第0个位置插入一条当前时间
    self.history.insert(0, self.now)
    # 使用self.cache.set方法将更新后的历史记录列表存储到缓存中,缓存键为self.key,过期时间为self.duration,表示该缓存记录在一定时间后过期。
    self.cache.set(self.key, self.history, self.duration)
    return True

继承BaseThrottle自定义的频率类

class MyThrottle(BaseThrottle):
    VISIT_RECORD = {}

    def allow_request(self, request, view):
        # 写限制逻辑
        # (1)取出访问者ip
        user_ip = request.META.get('REMOTE_ADDR')
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
        if user_ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[user_ip] = [datetime.datetime.now()]
            print("++++++", self.VISIT_RECORD)
            return True
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        visit_times = self.VISIT_RECORD[user_ip]
        # visit_times = [visit_time for visit_time in visit_times if (datetime.datetime.now() - visit_time).seconds
        #                <= 60]
        filtered_visit_times = []
        for visit_time in visit_times:
            if (datetime.datetime.now() - visit_time).seconds <= 60:
                filtered_visit_times.append(visit_time)
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过 (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        if len(filtered_visit_times) < 3:
            # 将当前时间插入到列表的第一个位置
            filtered_visit_times.insert(0, datetime.datetime.now())
            print("------", filtered_visit_times)
            self.VISIT_RECORD[user_ip] = filtered_visit_times
            return True
        return False
# 频率类写好,配置在视图类上,就会走它

继承SimpleRateThrottle自定义频率类

class IPRateThrottle(SimpleRateThrottle):
    scope = "Wayyy"

    def get_cache_key(self, request, view):
        token = request.GET.get("token")
        user_id = request.user.pk
        return f"{self.scope}:{token}:{user_id}"

过滤排序分页全局异常

过滤

针对于查询所有接口----->继承:GenericAPIView+ListModelMixin--->只需要在视图类中写一个类属性----->filter_backends = [过滤类,过滤类2]
	1 内置的:SearchFilter  类属性:search_fields=[可以按字段过滤]
    	127.0.0.1:8080/books/?search=红
    2 第三方
    	-django-filter
        -精准匹配
         127.0.0.1:8080/books/?name=红楼梦
        -能更强大
    3 自定义的
    	写一个类,继承:BaseFilterBackend
        重写 filter_queryset
        在filter_queryset中完成过滤,会把qs传入,返回过滤后的qs即可

源码分析:
    get请求来了------>会触发我list方法的执行,会调用GenericAPIView中的filter_queryset方法,在这个方法中会将filter_backends强转成列表形式(所以我在视图类中也可以直接让filter_backends=过滤类),然后使用for循环遍历这个列表,然后再实例化我这个过滤类,执行里边的filter_queryset方法进行筛选数据,将筛选后的数据返回出去
    
继承APIView写过滤:
	request 取出过滤条件
    book_list=Book.objects.all().filter(过滤)

排序

针对于查询所有接口---》继承:GenericAPIView+ListModelMixin---》只需要在视图类中写一个类属性---》filter_backends = [排序类]

内置排序类即可:OrderingFilter---》配置类属性ordering_fields


自定义排序类,完成自己的规则

分页

针对于查询所有接口-->才有分页

三种分页方式,必须继承分页类,重写几个类属性实现--->配置在视图类上-->继承:GenericAPIView+ListModelMixin

PageNumberPagination:用的最多,之前常见的分页方式,查询第几页,每页有多少条的分页方式 :page=10&size=3
LimitOffsetPagination:从第几条开始,取几条  offset=10&limit=3  从第10条开始,取3条
CursorPagination:只能上一页和下一页,需要排好序再分页


继承APIView 写分页:
class  Pager(APIView):
    def get(self,request,*args,**kwargs):
        # 获取所有数据
        ret=models.Book.objects.all()
        # 创建分页对象
        page=PageNumberPagination()
        # 在数据库中获取分页的数据
        page_list=page.paginate_queryset(ret,request,view=self)
        # 对分页进行序列化
        ser=BookSerializer1(instance=page_list,many=True)
        # return Response(ser.data)
        # 这个也是返回Response对象,但是比基本的多了上一页,下一页,和总数据条数(了解即可)
        return page.get_paginated_response(ser.data)
    	return Response(ser.data) # 只会有数据,不会有上一页和下一页,总条数
    
分页源码分析:
	get请求过来----->执行list方法,先进行过滤,过滤完以后,开始分页,会执行GenericAPIView中的paginate_queryset方法,这个方法又会触发paginator的方法执行,在paginator的方法中会调用pagination_class属性,返回我分页类的一个对象,在paginate_queryset会拿到这个对象,判断是否为None,如果为None返回None,否则就
会调用分页类中的paginate_queryset方法,来完成分页

全局异常处理

前后端分离了,后期,后端出了异常,我们不想让前端看到,我们需要捕获全局异常,统一返回格式

之所以能捕捉到异常是因为在APIView中的dispatch中try了一下,如果出现异常了就会执行APIView中的handle_exception方法,这个方法中需要传入一个我的异常信息,这个方法会调用get_exception_handler方法,get_exception_handler方法就是在配置文件中拿到我配置的捕捉异常的那个函数,如果我没有配置就会用drf中的那个捕捉异常的函数

自己处理全局异常的步骤:
	1 写一个函数,在内部处理
    from rest_framework.views import exception_handler
    def common_exception_handler(exc,context):
        res=exception_handler(exc,context)
        if res: #这次异常是drf的,并且它处理了
            # 我们要统一返回格式
            return Response({'code':888,'msg':"系统异常(drf异常),请联系系统管理员:%s"%res.data.get('detail',None)})
        else:
            return Response({'code': 999, 'msg': "系统异常(非drf异常),请联系系统管理员:%s" % str(exc)})
    2 配置在配置文件上

接口文档

# 接口写完,必须编写接口文档,给别人用

# 接口文档规范:要有什么东西
	1 描述
    2 地址
    3 请求方式
    4 请求编码格式
    5 请求参数,详解
    6 返回示例  json
    7 返回示例中字段详解
# 编写的位置:
	1 直接写在文件中共享(word,md)
    2 平台上写
    	- 公司搭建的平台(自己研发,第三方开源)
        - 使用第三方接口文档平台
    3 自动生成
    	-coreaip

JWT认证

# JWT是什么
	json web tokne缩写,一种前后端认证的方法,区别与session的方案,不需要在后端存储数据,也能实现会话保持
    
# jwt原理
	-三段式:
	-签发阶段
    	-登录:
        	用户名+密码
            手机号+验证码
            本机号码一键登录--》向后端就只传了手机号--》根据手机号签发token
            
    -认证阶段
    	-drf中的认证类
        	-校验token是否合法
            
            
# 别人截获到token后,模拟发送请求
	-1 设置过期时间---》10m
    -2 需要登录后才能访问的接口,不仅要带token还是带个加密串 sign
    -3 双token认证
    	-返回两个token:一个token:7天过期,一个token:10分钟过期
        -以后用就只带10分钟过期的token
        -过了10分钟了,10分钟token失效了
        -携带着7的token到后端,生成一个10分钟的返回
        	-原来的token串使用md5+盐生成一个串--》签名
        
# django中使用jwt--》快速签发
	
# 快速签发定制返回格式
	
    
# 认证:jwt提供的认证 一个认证类,一个权限类


# 自定义用户表
	签发
    	payload = jwt_payload_handler(user)
    	token = jwt_encode_handler(payload)
    认证:认证类
        token = request.META.get('HTTP_TOKEN')
        payload = jwt_decode_handler(token)
        user = User.objects.get(pk=payload.get('user_id'))
    
# 多方式登录(auth的user表)
	-把校验和签发token逻辑写在序列化类的 全局钩子中了
    -在全局钩子中,只要校验不通过,就抛异常

权限控制

# acl:访问控制列表
	用户跟权限多对多
# 基于角色的访问控制 rbac
	用户跟角色关系,角色和权限管理
    -用户表
    -角色表
    -权限表
    -用户和角色多对多中间表
    -角色和权限多对多中间表
    -用户和权限多对多
    
    
# abac:
	基于属性+角色的访问控制
    	
    基于属性+访问控制列表
    	张三:【看视频,发视频】
        张三:晚上12点凌晨7点 不能发视频

作业

# 1 整理回顾的drf内容
# 2 读 权限和认证源码

# 3 读 频率源码

# 4 自己写个频率类,实现 一分钟访问3次,根据ip地址限制



from rest_framework.throttling import BaseThrottle

class MyThrottle(BaseThrottle):
    VISIT_RECORD = {'192.168.1.1':[当前时间,当前时间,访问时间列表]}

    def allow_request(self, request, view):
        # 写限制逻辑
        # (1)取出访问者ip
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        return False
# 频率类写好,配置在视图类上,就会走它

写个频率类,实现 一分钟访问3次,根据ip地址限制

class MyThrottle(BaseThrottle):
    VISIT_RECORD = {}

    def allow_request(self, request, view):
        # 获取到当前访问对象的IP
        user_ip = request.META.get('REMOTE_ADDR')
        # 如果当前IP不在这个字典中
        if user_ip not in self.VISIT_RECORD:
            # 以user_ip为key值,当前时间为value值保存到字典中
            self.VISIT_RECORD[user_ip] = [datetime.datetime.now()]
            # 返回True
            return True
        # 取出当前IP的列表赋给visit_times
        visit_times = self.VISIT_RECORD[user_ip]
        # 定义一个空的列表
        filtered_visit_times = []
        # 循环我IP的那个列表赋给visit_time
        for visit_time in visit_times:
            # 判断当前时间减去我遍历出来的时间是否小于60秒
            if (datetime.datetime.now() - visit_time).seconds <= 60:
                # 如果小于60秒将这个时间追加到filtered_visit_times列表中
                filtered_visit_times.append(visit_time)
        # 如果我filtered_visit_times列表小于3
        if len(filtered_visit_times) < 3:
            # 我在filtered_visit_times第0个位置插入一条当前的时间
            filtered_visit_times.insert(0, datetime.datetime.now())
            # 我再将filtered_visit_times这个列表赋给VISIT_RECORD[user_ip]这个列表
            self.VISIT_RECORD[user_ip] = filtered_visit_times
            return True
        return False