断点调试 认证权限频率源码执行流程 自定义频率SimpleRateThrottle 基于APIView编写分页 全局异常处理

回顾

# 1 认证的使用
有些接口需要登录才能访问
  -原生Django如何使用的认证:auth的user表,auth自带了认证
  -自己登录,使用自定义的用户表
  -认证的使用步骤:
  	1.写一个类,继承BaseAuthentication
    2.重写 authenticate,在方法中完成认证,并且获取到当前登录用户,返回
    	咱们代码从地址栏中取出
      	-request,query_params.get('token')
        -原生Django,取出当前端传入cookie。从哪取的?
        request.COOKIE.get('seseionid')
        -后期如果想从请求头中取
        	request.META.get('HTTP_TOKEN')
        -如果认证通过,返回两个值,第一个是当前登录用户,第二个是token,或者返回None
    3.局部全局使用

# 2 权限的使用
	-写的权限类不一样
  	-重写的方法不一样:has_permission 逻辑可能比较复杂
    权限认证方式:
    	ACL:访问控制列表
      rbac:公司内部系统,基于角色的访问控制
      abc:rbc升级版,加了属性认证
    return TrueFalse
  想改成中文提示:self.message
  只要字段中有choeice字段就能通过get_字段名_display()获取到对应的字符串名
  
# 3 频率
	写类继承SimpleRateThrottle
  	重写get_cache_key,返回什么,就以什么做频率限制
    类属性:scope='名字'
    配合配置文件中:DEFAULT_THROTTLE_RATES

# 4 过滤(查询所有才有过滤)
	-内置的:模糊匹配
    -第三方:精准匹配
    -自定义过滤类
    -filter_backends
    
# 5 排序
	-内置排序即可
            
            
# 6 分页
	-三种分页方式:重写三个类
    -但是一个接口只能有一种分页类

原生django的cookie+session认证底层原理

image-20230208150801193

断点调试的使用

程序是debug模式运行,可以在任意位置下停下,查看下当前情况下变量的变化情况

pycharm 来调试程序
	-以debug形式运行
    -在左侧空白处,点击加入断电 (红圈)
    -step over    单步调试
    -step into    进入到函数内部运行
    -快速调到下一个断电,绿色箭头
    
知道Django的执行流程,先经过wsgi ---> 中间件 ---> 路由匹配,匹配成功再执行视图函数的方法,在这之前又执行了APIView,dispatch、认证的流程

step Program快速跳到下一个断点,step over执行下一行,step into执行里面的代码

image-20230208162350178

认证 权限 频率源码分析

权限类的执行源码分析

# 权限的源码执行流程
	-写一个权限类,局部使用,配置在视图类的,就会执行权限类的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()----》[CommonPermission(),]
        # permission 是我们配置在视图类上权限类的对象
        for permission in self.get_permissions():
            # 权限类的对象,执行has_permission,这就是为什么我们写的权限类要重写has_permission方法	
            # self 是视图类的对象,就是咱们自己的的权限类的has_permission的view参数
            if not permission.has_permission(request, self):
                # 如果return 的是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  是咱们配置在视图类上的列表,里面是一个个的权限类,没加括号
        # permission_classes = [CommonPermission]
        # [CommonPermission(),]   本质返回了权限类的对象,放到列表中
        return [permission() for permission in self.permission_classes]
    
    
    
    
# 总结:
	-APIView---dispatch----》initial---》倒数第二行---》self.check_permissions(request)
    	里面取出配置在视图类上的权限类,实例化得到对象,一个个执行对象的has_permission方法,如果返回False,就直接结束,不再继续往下执行,权限就认证通过
        
    -如果视图类上不配做权限类:permission_classes = [CommonPermission],会使用配置文件的api_settings.DEFAULT_PERMISSION_CLASSES
    优先使用项目配置文件,其次使用drf内置配置文件

是对象调对象的绑定方法,self会自动传进来的

image-20230208164659987

image-20230208170318882

image-20230208171216144

当没有视图类配置perission_classes就会走全局的

image-20230208171735582

认证源码执行流程分析

# 之前读过: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方法   219行左右
    @property
    def user(self):
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
    
    
 # self 是Request的对象,找Request类的self._authenticate()   373 行
    def _authenticate(self):
        # self.authenticators 我们配置在视图类上认证类的一个个对象,放到列表中
        # Request类初始化的时候,传入的
        for authenticator in self.authenticators:
            try:
                # 返回了两个值,第一个是当前登录用户,第二个的token,只走这一个认证类,后面的不再走了
                # 可以返回None,会继续执行下一个认证类
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                # 解压赋值:
                #self.user=当前登录用户,self是当次请求的新的Request的对象
                #self.auth=token
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()
        
        
        
 # self.authenticators  去Request类的init中找     152行左右
    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        .....
        self.authenticators = authenticators or ()
		.....
        
        
 # 什么时候调用Reqeust的__init__?---》APIVIew的dispatch上面的492行的:request = self.initialize_request(request, *args, **kwargs)-----》385行----》
    def initialize_request(self, request, *args, **kwargs):
        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
    
    
    
 # 总结:
	 1 配置在视图类上的认证类,会在执行视图类方法之前执行,在权限认证之前执行
   2 自己写的认证类,可以返回两个值或None
   3 后续可以从request.user 取出当前登录用户(前提是你要在认证类中返回)

