66. drf之登录_认证_权限_频率组件
1. 登录接口
1.1 登录
新建django项目,app01的models.py
SQLite 的 varchar(max_length) 不强制长度校验,max_length 只是元数据,写入超长字符串直接全部存进去,完全不报错。
SQLite模型写 max_length=32,存 36 位带横杠 uuid 毫无报错。
换成 MySQL,会进行长度限制,直接抛出 Data too long 1406。
# 1.用户表,用来写登录接口
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
age = models.IntegerField()
# 2.用户token表,记录用户的登录情况
class UserToken(models.Model):
# 字符总数:36 个字符(32 位十六进制 + 4 个-分隔符)
token = models.CharField(max_length=64)
user = models.OneToOneField(to=User, on_delete=models.CASCADE)
# 3.图书表
class Book(models.Model):
name = models.CharField(max_length=32)
publish = models.CharField(max_length=32)
price = models.IntegerField()
将模型表迁移至数据库:makemigrations migrate
app01的views.py
"""
登录接口
前端传入的数据类型:{username, password}
请求方式:post
后端视图类继承哪个基类:
需要进行数据库操作,但是不需要序列化输入或反序列化校验
继承APIView,还要自动生成路由---ViewSet
"""
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User, UserToken
import uuid
class LoginView(ViewSet):
@action(methods=['post'], detail=False) # False为操作所有,不需要pk
def login(self, request):
# 1.取出用户名密码
username1 = request.data.get('username')
password1 = request.data.get('password')
# 2.校验,返回结果给前端
user_exist = User.objects.filter(username=username1, password=password1).first()
if user_exist:
# 生成一个随机字符串,字符总数:36 个字符(32 位十六进制 + 4 个-分隔符)
token_str = str(uuid.uuid4())
# 保存到UserToken表 如果之前没有数据,则是新增,如果之前有数据即之前登录过则是修改
# 根据当前用户名去UserToken表中查询token。如果查不到,则新增;如果能查到,则修改
UserToken.objects.update_or_create(user=user_exist, defaults={'token': token_str})
# 返回给前端登录成功
return Response({'code': 100, 'msg': '登录成功', 'token': token_str})
else:
# 返回给前端用户名或密码错误
return Response({'code': 101, 'msg': '用户名或密码错误'})
app01的urls.py
from rest_framework.routers import SimpleRouter
from .views import LoginView
router = SimpleRouter()
router.register(r'login11', LoginView, basename='login11')
urlpatterns = []
urlpatterns += router.urls
总路由
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/app01/', include('app01.urls')),
]
当数据库中没有基础数据时,视图类中会校验失败,返回用户名或密码错误,因此需要先在数据库中添加数据

登录成功,返回token

数据库中会新增对应token

再次登录时token会刷新

登录失败

