限流组件
限流,限制用户对某个接口访问频率,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。
-
对于匿名用户,使用用户IP作为唯一标识;
-
对于登录用户,使用用户ID或名称作为唯一标识;
- 限流组件本质:循环每一个限流类实例对象,执行allow_request方法来判断是否限流;
使用步骤:
1、编写限流类
from rest_framework.throttling import SimpleRateThrottle from django.core.cache import cache as default_cache MyThrottle(SimpleRateThrottle): cache = default_cache # 访问记录存放在django的缓存中 scope = "user" # 构建缓存中的key # 设置访问频率,例如:1分钟允许访问10次 # 其它:'s','sec','m','min','h','hour','d','day' THROTTLE_RATES = {"user": "5/m"} def get_cache_key(self, request, view): if request.user: ident = request.user.pk # 用户ID else: ident = self.get_ident(request) # 获取用户IP return self.cache_format % {"scope": self.scope, "ident": ident}
2、django-redis配置-->settings.py
# redis配置 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", # 安装redis的主机的 IP 和 端口 "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": { "max_connections": 1000, "encoding": 'utf-8' }, # "PASSWORD": "qwe123" # redis密码 } } }
3、安装django-redid
pip3 install django-redis
4、启动redis服务
应用组件
局部应用(限流类定义在视图中)
views.py
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.throttling import SimpleRateThrottle from rest_framework import exceptions from rest_framework import status from django.core.cache import cache as default_cache from utils.view import MyAPIView from utils.ext.per import ManagerPermission, BossPermission, UserPermission class ThrottledException(exceptions.APIException): status_code = status.HTTP_429_TOO_MANY_REQUESTS default_code = 'throttled' class MyThrottle(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": "5/m"} def get_cache_key(self, request, view): if request.user: ident = request.user.pk # 用户ID else: ident = self.get_ident(request) # 获取用户IP return self.cache_format % {"scope": self.scope, "ident": ident} def throttle_failure(self): # 自定义限流错误提示 wait = self.wait() detail = { "code": 1005, "data": "访问频率", "detail": "需要等待{0}s才能访问".format(int(wait)) } raise ThrottledException(detail) class UserView(APIView): """无需登录接口 http://127.0.0.1:8000/api/auth/user/""" authentication_classes = [] throttle_classes = [MyThrottle,] def get(self,request): return Response("用户信息")
全局应用(限流类定义在自定义throttle.py模块)
utils\ext\throttle.py
from rest_framework.throttling import SimpleRateThrottle from django.core.cache import cache as default_cache class MyThrottle(SimpleRateThrottle): #scope = "XXX" 读取settings.py中全局配置 #THROTTLE_RATES = {"XXX": "5/m"} cache = default_cache def get_cache_key(self, request, view): if request.user: ident = request.user.pk # 用户ID else: ident = self.get_ident(request) # 获取请求用户IP(去request中找请求头) return self.cache_format % {'scope': self.scope, 'ident': ident}
settings.py
REST_FRAMEWORK = { "UNAUTHENTICATED_USER": None, "DEFAULT_AUTHENTICATION_CLASSES": [ "ext.auth.QueryParamsAuthentication", "ext.auth.HeaderAuthentication", "ext.auth.NoAuthentication", ], "DEFAULT_THROTTLE_CLASSES":[ "utils.ext.throttle.MyThrottle", ], "DEFAULT_THROTTLE_RATES":{ "user":"10/m", "xx":"100/h" } }
自定义限流类错误提示
默认超时提示:
{ "detail": "请求超过了限速。 Expected available in 20 seconds." }
throottle.py
#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'tian' __data__ = '2024/3/19 21:20' # software: PyCharm from rest_framework.throttling import SimpleRateThrottle from django.core.cache import cache as default_cache from rest_framework import exceptions from rest_framework import status class ThrottledException(exceptions.APIException): status_code = status.HTTP_429_TOO_MANY_REQUESTS default_code = 'throttled' class MyThrottle(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": "5/m"} def get_cache_key(self, request, view): if request.user: ident = request.user.pk # 用户ID else: ident = self.get_ident(request) # 获取用户IP return self.cache_format % {"scope": self.scope, "ident": ident} def throttle_failure(self): # 自定义限流类错误提示 wait = self.wait() detail = { "code": 1005, "data": "访问频率", "detail": "需要等待{0}s才能访问".format(int(wait)) } raise ThrottledException(detail)
返回自定义错误提示
HTTP 429 Too Many Requests Allow: GET, HEAD, OPTIONS Content-Type: application/json Vary: Accept { "code": "1005", "data": "访问频率", "detail": "需要等待55s才能访问" }
多个限流类执行流程
本质,每个限流的类中都有一个 allow_request
方法,此方法内部可以有三种情况:
-
返回True,表示当前限流类允许访问,继续执行后续的限流类。
-
返回False,表示当前限流类不允许访问,继续执行后续的限流类。所有的限流类执行完毕后,读取所有不允许的限流,并计算还需等待的时间。
-
抛出异常,表示当前限流类不允许访问,后续限流类不再执行。