Request类的user方法,包装成了数据属性

image-20230208185531676

image-20230208173027646

频率源码执行流程分析

# 之前读过: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() 配置在视图类上的频率类的对象,放到列表中
        # 每次取出一个频率类的对象,执行allow_request方法,如果是False,频率超了,不能再走了
        # 如果是True,没有超频率,可以直接往后
        for throttle in self.get_throttles():
            if not throttle.allow_request(request, self):
                throttle_durations.append(throttle.wait())

        if throttle_durations:
            # Filter out `None` values which may happen in case of config / rate
            # changes, see #1438
            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

image-20230208193711085

自定义频率类

     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])

image-20230208223724133

SimpleRateThrottle

# 写一个频率类,重写allow_request方法,在里面实现频率控制

# SimpleRateThrottle---》allow_request
    def allow_request(self, request, view):
		# 这里就是通过配置文件和scop取出 频率限制是多少,比如一分钟访问5此
        if self.rate is None:
            return True

        # 返回了ip,就以ip做限制
        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True
		# 下面的逻辑,跟咱们写的一样
        self.history = self.cache.get(self.key, [])
        self.now = self.timer()
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()
    
    
    
# SimpleRateThrottle的init方法
    def __init__(self):
        if not getattr(self, 'rate', None):
            # self.rate= '5、h'
            self.rate = self.get_rate()
        # 5    36000
        self.num_requests, self.duration = self.parse_rate(self.rate)
# SimpleRateThrottle的get_rate() 方法
    def get_rate(self):

        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            #  self.scope 是 lqz 字符串
            # return '5/h'
            return self.THROTTLE_RATES[self.scope]
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)
            
            
#     SimpleRateThrottle的parse_rate 方法
	def parse_rate(self, rate):
        # '5/h'
        if rate is None:
            return (None, None)
        # num =5
        # period= 'hour'
        num, period = rate.split('/')
        # num_requests=5
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        # (5,36000)
        return (num_requests, duration)

image-20230208194443557

基于APIView编写分页

# 分页功能,只有查询所有才有
模仿ListAPIView,去ListModelMixin里面查找List方法有分页和过滤 ---> 在当前没有对应属性的方法,去GenericAPIView里头查找

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':'系统错误,请联系系统管理员'}
    
    
    
# 写一个函数,内部处理异常,在配置文件中配置一下即可

def common_exception_handler(exc, context):
    # exc 错误对象
    # context:上下文,有view:当前出错的视图类的对象,args和kwargs视图类方法分组出来的参数,request:当次请求的request对象
    # 只要走到这里,就要记录日志 ,只有错了,才会执行这个函数
    # 记录日志尽量详细
    print('时间,登录用户id,用户ip,请求方式,请求地址,执行的视图类,错误原因')
    res = exception_handler(exc, context)
    if res:  # 有值,说明返回了Response 对象,没有值说明返回None
        # 如果是Response 对象说明是drf的异常,已经被处理了,如果是None表明没有处理,就是非drf的异常
        res = Response(data={'code': 888, 'msg': res.data.get('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',
}
posted @   小福福  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
  1. 1 原来你也在这儿 温余福
  2. 2 世间美好和你环环扣扣 温余福
  3. 3 随风起舞 温余福
  4. 4 罪恶都市 温余福
世间美好和你环环扣扣 - 温余福
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 尹初七

作曲 : 温余福

编曲 : 彭圣杰

偏偏秉烛夜游

偏偏秉烛夜游

午夜星辰 似奔走之友

爱你每个结痂伤口

酿成的陈年烈酒

入喉尚算可口

入喉尚算可口

怎么泪水 还偶尔失守

邀你细看心中缺口

裂缝中留存 温柔

此时已莺飞草长 爱的人正在路上

此时已莺飞草长 爱的人正在路上

我知他风雨兼程 途经日暮不赏

穿越人海 只为与你相拥

此刻已皓月当空 爱的人手捧星光

我知他乘风破浪 去了黑暗一趟

感同身受 给你救赎热望

知道你不能 还要你感受

知道你不能 还要你感受

让星光加了一点彩虹

让樱花偷偷 吻你额头

让世间美好 与你环环相扣

此时已莺飞草长 爱的人正在路上

此时已莺飞草长 爱的人正在路上

我知他风雨兼程 途经日暮不赏

穿越人海 只为与你相拥

此刻已皓月当空 爱的人手捧星光

我知他乘风破浪 去了黑暗一趟

感同身受 给你救赎热望

此时已莺飞草长 爱的人正在路上

此时已莺飞草长 爱的人正在路上

我知他风雨兼程 途经日暮不赏

穿越人海 只为与你相拥

此刻已皓月当空 爱的人手捧星光

我知他乘风破浪 去了黑暗一趟

感同身受 给你救赎热望

知道你不能 还要你感受

知道你不能 还要你感受

让星光加了一点彩虹

当樱花开的纷纷扬扬

当世间美好 与你环环相扣

特别鸣谢:槿葵,我们的海报制作妹妹。

原唱:柏松

吉他:柏松

和声:柏松

录音:柏松

混音:张强

点击右上角即可分享
微信分享提示