DRF限流组件(源码分析)
限流,限制用户访问频率,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。
- 对于匿名用户,使用用户IP作为唯一标识。
- 对于登录用户,使用用户ID或名称作为唯一标识。
缓存={ 用户标识:[12:33,12:32,12:31,12:30,12,] 1小时/5次 12:34 11:34 {
1. 配置缓存
# 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", } } }
2. 自定义限流类
# -*- encoding:utf-8 -*- # @time: 2023/4/21 15:41 # @author: ifeng from django.core.cache import cache as default_cache from rest_framework import exceptions from rest_framework import status from rest_framework.throttling import SimpleRateThrottle 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' # 设置其他访问评率, 例如: 一分钟允许访问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.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 s才能访问' % (int(wait)) } raise ThrottledException(detail)
3. 使用限流类
- 局部配置(views)
class UserView(APIView): throttle_classes = [MyRateThrottle, ] # 限流
- 全局配置(settings)
REST_FRAMEWORK = { # 限流 "DEFAULT_THROTTLE_CLASSES": ["app01.throttle.MyRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "user": "10/m", # "xx":"100/h" } }
4. 多个限流类
本质,每个限流的类中都有一个 allow_request 方法,此方法内部可以有三种情况:
- 返回True,表示当前限流类允许访问,继续执行后续的限流类。
- 返回False,表示当前限流类不允许访问,继续执行后续的限流类。所有的限流类执行完毕后,读取所有不允许的限流,并计算还需等待的时间。
- 抛出异常,表示当前限流类不允许访问,后续限流类不再执行。
5. 源码分析
- 这是限流大体的执行逻辑, 后面将对allow_reqeust中具体分析

- allow_request()在自定义的类里面没定义, 所以我们到父类SimpleRateThrottle执行allow_request()方法
