drf内容简介
1. drf 入门规范
2. 序列化组件----(最重要)
3. 请求与响应
4. 视图组件----(重要)
5. 路由组件
6. 认证、权限、频率----(重要)
7. 过滤、排序、分页、全局异常处理
8. 接口文档
9. jwt认证
10. 权限:ACL、RBAC
drf入门规范
前后端开发模式
前后端混合开发模式:
前后端分离开发模式:后端只负责写接口(5个接口)
API接口
四大特点:
请求地址(url):长得像返回数据的URL链接
请求方式:get、post、put、patch、delete
请求参数:地址的参数、请求体中的参数
响应结果:返回数据
写了接口给谁用?前端(Web、app),提供给第三方调用
接口测试工具----postman
发送http请求工具
get请求可以在请求体中携带数据
get请求和post请求有什么区别?
1. 数据传递方式:
- GET请求:用于从服务器获取数据,将参数附加在URL的查询字符串中。这些参数通常以键值对的形式出现,例如:`http://example.com/resource?param1=value1¶m2=value2`。GET请求的数据通常可见,因为它们附加在URL上,而且有长度限制。
- POST请求:用于向服务器提交数据,将数据包含在请求体中。这意味着数据不会显示在URL中,而是以更隐私的方式传递给服务器。POST请求没有固定的数据长度限制。
2. 数据安全性:
- GET请求:因为数据附加在URL中,所以不适合传递敏感信息,因为这些数据容易被拦截或暴露在日志文件中。GET请求更适合用于无副作用的请求,如搜索查询。
- POST请求:由于数据在请求体中,相对较安全,适合传递敏感信息,如登录凭据或表单数据。但仍然需要使用HTTPS协议来保护数据的传输过程。
3. 数据缓存:
- GET请求:可以被缓存,因为它们通常用于获取不会频繁更改的数据,如静态资源(图片、CSS、JavaScript等)。
- POST请求:通常不会被缓存,因为它们可能会引发服务器上的副作用,如数据库写入或状态更改。
4. 数据量:
- GET请求:由于数据附加在URL中,受到URL长度的限制,通常用于传递较小的数据。
- POST请求:由于数据包含在请求体中,可以传递较大的数据量。
5. 可见性和书签:
- GET请求:因为参数在URL中可见,所以可以轻松地将URL保存为书签或通过分享链接来传递参数。
- POST请求:由于数据不可见,不适合保存为书签或传递参数。
总的来说,GET请求用于获取数据,数据附加在URL中,通常用于无副作用的操作,而POST请求用于向服务器提交数据,数据包含在请求体中,适合传递敏感信息和较大的数据量。选择使用哪种请求方法取决于具体的需求和安全考虑。
请求编码格式:
urlencoded:key=value&key=value===》后端django===》request.POST取出数据
form-data:数据和文件混到一起===》后端django===》request.FILES取出文件 ,request.POST取出数据
Json:{name:lqz,age:19}-----》后端django--》request.body---->装饰器
-collections创建,保存导出
接口规范:restful规范
1. 数据的安全保障
URL链接一般都采用 https 协议传输,它比http安全
2. 接口特征表现
URL中带api标识
-https://api.baidu.com/books/
-https://www.baidu.com/api/books/
3. 多数据版本共存
URL中带版本信息
https://api.baidu.com/v1/books
https://www.baidu.com/api/v2/books
4. 数据即是资源,均使用名词
接口一般都是完成前后端数据的交互,交互的数据我们称之为资源
-
一般提倡用资源的复数形式,在url链接中奖励不要出现操作资源的动词,错误示范:https://api.baidu.com/delete-user
-
特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
5. 资源操作由请求方式决定(method)
操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
https://api.baidu.com/books - get请求:获取所有书
https://api.baidu.com/books/1 - get请求:获取主键为1的书
https://api.baidu.com/books - post请求:新增一本书书
https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
https://api.baidu.com/books/1 - patch请求:局部修改主键为1的书
https://api.baidu.com/books/1- delete请求:删除主键为1的书
6. 过滤,通过在URL上传参的形式传递搜索条件
https://api.example.com/v1/zoos?limit=10:指定返回记录的数量
https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置
https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数
https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件
7. 响应状态码
http响应状态码:1xx、2xx、3xx、4xx、5xx
趣味图解HTTP状态码的含义:https://www.sohu.com/a/278045231_120014184
-404和403 和 405
-301和302
-1xx表示请求正在处理---》前端一般看不到
-2xx 表示请求处理成功--》经常看到
-201和200有什么区别
-3xx:重定向
-4xx:客户端错误
-5xx:服务端出错
成功都返回200,而在响应体中带状态码----》code:不同公司就不一样
{
"code": 101,
"msg": "用户名或密码错误"
}
-补充一:mysql 错误操作都会有个 数字(10060) 文字解释
-补充二:
-https://open.weibo.com/wiki/Error_code
8. 错误处理,应返回错误信息
{
"code": 101,
"msg": "用户名或密码错误"
}
9. 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
10. 需要URL请求的资源需要访问资源的请求链接(响应中带链接)
序列化合反序列化
-序列化:把我们识别的数据转成对方识别的数据:字典,列表---》转成json格式字符串的过程
-反序列化:别人提供给我们数据转成我们识别的格式---》json格式字符串---》字典,对象。。
drf:django框架上的一个app,方便我们快速编写符合restful规范的接口
快速编写5个接口
cbv源码分析
cbv:基于类的视图------》使用类编写---》在类中写跟请求方式同名的方法----》路由配置:类名.as_view()
请求过来,什么请求就会执行跟请求方式同名的方法
APIView的执行流程
-执行视图类中跟请求方式同名的方法
-去除了csrf认证
-包装了新的request
-执行了三大认证
-处理了全局异常
-视图类的对象 self.request
序列化组件
序列化组件的作用
1 可以序列化--》qs对象,单个对象
2 反序列化--》前端传入的json格式数据---》保存到数据库
3 反序列化的校验:字段自己,局部钩子,全局钩子
快速使用
-写个类,继承Serializer
-在类中写字段
-在视图类中:实例化得到序列化类的对象:多条,和单条
-序列化:序列化类的对象.data retrun Response(ser.data)
常用字段类
-跟models中之前学过的有对应关系
-ListField DictField --->序列化和反序列化都会用
字段参数
-限制反序列化校验字段自己规则的
-read_only write_only:控制字段只做序列化或反序列化的,如果不写就是
使用序列化类做反序列化
# 伪代码 # -新增 ser=BookSerializer(data=request.data) ser.is_valid(raise_exception=True)# --->只要校验不通过,直接抛异常 ser.save() # 继承Serializer,需要在序列化类中重写create方法,完成新增 # 序列化类中重写create方法 def create(self, validated_data): # validated_data 前端传入,校验过后的数据 # 新增一个对象 return 新增的对象 # 后续再视图类中只要调用ser.data,这里必须返回 # -修改 ser=BookSerializer(instance='要修改的对象',data=request.data) ser.is_valid(raise_exception=True)# --->只要校验不通过,直接抛异常 ser.save() # 继承Serializer,需要在序列化类中重写update方法,完成修改 # 序列化类中重写update def update(self, instance, validated_data): # instance 要修改的对象,哪里来的?BookSerializer(instance=要修改的对象的id,data=request.data) # validated_data:校验过后数据 res=Book.objects.filter(pk=instance).update(**validated_data) return res # 返回的res最后干啥了?只要在 视图类中 调用ser.data,他会根据这个返回值做序列化
反序列化校验
-字段自己
-局部钩子--》序列化类中写 validate_字段名 传参数
-全局钩子---》序列化类中写 validate 传参数,字典
-登录接口
定制返回格式
-source:1 改名 2 跨表查 3 拿表模型中的方法
-SerializerMethodField:在序列化类中写
-publish=serializers.SerializerMethodField(read_only)
-配合一个 get_username(self,obj)的方法,返回什么,这个字段就是什么
-在表模型中写:方法
publish=serializers.DictField()
-只能做序列化用了,反序列化得单独用---》需要使用read_only和write_only控制
ModelSerializer的使用
class Meta: model=表名 fields=[需要序列化或反序列化的字段,表中没有也要写] extra_kwargs={} # 传入字段参数 重写字段 大部分情况下不需要写create和update了 局部,全局钩子跟之前一模一样
视图组件
# 1 两个视图基类 APIVIew -类属性 renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES GenericAPIView:要使用序列化类,数据库打交道,就可以继承它 -queryset = None -serializer_class = None -lookup_field = 'pk' -filter_backends = api_settings.DEFAULT_FILTER_BACKENDS -pagination_class = api_settings.DEFAULT_PAGINATION_CLASS -get_queryset(self) #获取所有要序列化的数据,在里面拿到qs对象后要 .all() -get_object # 获取单个对象 -get_serializer()--->内部调用了get_serializer_class--》最终返回序列化类的对象,传入要传的参数---》instance ,data many -filter_queryset---》给ListModelMixin用了--》把配置的filter_backends 依次执行完成过滤 # 5个视图扩展类 =不是视图类,需要搭配GenericAPIView ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin 一个视图类中写四个接口--》必须借助于ViewSetMixin class BookView(ViewSetMixin,GenericAPIView,ListModelMixin,CreateModelMixin,RetrieveModelMixin,DestroyModelMixin): queryset = None serializer_class = None # 6 9个视图子类 # 7 视图集 -ModelViewSet:重写方法实现你想要的功能 -ReadOnlyModelViewSet -ViewSetMixin:路由写法变了 -ViewSet:ViewSetMixin+APIView -GenericViewSet:ViewSetMixin+GenericAPIView
请求与响应
# 请求: -请求源码:request对象 -request.data -request.query_params -跟之前一样 -request._request -__getattr__ -请求能解析编码格式:parser_classes -局部使用 -全局使用 # 响应 -响应Response 源码 -data:响应体的内容,序列化后的数据给了他 -headers:响应头 django原生响应头 响应对象['xx']=xxx -status:响应状态码 -响应编码格式--》浏览器,postman,看到的样子 -局部和全局配置 # 写了个视图类 获取所有接口 class BookView(ViewSetMixin,GenericAPIView,ListModelMixin): queryset = None serializer_class = None def list(self,request,*args,**kwargs): res=super().list(request,*args,**kwargs) res.data # {code:100,msg:成功,data:[{},{}]} return Response({code:100,msg:成功,data:res.data})
路由组件
# 1 只要继承ViewSetMixin及其子类,路由写法就变了 -方式一:映射方式 视图类.as_view({'get':'lqz'}) -方式二:自动生成 -SimpleRouter DefaultRouter # 2 action装饰器:methods detail
认证权限频率组件
# 认证的使用 1 写个类,继承BaseAuthentication 2 重写autenticate 3 取出用户传入的 token ---》通过token能知道是谁 4 认证通过取出用户,认证失败,抛出异常 5 return 查到的对象,token---》后续再request.user中就是当时返回的第一个值 6 视图类中配置 7 全局配置 # 权限类 1 写个类,继承BasePermission 2 has_permission 3 判断用户的权限 acl 权限,取出当前用户所有权限,判断当次请求是否在权限中 rbac,根据当前用户取出所有角色,通过角色取出所有权限,去个重,判断是否在权限中 4 有权限返回True,没有返回False 5 视图类中配置 6 全局配置 # 频率类 1 写个类,继承SimpleRatethrottle 2 重写get_cache_key 返回值 唯一的 ip,userid 3 类属性 scope='字符串' 4 配置文件配置 'DEFAULT_THROTTLE_RATES': { '字符串': '3/m', }, 5 视图类中配置 6 全局配置 # 权限---》认证---》频率 # 为什么我们写了 权限类,配置行,它就会执行权限控制? -APIView 的dispatch中执行了3大认证---》 def dispatch(self, request, *args, **kwargs): self.initial(request, *args, **kwargs) # 三大认证 - self.initial 需要从根上 def initial(self, request, *args, **kwargs): self.perform_authentication(request) self.check_permissions(request) # 权限类的执行位置 self.check_throttles(request) -self.check_permissions(request) def check_permissions(self, request): for permission in self.get_permissions(): # 你配置在视图类上一个个权限类的对象列表 [权限对象1,权限对象2] if not permission.has_permission(request, self): # self,是视图类的对象,因为在APIView中 self.permission_denied( request, message=getattr(permission, 'message', None), code=getattr(permission, 'code', None) ) - def get_permissions(self): return [permission() for permission in self.permission_classes] # 认证类源码---》难 -APIView 的dispatch中执行了3大认证---》 def dispatch(self, request, *args, **kwargs): self.initial(request, *args, **kwargs) # 三大认证 - self.initial 需要从根上 def initial(self, request, *args, **kwargs): self.perform_authentication(request)# 认证类的执行位置 self.check_permissions(request) self.check_throttles(request) - def perform_authentication(self, request): request.user -去Request类中找user---》方法包装成了数据属性 @property def user(self): if not hasattr(self, '_user'): with wrap_attributeerrors(): self._authenticate() return self._user -去Request类中找self._authenticate() def _authenticate(self): for authenticator in self.authenticators: # 配置在视图类上一个个认证类的对象列表 try: # 调用认证类对象的authenticate方法,传入 self,是request对象 user_auth_tuple = authenticator.authenticate(self) # 当前登录用户,token except exceptions.APIException: self._not_authenticated() raise if user_auth_tuple is not None: self._authenticator = authenticator #self就是request对象,后续 request.user就是认证类返回的第一个参数 self.user, self.auth = user_auth_tuple # 解压赋值 return self._not_authenticated() -认证类可以配置多个,但是如果有一个返回了,后续的就不走了 -self.authenticators 是request对象的属性,是在Request实例化的时候传入的,它什么时候实例化的,包装新的Reqeust时传入的---》APIView的dispatch--》
频率源码分析
# 1 频率源码 -APIView----disaptch---》self.initial(request, *args, **kwargs)---》416行:self.check_throttles(request)----》352行 check_throttles def check_throttles(self, request): # self.get_throttles()就是咱们配置在视图类上频率类的对象列表[频率类对象,] for throttle in self.get_throttles(): # 执行频率类对象的allow_request,传了2个,返回True或False if not throttle.allow_request(request, self): # 反会给前端失败,显示还剩多长时间能再访问 throttle_durations.append(throttle.wait()) # 2 频率类要写 1 写一个类,继承,BaseThrottle 2 在类中重写:allow_request方法,传入 3个参数 3 在allow_request写限制逻辑,如果还能访问--》返回True 4 如果超了次数,就不能访问,返回False 5 局部配置在视图类上 6 全局配置在配置文件中 # 3 我们在drf中写的时候,不需要继承 BaseThrottle,继承了SimpleRateThrottle,重写get_cache_key -我们猜测:一定是 SimpleRateThrottle帮咱们写了咱们需要写的 # 4 自定义频率类,实现一分钟只能访问三次的控制: # (1)取出访问者ip # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间, # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过 # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败 #5 SimpleRateThrottle 源码分析 - SimpleRateThrottle内部一定有:allow_request---》 def allow_request(self, request, view): # 咱们没写,以后咱们可以在频率类中直接写 # rate='3/m' 以后不用写scope了,就会按一分钟访问3次现在 if self.rate is None: return True # 取出:重写的get_cache_key返回的值,咱们返回了访问者ip self.key = self.get_cache_key(request, view) if self.key is None: return True # 根据当前访问者ip,取出 这个人的访问时间列表 [访问时间1,访问2,访问3,访问4] 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() # 判断访问时间列表是否大于 3 if len(self.history) >= self.num_requests: return self.throttle_failure() return self.throttle_success()
自定义频率类
from rest_framework.throttling import BaseThrottle class MyThrottle(BaseThrottle): # VISIT_RECORD = {'192.168.1.1':[当前时间,当前时间,访问时间列表]} VISIT_RECORD = {} def __init__(self): self.history = [] def allow_request(self, request, view): 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, ] # VISIT_RECORD = {'192.168.1.1':[当前时间2,当前时间1,]} return True # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间, self.history = self.VISIT_RECORD[ip] # 访问时间列表 while self.history and ctime - self.history[-1] > 60: # 循环删除1分钟之前访问的实际 self.history.pop() # 最后self.history都剩下是一分钟之内的时间了 # (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])
过滤、排序、分页、全局异常处理
过滤
#1 针对于查询所有接口---》继承:GenericAPIView+ListModelMixin---》只需要在视图类中写一个类属性---》filter_backends = [过滤类,过滤类2] # 过滤类: 1 内置的:SearchFilter 类属性:search_fields=[可以按字段过滤] 127.0.0.1:8080/books/?search=红 2 第三方 -django-filter -精准匹配 127.0.0.1:8080/books/?name=红楼梦 -能更强大 3 自定义的 写一个类,继承:BaseFilterBackend 重写 filter_queryset 在filter_queryset中完成过滤,会把qs传入,返回过滤后的qs即可 # 2 源码分析 ListModelMixin---》list方法---》queryset = self.filter_queryset(self.get_queryset()) 过滤后的数据----》执行了GenericAPIView的---》filter_queryset---》取出一个个配置在视图类上的过滤类,依次实例化得到对象后执行对象的filter_queryset方法完成过滤---》最终返回的数据,就是过滤后的数据 # 3 继承APIView写过滤 request 取出过滤条件 book_list=Book.objects.all().filter(过滤)
排序
1 针对于查询所有接口---》继承:GenericAPIView+ListModelMixin---》只需要在视图类中写一个类属性---》filter_backends = [排序类]
2 内置排序类即可:OrderingFilter---》配置类属性ordering_fields
自定义排序类,完成自己的规则
分页
#1 针对于查询所有接口-->才有分页 # 2 三种分页方式,必须继承分页类,重写几个类属性实现--->配置在视图类上--》继承:GenericAPIView+ListModelMixin PageNumberPagination:用的最多,之前常见的分页方式,查询第几页,每页有多少条的分页方式 :page=10&size=3 LimitOffsetPagination:从第几条开始,取几条 offset=10&limit=3 从第10条开始,取3条 CursorPagination:只能上一页和下一页,需要排好序再分页 # 3 继承APIView 写分页 class Pager(APIView): def get(self,request,*args,**kwargs): # 获取所有数据 ret=models.Book.objects.all() # 创建分页对象 page=PageNumberPagination() # 在数据库中获取分页的数据 page_list=page.paginate_queryset(ret,request,view=self) # 对分页进行序列化 ser=BookSerializer1(instance=page_list,many=True) # return Response(ser.data) # 这个也是返回Response对象,但是比基本的多了上一页,下一页,和总数据条数(了解即可) return page.get_paginated_response(ser.data) return Response(ser.data) # 只会有数据,不会有上一页和下一页,总条数
全局异常处理
# 前后端分离了,后期,后端出了异常,我们不想让前端看到,我们需要捕获全局异常,统一返回格式 # drf 源码中已经处理 APIView--->dispatch---> try: # 1 执行三大认证 # 2 执行视图类的方法 except Exception as exc: response = self.handle_exception(exc) -463行左右: # exception_handler就是配置文件中配置的一个函数-->默认的 # 后期自己写了 exception_handler = self.get_exception_handler() response = exception_handler(exc, context) # 默认执行的是:rest_framework.views.exception_handler函数---》只处理了drf的异常 # 咱们处理全局异常的步骤: 1 写一个函数,在内部处理 from rest_framework.views import exception_handler def common_exception_handler(exc,context): res=exception_handler(exc,context) if res: #这次异常是drf的,并且它处理了 # 我们要统一返回格式 return Response({'code':888,'msg':"系统异常(drf异常),请联系系统管理员:%s"%res.data.get('detail',None)}) else: return Response({'code': 999, 'msg': "系统异常(非drf异常),请联系系统管理员:%s" % str(exc)}) 2 配置在配置文件上
接口文档
接口写完,必须编写接口文档,给别人用
接口文档规范:要有什么东西
1 描述
2 地址
3 请求方式
4 请求编码格式
5 请求参数,详解
6 返回示例 json
7 返回示例中字段详解
编写的位置:
1 直接写在文件中共享(word,md)
2 平台上写
- 公司搭建的平台(自己研发,第三方开源)
- 使用第三方接口文档平台
3 自动生成
-coreaip
JWT认证
JWT是什么?
Json Web Token缩写,一种前后端认证的方法,区别与session的方案,不需要在后端存储数据,也能实现会话保持
三段式:
签发阶段:
登录:
用户名+密码
手机号+验证码
本机号码一键登录--》向后端就只传了手机号--》根据手机号签发token
认证阶段
drf中的认证类
校验Token是否合法
别人截获到token后,模拟发送请求
-1 设置过期时间---》10m
-2 需要登录后才能访问的接口,不仅要带token还是带个加密串 sign
-3 双token认证
-返回两个token:一个token:7天过期,一个token:10分钟过期
-以后用就只带10分钟过期的token
-过了10分钟了,10分钟token失效了
-携带着7的token到后端,生成一个10分钟的返回
-原来的token串使用md5+盐生成一个串--》签名
django中使用jwt--》快速签发
快速签发定制返回格式
认证:jwt提供的认证 一个认证类,一个权限类
# 自定义用户表 签发 payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) 认证:认证类 token = request.META.get('HTTP_TOKEN') payload = jwt_decode_handler(token) user = User.objects.get(pk=payload.get('user_id')) # 多方式登录(auth的user表) -把校验和签发token逻辑写在序列化类的 全局钩子中了 -在全局钩子中,只要校验不通过,就抛异常
权限控制
acl:访问控制列表
用户跟权限多对多
基于角色的访问控制 rbac
用户跟角色关系,角色和权限管理
-用户表
-角色表
-权限表
-用户和角色多对多中间表
-角色和权限多对多中间表
-用户和权限多对多
abac:
基于属性+角色的访问控制
基于属性+访问控制列表
张三:【看视频,发视频】
张三:晚上12点凌晨7点 不能发视频
标签:请求,self,request,视图,---,整合,序列化,drf From: https://www.cnblogs.com/zfq132/p/17700453.html