限流
限流,限制用户访问频率,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。
对于匿名用户,使用用户IP作为唯一标识。
对于登录用户,使用用户ID或名称作为唯一标识。
原理:
内部处理机制是,最后的一个时间和前面的用户标识时间做比较,就如下面的一样,1小时只能访问5次。所以我们把现在的时间12:34减去一小时,就是11:34。然后11:34与用户标识比较,小于该11:34的就去掉用户标识里面的。
缓存={
用户标识:[12:33,12:32,12:31,12:30,12,] 1小时/5次 12:34 11:34
{
pip3 install django-redis
# settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": "qwe123",
}
}
}
#urls.py
from django.urls import path, re_path
from app01 import views
urlpatterns = [
path('api/order/', views.OrderView.as_view()),
]
views.py文件里面存在MyRateThrottle自定义类继承SimpleRateThrottle类,MyRateThrottle中get_cache_key方法是返回用户的唯一标识。用户如果登录就获取用户的id,用户没有登录就获取用户的ip地址。
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import exceptions
from rest_framework import status
from rest_framework.throttling import SimpleRateThrottle
from django.core.cache import cache as default_cache
class ThrottledException(exceptions.APIException):
status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_code = 'throttled'
class MyRateThrottle(SimpleRateThrottle):
cache = default_cache # 访问记录存放在django的缓存中(需设置缓存)
scope = "user" # 构造缓存中的key
cache_format = 'throttle_%(scope)s_%(ident)s'
# 设置访问频率,例如:1分钟允许访问10次
# 其他:'s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day'
THROTTLE_RATES = {"user": "10/m"}
def get_cache_key(self, request, view):
if request.user:
ident = request.user.pk # 用户ID
else:
ident = self.get_ident(request) # 获取请求用户IP(去request中找请求头)
# throttle_u # throttle_user_11.11.11.11ser_2
return self.cache_format % {'scope': self.scope, 'ident': ident}
def throttle_failure(self):
wait = self.wait()
detail = {
"code": 1005,
"data": "访问频率限制",
'detail': "需等待{}s才能访问".format(int(wait))
}
raise ThrottledException(detail)
class OrderView(APIView):
throttle_classes = [MyRateThrottle, ]
def get(self, request):
return Response({"code": 0, "data": "数据..."})
scope = "user" # 构造缓存中的key
cache_format = 'throttle_%(scope)s_%(ident)s'
THROTTLE_RATES = {"user": "10/m"}
上面设置的scope = "user"和下面的THROTTLE_RATES里面的user是相对应的,内部会去读取它
{"user": "10/m"} 10代表十次,m代表分钟。
cache = default_cache是缓存的意思,因为内部保存是保存到缓存中,所以我们要导入:
from django.core.cache import cache as default_cache
如果用户超过了访问的频次,我们是不是应该要给用户提示?
def throttle_failure(self):
wait = self.wait()
detail = {
"code": 1005,
"data": "访问频率限制",
'detail': "需等待{}s才能访问".format(int(wait))
}
raise ThrottledException(detail)
那么throttle_failure这个方法就是给用户做提示的作用。
然后我们还定义了detail这个字典,用来抛出异常。
class ThrottledException(exceptions.APIException):
status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_code = 'throttled'
然后这个类是用来抛出状态码的,里面的两个其实不重要,我们可以直接pass。throttle_failure里面的才是重要的。
最后怎么放到视图函数里面,也是和之前的套路一样,
class OrderView(APIView):
throttle_classes = [MyRateThrottle, ]
def get(self, request):
return Response({"code": 0, "data": "数据..."})
多个限流类
本质,每个限流的类中都有一个 allow_request
方法,此方法内部可以有三种情况:
也就是在MyRateThrottle(SimpleRateThrottle),括号里面这个类内部有allow_request
方法,它返回值要么是True,要么是False。
1.返回True,表示当前限流类允许访问,继续执行后续的限流类。
2.返回False,表示当前限流类不允许访问,继续执行后续的限流类。所有的限流类执行完毕后,读取所有不允许的限流,并计算还需等待的时间。
3.抛出异常,表示当前限流类不允许访问,后续限流类不再执行。
意思就是,如果是true的话就会继续往下读取限流类,如果是false的话,也是会继续限流类,但要设置另外一种throttle_failure方法了,不可能总是返回fasle.
def throttle_failure(self,request,wait):
detail = {
"code": 1005,
"data": "访问频率限制",
'detail': "需等待{}s才能访问".format(int(wait))
}
raise ThrottledException(detail)
但是最好使用抛出异常的方式。
全局配置
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES":["xxx.xxx.xx.限流类", ],
"DEFAULT_THROTTLE_RATES": {
"user": "10/m",
"xx":"100/h"
}
}
自定义跟权限和认证一样的,这里不多解释。
Serializer(*)
drf中为我们提供了Serializer,他主要有两大功能:
1.对请求数据校验(底层调用Django的Form和ModelForm)
2.对数据库查询到的对象进行序列化
数据校验
示例1:基于Serializer:
import re
from distutils.command.install import value
from django.core.validators import EmailValidator
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers, viewsets
from rest_framework import exceptions
from app1 import models
class RegexValidator(object):
def __init__(self,base):
self.base = str(base)
def __call__(self, value):
match_object = re.match(self.base,value)
if not match_object:\
raise serializers.ValidationError("格式错误")
class UserSerializer(serializers.ModelSerializer):
username = serializers.CharField(label="姓名",min_length=6,max_length=32)
age = serializers.IntegerField(label="年龄",min_value=0,max_value=200)
level = serializers.ChoiceField(label="级别",min_val=0,max_value=200)
email = serializers.CharField(label="邮箱",validators=[EmailValidator,])
email1 = serializers.EmailField(label="邮箱1")
email2 = serializers.EmailField(label="邮箱2",validators=[RegexValidator(r"^\w+@\w+\.\w+$")])
email3 = serializers.EmailField(label="邮箱3")
def validate(self, attrs):
if re.match(r"^\w+@\w+\.\w+$",value):
return value
raise exceptions.ValidationError("邮箱格式错误")
class UserViewSet(APIView):
def post(self,request):
ser = UserSerializer(data=request.data,)
if not ser.is_valid():
return Response({"code":1006,"data":ser.errors})
print(ser.validated_data)
return Response({"code":0,"data":"创建成功"})
示例2:基于ModelSerializer:
# models.py
from django.db import models
class Role(models.Model):
""" 角色表 """
title = models.CharField(verbose_name="名称", max_length=32)
class Department(models.Model):
""" 部门表 """
title = models.CharField(verbose_name="名称", max_length=32)
class UserInfo(models.Model):
""" 用户表 """
level_choices = ((1, "普通会员"), (2, "VIP"), (3, "SVIP"),)
level = models.IntegerField(verbose_name="级别", choices=level_choices, default=1)
username = models.CharField(verbose_name="用户名", max_length=32)
password = models.CharField(verbose_name="密码", max_length=64)
age = models.IntegerField(verbose_name="年龄", default=0)
email = models.CharField(verbose_name="邮箱", max_length=64)
token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)
# 外键
depart = models.ForeignKey(verbose_name="部门", to="Department", on_delete=models.CASCADE)
# 多对多
roles = models.ManyToManyField(verbose_name="角色", to="Role")
# models.py
from django.db import models
class Role(models.Model):
""" 角色表 """
title = models.CharField(verbose_name="名称", max_length=32)
class Department(models.Model):
""" 部门表 """
title = models.CharField(verbose_name="名称", max_length=32)
class UserInfo(models.Model):
""" 用户表 """
level_choices = ((1, "普通会员"), (2, "VIP"), (3, "SVIP"),)
level = models.IntegerField(verbose_name="级别", choices=level_choices, default=1)
username = models.CharField(verbose_name="用户名", max_length=32)
password = models.CharField(verbose_name="密码", max_length=64)
age = models.IntegerField(verbose_name="年龄", default=0)
email = models.CharField(verbose_name="邮箱", max_length=64)
token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)
# 外键
depart = models.ForeignKey(verbose_name="部门", to="Department", on_delete=models.CASCADE)
# 多对多
roles = models.ManyToManyField(verbose_name="角色", to="Role")
提示:save方法会返回新生成的数据对象。
示例3:基于ModelSerializer(含FK+M2M):
里面的depart和roles字段得使用json格式传递,使用data传递是不能的。
序列化
通过ORM从数据库获取到的 QuerySet 或 对象 均可以被序列化为 json 格式数据。
示例1:序列化基本字段
注释里面"""添加用户改为获取用户"""
里面的queryset是获取所有对象,所以是many=True。如果是后面加多个filter的话是单个对象,many就得风雨False
示例2:自定义字段
source='get_xxx'实际上是获取对象.get.level_display这个字段。
source = "depart.title "对象.depart.title跨表到部门表拿值,
示例3:序列化类的嵌套
视图
APIView
View,django
1.APIView,drf,在请求到来时,新增了:免除csrf、请求封装、版本、认证、权限、限流的功能。
class GenericAPIView(APIView):
pass # 10功能
class GenericViewSet(xxxx.View-2个功能, GenericAPIView):
pass # 5功能能
class UserView(GenericViewSet):
def get(self,request):
pass
APIView
是drf中 “顶层” 的视图类,在他的内部主要实现drf基础的组件的使用,例如:版本、认证、权限、限流等。
# urls.py
from django.urls import path, re_path, include
from app01 import views
urlpatterns = [
path('api/users/', views.UserView.as_view()),
path('api/users/<int:pk>/', views.UserDetailView.as_view()),
]
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
# 认证、权限、限流等
def get(self, request):
# 业务逻辑:查看列表
return Response({"code": 0, 'data': "..."})
def post(self, request):
# 业务逻辑:新建
return Response({'code': 0, 'data': "..."})
class UserDetailView(APIView):
# 认证、权限、限流等
def get(self, request,pk):
# 业务逻辑:查看某个数据的详细
return Response({"code": 0, 'data': "..."})
def put(self, request,pk):
# 业务逻辑:全部修改
return Response({'code': 0, 'data': "..."})
def patch(self, request,pk):
# 业务逻辑:局部修改
return Response({'code': 0, 'data': "..."})
def delete(self, request,pk):
# 业务逻辑:删除
return Response({'code': 0, 'data': "..."})
GenericAPIView
GenericAPIView
继承APIView,在APIView的基础上又增加了一些功能。例如:get_queryset
、get_object
等。
实际在开发中一般不会直接继承它,他更多的是担任 中间人
的角色,为子类提供公共功能。
# urls.py
from django.urls import path, re_path, include
from app01 import views
urlpatterns = [
path('api/users/', views.UserView.as_view()),
path('api/users/<int:pk>/', views.UserDetailView.as_view()),
]
# views.py
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
class UserView(GenericAPIView):
queryset = models.UserInfo.objects.filter(status=True)
serializer_class = 序列化类
def get(self, request):
queryset = self.get_queryset()
ser = self.get_serializer(intance=queryset,many=True)
print(ser.data)
return Response({"code": 0, 'data': "..."})
注意:最大的意义,将数据库查询、序列化类提取到类变量中,后期再提供公共的get/post/put/delete等方法,让开发者只定义类变量,自动实现增删改查。
GenericViewSet
GenericViewSet
类中没有定义任何代码,他就是继承 ViewSetMixin
和 GenericAPIView
,也就说他的功能就是将继承的两个类的功能继承到一起。
GenericAPIView
,将数据库查询、序列化类的定义提取到类变量中,便于后期处理。
ViewSetMixin
,将 get/post/put/delete 等方法映射到 list、create、retrieve、update、partial_update、destroy方法中,让视图不再需要两个类。
# urls.py
from django.urls import path, re_path, include
from app01 import views
urlpatterns = [
path('api/users/', views.UserView.as_view({"get":"list","post":"create"})),
path('api/users/<int:pk>/', views.UserView.as_view({"get":"retrieve","put":"update","patch":"partial_update","delete":"destory"})),
]
# views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response
class UserView(GenericViewSet):
# 认证、权限、限流等
queryset = models.UserInfo.objects.filter(status=True)
serializer_class = 序列化类
def list(self, request):
# 业务逻辑:查看列表
queryset = self.get_queryset()
ser = self.get_serializer(intance=queryset,many=True)
print(ser.data)
return Response({"code": 0, 'data': "..."})
def create(self, request):
# 业务逻辑:新建
return Response({'code': 0, 'data': "..."})
def retrieve(self, request,pk):
# 业务逻辑:查看某个数据的详细
return Response({"code": 0, 'data': "..."})
def update(self, request,pk):
# 业务逻辑:全部修改
return Response({'code': 0, 'data': "..."})
def partial_update(self, request,pk):
# 业务逻辑:局部修改
return Response({'code': 0, 'data': "..."})
def destory(self, request,pk):
# 业务逻辑:删除
return Response({'code': 0, 'data': "..."})
注意:开发中一般也很少直接去继承他,因为他也属于是 中间人
类,在原来 GenericAPIView
基础上又增加了一个映射而已。