1.2 图书表book相关接口
序列化组件app01的serializer.py
from .models import Book
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
app01的views.py
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User, UserToken, Book
import uuid
from rest_framework.generics import RetrieveAPIView, CreateAPIView
from .serializer import BookSerializer
# book接口查询一条
class BookDetailView(RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# book接口新增一条
class BookView(CreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
app01的urls.py
from django.urls import path, include
from rest_framework.routers import SimpleRouter
from .views import LoginView, BookView, BookDetailView
router = SimpleRouter()
router.register(r'login11', LoginView, basename='login11')
urlpatterns = [
path('book/', BookView.as_view()),
path('book/<int:pk>/', BookDetailView.as_view()),
]
urlpatterns += router.urls
新增一条


查询一条

2. 认证组件
2.1 需求
如果未登录,可以查询一条,必须登录才能新增
根据APIView源码,在运行视图类函数之前,先运行了认证、权限、限流这三个组件只需要根据以下步骤,就能完成登录的认证:
1.写个类,继承BaseAuthentication
2.在类中重写authenticate
在函数内,取出前端传入的token,校验是否登录
如果token正确,是登录用户,向前端返回值,往下运行
如果token错误或数据库中没有数据,抛异常
3.使用:在视图类配置或配置文件中配置 类似于之前的请求和响应局部配置与全局配置
2.2 认证组件
认证组件需要在一个单独的文件中,定义为app01的auth.py
from rest_framework.authentication import BaseAuthentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
# 1.取出前端传入的token
token = request.META.get('HTTP_TOKEN')
# 2.校验token
user_token = UserToken.objects.filter(token=token).first()
if user_token:
# 返回当前登录用户与token
# 后续在request中通过request.user就会取出返回的第一个参数,即当前登录用户
return user_token.user, token
else:
raise AuthenticationFailed('未登录')
2.3 局部配置认证组件
app01的views.py
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User, UserToken, Book
import uuid
from rest_framework.generics import RetrieveAPIView, CreateAPIView
from .serializer import BookSerializer
from .auth import LoginAuth
# 登录接口
class LoginView(ViewSet):
@action(methods=['post'], detail=False) # False为操作所有,不需要pk
def login(self, request):
# 1.取出用户名密码
username1 = request.data.get('username')
password1 = request.data.get('password')
# 2.校验,返回结果给前端
user_exist = User.objects.filter(username=username1, password=password1).first()
if user_exist:
# 生成一个随机字符串,字符总数:36 个字符(32 位十六进制 + 4 个-分隔符)
token_str = str(uuid.uuid4())
# 保存到UserToken表 如果之前没有数据,则是新增,如果之前有数据即之前登录过则是修改
# 根据当前用户名去UserToken表中查询token。如果查不到,则新增;如果能查到,则修改
UserToken.objects.update_or_create(user=user_exist, defaults={'token': token_str})
# 返回给前端登录成功
return Response({'code': 100, 'msg': '登录成功', 'token': token_str})
else:
# 返回给前端用户名或密码错误
return Response({'code': 101, 'msg': '用户名或密码错误'})
# book接口查询一条
class BookDetailView(RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# book接口新增一条
class BookView(CreateAPIView):
authentication_classes = [LoginAuth] # 只要加了这个配置,以后这个类的所有函数,都需要在登录后才能运行
queryset = Book.objects.all()
serializer_class = BookSerializer
新增一条

给请求头加上已经登录用户的token

新增成功

2.4 全局配置认证组件
[1] 全局使用
rest_framework的源码settings.py中包含配置格式,按照这个格式写到项目的settings.py中
配置文件中全局配置之后对views.py中所有的接口都生效
在配置文件末尾添加
from app01.auth import LoginAuth
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'app01.auth.LoginAuth'
],
}
以上在项目的配置文件导入模块会导致项目启动不了

去掉导入模块,使用app01.auth.LoginAuth即可
[2] 局部禁用
全局配置认证组件之后,认证组件中的所有接口都需要认证
只需在该视图类中将authentication_classes属性设置为空列表即可实现局部禁用。
class UserView(ViewSet):
authentication_classes = []
以上例子中,全局配置认证之后,注册功能也需要登录后才能注册,显然不合理
2.5 补充
一旦通过认证,在request中就有当前登录用户def get(self, request):
print(request.user.name,'访问了接口')
3. 权限组件
3.1 概念
用户登录之后,不同的角色权限不同。 管理员、普通用户
权限设计:
ACL(Access Control List)访问控制列表
RBAC(Role-Based Access Control)基于角色的访问控制
ABAC(Attribute-Based Access Control)基于属性的访问控制
3.2 权限组件使用步骤(固定用法)
[1] 创建权限类
创建一个自定义的权限类,继承BasePermission。
[2] 在类中重写has_permission函数
has_permission函数中完成权限的校验
如果有权限,返回True
如果没有权限,返回False 通过self.message='字符串'设置错误提示信息
如果不使用self.message设置错误提示信息,则使用系统默认的英文提示
[3] 把权限类配置在视图类上,视图类就会受权限类的控制
局部配置:视图类中 permission_classes = [CommonPermission]
全局配置:项目的settings.py中
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
局部禁用:视图类中 permission_classes = [ ]
3.3 代码示例
[1] 需求
用户登录之后,必须是vip才能对图书进行新增
app01的models.py中给用户表加用户角色
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
age = models.IntegerField()
role = models.IntegerField(choices=((1, '普通用户'), (2, '纯路人'), (3, 'vip')))
makemigrations迁移至数据库中,必须设置默认值

在数据库中再添加一条记录

[2] 权限组件
权限组件需要在一个单独的文件中,定义为app01的permission.pyfrom rest_framework.permissions import BasePermission
class VipPerm(BasePermission):
def has_permission(self, request, view):
# 在auth组件中return了当前用户,或通过auth之后在request中就有当前登录用户
# 校验是否为vip,有权限则返回True
if request.user.role == 3:
return True
else:
self.message = f'你不是vip,你是{request.user.get_role_display()}' # 根据键获取对应的值
return False
[3] 使用权限组件
app01的views.py
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User, UserToken, Book
import uuid
from rest_framework.generics import RetrieveAPIView, CreateAPIView
from .serializer import BookSerializer
from .permission import VipPerm
from .auth import LoginAuth
# 登录接口
class LoginView(ViewSet):
authentication_classes = []
@action(methods=['post'], detail=False) # False为操作所有,不需要pk
def login(self, request):
# 1.取出用户名密码
username1 = request.data.get('username')
password1 = request.data.get('password')
# 2.校验,返回结果给前端
user_exist = User.objects.filter(username=username1, password=password1).first()
if user_exist:
# 生成一个随机字符串,字符总数:36 个字符(32 位十六进制 + 4 个-分隔符)
token_str = str(uuid.uuid4())
# 保存到UserToken表 如果之前没有数据,则是新增,如果之前有数据即之前登录过则是修改
# 根据当前用户名去UserToken表中查询token。如果查不到,则新增;如果能查到,则修改
UserToken.objects.update_or_create(user=user_exist, defaults={'token': token_str})
# 返回给前端登录成功
return Response({'code': 100, 'msg': '登录成功', 'token': token_str})
else:
# 返回给前端用户名或密码错误
return Response({'code': 101, 'msg': '用户名或密码错误'})
# book接口查询一条
class BookDetailView(RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# book接口新增一条
class BookView(CreateAPIView):
authentication_classes = [LoginAuth] # 需要先通过登录校验才能进行权限校验
permission_classes = [VipPerm] # 只要加了这个配置,以后这个类的所有函数,都需要在校验权限后才能运行
queryset = Book.objects.all()
serializer_class = BookSerializer
[4] 测试--校验权限
(1) 未登录时新增图书

(2) 登录普通用户新增图书
登录

新增图书先带上token

权限校验失败

错误的token

(3) 登录vip用户新增图书
登录

新增图书先带上token

新增成功

3.4 权限组件练习
普通用户可以操作book的查所有和查一条,vip用户可以操作book的所有接口
登录组件app01的auth.py
from rest_framework.authentication import BaseAuthentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
# 1.取出前端传入的token
token = request.META.get('HTTP_TOKEN')
# 2.校验token
user_token = UserToken.objects.filter(token=token).first()
if user_token:
# 返回当前登录用户与token
# 后续在request中通过request.user就会取出返回的第一个参数,即当前登录用户
return user_token.user, token
else:
raise AuthenticationFailed('未登录')
app01的models.py
from django.db import models
# Create your models here.
# 1.用户表,用来写登录接口
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
age = models.IntegerField()
role = models.IntegerField(choices=((1, '普通用户'), (2, '纯路人'), (3, 'vip')))
# 2.用户token表,记录用户的登录情况
class UserToken(models.Model):
# 字符总数:36 个字符(32 位十六进制 + 4 个-分隔符)
token = models.CharField(max_length=64)
user = models.OneToOneField(to=User, on_delete=models.CASCADE)
# 3.图书表
class Book(models.Model):
name = models.CharField(max_length=32)
publish = models.CharField(max_length=32)
price = models.IntegerField()
权限组件app01的permission.py
from rest_framework.permissions import BasePermission
class RolePerm(BasePermission):
def has_permission(self, request, view):
# 在auth组件中return了当前用户,或通过auth之后在request中就有当前登录用户
# 校验普通用户有对应的权限则返回True,是vip用户也返回True
if request.user.role == 1: # 普通用户
if view.action in ['list', 'retrieve']: # 操作是查询所有和查询一条
return True
else:
return False
elif request.user.role == 3: # vip用户,所有权限都返回True
return True
序列化组件app01的serializer.py
from .models import Book
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
app01的views.py
"""
登录接口
前端传入的数据类型:{username, password}
请求方式:post
后端视图类继承哪个基类:
需要进行数据库操作,但是不需要序列化输入或反序列化校验
继承APIView,还要自动生成路由---ViewSet
"""
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User, UserToken, Book
import uuid
from .serializer import BookSerializer
from .models import Book
from .permission import RolePerm
from .auth import LoginAuth
from rest_framework.viewsets import ModelViewSet
# 登录接口
class LoginView(ViewSet):
authentication_classes = []
@action(methods=['post'], detail=False) # False为操作所有,不需要pk
def login(self, request):
# 1.取出用户名密码
username1 = request.data.get('username')
password1 = request.data.get('password')
# 2.校验,返回结果给前端
user_exist = User.objects.filter(username=username1, password=password1).first()
if user_exist:
# 生成一个随机字符串,字符总数:36 个字符(32 位十六进制 + 4 个-分隔符)
token_str = str(uuid.uuid4())
# 保存到UserToken表 如果之前没有数据,则是新增,如果之前有数据即之前登录过则是修改
# 根据当前用户名去UserToken表中查询token。如果查不到,则新增;如果能查到,则修改
UserToken.objects.update_or_create(user=user_exist, defaults={'token': token_str})
# 返回给前端登录成功
return Response({'code': 100, 'msg': '登录成功', 'token': token_str})
else:
# 返回给前端用户名或密码错误
return Response({'code': 101, 'msg': '用户名或密码错误'})
# 查询、新增所有接口使用ModelViewSet
class BookView(ModelViewSet):
authentication_classes = [LoginAuth] # 使用登录组件
permission_classes = [RolePerm] # 使用权限组件
queryset = Book.objects.all()
serializer_class = BookSerializer
app01的urls.py
from rest_framework.routers import SimpleRouter
from .views import LoginView, BookView
router = SimpleRouter()
router.register(r'login11', LoginView, basename='login11')
router.register(r'books', BookView, basename='books')
urlpatterns = []
urlpatterns += router.urls
未登录不能操作接口


使用普通用户登录

普通用户可以查询一条

普通用户可以查询所有

普通用户无法新增图书


登录VIP用户

vip用户可以新增图书

vip用户也可以查询一条和查询所有
vip用户删除id为5的图书


4. 频率组件
4.1 概念
限制访问频率比如某个接口,一分钟只能访问5次,超过了就需要等待
按IP地址限制
按用户id限制
4.2 频率组件的使用步骤(固定用法)
[1] 创建频率类
创建一个自定义的频率类,继承SimpleRateThrottle(大部分场景)或BaseThrottle(基础场景)
这个类用于限制对某些操作或资源的访问频率
[2] 在类中重写get_cache_key函数
这个函数决定了如何从请求中获取缓存键值,以便在缓存中存储和检索频次信息。
以下信息作为缓存键值的组成部分:
用户身份:可以使用用户的唯一标识符或者请求中的某些认证信息。
资源标识:如果需要对不同的资源进行频次限制,可以加入资源标识。
操作类型:如果需要对不同的操作类型进行频次限制,可以加入操作类型。
根据具体情况,将这些信息组合起来构成一个唯一的缓存键值,并在get_cache_key函数中返回。
总结:函数返回什么,就以什么做频率限制
[3] 自定义频率类中写一个类属性,名称自定义
在频次类中,添加一个自定义名称的类属性。
这个属性用于在缓存中存储频率信息。
class IpThrottle(SimpleRateThrottle):
rate = '3/m' # s秒 m分钟 h小时 d天
def get_cache_key(self, request, view):
# 返回客户端ip,即以ip做限制
return request.META.get('REMOTE_ADDR')
在使用时,通过IpThrottle.rate来访问这个自定义属性。
[4] 使用频率类
局部配置:视图类中
class BookView(APIView):
throttle_classes = [SimpleRate_Throttle]
全局配置:项目的settings.py中
'DEFAULT_THROTTLE_CLASSES': [
'app01.my_throttling.SimpleRate_Throttle'
],
局部禁用:视图类中
throttle_classes = [ ]
4.3 SimpleRateThrottle 和 BaseThrottle
BaseThrottle:最底层抽象基类
SimpleRateThrottle:继承自 BaseThrottle,已经封装好了通用频率、缓存、时间戳逻辑,大部分场景使用这个。
BaseThrottle(ABC)
└── SimpleRateThrottle(BaseThrottle)
├─ AnonRateThrottle
└─ UserRateThrottle
1. BaseThrottle(基础抽象类)
只定义接口,完全没有实现任何限流算法、缓存、频率解析。
必须实现的方法
from rest_framework.throttling import BaseThrottle
class MyThrottle(BaseThrottle):
def allow_request(self, request, view):
"""必须实现:返回True放行 / False拦截"""
pass
def wait(self):
"""可选:返回还需要等待多少秒,给429响应用;返回None表示不提供等待时间"""
pass
什么时候用 BaseThrottle
需要完全自定义限流逻辑,不走"次数 / 时间窗口"这套模型。
举例:
根据数据库表里剩余调用额度限流;
根据黑名单直接拦截;
调用第三方接口判断是否放行;
不使用 Django Cache 存储计数。
缺点:
自己要写全部:计数、存储、时间窗口、过期、解析速率字符串;
重复造工具,普通频率限流不要直接继承它。
2. SimpleRateThrottle(简单速率节流,项目最常用)
已经实现好了:
1. 解析速率配置 scope→ 从 settings 读取 5/min这种字符串
2. 使用 Django Cache 存储访问时间戳列表
3. 实现滑动时间窗口算法
4. 实现allow_request()、wait()
只需手动编写两个地方:
1. 设置 scope(对应 settings 里面的速率 key)
2. 重写 get_cache_key(request, view):返回用来做限流的唯一标识(IP /user_id/ 手机号)
from rest_framework.throttling import SimpleRateThrottle
class SmsThrottle(SimpleRateThrottle):
scope = "sms" # 对应 DEFAULT_THROTTLE_RATES["sms"]
def get_cache_key(self, request, view):
phone = request.data.get("phone")
if not phone:
return None # 返回None,代表不限流
return self.cache_format % {"ident": phone}
get_cache_key()只负责产出 key;判断频率、过期清理缓存全部由基类处理。
适合场景:
绝大多数基于「某一个标识 + 单位时间最多 N 次」的限流:
IP 限流
用户 ID 限流
手机号、设备号限流
局限:
强依赖 Django Cache;
固定是滑动窗口模型;
如果限流规则不是 “X 次 / Y 时间”,就不适合。
| 对比项 | BaseThrottle | SimpleRateThrottle |
|---|---|---|
| 层级 | 顶层抽象基类 | 继承 BaseThrottle,封装通用速率逻辑 |
| 是否实现 allow_request | ❌ 需要自己全部写 | ✅已实现,不用重写 |
速率解析(5/min) |
❌ 无,自己解析 | ✅通过scope自动读取 settings 配置 |
| 缓存存储计数 | ❌ 无,自己选择存储介质 | ✅默认使用 Django Cache(Redis / 内存) |
| 滑动窗口算法 | ❌ 自己实现 | ✅内置滑动窗口 |
| 需要重写什么 | allow_request(),可选wait() |
只重写 get_cache_key() + 设置 scope |
| 适用场景 | 完全自定义限流逻辑,非标准速率模型 | 标准的「某个标识单位时间 N 次」限流 |
| DRF 内置子类 | 无 | AnonRateThrottle、UserRateThrottle |
关键源码小细节(帮助理解)
1. SimpleRateThrottle.allow_request()内部做的事情:
(1). 通过scope拿到 rate 速率配置;
(2). 调用get_cache_key()拿到缓存 key;
(3). cache 取出该 key 的时间戳列表;
(4). 剔除窗口外过期时间;
(5). 判断当前数量是否超过最大次数;
(6). 没超限:把当前时间戳写回 cache;超限返回 False。
2. 返回 get_cache_key = None:SimpleRateThrottle会直接返回 True,跳过限流。
怎么选(开发准则)
1. 只要是:按某个标识,限制单位时间访问次数 → 优先继承 SimpleRateThrottle(99% 业务接口、短信接口)
2. 如果:
不从 cache 读计数,从 MySQL 读额度;
不是 “X 次每 Y 时间” 模型;
需要接入外部风控服务;
直接继承 BaseThrottle,自己完整实现 allow_request()。
举一个 BaseThrottle 极简示例(数据库额度限流)
from rest_framework.throttling import BaseThrottle
class QuotaDBThrottle(BaseThrottle):
def allow_request(self, request, view):
# 从数据库读取用户剩余调用次数
user = request.user
if not user.is_authenticated:
return True
quota = UserQuota.objects.filter(user=user).first()
if quota.remain_count <= 0:
return False
quota.remain_count -=1
quota.save()
return True
def wait(self):
return None
这种不走时间窗口,走数据库剩余额度,就不适合 SimpleRateThrottle。
4.4 频率组件中类的属性名介绍
| 名称 | 类别 | 是否允许子类修改 | 默认值 | 说明 & 注意事项 |
|---|---|---|---|---|
scope |
类属性 | ✅ 可改值,属性名不能改 | None |
作为 key,从 DRF 配置REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']读取速率;
rate不为 None,scope 直接失效;
ImproperlyConfigured |
rate |
类属性 | ✅ 可改值,属性名不能改 | None |
硬编码限流速率,格式N/second|minute|hour|day;
|
cache_format |
类属性 | ✅ 可改写 | throttle_%(ident)s |
缓存 key 模板;%(ident)s会被get_cache_key()返回值替换;
|
| 模式 | 子类写法 | 速率来源 |
|---|---|---|
| 模式 1(生产推荐) | 只写scope |
读取 settings 中DEFAULT_THROTTLE_RATES对应 key |
| 模式 2(测试快速) | 只写rate |
硬编码写成固定,不走 settings 配置 |
| 错误模式 | scope与rate同时写 |
rate生效,scope完全无效 |
频率组件:
from rest_framework.throttling import SimpleRateThrottle
class SmsThrottle(SimpleRateThrottle):
scope = "sms"
cache_format = "demo:throttle:%(ident)s"
def get_cache_key(self, request, view):
phone = request.data.get("phone")
if not phone:
return None
return self.cache_format % {"ident": phone}
项目的settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
"sms": "1/min"
}
}
4.5 代码示例
[1] 需求
按ip地址限制,同一个ip地址,1分钟只能请求3次
[2] 频率组件
频率组件需要在一个单独的文件中,定义为app01的throttle.py
from rest_framework.throttling import SimpleRateThrottle
# 1分钟只能访问3次
class IpThrottle(SimpleRateThrottle):
rate = '3/m' # s秒 m分钟 h小时 d天
def get_cache_key(self, request, view):
# 返回客户端ip,即以ip做限制
return request.META.get('REMOTE_ADDR')
拓展:假如返回id
#返回用户id--必须先登录
return request.user.id
[3] 使用频率组件
app01的views.py
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User, UserToken, Book
import uuid
from rest_framework.generics import RetrieveAPIView, CreateAPIView
from .serializer import BookSerializer
from .permission import VipPerm
from .auth import LoginAuth
from .throttle import IpThrottle
# 登录接口
class LoginView(ViewSet):
authentication_classes = []
@action(methods=['post'], detail=False) # False为操作所有,不需要pk
def login(self, request):
# 1.取出用户名密码
username1 = request.data.get('username')
password1 = request.data.get('password')
# 2.校验,返回结果给前端
user_exist = User.objects.filter(username=username1, password=password1).first()
if user_exist:
# 生成一个随机字符串,字符总数:36 个字符(32 位十六进制 + 4 个-分隔符)
token_str = str(uuid.uuid4())
# 保存到UserToken表 如果之前没有数据,则是新增,如果之前有数据即之前登录过则是修改
# 根据当前用户名去UserToken表中查询token。如果查不到,则新增;如果能查到,则修改
UserToken.objects.update_or_create(user=user_exist, defaults={'token': token_str})
# 返回给前端登录成功
return Response({'code': 100, 'msg': '登录成功', 'token': token_str})
else:
# 返回给前端用户名或密码错误
return Response({'code': 101, 'msg': '用户名或密码错误'})
# book接口查询一条
class BookDetailView(RetrieveAPIView):
throttle_classes = [IpThrottle] # 只要加了这个配置,以后这个类的所有函数,都需要在校验频率后才能运行
queryset = Book.objects.all()
serializer_class = BookSerializer
# book接口新增一条
class BookView(CreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
[4] 测试--校验频率
由于频率组件加在查询接口,1分钟内最多只能查询3次,

超过次数将受到限制

全局配置频率组件:
如果在项目的settings.py中全局配置频率组件,这个项目的所有接口都受到频率限制,登录、查询、新增接口任意组合1分钟只能访问3次
4.6 练习
继承BaseThrottle 写频率限制
自定义的逻辑:
(1) 取出访问者的ip
(2) 判断当前ip如果不在访问字典里,添加进去,并且返回True,表示第一次访问;如果在字典里,继续往下运行
(3) 循环判断当前ip的列表,如果有值,当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间
(4) 判断,当列表小于3,说明1分钟以内访问不足3次,把当前时间插入到列表第一个位置,返回True,顺利通过
(5) 当大于等于3,说明1分钟内访问超过3次,返回False验证失败
频率组件app01的throttle.py
from rest_framework.throttling import BaseThrottle import time class IpThrottle(BaseThrottle): # 这个字典用来存储不同ip地址访问的时间{ip1:[时间1], ip2:[时间1, 时间2]} VISIT_RECORD = {} def __init__(self): self.history = None # 存储某个用户的访问时间列表,即用来存储以上字典中的某一个键对应的值 def allow_request(self, request, view): # 继承BaseThrottle必须重写这个函数 # 如果没超频率,返回True # 如果超了频率,返回False # (1) 取出访问者的ip ip = request.META.get('REMOTE_ADDR') # (2)判断当前ip如果不在访问字典里,添加进去,并且返回True,表示第一次访问;如果在字典里,继续往下运行 ctime = time.time() if ip not in self.VISIT_RECORD: # 不在字典中,表示第一次访问 self.VISIT_RECORD[ip] = [ctime] return True # (3)循环判断当前ip的列表,如果有值,当前时间减去列表的最后一个时间,如果大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间 self.history = self.VISIT_RECORD.get(ip) while self.history and ctime - self.history[-1] > 60: # 只要大于60s循环pop self.history.pop() # (4)判断,当列表小于3,说明1分钟以内访问不足3次,把当前时间插入到列表第一个位置,返回True,顺利通过 # (5)当大于等于3,说明1分钟内访问超过3次,返回False验证失败 if len(self.history) < 3: self.history.insert(0, ctime) return True else: return False def wait(self): # 必须要重写wait函数 ctime = time.time() # 返回用户访问还需要等待的时间 return 60 - (ctime - self.history[-1])
app01的views.py
from .serializer import BookSerializer
from .models import Book
from rest_framework.generics import ListAPIView
from .throttle import IpThrottle
# 查询所有
class BookView(ListAPIView):
throttle_classes = [IpThrottle] # 使用频率组件
queryset = Book.objects.all()
serializer_class = BookSerializer
app01的urls.py
from django.urls import path
from .views import BookView
urlpatterns = [
path('books/', BookView.as_view()),
]
查询所有

1分钟内访问超过3次之后


浙公网安备 33010602011771号