Day85--drf06--整体流程及源码分析、全局异常处理与接口文档
2021/12/12 22:17:00
本文主要是介绍Day85--drf06--整体流程及源码分析、全局异常处理与接口文档,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
昨日回顾
1 频率限制 -写一个类,继承SimpleRateThrottle,重写get_cache_key, 返回什么就以什么做限制(限制ip,限制用户id,手机号), -再写一个类属性scope='字符串', 需要跟配置文件中对应 '字符串':'5/m' -局部配置,全局配置 # 注意:若是以ip限制全局配置,那么所有的接口总计不能超过指定的次数,就不单是某个接口次数限制 2 过滤和排序 -内置过滤类:SearchFilter -内置排序类:OrderingFilter -配置在继承GenericAPIView+ListModelMixin及其子类的view接口中,类属性:filter_backends=[] -配置相应的字段 -search_fields=['name'] -ordering_fields = ['price'] 3 第三方过滤类 -配置在类属性上:filter_backends = [DjangoFilterBackend] -配置字段:filterset_fields=['name','price'] -http://127.0.0.1:8000/books/?name=红楼梦&price=12 4 自定义过滤类 -自己写一个类,继承BaseFilterBackend,重写filter_queryset,返回queryset对象,queryset是过滤后的 -配置在继承GenericAPIView+ListModelMixin及其子类,类属性:filter_backends 5 分页功能 -三个分页类 PageNumberPagination:基本分页 ?page=1&size=2 -四个类属性 LimitOffsetPagination:偏移分页 ?limit=3&offset=2 -四个类属性 CursorPagination:游标分页 选择上一页和下一页 -三个类属性 -cursor_query_param = 'cursor' # 查询条件 -page_size = 2 # 每页显示多少条 -ordering = 'id' #按谁排序 -只需要配置在继承GenericAPIView+ListModelMixin及其子类,类属性:pagination_class
今日内容
1. drf整体流程
# drf处于的位置:路由匹配成功,进视图类之前 # 整体处理流程: 1.包装新的request对象 2.处理编码(urlencoded,formdata,json) 3.三大认证(顺序:认证、权限、频率)(*****) 4.进入视图类: -4.1 若是继承(APIView)(*****) -去模型中取数据 -序列化(*****) -返回数据 -4.2 若是继承(GenericAPIView+ListModelMixin)(*****) -去模型中取数据 -进行过滤和排序 -分页 -序列化(*****) -返回数据 -5.处理了响应(浏览器,json) -6.处理了全局异常
2. 源码分析--认证、频率、权限
2.1 认证源码
# 1.入口:APIView的dispatch()中--->self.initial()中处理三大认证--->认证类的代码:self.perform_authentication(request) # 2.self.perform_authentication(request)--->就是APIView的perform_authentication() def perform_authentication(self, request): request.user # 执行了新的request对象的user方法 # 3.Request类的user方法 @property def user(self): if not hasattr(self, '_user'): with wrap_attributeerrors(): # 核心就是这句话,是Request的_authenticate()方法 self._authenticate() return self._user # 4.Request类的_authenticate(self)方法 (核心) def _authenticate(self): for authenticator in self.authenticators: # self.authenticators是个列表,列表中放了一个个认证类的对象 try: # self是request,所以自定义认证类的authenticate,有两个参数(self,request),第二个参数request给了这里的self user_auth_tuple = authenticator.authenticate(self) # 执行认证类的authenticate方法 except exceptions.APIException: self._not_authenticated() raise if user_auth_tuple is not None: self._authenticator = authenticator self.user, self.auth = user_auth_tuple # 解压赋值,后续的request对象就有user属性了 return # 源码可知:若是写了多个认证类,只要有一个认证类中认证通过,返回了user和token,后续的认证类就不会for循环执行了 self._not_authenticated() # 5.Request类的self.authenticators属性 是多个认证类对象的列表 -是在Request初始化的时候,传入的 -Request类是在什么时候初始化的---》APIView的dispatch中的刚开始位置 -APIView的dispatch()---> request = self.initialize_request(request, *args, **kwargs) # 6.APIView的self.initialize_request方法 def initialize_request(self, request, *args, **kwargs): return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(),# APIView的 negotiator=self.get_content_negotiator(), parser_context=parser_context ) # 7.APIView的get_authenticators方法 def get_authenticators(self): # 列表中放了一个个认证类的对象 return [auth() for auth in self.authentication_classes]
2.2 权限源码
# 1.入口:APIView的dispatch()中--->self.initial()中处理三大认证--->权限类的代码self.check_permissions(request) # 2.APIView的check_permissions方法 def check_permissions(self, request): for permission in self.get_permissions(): # 列表,是一个个视图类中配置的权限类的对象 if not permission.has_permission(request, self): self.permission_denied( request, message=getattr(permission, 'message', None), code=getattr(permission, 'code', None) ) # 3.APIView的self.get_permissions(): def get_permissions(self): # 列表里放了一个个权限类的对象 return [permission() for permission in self.permission_classes] # 4.权限认证失败,返回中文 -在权限类中配置message即可(给对象,类都可以)
2.3 频率源码
# 1.入口:APIView的dispatch()中--->self.initial()中处理三大认证--->频率类的代码self.check_throttles(request) # 2.APIView的self.check_throttles(request) def check_throttles(self, request): throttle_durations = [] for throttle in self.get_throttles(): # 列表,是一个个视图类中配置的频率类的对象 # 如果被限制了,就把剩余时间 追加到 throttle_durations 限制持续时间 列表中 if not throttle.allow_request(request, self): throttle_durations.append(throttle.wait()) # 3.APIView的self.get_throttles() def get_throttles(self): # 列表里放了一个个频率类的对象 return [throttle() for throttle in self.throttle_classes] # 从频率的源码可知: 若是自定义的频率类 是继承SimpleRateThrottle的,可以直接将限制的值 写在频率限制类中 THROTTLE_RATES = {'ip_m_3': '3/m'} # SimpleRateThrottle源码分析: def get_rate(self): # 根据配置的限制值的key:scope,从THROTTLE_RATES中 取出限制值:rate """ Determine the string representation of the allowed request rate. """ if not getattr(self, 'scope', None): msg = ("You must set either `.scope` or `.rate` for '%s' throttle" % self.__class__.__name__) raise ImproperlyConfigured(msg) try: return self.THROTTLE_RATES[self.scope] # scope:'user' => '3/min' except KeyError: msg = "No default throttle rate set for '%s' scope" % self.scope raise ImproperlyConfigured(msg) def parse_rate(self, rate): # 根据配置拆分出 限制次数 和 持续时间 """ Given the request rate string, return a two tuple of: <allowed number of requests>, <period of time in seconds> """ if rate is None: return (None, None) # 3 m num, period = rate.split('/') # rate:'3/min' num_requests = int(num) duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] return (num_requests, duration) def allow_request(self, request, view): if self.rate is None: return True # 当前登录用户的ip地址 self.key = self.get_cache_key(request, view) # key:'throttle_user_1' if self.key is None: return True # 从缓存中取出 存放IP访问时间 的列表,若是初次访问,缓存为空,self.history为[] self.history = self.cache.get(self.key, []) # 获取一下当前时间,存放到 self.now self.now = self.timer() # Drop any requests from the history which have now passed the throttle duration # 将时间列表中 超过持续时间的 全部删除 # 当前访问与第一次访问时间间隔如果大于60s,将第一次记录清除,不再算作一次计数 # self.history:[10:23,10:55] # now:10:56 while self.history and self.now - self.history[-1] >= self.duration: self.history.pop() # history的长度与限制次数3进行比较 # history 长度第一次访问0,第二次访问1,第三次访问2,第四次访问3失败 if len(self.history) >= self.num_requests: # 直接返回False,代表频率限制了 return self.throttle_failure() # history的长度未达到限制次数3,代表可以访问 return self.throttle_success() def throttle_success(self): # 将当前时间插入到history列表的开头,将history列表、key、过期时间作为数据存到缓存中 """ Inserts the current request's timestamp along with the key into the cache. """ self.history.insert(0, self.now) self.cache.set(self.key, self.history, self.duration) return True
3. 自定义全局异常处理
# 1.自定义一个全局异常处理函数 def common_exception_handler(exc, context): """ # 一般会将错误信息添加到日志: 通过参数 exc可获取错误提示信息,通过参数context可获取错误详细信息 str(exec) # 错误提示信息 str(context['view']) # 错误发生的视图 context['request'].META.get('REMOTE_ADDR') # IP地址 context['request'].user.id # 用户的ID """ # 1. 执行drf默认的异常处理 response = exception_handler(exc, context) # 2.判断是否能被默认的异常处理捕获 if response: # 2.1 若能被默认的捕获到,会返回一个Response对象出来,我们可.data 拿出原本默认要返回给前端的数据,再结合自己的格式 指定返回 return Response(data={'code': 9998, 'msg': response.data}) else: # 2.2 若不能被默认的捕获到,会返回None,就自定义处理 返回给前端 return Response(data={'code': 9999, 'msg': '服务器异常,请联系系统管理员'}) # 2.在配置文件中 REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler', }
4. 自动生成接口文档
# 前后的分离 -前端一批人 -根本不知道你写了什么接口,请求参数什么样,响应数据什么样 -使用什么编码都不知道 -后端一批人 -我们写了很多接口 # 需要写接口文档(不同公司有规范) -1 公司有接口文档平台,后端在平台上录入接口 -2 使用第三方接口文档平台,后端写了在平台录入 -Yapi:开源 -3 使用md,word文档写,写完传到git上 -4 自动生成接口文档(swagger,coreapi) -通过swagger自动生成后导出,再导入到Yapi中 # coreapi 第三方模块--自动生成接口文档 # 1 安装:pip install coreapi # 2 在路由中配置 from rest_framework.documentation import include_docs_urls urlpatterns = [ ... path('docs/', include_docs_urls(title='站点页面标题')) ] # 3 视图类:自动接口文档能生成的是继承自APIView及其子类的视图。 -1) 单一方法的视图,可直接使用类视图的文档字符串,如 class BookListView(generics.ListAPIView): """ 返回所有图书信息. """ -2) 包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如 class BookListCreateView(generics.ListCreateAPIView): """ get: 返回所有图书信息. post: 新建图书. """ -3) 对于视图集ViewSet,仍在类视图的文档字符串中,分开定义,但是应使用action名称区分,如 class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet): """ list: 返回图书列表数据 retrieve: 返回图书详情数据 latest: 返回最新的图书数据 read: 修改图书的阅读量 """ # 4 在配置文件中配置 REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', }
补充
1.函数显示传参类型和返回值
# python3.5以上版本,typing模块提高代码健壮性 (公司常见写的方式) from typing import List, Tuple, Dict def test(a: int, string: str, f: float, b: bool) -> Tuple[List, Tuple, Dict, bool]: ll=[1,2,3,4] tup = (string, a, string) dic = {"xxx": f} boo = b return ll, tup, dic, boo print(test(12, "lqz", 2.3, False))
作业
# 1 三个认证的源码---》自己捋一遍 # 2 写一个全局异常处理函数,保证无论出什么异常,前端都返回固定格式 def common_exception(exc, context): # 第一步:记录异常信息到日志 print( f'异常视图:{str(context["view"])} 访问IP:{context["request"].META.get("REMOTE_ADDR")} ' f'访问用户ID:{context["request"].user.id} 异常信息:{str(exc)}') # 第二步:调用rest_framework 的异常捕获 response = exception_handler(exc, context) # 第三步:判断是否能被内置的捕获到 if response: # 若能,将内部返回的数据取出来,再加上我们自己的格式 返回给前端 return Response(data={'code': 9999, 'msg': response.data}) return Response(data={'code': 99998, 'msg': '系统出错,请联系管理员'}) # 3 试一下coreapi自动生成接口文档 # 4 给你一个地址,上地址看看Yapi怎么用
这篇关于Day85--drf06--整体流程及源码分析、全局异常处理与接口文档的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南