认证组件
目录
认证组件
# 访问某个接口:需要登录后才能访问
# 第一步:先写个登录功能,用户表
-User表
-UserToken表:存储用户登录状态 [这个表可以没有,如果没有,把字段直接写在User表上也可以]
登录接口
#### 表模型
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
class UserToken(models.Model): # 跟User是一对一
token = models.CharField(max_length=32)
user = models.OneToOneField(to='User', on_delete=models.CASCADE, null=True)
# user :反向,表名小写,所有有user字段
### 路由
router.register('user', views.UserView, 'user') # /api/v1/user/login post 请求
# 视图类
#### 登录接口 自动生成路由+由于登录功能,不用序列化,继承ViewSet
from .models import User, UserToken
import uuid
class UserView(ViewSet):
@action(methods=['POST'], detail=False)
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
if user:
# 用户存在,登录成功
# 生成一个随机字符串--uuid
token = str(uuid.uuid4()) # 生成一个永不重复的随机字符串
# 在userToken表中存储一下:1 从来没有登录过,插入一条, 2 登录过,修改记录
# 如果有就修改,如果没有就新增 (if 自己写)
# kwargs 传入的东西查找,能找到,使用defaults的更新,否则新增一条
UserToken.objects.update_or_create(user=user, defaults={'token': token})
return Response({'code': '100', 'msg': '登录成功', 'token': token})
else:
return Response({'code': '101', 'msg': '用户名或密码错误'})
``python
以后,有的接口需要登录才能访问,有的接口,不登录就能访问
-登录认证的限制
写一个登录接口,返回token,以后只要带着token过来,就是登录了,不带就是没有登录
需要实现的功能:
查询所有不需要登录就能访问
查询单个,需要登录才能访问
#### 认证组件使用步骤
```python
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
1.写一个认证类,继承BaseAuthentication
2.重写authenticate方法,在该方法中实现了登录认证,token携带的请求头中
3.如果认证成功,返回两个值【返回None或两个值[登录用户和token]】
4.认证不通过,抛异常AuthenticationFailed
5.局部使用和全局使用
-局部,只在视图类中使用【当前视图类管理的所有接口】
class BookDetailView(ViewSetMixin, RetrieveAPIView):
authentication_classes = [LoginAuth]
-全局:全局所有接口都生效(登录接口不要)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':['app01.authenticate.LoginAuth']
}
-局部禁用:
class BookDetailView(ViewSetMixin, RetrieveAPIView):
authentication_classes = [] # 里面不写认证类就是局部禁用
代码展示:
视图层:
# 查询所有
class BooksView(GenericViewSet, ListModelMixin):
# 使用GeneircViewSet是因为可以自动生成路由,ListModelMixin是因为有list方法
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = []
# 查询单个
class BookView(GenericViewSet, RetrieveModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [LoginAuthentication] # # 需要写一个认证类,需要咱们写
### 认证类代码
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
# 在这里实现认证,如果是登录的,继续往后走返回两个值,如果不是抛异常
# 请求中是否携带token,判断是否登录,放在地址栏中
token = request.query_params.get('token', None)
if token: # 前端传入token了,去表中查,如果能查到,登录了,返回两个值[固定的:当前登录用户,token]
user_token = UserToken.objects.filter(token=token).first()
if user_token:
return user_token.user, token
else:
# 没有登录抛异常
raise AuthenticationFailed('token认证失败')
else:
raise AuthenticationFailed('token没传')
### 路由代码
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user', views.UserView, 'user')
router.register('books', views.BooksView, 'books')
router.register('book', views.BookView, 'book')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include(router.urls)),
]
权限组件
# 即便登录成功了,有些接口,还是不能访问,因为没有权限
登录后,有的接口有权限访问,有的没有权限访问
查询单个和查询所有,都要登录才能访问---》全局认证
-查询单个需要超级管理员才能访问
-查询所有,所有登录用户都能访问
注意:权限是一个字段,在user表中,加入user_type字段
权限的使用
1.写一个权限类,继承BasePermission
2.重写has_permission方法,在该方法中实现权限认证,在这方法中,request.user就是当前登录用户
3.如果有权限,返回True
4.没有权限,返回False,定制返回的中文:self.message='中文'
5.局部使用和全局使用
-局部:只在某个视图类中使用【当前视图类管理的所有接口】
class BookView(GenericViewSet, RetrieveModelMixin):
queryset = Book.objects.all() # 指定模型表数据
serializer_class = BookSerializer # 执行序列化类
authentication_classes = [LoginAuthentication] # 执行认证类
permission_classes = [LoginPermissions] # 指定权限类
-全局:全局所有接口都生效
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'app01.permissions.CommonPermission',
],
}
-局部禁用;
class UserView(ViewSet):
authentication_classes = [] # 局部禁用认证
permission_classes = [] # 局部禁用权限
权限的分类:
-ACL:访问控制列表
-rbac:公司内部系统,基于角色的访问控制
-abac:rbac升级版,加了属性认证
代码展示:
视图层;
class BooksView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all() # 指定模型表数据
serializer_class = BookSerializer # 指定序列化类
authentication_classes = [LoginAuthentication] # 指定认证类
permission_classes = [LoginPermissions] # 指定权限类
class BookView(GenericViewSet, RetrieveModelMixin):
queryset = Book.objects.all() # 指定模型表数据
serializer_class = BookSerializer # 指定序列化类
authentication_classes = [LoginAuthentication] # 指定认证类
permission_classes = [LoginPermissions] # 指定权限类
权限类:
from rest_framework.permissions import BasePermission
class LoginPermissions(BasePermission):
def has_permission(self, request, view):
# 实现权限的控制,知道当前用户是谁,当前用户登录的权限是什么request.user
if request.user.user_type == 1:
return True
else:
# 没有权限,向对象中放一个属性 message
# 如果表模型中,使用了choice,就可以通过 get_字段名_display() 拿到choice对应的中文
self.message = '您是【%s】没有权限查看' % request.user.get_user_type_display()
return False
路由器不变,因为还是三大认证的流程
频率组件
控制某个接口访问频率(次数)
实现功能:查询所有接口,同一个ip亿欧分钟只能访问5次
使用步骤
1.写一个频率类,继承SimpleRateThrottle
2.重写get_cache_key方法,返回什么,就以什么做限制---》ip,用户id做限制
3.配置一个类属性,scope='起个名字',在配置文件中使用
4.在配置文件中配置
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'loginTh': '5/m'
},
}
5.局部使用和全局使用
-局部:只在某个视图类中使用【当前视图类管理的所有接口】
class BookView(GenericViewSet, RetrieveModelMixin):
queryset = Book.objects.all() # 指定表模型数据
serializer_class = BookSerializer # 指定序列化类
authentication_classes = [] # 局部禁用认证
permission_classes = [LoginPermissions] # 局部使用权限
throttle_classes = [LoginThrottling] # 局部使用频率
-全局所有接口都生效:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': ['app01.throttling.LoginThrottling'],
}
-局部禁用:
class BooksView(GenericViewSet, ListModelMixin):
throttle_classes = []
代码展示:
视图层:
class BooksView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [] # 局部禁用认证
permission_classes = [] # 局部禁用权限
throttle_classes = [LoginThrottling] # 局部使用频率
class BookView(GenericViewSet, RetrieveModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [] # 局部禁用认证
permission_classes = [LoginPermissions] # 局部使用权限
throttle_classes = [] # 局部禁用频率
频率类:
from rest_framework.throttling import SimpleRateThrottle
class LoginThrottling(SimpleRateThrottle):
scope = 'loginTh'
def get_cache_key(self, request, view):
# 返回什么,就以什么做频率限制
# 客户端的ip地址需要从request.META.get('REMOTE_ADDR')
return request.META.get('REMOTE_ADDR') # 以ip做限制
# return request.user.pk # 以用户id做限制
过滤排序
restful规范中,要求了请求地址中带过滤条件
-5个接口,只有一个接口需要有过滤和排序,查询所有接口
实现功能:
查询所有图书接口,查询以 红 开头的所有图书
内置过滤类的使用(继承GenericAPIView)
from rest_framework.filters import SearchFilter
class BooksView(GenericViewSet, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [] # 局部禁用登录认证
# permission_classes = [LoginPermissions]
# throttle_classes = [LoginThrottling]
# SearchFilter内置的,固定用法,模糊匹配
# 就有过滤功能,指定按哪个字段过滤
filter_backends = [SearchFilter]
search_fields = ['name'] # 可以按名字模糊查询,可以写多个
# 可以使用的搜索方式
http://127.0.0.1:8000/api/v1/books/?search=游 # name中只要有红就会搜出来
使用第三方django-filter实现过滤
from django_filters.rest_framework import DjangoFilterBackend
安装:django_filters
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = []
authentication_classes = []
throttle_classes = []
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name','price'] # 支持完整匹配 name=聊斋11&price=933
# 支持的查询方式
http://127.0.0.1:8000/api/v1/books/?price=939
http://127.0.0.1:8000/api/v1/books/?price=939&name=红楼梦
自己定制过滤类实现过滤
查询价格大于100的所有图书
http://127.0.0.1:8000/api/v1/books/?price_gt=100
#第一步; 定义一个过滤类,继承BaseFilterBackend,重写filter_queryset方法
class CommonFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
# 在里面实现过滤,返回qs对象,就是过滤后的数据
price_gt = request.query_params.get('price_gt', None)
if price_gt:
qs = queryset.filter(price__gt=int(price_gt))
return qs
else:
return queryset
# 第二步:配置在视图类上
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [CommonFilter] # 可以定制多个,从左往右,依次执行
排序的使用
内置就够用了
from rest_framework.filters import OrderingFilter
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [OrderingFilter] # 可以配合查询一起用,查询以后进行排序
# filter_backends = [BookFilters,OrderingFilter]
ordering_fields = ['price']
# 支持的查询方法:
http://127.0.0.1:8000/api/v1/books/?ordering=price # 没有符号就是升序
http://127.0.0.1:8000/api/v1/books/?ordering=-price # 有符号就是降序
http://127.0.0.1:8000/api/v1/books/?ordering=-id,price
分页
# 分页,只有查询所有接口,才有分页
# drf内置了三个分页器,对应三种分页方式
#内置的分页类不能直接使用,需要继承,定制一些参数后才能使用
# 分页使用,自定义一个分页类(三种)
class CommonPageNumberPagination(PageNumberPagination):
page_size = 2 # 每页显示2条
page_query_param = 'page' # page=10 查询第10页的数据,每页显示2条
page_size_query_param = 'size' # page=10&size=5 查询第10页,每页显示5条
max_page_size = 5 # 每页最大显示10条
# LimitOffset
class CommonLimitOffsetPagination(LimitOffsetPagination):
default_limit = 3 # 每页显示2条
limit_query_param = 'limit' # limit=3 取3条
offset_query_param = 'offset' # offset=1 从第一个位置开始,取limit条
max_limit = 5
# offset=3&limit=2 0 1 2 3 4 5
# app 用下面
class CommonCursorPagination(CursorPagination):
cursor_query_param = 'cursor' # 查询参数
page_size = 2 # 每页多少条
ordering = 'id' # 排序字段
# 配置在视图类上即可
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = []
authentication_classes = []
throttle_classes = []
# 之前的东西一样用 ,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用
# pagination_class = PageNumberPagination
#基本分页方式(基本是这种,网页端):http://127.0.0.1:8000/api/v1/books/?page=2&size=3
# pagination_class = LimitOffsetPagination
# 偏移分页 http://127.0.0.1:8000/api/v1/books/?limit=4&offset=1
# 从第一条开始,取4条
pagination_class = CommonCursorPagination
# 游标分页,只能下一页,上一页,不能跳到中间,但它的效率最高,大数据量分页,使用这种较好
断点调试使用
程序是 debug模式运行,可以在任意位置停下,查看当前情况下变量数据的变压情况
pycharm来调试城市
-以debug形式运行
-在左侧空白处,点击加入断点
-step over 单步调试
-step into 进入到函数内部运行
-快速跳到下一个断点,绿色箭头
权限源码分析
# 权限的源码执行流程
-写一个权限类,局部使用,配置在视图类中,就会执行权限类的has_permission方法,完成权限校验
# 之前度过,drf的APIView,在执行视图类的方法之前,执行了3大认证---dispatch方法中的497行self.initial(request, *args, **kwargs)---》执行3大认证
APIView类的399行左右
def initial(self, request, *args, **kwargs):
# 能够解析的编码,版本控制
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# 认证组件的执行位置
self.perform_authentication(request)
# 权限组件的执行位置【读他】
self.check_permissions(request)
# 频率组件的执行位置
self.check_throttles(request)
APIView的326左右
def check_permissions(self, request):
# self.get_permissions()其实是我们在局部写的权限类循环取出产生对象放到列表中
# for循环其实是将一个个的权限对象取出来,
for permission in self.get_permissions():
# 权限类的对象执行has_permission方法,会将自己作为第一个参数传入,所以这里的self其实 是view,因为本身我们写的对象是自动传入隐藏,
if not permission.has_permission(request, self):
# 如果我们写的权限类return的是False,那么就会取反为True,会执行下面的
# 如果return的是True,那么取反就是False,不会执行下面的,继续再次循环
# 如果配了多个权限类,第一个没过,直接不会执行下一个权限类
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
APIView的274行左右 get_permissions方法
def get_permissions(self):
# self.permission_classes就是我们配置在视图类上的列表,里面是一个个的权限类
# 通过for循环将权限类取出来,然后加括号产生一个对象,放到列表中
# 所以本质是返回了一个列表,里面是我们写的权限类的对象
return [permission() for permission in self.permission_classes]
"总结"
-APIView--dispatch--initial--》倒数第二行执行权限类的方法self.check_permissions(request),
里面取出配置在视图类上的权限类,实例化得到一个个对象放到列表中,然后再for循环一个个对象取出来执行has_permission方法,如果返回False,就直接结束,不在继续往下执行,权限就认证通过
-如果视图类上,不配做权限类,permission_classes = [CommonPermission],会使用配置文件的api_settings.DEFAULT_PERMISSION_CLASSES
-实现使用项目配置文件,其次使用def内置配置文件
认证源码分析
# 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的
-497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
# APIView类的399行左右:
def initial(self, request, *args, **kwargs):
# 能够解析的编码,版本控制。。。。
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# 认证组件的执行位置【读它】
self.perform_authentication(request)
# 权限组件
self.check_permissions(request)
# 频率组件
self.check_throttles(request)
APIView的 316行左右
def perform_authentication(self, request):
request.user # 这里其实是点了一个方法,包装成了数据属性,打开看看,request.user,我们看出来这个方法其实是在Request类中的方法,所以需要在Request类中取找
Request类的user方法 219行左右
@property # 这里就可以看到这个user是方法伪装成了属性
def user(self):
if not hasattr(self, '_user'):
# 这里是判断对象中有没有user这个属性或者方法,没有就会执行下面的方法
with wrap_attributeerrors():
# self.方法,这里的self是Request类的对象,所以,方法也是在Request类中
self._authenticate()
return self._user
self 是Request的对象,找Request类的self._authenticate() 373 行
def _authenticate(self):
# self.authenticators就是我们配置在视图类上认证类的一个个对象,在列表中
# Request类初始化的时候,传入的
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
# 返回了两个值,组成了元组,第一个是当前登录用户,第二个是token,只走这里后面就不走了
# 可以返回None,会执行下一个认证类
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
# 解压赋值:
# self.user=当前登录用户,self是当前请求的新的Resquest对象
# self.auth=token
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
# self.authenticators去Request类的init中找 152行左右
class Request:
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
assert isinstance(request, HttpRequest), (
.format(request.__class__.__module__, request.__class__.__name__)
)
self.authenticators = authenticators or ()
什么时候调用Request的__init__---APIView的dispatch上面的492行,这里的对象是我们写的视图类的对象:
request = self.initialize_request(request, *args, **kwargs)
我们打开这个方法,可以发现,实际是这个方法调用了Resquest类产生了一个对象
def initialize_request(self, request, *args, **kwargs):
parser_context = self.get_parser_context(request)
# 这里返回的就是一个Request类的对象,被request接收
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
# 我们发现在这里给对象的authenticators属性添加了一个值,那么这个方法是什么呢?
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
"我们现在来探究在request对象产生的时候,给authenticators赋值的方法产生了什么"
def get_authenticators(self):
# 这个方法,其实就是将我们写的一个个的认证类循环取出产生对象放到列表中
return [auth() for auth in self.authentication_classes]
总结:
1.配置在视图类上的认证类,会在执行视图类方法之前执行,也就是在APIView的dispatch方法里面产生新的request对象时,会直接将我们认证类的对象添加到request对象的属性中,在权限认证之前执行
2.自己写的认证类,可以返回两个值或者None,None的情况下会继续执行下一个认证类,直到最后一个认证类要返回两个值,否则后续的权限和频率无法从request.user中得到当前登录用户
3.后续可以从request.user 取出当前登录用户(前提是你要在认证类中返回)
频率源码分析
# 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的
-497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
# APIView类的399行左右:
def initial(self, request, *args, **kwargs):
# 能够解析的编码,版本控制。。。。
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# 认证组件的执行位置
self.perform_authentication(request)
# 权限组件
self.check_permissions(request)
# 频率组件【读它】
self.check_throttles(request)
APIView 的352行
def check_throttles(self, request):
throttle_durations = []
# self.get_throttles()这个方法就是配置在视图类上的频率类的对象,放到列表中
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
# 每次取出一个频率类的对象,执行allow_request方法,如果是False,频率超了,不能再走
# 会走下面这句话
throttle_durations.append(throttle.wait())
# 如果返回的是True取反则是False则不会走下面的,继续走最下面的IF
if throttle_durations:
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
self.throttled(request, duration)
总结:
-我们写的频率类:继承BaseThrottle,重写allow_request,在内部判断,如果超频了,就返回False,如果没超频率,就返回True
自定义频率类(继承BaseThrottle)
class SuperThrottle(BaseThrottle):
VISIT_RECORD = {}
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 自己写逻辑,判断是否超频
# (1)取出访问者ip
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 {ip地址:[时间1,时间2,时间3,时间4]}
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
# (1)取出访问者ip
ip = request.META.get('REMOTE_ADDR')
import time
ctime = time.time()
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
if ip not in self.VISIT_RECORD:
self.VISIT_RECORD[ip] = [ctime, ]
return True
# self.history = [时间1]
self.history = self.VISIT_RECORD.get(ip,[])
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
while self.history and ctime - self.history[-1] > 60:
self.history.pop()
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
if len(self.history) < 3:
self.history.insert(0, ctime)
return True
else:
return False
def wait(self):
import time
ctime = time.time()
return 60 - (ctime - self.history[-1])
-------------------------------下面是自己写的----------------------------------------------
class SuperThrottle(BaseThrottle):
VISIT_RECORD = {}
def allow_request(self, request, view):
# 自己写逻辑,判断是否超频
# (1)取出访问者ip
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 {ip地址:[时间1,时间2,时间3,时间4]}
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
# (4)判断,当列表小于5,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于5,说明一分钟内访问超过三次,返回False验证失败
ip = request.META.get('REMOTE_ADDR')
now_time = time.time()
if ip not in self.VISIT_RECORD:
self.VISIT_RECORD[ip] = [now_time,]
return True
self.VISIT_RECORD.get(ip).append(now_time)
# for history_time in self.VISIT_RECORD.get(ip):
if now_time - self.VISIT_RECORD.get(ip)[-1] > 60:
self.VISIT_RECORD.pop(ip)
return True
if len(self.VISIT_RECORD.get(ip)) < 5:
return True
return False
SimpleRateThrottle(源码分析)
写一个频率类,重写allow_request方法,在里面实现频率控制,在里面实现频率控制
# SimpleRateThrottle---》allow_request
def allow_request(self, request, view):
# 这里进行了一个判断,对象点一个属性,我们可以看一下这个rate是什么,在类的__init__里
# 就是通过配置文件和scop取出,频率限制多少,比如一分钟访问5次
if self.rate is None:
return True
# 这里频率对象点get_cache_key就是我们写的方法,获取到我们限制的ip或者其他的
self.key = self.get_cache_key(request, view)
# 如果这个值没有,就返回True继续
if self.key is None:
return True
# 下面的逻辑跟我们之前写的差不多,也是获取时间
self.history = self.cache.get(self.key, [])
self.now = self.timer()
# 这里的self.duration就是我们配置文件里面写的频率限制的时间,小时对应的数字
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
# 这里的num_requests就是我们配置文件里面写的频率限制的次数
if len(self.history) >= self.num_requests:
return self.throttle_failure()
return self.throttle_success()
---------------------------------------------------------------------------------------------
class SimpleRateThrottle(BaseThrottle):
def __init__(self):
# 利用反射判断self里面有没有这个方法,如果没有就添加,执行一次就行
if not getattr(self, 'rate', None):
# 所以这里的就是将配置文件的频率限制赋值给了对象的属性rate
self.rate = self.get_rate()
# 这句话才是真正的通过配置文件取出频率的限制,再研究这个方法是怎么赋值的
self.num_requests, self.duration = self.parse_rate(self.rate)
"self.get_rate()就是将我们写在配置文件中的频率限制取出来,返回给对象的属性rate接收"
def get_rate(self):
try:
return self.THROTTLE_RATES[self.scope]
# 这里就是取取配置文件中,我们写的scope对应的值,然后返回,如果取不到就抛异常
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
"下面我们来看self.parse_rate(self.rate)这个方法"
def parse_rate(self, rate):
# 如果rate没有值,那么就返回两个none,none
if rate is None:
return (None, None)
# 如果rate有值,那么就按照/切割,然后解压赋值,然后将数字转为整型,因为配置文件我们写字符串
num, period = rate.split('/')
num_requests = int(num)
# 下面这句话表示从这个字典里面按照我们写的频率限制的首字母对取值,然后赋值最后返回出去,最终被self.num_requests, self.duration = self.parse_rate(self.rate)这个接收
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
基于APIView编写分页
# 分页功能,只有查询所有才有
class BookView(ViewSetMixin, APIView):
def list(self, request):
books = Book.objects.all()
# 使用步骤
# 1 实例化得到一个分页类的对象
paginator = CommonLimitOffsetPagination()
# 2 调用分页类对象的paginate_queryset方法来完成分页,返回的page是 要序列化的数据,分页好的
page = paginator.paginate_queryset(books, request, self)
if page is not None:
serializer = BookSerializer(instance=page, many=True)
# 3 返回数据,调用paginator的get_paginated_response方法
# return paginator.get_paginated_response(serializer.data)
return Response({
'total': paginator.count,
'next': paginator.get_next_link(),
'previous': paginator.get_previous_link(),
'results': serializer.data
})
异常处理
APIView---》dispatch---》三个认证,视图类的方法,如果出了异常,就会被异常捕获,捕获后统一处理
drf内置了一个函数,只要上面过程出了异常,就会执行这个函数,这个函数只处理了drf的异常
-主动抛的非drf异常
-程序出错
都不会被处理
我们的目标,无论主动抛还是程序运行出错,都统一返回格式---》能记录日志
公司里一般返回:{code:999,'msg':'系统错误,请联系系统管理员'}
# 写一个函数,内部处理异常,在配置文件中配置一下即可
# 首先我们需要知道这个函数在哪里,我们先从drf的配置文件中去找,找到
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
表示的就是,这个异常捕获函数的位置,在views中
# 写一个函数,内部处理异常,在配置文件中配置一下即可
def common_exception_handler(exc, context):
# exc 错误对象
# context:上下文,有view:当前出错的视图类的对象,args和kwargs视图类方法分组出来的参数,request:当次请求的request对象
# 只要走到这里,就要记录日志 ,只有错了,才会执行这个函数
# 记录日志尽量详细
print('时间,登录用户id,用户ip,请求方式,请求地址,执行的视图类,错误原因')
# 这里是调用原来的方法,接收返回值return Response(data, status=exc.status_code, headers=headers)
res = exception_handler(exc, context)
if res: # 有值,说明返回了Response 对象,没有值说明返回None
# 如果是Response 对象说明是drf的异常,已经被处理了,如果是None表明没有处理,就是非drf的异常
res = Response(data={'code': 888, 'msg': res.data.get('detail', '请联系系统管理员')})
# 这里用res.data.get('detail')是因为在原来的异常处理结果的request对象里面有一个data对象,这个对象里面有一个data = {'detail': exc.detail}这样的字典
else:
# res = Response(data={'code': 999, 'msg': str(exc)})
# 记录日志
res = Response(data={'code': 999, 'msg': '系统错误,请联系系统管理员'})
return res
# 在配置文件中配置
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler',
}