- REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
- REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态
- REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
- 所有的数据,无论是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性
- 对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)
REST是什么呢?它是一种架构风格,腾讯公司或其他公司建立API时要遵守的一种规则/风格,当然也有其他规则可以用。
那么何为REST架构风格呢?首先我们来说说Web,因为rest是以web为平台的。我们知道,web是分布式信息系统为超文本文件和其他对象(资源)提供访问入口。
在web上访问一个资源,需要3点:标识,表示,交互。通过这三个操作,又引出了三个概念:uri(包括url和urn),用来识别资源;representation(例如,图片,html,媒体)用来表示资源;通过协议与资源进行交互。所以,REST就是通过使用HTTP协议和URI,利用client/server对资源进行CRUD操作。
那么为什么要使用REST设计呢?肯定是有它的优点的。
1.客户端-服务端分离
优点:提高用户界面的便携性,通过简化服务器提高可伸缩性....
2..无状态(Stateless):从客户端的每个请求要包含服务器所需要的所有信息
优点:提高可见性(可以单独考虑每个请求),提高了可靠性(更容易从局部故障中修复),提高可扩展性(降低了服务器资源使用)
有状态与无状态区别:
如查询员工工资,如果查询工资是需要登陆系统,进入查询工资的页面,执行相关操作,获取工资的多少,则这种情况下是有状态的,因为查询工资的每一步操作都依赖于前一步操作,只需要前面操作不成功,后序操作就无法执行;如果输入一个url即可得到指定员工的工资,则这种情况下是无状态的,因为获取员工工资不依赖于其他资源或者状态,且这种情况下,员工工资是一个资源,由一个url与其对应,可以通过HTTP的GET方法得到资源。
3.缓存(Cachable):服务器返回信息必须被标记是否可以缓存,如果缓存,客户端可能会重用之前的信息发送请求
优点:减少交互次数,减少交互的平均延迟
4.统一接口
优点:提高交互的可见性,鼓励单独改善组件
5.支持按需代码(Code-On-Demand 可选)
优点:提高可扩展性
下面来解释一下何为表征状态转移:
举个例子:例如我订阅了一个人的博客,想要获取他发表的所有文章(这里『他发表的所有文章』就是一个资源Resource)。于是我就向他的服务发出请求,说『我要获取你发表的所有文章,最好是atom格式的』,这时候服务器向你返回了atom格式的文章列表第一页(这里『atom格式的文章列表』就是表征Representation)。
你看到了第一页的页尾,想要看第二页,这时候有趣的事情就来了。如果服务器记录了应用的状态(stateful),那么你只要向服务询问『我要看下一页』,那么服务器自然就会返回第二页。类似的,如果你当前在第二页,想服务器请求『我要看下一页』,那就会得到第三页。但是REST的服务器恰恰是无状态的(stateless),服务器并没有保持你当前处于第几页,也就无法响应『下一页』这种具有状态性质的请求。因此客户端需要去维护当前应用的状态(application state),也就是『如何获取下一页资源』。当然,『下一页资源』的业务逻辑必然是由服务端来提供。服务器在文章列表的atom表征中加入一个URI超链接(hyper link),指向下一页文章列表对应的资源。客户端就可以使用统一接口(Uniform Interface)的方式,从这个URI中获取到他想要的下一页文章列表资源。上面的『能够进入下一页』就是应用的状态(State)。服务器把『能够进入下一页』这个状态以atom表征形式传输(Transfer)给客户端就是表征状态传输(REpresentational State Transfer)这个概念。
REST是面向资源进行的,而资源是通过URI进行暴露的。
URI 的设计只要负责把资源通过合理方式暴露出来就可以了。对资源的操作与它无关,操作是通过 HTTP动词来体现,所以REST 通过 URI 暴露资源时,会强调不要在 URI 中出现动词。
例如:比如:左边是错误的设计,而右边是正确的
GET /rest/api/getDogs --> GET /rest/api/dogs 获取所有小狗狗 GET /rest/api/addDogs --> POST /rest/api/dogs 添加一个小狗狗 GET /rest/api/editDogs/:dog_id --> PUT /rest/api/dogs/:dog_id 修改一个小狗狗 GET /rest/api/deleteDogs/:dog_id --> DELETE /rest/api/dogs/:dog_id 删除一个小狗狗
REST很好利用了HTTP本身就有的一些特征,如HTTP动词,HTTP状态码,HTTP报头等等。
更多参考:https://github.com/aisuhua/restful-api-design-references
注:上面的解释是看见比较好的博客copy下来的,
RESTful API设计
API与用户的通信协议,总是使用HTTPs协议。
域名
https://api.example.com 尽量将API部署在专用域名(会存在跨域问题)
https://example.org/api/ API很简单
版本
URL,如:https://api.example.com/v1/
请求头
跨域时,引发发送多次请求
路径
视网络上任何东西都是资源,均使用名词表示(可复数)
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
method
- GET : 从服务器取出资源(一项或多项)
- POST : 在服务器新建一个资源
- PUT : 在服务器更新资源(客户端提供改变后的完整资源)
- PATCH : 在服务器更新资源(客户端提供改变的属性)
- DELETE :从服务器删除资源
过滤
通过在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
状态码
- 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
- 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT - [DELETE]:用户删除数据成功。
- 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
- 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
- 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
- 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
更多状态码请看 http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
错误处理
状态码是4xx时,应返回错误信息,error当做key。
{
error: “Invalid API key”
}
返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
- GET /collection:返回资源对象的列表(数组)
- GET /collection/resource:返回单个资源对象
- POST /collection:返回新生成的资源对象
- PUT /collection/resource:返回完整的资源对象
- PATCH /collection/resource:返回完整的资源对象
- DELETE /collection/resource:返回一个空文档
Hypermedia API
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
{ “link”: { “rel”: “collection https://www.example.com/zoos“, “href”: “https://api.example.com/zoos“, “title”: “List of zoos”, “type”: “application/vnd.yourformat+json” }}
基于Django rest-framework框架的实现
先简单创建一个Django项目并且使用pip install djangorestframwork 安装django rest-framwork
要想快速实现的话可以点击这个快速实例
序列化
在开始rest-framework说使用之前我们先说一说序列化
开发我们的Web API的第一件事是为我们的Web API提供一种将代码片段实例序列化和反序列化为诸如json
之类的表示形式的方式。我们可以通过声明与Django forms非常相似的序列化器(serializers)来实现。
models部分:
from django.db import models # Create your models here. class Book(models.Model): title = models.CharField(max_length=32) price = models.IntegerField() pub_date = models.DateField() publish = models.ForeignKey("Publish") authors = models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name = models.CharField(max_length=32) email = models.EmailField() def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() def __str__(self): return self.name
路由系统
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/', views.bookView.as_view()), ]
CBV视图(views)部分:
from django.shortcuts import render, HttpResponse from app01.models import * from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers # 为queryset,model对象做序列化===》相当于form组件使用 # 这个可以放在一个单独的.py文件中 class BookSerializers(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) price = serializers.IntegerField() pub_date = serializers.DateField() # 上面的只是针对的一对一字段的,若果用在一对多字段上的时候就会输出关联那张表的 def __str__(self) # publish = serializers.CharField() # 这个表示的是实现关联表中publish表中的name字段,可以自己定制 publish = serializers.CharField(source="publish.name") # authors=serializers.CharField(source="authors.all") # 当出现多对多的时候上面的方式也不可行,上面会显示的是一个queryset对象 # 自己定制显示什么 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for obj in obj.authors.all(): temp.append(obj.name) return temp # 这个相当于modelform组件一样 class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' depth = 1 # 当遇到一对多字段的时候 publish = serializers.CharField(source='publish.name') # 多对多字段 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for obj in obj.authors.all(): temp.append(obj.name) return temp # Create your views here. class BookViewSet(APIView): def get(self, request, *args, **kwargs): # 序列化 # 方式一: # book_data = list(Book.objects.all().values('name', 'email')) # return HttpResponse(book_data) # 方式二: # from django.forms.models import model_to_dict # book_data = Book.objects.all() # temp = [] # for obj in book_data: # temp.append(model_to_dict(obj)) # return HttpResponse(temp) # 方式三: # from django.core import serializers # book_data = Book.objects.all() # ret = serializers.serialize("json", book_data) # print(type(ret)) # return HttpResponse(ret) # 方式四:序列组件 # 这里面和Django里面的form组件和modelform组件相似 # 这里记住要是使用浏览器访问的话这个必须要在setting中的INSTALLED_APPS注册rest_framework要不就会报错 # 最好在项目一开始的时候就在setting里面注册 book_data = Book.objects.all() # many=True 表示的queryset对象,反之many=False就表示为model对象 # 相当于form组件 # bs = BookSerializers(book_data, many=True) bs = BookModelSerializers(book_data, many=True) return Response(bs.data)
记住要把在setting里面注册restframework。
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', 'rest_framework' ]
serializers
在app01/utils/serializers.py 写serializers代码(类似于form)
from rest_framework import serializers # 为queryset,model对象做序列化===》相当于form组件使用 class BookSerializers(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) price = serializers.IntegerField() pub_date = serializers.DateField() # 上面的只是针对的一对一字段的,若果用在一对多字段上的时候就会输出关联那张表的 def __str__(self) # publish = serializers.CharField() # 这个表示的是实现关联表中publish表中的name字段,可以自己定制 publish = serializers.CharField(source="publish.name") # authors=serializers.CharField(source="authors.all") # 当出现多对多的时候上面的方式也不可行,上面会显示的是一个queryset对象 # 自己定制显示什么 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for obj in obj.authors.all(): temp.append(obj.name) return temp
ModelSerializer
在app01/utils/serializers.py 写ModelSerializer代码(类似于modelform)
from app01.models import * # 导入表 from rest_framework import serializers # 这个相当于modelform组件一样 class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' # 当遇到一对多字段的时候 publish = serializers.CharField(source='publish.name') # 多对多字段 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for obj in obj.authors.all(): temp.append(obj.name) return temp
当一个model有外键的时候,默认显示的是外键的id,此时要显示外键的所有值可以用下面,depth,会把外键的所有值显示出来,depth应该是整数,表明嵌套的层级数量。
class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' # 上面表示显示所有字段也可以只显示我们想要的 # fields = ('id',) ,表示只显示id # 要是不想显示哪个字段就可以使用 # exclude=('id',), 表示出了id其他的都显示 depth = 1
上面就是关于get请求获取所有的数据。
同样 ModelSerializer也支持全局钩子和局部钩子,和form组件一样局部钩子为validate_字段名(form组件为clean_字段名),
全局钩子为validate。
提交post请求
路由系统
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/', views.BookViewSet.as_view(), name="book"), ]
在app01/utils/serializers.py 里面的代码
from rest_framework import serializers
from app01.models import *
class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' publish = serializers.HyperlinkedIdentityField( view_name="detail_publish", lookup_field="publish_id", lookup_url_kwarg="pk", ) # 当遇到一对多字段的时候 # source='publish.name'这个后面是publish.name所以我们前面传回来的应该是publish.name对应的名字 # # 如果是publish.pk 前面传过来的就是 publish.pk对应的id publish = serializers.CharField(source='publish.name') # 多对多字段 authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for obj in obj.authors.all(): temp.append(obj.name) return temp # 因为上面自己定制了publish原来的save不支持这样保存,所以下面要重写create方法 def create(self, validated_data): print(validated_data) # 如果上面定制的publish.name 改为 publish.pk下面这2行就不用写了 if not validated_data["publish"]["name"].isdigit(): publish_id = Book.objects.filter(publish__name=validated_data["publish"]["name"]).values('publish_id').first() validated_data['publish_id'] = publish_id['publish_id'] else: validated_data['publish_id']=validated_data["publish"]["name"] # 其实前端一般都会传数字不会传汉字, 所以可以向下面这样写 # validated_data['publish_id'] = validated_data["publish"]["name"] validated_data.pop('publish') authors = validated_data.pop('authors') book = Book.objects.create(**validated_data) book.authors.add(*authors) return book # def update(self, instance, validated_data): print('validated_data', validated_data) if not validated_data["publish"]["name"].isdigit(): publish_id = Book.objects.filter(publish__name=validated_data["publish"]["name"]).values('publish_id').first() validated_data['publish_id'] = publish_id['publish_id'] else: validated_data['publish_id']=validated_data["publish"]["name"] validated_data.pop('publish') authors = validated_data.pop('authors') instance.update(**validated_data) # 更新普通字段和一对多字段 nid = instance.values('id')[0]['id'] instance.first().authors.set(authors) # 更新多对多字段 book_data = Book.objects.filter(id=nid).first() # 使用下面的放法前面传进来的instance是obj对象,而这里传进来的queryset对象像下面出入一样 # book_obj = Book.objects.filter(pk=pk).first() # bs = BookModelSerializers(book_obj, data=request.data) # instance.authors.set(authors) # book_data = super().update(instance, validated_data return book_data
CBV视图(view)部分
from django.shortcuts import render, HttpResponse from app01.models import * from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers # 把Serializers和ModelSerializers单独放在一个py文件中 from app01.utils.serilizer import * import json # Create your views here. class BookViewSet(APIView): def get(self, request, *args, **kwargs): book_data = Book.objects.all() # many=True 表示的queryset对象,反之many=False就表示为model对象 # 相当于form组件 # bs = BookSerializers(book_data, many=True) # 这个就是相当于modelform组件, bs = BookModelSerializers(book_data, many=True) return Response(bs.data) def post(self, request, *args, **kwargs): bs = BookModelSerializers(data=request.data) if bs.is_valid(): # 直接把这个传到后面就不需要做验证了,对于多对多关系提交自定制显示 bs.save(authors=request.data['authors']) return Response(bs.data) else: return Response(bs.errors) class BookDetailViewSet(APIView): def get(self, request, pk): book_obj = Book.objects.filter(pk=pk).first() bs = BookModelSerializers(book_obj, context={'request': request}) return Response(bs.data) def put(self, request, pk): book_obj = Book.objects.filter(pk=pk) # 对应serilizer里面的 # book_obj = Book.objects.filter(pk=pk).first() # 这里如果是一对多或者多对多的自定制的话就需要自己写update方法 bs = BookModelSerializers(book_obj, data=request.data) if bs.is_valid(): bs.save(authors=request.data['authors']) return Response(bs.data) else: return Response(bs.errors) def delete(self, request, pk): Book.objects.filter(pk=pk).delete() return Response()
单条数据的get和put以及delet
class BookDetailViewSet(APIView):
def get(self, request, pk):
book_obj = Book.objects.filter(pk=pk).first() bs = BookModelSerializers(book_obj) return Response(bs.data) def put(self, request, pk): book_obj = Book.objects.filter(pk=pk) # 这里如果是一对多或者多对多的自定制的话就需要自己写update方法 bs = BookModelSerializers(book_obj, data=request.data) if bs.is_valid(authors=request.data['authors']):
bs.save()
return Response(bs.data)
else:
return Response(bs.errors)
def delete(self, request, pk):
Book.objects.filter(pk=pk).delete()
return Response()
超链接API:Hyperlinked
class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = '__all__' publish = serializers.HyperlinkedIdentityField( view_name="detail_publish", lookup_field="publish_id", lookup_url_kwarg="pk", )
上面的黄色字段就是超链接的关键,
view_name表示的是该路由的别名获取到publish/(?P<pk>\d+)/
lookup_field 表示在这个序列化里面获取到pk的值(也就是一个字段publish_id)
lookup_url_kwarg 表示把上面获取到的pk值放到view_name的(?P<pk>\d+)里面
在CBV相应部分
class BookDetailViewSet(APIView): def get(self, request, pk): book_obj = Book.objects.filter(pk=pk).first() bs = BookModelSerializers(book_obj, context={'request': request}) return Response(bs.data)
主要是在后面添加黄色部位
路由系统为
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/$', views.BookViewSet.as_view(), name="book"), url(r'^books/(?P<pk>\d+)/$', views.BookDetailViewSet.as_view(), name='detail_book'), url(r'^publish/(?P<pk>\d+)/$', views.PublishDetailViewSet.as_view(), name='detail_publish'), ]
试图三部曲
mixin类编写试图
from rest_framework import mixins from rest_framework import generics
from app01.utils.serializers import * class BookViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializers def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) class BookDetailViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializers def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs)
使用通用的基于类的视图
通过使用mixin类,我们使用更少的代码重写了这些视图,但我们还可以再进一步。REST框架提供了一组已经混合好(mixed-in)的通用视图,我们可以使用它来简化我们的views.py
模块。
from rest_framework import mixins from rest_framework import generics
from app01.utils.serializers import * class BookViewSet(generics.ListCreateAPIView): queryset = Book.objects.all() serializer_class = BookModelSerializers class BookDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Book.objects.all() serializer_class = BookModelSerializers class PublishViewSet(generics.ListCreateAPIView): queryset = Publish.objects.all() serializer_class = PublshModelSerializers class PublishDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Publish.objects.all() serializer_class = PublshModelSerializers
viewsets.ModelViewSet(最终版)
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^books/$', views.BookViewSet.as_view({"get": "list", "post": "create"}), name="book_list"), url(r'^books/(?P<pk>\d+)$', views.BookViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }), name="book_detail"), ]
views.py
from rest_framework import viewsets from app01.models import *
from app01.utils.serializers import *
class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() # BookModelSerializers和前面的一样没什么变化功能类似于modelform serializer_class = BookModelSerializers
认证
基于token的用户认证以及局部认证组件
沿用上面的
token:服务端动态生成的1串用来检验用户身份的字符串,可以放在header、cokies、url参数(安全性较差)、请求体(CSRF token);
token和session类似,不同于 session的是token比较灵活,不仅仅可以cokies里
url.py的代码
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.LoginView.as_view(), name="login"), url(r'^books/$', views.BookModelView.as_view({"get": "list", "post": "create"}), name="books"), url(r'^books/(?P<pk>\d+)/$', views.BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}),name="detail_book"), url(r'^authors/$', views.AuthorModelView.as_view({"get": "list", "post": "create"}), name="author"), url(r'^authors/(?P<pk>\d+)/$',views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_author"), url(r'^publishers/$', views.PublisherModelView.as_view({"get": "list", "post": "create"}), name="publishers"), url(r'^publishers/(?P<pk>\d+)/$',views.PublisherModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_publishers"), ]
app01里面的models.py的代码
from django.db import models # Create your models here. class UserInfo(models.Model): USER_TYPE = ( (1, '普通用户'), (2, 'VIP'), (3, 'SVIP') ) user_type = models.IntegerField(choices=USER_TYPE, default=1) username = models.CharField(max_length=32) password = models.CharField(max_length=64) def __str__(self): return self.username # 设置 one to one 1个用户不能在不同设备上登录 # 设置 Freikey 支持1个用户 在不同设备上同时登录 class UserToken(models.Model): user = models.OneToOneField(UserInfo, on_delete=models.CASCADE) token = models.CharField(max_length=64) def __str__(self): return self.token class Book(models.Model): title = models.CharField(max_length=32) price = models.IntegerField() pub_date = models.DateField() publish = models.ForeignKey("Publish") authors = models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name = models.CharField(max_length=32) email = models.EmailField() def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() def __str__(self): return self.name
CBV视图里面的代码
from django.shortcuts import render from rest_framework import viewsets from rest_framework.views import APIView from app01 import models from django.http import JsonResponse from app01.utils.auth import CustomAuthentication from app01.utils.serializers import BookModelSerializers,PublishModelSerializers,AuthorModelSerializers # Create your views here. def md5(user): import hashlib import time # 当前时间,相当于生成一个随机的字符串 ctime = str(time.time()) m = hashlib.md5(bytes(user, encoding='utf-8')) m.update(bytes(ctime, encoding='utf-8')) return m.hexdigest() class LoginView(APIView): """60s内任何人都只可以登录3次""" authentication_classes = [] permission_classes = [] def post(self, request, *args, **kwargs): ret = {'code': 1000, 'msg': None} try: user = request._request.POST.get('username') pwd = request._request.POST.get('password') obj = models.UserInfo.objects.filter(username=user, password=pwd).first() if not obj: ret['code'] = 1001 ret['msg'] = '用户名或密码错误' # 为用户创建token token = md5(user) # 存在就更新,不存在就创建 models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '请求异常' return JsonResponse(ret) class StartAuthentication(): """ 局部使用认证功能的情况下,优先继承该类 """ authentication_classes = [CustomAuthentication] class AuthorModelView(StartAuthentication,viewsets.ModelViewSet): """不管有没有登陆,都可以查看且60内只能看三次""" # 如果不使用继承类来实现局部认证就可以使用下面情况 # authentication_classes = [CustomAuthentication] queryset = models.Author.objects.all() serializer_class = AuthorModelSerializers
app01/utils/auth.py 里面的代码
from rest_framework import authentication,exceptions from app01.models import UserToken class CustomAuthentication(authentication.BaseAuthentication): def authenticate(self, request): token = request.GET.get('token') token_obj = UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed('验证失败') return (token_obj.user, token_obj.token) def authenticate_header(self, request): pass
上面的局部认证只要在相应的类里面继承(StartAuthentication)就行了
全局视图认证组件
只要在settings.py配置
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["resdemo.service.auth.CustomAuthentication"] }
上面黄色部位就是你的token认证组件的位置(也就是上面的author.py里面的CustomAuthentication类
如果要某一个不需要认证就在其中添加
authentication_classes = [] #里面为空,代表不需要认证,这个就是优先执行自己的,不执行全局的,因为自己的为空所以就是不要认证。
如上所示我们配置了全局模式当我们有一个不需要认证的时候就直接在这里面加上 authentication_classes = []
使用RestAPI认证功能小结
1、创建2张表userinfo 和usertoken表
2、认证类的authenticate方法去请求头中获取token信息,然后去token表中查询token是否存在;
3、查询到token 是正常用户(返回 用户名)否则为匿名用户(raise异常终止认证、或者 return none进行下一个认证)
4、局部应用
方式1::哪个CBV需要认证在类中定义authentication_classes =[CustomAuthentication ]
方式2:额外定义1个类,CBV多继承
方式3:全局配置使用认证功能,那个CBV不使用authentication_classes =[ ]
5、全局使用 在配置文件中配置 ,注意重新创建一个模块,把认证类放里面;
自己写认证方法总结:
1、创建认证类
- 继承BaseAuthentication --->>1.重写authenticate方法;2.authenticate_header方法直接写pass就可以(这个方法必须写) 继承的类在 from rest_framework import authentication中authentication.BaseAuthentication
2、authenticate()方法返回值(三种)
- None ----->>>当前认证不管,等下一个认证来执行
- raise exceptions.AuthenticationFailed('用户认证失败') # from rest_framework import exceptions
- 有返回值元祖形式:(元素1,元素2) #元素1复制给request.user; 元素2复制给request.auth
3、需要创建导入的类
from rest_framework import authentication,exceptions from app01.models import UserToken
权限组件
添加权限
1、在app01/utils/permission.py的文件中的代码
#!/usr/bin/evn python #-*-coding:utf-8-*- from rest_framework import permissions class SVIPPermission(permissions.BasePermission): message = "不是SVIP不给查看" # 当没有权限的时候提示信息 def has_permission(self, request, view): if request.user.user_type != 3: return False return True class MyPermission(permissions.BasePermission): message = "普通用户不给查看" def has_permission(self, request, view): if request.user.user_type == 1: return False return True
2、在setting里面设置全局权限
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.TokenAuth"], "DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.SVIPPermission"], }
如果某一个视图不想要权限的话就可以在该视图中加 permission_class = []
或者不执行全局的权限只执行自己想要的权限
3、views.py 添加权限
from django.shortcuts import render from rest_framework import viewsets from rest_framework.views import APIView from app01 import models from django.http import JsonResponse from app01.utils.auth import CustomAuthentication from app01.utils.serializers import BookModelSerializers,PublishModelSerializers,AuthorModelSerializers from app01.utils.permission import MyPermission # Create your views here. def md5(user): import hashlib import time # 当前时间,相当于生成一个随机的字符串 ctime = str(time.time()) m = hashlib.md5(bytes(user, encoding='utf-8')) m.update(bytes(ctime, encoding='utf-8')) return m.hexdigest() class LoginView(APIView): """60s内任何人都只可以登录3次""" authentication_classes = [] permission_classes = [] def post(self, request, *args, **kwargs): ret = {'code': 1000, 'msg': None} try: user = request._request.POST.get('username') pwd = request._request.POST.get('password') obj = models.UserInfo.objects.filter(username=user, password=pwd).first() if not obj: ret['code'] = 1001 ret['msg'] = '用户名或密码错误' # 为用户创建token token = md5(user) # 存在就更新,不存在就创建 models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '请求异常' return JsonResponse(ret) class StartAuthentication(): """ 局部使用认证功能的情况下,优先继承该类 """ authentication_classes = [CustomAuthentication] class AuthorModelView(viewsets.ModelViewSet): """不管有没有登陆,都可以查看且""" # 不执行认证和权限认证 authentication_classes = [] permission_classes = [] queryset = models.Author.objects.all() serializer_class = AuthorModelSerializers class BookModelView(viewsets.ModelViewSet): """登陆后SVIP用户可以查看""" # 执行全局的权限和认证,全局的权限是只能SVIP用户可以访问 queryset = models.Book.objects.all() serializer_class = BookModelSerializers class PublisherModelView(viewsets.ModelViewSet): """登陆后普通用户不可以看""" # 不执行全局的权限,只执行MyPermission这个权限 permission_classes = [MyPermission] queryset = models.Publish.objects.all() serializer_class = PublishModelSerializers
4、url.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.LoginView.as_view(), name="login"), url(r'^books/$', views.BookModelView.as_view({"get": "list", "post": "create"}), name="books"), url(r'^books/(?P<pk>\d+)/$', views.BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}),name="detail_book"), url(r'^authors/$', views.AuthorModelView.as_view({"get": "list", "post": "create"}), name="author"), url(r'^authors/(?P<pk>\d+)/$',views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_author"), url(r'^publishers/$', views.PublisherModelView.as_view({"get": "list", "post": "create"}), name="publishers"), url(r'^publishers/(?P<pk>\d+)/$',views.PublisherModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_publishers"), ]
5、app01/utils/auth.py的代码上面有不需要改变的,这里就不写了
总结:
(1)使用
- 自己写的权限类:1.必须继承BasePermission类; 2.必须实现:has_permission方法
- 这个继承的类在 from rest_framework import permission中permission.BaseAuthentication
(2)返回值
- True 有权访问
- False 无权访问
(3)局部
- permission_classes = [MyPremission,]
(4)全局
REST_FRAMEWORK = { #权限 "DEFAULT_PERMISSION_CLASSES":['app01.utils.permission.SVIPPremission'], }
节流
throttle(访问频率)组件
在app01.utils.throttle.py里面的代码
from rest_framework import throttling import time VISIT_RECORD = {} class VisitThrottle(throttling.BaseThrottle): """60s内只能访问三次""" def __init__(self): self.history = None # 初始化访问记录 def allow_request(self, request, view): remote_addr = self.get_ident(request) ctime = time.time() # 如果当前IP不在访问记录里面,就添加到记录 if remote_addr not in VISIT_RECORD: VISIT_RECORD[remote_addr] = [ctime, ] # 键值对的形式保存 return True # True表示可以访问 # 获取当前ip的历史访问记录 history = VISIT_RECORD.get(remote_addr) self.history = history # 如果有历史访问记录,并且最早一次的访问记录离当前时间超过60s,就删除最早的那个访问记录, # 只要为True,就一直循环删除最早的一次访问记录 # 结合下面的3次可以知道当我们事件 while history and history[-1] < ctime - 60: # 最早的访问一次加60s小于当前时间 history.pop() # 如果访问记录不超过三次,就把当前的访问记录插到第一个位置(pop删除最后一个) if len(history) < 3: history.insert(0, ctime) return True else: return False def wait(self): '''还需要等多久才能访问''' ctime = time.time() return 60 - (ctime - self.history[-1])
在views.py的代码
from django.shortcuts import render from rest_framework import viewsets from rest_framework.views import APIView from app01 import models from django.http import JsonResponse from app01.utils.auth import CustomAuthentication from app01.utils.serializers import BookModelSerializers,PublishModelSerializers,AuthorModelSerializers from app01.utils.permission import MyPermission # Create your views here. def md5(user): import hashlib import time # 当前时间,相当于生成一个随机的字符串 ctime = str(time.time()) m = hashlib.md5(bytes(user, encoding='utf-8')) m.update(bytes(ctime, encoding='utf-8')) return m.hexdigest() class LoginView(APIView): """60s内任何人都只可以登录3次""" authentication_classes = [] permission_classes = [] def post(self, request, *args, **kwargs): ret = {'code': 1000, 'msg': None} try: user = request._request.POST.get('username') pwd = request._request.POST.get('password') obj = models.UserInfo.objects.filter(username=user, password=pwd).first() if not obj: ret['code'] = 1001 ret['msg'] = '用户名或密码错误' # 为用户创建token token = md5(user) # 存在就更新,不存在就创建 models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '请求异常' return JsonResponse(ret) class StartAuthentication(): """ 局部使用认证功能的情况下,优先继承该类 """ authentication_classes = [CustomAuthentication] class AuthorModelView(viewsets.ModelViewSet): """不管有没有登陆,都可以查看且60内只能看三次""" # 不执行认证和权限认证 authentication_classes = [] permission_classes = [] queryset = models.Author.objects.all() serializer_class = AuthorModelSerializers class BookModelView(viewsets.ModelViewSet): """登陆后SVIP用户可以查看不限次数""" # 执行全局的权限和认证,全局的权限是只能SVIP用户可以访问 throttle_classes = [] queryset = models.Book.objects.all() serializer_class = BookModelSerializers class PublisherModelView(viewsets.ModelViewSet): """登陆后普通用户不可以看且60内只能看三次""" # 不执行全局的权限,只执行MyPermission这个权限 permission_classes = [MyPermission] queryset = models.Publish.objects.all() serializer_class = PublishModelSerializers
settings中全局配置节流
REST_FRAMEWORK = { #节流 "DEFAULT_THROTTLE_CLASSES":['app01.utils.throttle.VisitThrottle'], }
内置的节流类
(1)throttle.py
from rest_framework import throttling class VisitThrottle2(throttling.SimpleRateThrottle): '''匿名用户60s只能访问三次(根据ip)''' scope = 'NBA' # 这里面的值,自己随便定义,settings里面根据这个值配置Rate def get_cache_key(self, request, view): # 通过ip限制节流 return self.get_ident(request) class UserThrottle(throttling.SimpleRateThrottle): '''登录用户60s可以访问10次''' scope = 'NBAUser' # 这里面的值,自己随便定义,settings里面根据这个值配置Rate def get_cache_key(self, request, view): return request.user.username
(2)settings.py
#全局 REST_FRAMEWORK = { #节流 "DEFAULT_THROTTLE_CLASSES":['app01.utils.throttle.UserThrottle'], #全局配置,登录用户节流限制(10/m) "DEFAULT_THROTTLE_RATES":{ 'NBA':'3/m', #没登录用户3/m,NBA就是scope定义的值 'NBAUser':'10/m', #登录用户10/m,NBAUser就是scope定义的值 } }
(3) views.py
from django.shortcuts import render from rest_framework import viewsets from rest_framework.views import APIView from app01 import models from django.http import JsonResponse from app01.utils.auth import CustomAuthentication from app01.utils.serializers import BookModelSerializers,PublishModelSerializers,AuthorModelSerializers from app01.utils.permission import MyPermission from app01.utils.throttle import VisitThrottle2 # Create your views here. def md5(user): import hashlib import time # 当前时间,相当于生成一个随机的字符串 ctime = str(time.time()) m = hashlib.md5(bytes(user, encoding='utf-8')) m.update(bytes(ctime, encoding='utf-8')) return m.hexdigest() class LoginView(APIView): """60s内任何人都只可以登录3次""" authentication_classes = [] permission_classes = [] def post(self, request, *args, **kwargs): ret = {'code': 1000, 'msg': None} try: user = request._request.POST.get('username') pwd = request._request.POST.get('password') obj = models.UserInfo.objects.filter(username=user, password=pwd).first() if not obj: ret['code'] = 1001 ret['msg'] = '用户名或密码错误' # 为用户创建token token = md5(user) # 存在就更新,不存在就创建 models.UserToken.objects.update_or_create(user=obj, defaults={'token': token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '请求异常' return JsonResponse(ret) class StartAuthentication(): """ 局部使用认证功能的情况下,优先继承该类 """ authentication_classes = [CustomAuthentication] class AuthorModelView(viewsets.ModelViewSet): """不管有没有登陆,都可以查看且60内只能看三次使用的是自定制的节流""" # 不执行认证和权限认证 authentication_classes = [] permission_classes = [] # 因为全局设置的是登录用户(10/m),这里不需要登录,所以就是用匿名用户ip限流(3/m) throttle_classes = [VisitThrottle2] queryset = models.Author.objects.all() serializer_class = AuthorModelSerializers class BookModelView(viewsets.ModelViewSet): """登陆后SVIP用户可以查看不限次数""" # 执行全局的权限和认证,全局的权限是只能SVIP用户可以访问 throttle_classes = [] queryset = models.Book.objects.all() serializer_class = BookModelSerializers class PublisherModelView(viewsets.ModelViewSet): """登陆后普通用户不可以看,且SVIP用户60s内能看10次使用的是内置的节流""" # 不执行全局的权限,只执行MyPermission这个权限 permission_classes = [MyPermission] queryset = models.Publish.objects.all() serializer_class = PublishModelSerializers
(4) 里面的认证和权限和上面的一样
说明:
- API.utils.throttle.UserThrottle 这个是全局配置(根据ip限制,10/m)
- DEFAULT_THROTTLE_RATES --->>>设置访问频率的
- throttle_classes = [VisitThrottle,] --->>>局部配置(不实用settings里面默认的全局配置,启用现在的配置)
所有的认证、权限、节流的代码
from django.db import models # Create your models here. class UserInfo(models.Model): USER_TYPE = ( (1, '普通用户'), (2, 'VIP'), (3, 'SVIP') ) user_type = models.IntegerField(choices=USER_TYPE, default=1) username = models.CharField(max_length=32) password = models.CharField(max_length=64) def __str__(self): return self.username # 设置 one to one 1个用户不能在不同设备上登录 # 设置 Freikey 支持1个用户 在不同设备上同时登录 class UserToken(models.Model): user = models.OneToOneField(UserInfo, on_delete=models.CASCADE) token = models.CharField(max_length=64) def __str__(self): return self.token class Book(models.Model): title = models.CharField(max_length=32) price = models.IntegerField() pub_date = models.DateField() publish = models.ForeignKey("Publish") authors = models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name = models.CharField(max_length=32) email = models.EmailField() def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() def __str__(self): return self.name
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.CustomAuthentication"], "DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.SVIPPermission"], "DEFAULT_THROTTLE_CLASSES": ['app01.utils.throttle.UserThrottle'], # 全局配置,登录用户节流限制(10/m) "DEFAULT_THROTTLE_RATES": { 'NBA': '4/m', # 没登录用户4/m(表示60s4次),NBA就是scope定义的值 'NBAUser': '10/m', # 登录用户10/m,NBAUser就是scope定义的值 } }
#!/usr/bin/evn python #-*-coding:utf-8-*- from rest_framework import permissions class SVIPPermission(permissions.BasePermission): message = "不是SVIP不给查看" # 当没有权限的时候提示信息 def has_permission(self, request, view): if request.user.user_type != 3: return False return True class MyPermission(permissions.BasePermission): message = "普通用户不给查看" def has_permission(self, request, view): if request.user.user_type == 1: return False return True
#!/usr/bin/evn python # -*-coding:utf-8-*- from rest_framework import authentication,exceptions from app01.models import UserToken class CustomAuthentication(authentication.BaseAuthentication): def authenticate(self, request): token = request.GET.get('token') token_obj = UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed('验证失败') return (token_obj.user, token_obj.token) def authenticate_header(self, request): pass
#!/usr/bin/evn python #-*-coding:utf-8-*- from app01.models import * from rest_framework import serializers class AuthorModelSerializers(serializers.ModelSerializer): class Meta: model = Author fields = "__all__" class BookModelSerializers(serializers.ModelSerializer): class Meta: model = Book fields = "__all__" class PublishModelSerializers(serializers.ModelSerializer): class Meta: model = Publish fields = "__all__"
#!/usr/bin/evn python # -*-coding:utf-8-*- from rest_framework import throttling import time VISIT_RECORD = {} class VisitThrottle(throttling.BaseThrottle): """60s内只能访问三次""" def __init__(self): self.history = None # 初始化访问记录 def allow_request(self, request, view): remote_addr = self.get_ident(request) ctime = time.time() # 如果当前IP不在访问记录里面,就添加到记录 if remote_addr not in VISIT_RECORD: VISIT_RECORD[remote_addr] = [ctime, ] # 键值对的形式保存 return True # True表示可以访问 # 获取当前ip的历史访问记录 history = VISIT_RECORD.get(remote_addr) self.history = history # 如果有历史访问记录,并且最早一次的访问记录离当前时间超过60s,就删除最早的那个访问记录, # 只要为True,就一直循环删除最早的一次访问记录 # 结合下面的3次可以知道当我们事件 while history and history[-1] < ctime - 60: # 最早的访问一次加60s小于当前时间 history.pop() # 如果访问记录不超过三次,就把当前的访问记录插到第一个位置(pop删除最后一个) if len(history) < 3: history.insert(0, ctime) return True else: return False def wait(self): '''还需要等多久才能访问''' ctime = time.time() return 60 - (ctime - self.history[-1]) class VisitThrottle2(throttling.SimpleRateThrottle): '''匿名用户60s只能访问4次(根据ip)''' scope = 'NBA' # 这里面的值,自己随便定义,settings里面根据这个值配置Rate def get_cache_key(self, request, view): # 通过ip限制节流 return self.get_ident(request) class UserThrottle(throttling.SimpleRateThrottle): '''登录用户60s可以访问10次''' scope = 'NBAUser' # 这里面的值,自己随便定义,settings里面根据这个值配置Rate def get_cache_key(self, request, view): return request.user.username
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.LoginView.as_view(), name="login"), url(r'^books/$', views.BookModelView.as_view({"get": "list", "post": "create"}), name="books"), url(r'^books/(?P<pk>\d+)/$', views.BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}),name="detail_book"), url(r'^authors/$', views.AuthorModelView.as_view({"get": "list", "post": "create"}), name="author"), url(r'^authors/(?P<pk>\d+)/$',views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_author"), url(r'^publishers/$', views.PublisherModelView.as_view({"get": "list", "post": "create"}), name="publishers"), url(r'^publishers/(?P<pk>\d+)/$',views.PublisherModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detail_publishers"), ]