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