天天看點

基于Django實作 RESTful API 之RestFramework架構3一、首先實作使用者login登入認證功能二、基于RestFramework的認證元件做視圖處理類的user認證三、基于RestFramework的認證元件做視圖處理類的權限認證四、基于RestFramework的認證元件做視圖處理類的通路頻率限制五、基于RestFramework的視圖處理類的分頁元件六、基于RestFramework的視圖處理類響應器七、基于RestFramework的視圖處理類的url注冊器

接下來學習RestFramework架構中的認證、權限、頻率元件的使用

一、首先實作使用者login登入認證功能

做使用者登入認證功能可以通過session、cookie和token三種形式,下面的login認證基于token實作

  • 關鍵點

    -- 首先需要設計使用者表、使用者token表,使用者表需要包含使用者類型,使用者token表包含使用者的token值

    -- 使用者的token值應該是一個随機的、動态的字元串

    -- 使用者認證成功需要将使用者資訊和對應的token值傳回,後續通路url就可以通過token值來判斷使用者的狀态

附:models.py

from django.db import models

class Userinfo(models.Model):
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=64)
    user_type = models.IntegerField(choices=((1,"common_user"),(2,"VIP_user"),(3,"SVIP_user")))

class UserToken(models.Model):
    user = models.OneToOneField(to="Userinfo")
    token = models.CharField(max_length=128)
           
  • view.py認證邏輯
from rest_framework.views import APIView
from rest_framework.response import Response
import uuid


class LoginView(APIView):
    """
    1000:成功
    1001:使用者名或者密碼錯誤
    1002:異常錯誤
    """
    def post(self, request):
        #自定義的傳回體資料
        ret = {"code": 1000, "msg": None, "user": None}
        try:
            print(request.data)  # 重裝後的request,request.data從中取所需的資料
            name = request.data.get("name")#擷取前端使用者名
            pwd = request.data.get("pwd")#擷取前端密碼
            #資料庫校驗使用者名密碼是否正确
            user_obj = models.Userinfo.objects.filter(name=name, pwd=pwd).first()
            if user_obj:
                #通過uuid子產品獲得随機字元串作為token值
                random_str = uuid.uuid4()
                #UserToken表中有該使用者的資訊就用新token值覆寫,沒有就建立資料
                models.UserToken.objects.update_or_create(user=user_obj, defaults={"token": random_str})
                ret["user"] = user_obj.name
                ret["token"] = random_str
            else:
                ret["code"] = 1001
                ret["msg"] = "使用者名或者密碼錯誤"

        except Exception as e:
            ret["code"] = 1002
            ret["msg"] = str(e)

        return Response(ret)
           
這樣就實作了使用者的login認證。。。。。。。。。

二、基于RestFramework的認證元件做視圖處理類的user認證

首先看一下UserAuth的具體用法:

  • 關鍵點:

    --視圖處理類下定義名為authentication_classes屬性,屬性值是一個清單或者元組

    --寫一個UserAuth(類名随便)認證的類,将類名添加到authentication_classes清單中

    --UserAuth認證類必須有一個authenticate(self,request)方法,這個方法下寫使用者認證邏輯,認證成功:可以傳回資料,也可以什麼都不做。認證失敗:,必須抛異常

    -- UserAuth類繼承BaseAuthentication類,其實就是給類加了一個authenticate_header方法,沒理由,源碼要求!

這四點你一定看不懂是什麼意思,很正常,這都是RestFramework源碼給設定的特定規則,稍後認證源碼流程分析會一一分析解答

view.py代碼示例:代碼實作了book視圖類的使用者認證功能

from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication

#用于認證的類
class UserAuth(BaseAuthentication):
   def authenticate(self, request):
       token = request.query_params.get("token")
       usertoken_obj = models.UserToken.objects.filter(token=token).first()
       if usertoken_obj:
           return usertoken_obj.user, usertoken_obj.token
       else:
           raise AuthenticationFailed("使用者認證失敗,您無權通路!!!")

# book視圖處理類
class Booklist(ModelViewSet):
   authentication_classes = [UserAuth, ]

   queryset = models.Book.objects.all()
   serializer_class = BooklistSerializer
           

接下來一張圖帶你透析認證元件:

基于Django實作 RESTful API 之RestFramework架構3一、首先實作使用者login登入認證功能二、基于RestFramework的認證元件做視圖處理類的user認證三、基于RestFramework的認證元件做視圖處理類的權限認證四、基于RestFramework的認證元件做視圖處理類的通路頻率限制五、基于RestFramework的視圖處理類的分頁元件六、基于RestFramework的視圖處理類響應器七、基于RestFramework的視圖處理類的url注冊器

UserAuth認證源碼解析.jpg

- 這樣的寫法隻是給book視圖類加上了認證,可以将認證類單獨解除安裝一個py檔案中,然後在全局settings中添加對應的配置項,這樣就可以做到對所有的視圖類添加認證功能了!!!

根據實際需求選擇合适的方法!!!

app001-utils-Userauth.py

from app001 import models
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication

class UserAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        usertoken_obj = models.UserToken.objects.filter(token=token).first()
        if usertoken_obj:
            return usertoken_obj.user, usertoken_obj.token
        else:
            raise AuthenticationFailed("使用者認證失敗,您無權通路!!!")
           

settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'app001.utils.Userauth.UserAuth',
    )
}
           

view.py

from app001 import models
from app001.serializers.bookserializer import BooklistSerializer

# 重裝了APIView
from rest_framework.viewsets import ModelViewSet

# book視圖處理類
class Booklist(ModelViewSet):
   queryset = models.Book.objects.all()
   serializer_class = BooklistSerializer
           

三、基于RestFramework的認證元件做視圖處理類的權限認證

  • --視圖處理類下定義名為permission_classes屬性,屬性值是一個清單或者元組

    --寫一個UserAuth(類名随便)認證的類,将類名添加到permission_classes清單中

    --UserAuth認證類必須有一個has_permission(self,request,view)方法,這個方法下寫使用者認證邏輯,認證成功:傳回true。認證失敗:,傳回false。

permission源碼執行流程:

基于Django實作 RESTful API 之RestFramework架構3一、首先實作使用者login登入認證功能二、基于RestFramework的認證元件做視圖處理類的user認證三、基于RestFramework的認證元件做視圖處理類的權限認證四、基于RestFramework的認證元件做視圖處理類的通路頻率限制五、基于RestFramework的視圖處理類的分頁元件六、基于RestFramework的視圖處理類響應器七、基于RestFramework的視圖處理類的url注冊器

permission源碼解析_副本.jpg

  • app001-utils-permission_class.py
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
    message="您沒有通路權限!"
    def has_permission(self,request,view):
        if request.user.user_type >= 2:
            return True
        return False
           
from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
# 重裝了APIView
from rest_framework.viewsets import ModelViewSet

from app001.utils.permission_class import SVIPPermission

# book視圖處理類
class Booklist(ModelViewSet):
    permission_classes = [SVIPPermission,]

    queryset = models.Book.objects.all()
    serializer_class = BooklistSerializer
           

四、基于RestFramework的認證元件做視圖處理類的通路頻率限制

1、通路頻率限制和認證、權限的執行流程一樣,都是restframework源碼提供的一種書寫格式,再此就不一一贅述。

  • app001-utils-throttle_classes.py
import time
from rest_framework.throttling import BaseThrottle

VISIT_RECORD = {}

class VisitThrottle(BaseThrottle):

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # 1. 拿到使用者請求的IP
        # print(request.META)
        ip = request.META.get("REMOTE_ADDR")
        # 2. 目前請求的時間
        now = time.time()
        # 3. 記錄通路的曆史
        if ip not in VISIT_RECORD:
            VISIT_RECORD[ip] = []

        history = VISIT_RECORD[ip]
        self.history = history
        # [11:07:20, 10:07:11, 10:07:06, 10:07:01]
        while history and now - history[-1] > 60:
            history.pop()
        # 判斷使用者在一分鐘的時間間隔内是否通路超過3次
        if len(history) >= 3:
            return False
        history.insert(0, now)

        return True

    def wait(self):
        # 目前通路時間
        ctime = time.time()
        #  通路時間曆史記錄 self.history
        return 60 - (ctime - self.history[-1])
           
from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
# 重裝了APIView
from rest_framework.viewsets import ModelViewSet
from app001.utils.throttle_classes import VisitThrottle

# book視圖處理類
class Booklist(ModelViewSet):
    throttle_classes = [VisitThrottle, ]

    queryset = models.Book.objects.all()
    serializer_class = BooklistSerializer
           

2、基于RestFramework給我們提供了幾種頻率控制元件,省去了我們在自己寫了

  • SimpleRateThrottle類,A simple cache implementation, that only requires

    .get_cache_key()

    to be overridden.
  • AnonRateThrottle類,Limits the rate of API calls that may be made by a anonymous users.
  • UserRateThrottle類,Limits the rate of API calls that may be made by a given user.
  • ScopedRateThrottle類,Limits the rate of API calls by different amounts for various parts of

    the API.

from rest_framework.throttling import SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
    scope="visit_rate"

    def get_cache_key(self,request, view):
        return self.get_ident(request)
           
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": ("app001.utils.throttle_classes.VisitThrottle",),
    "DEFAULT_THROTTLE_RATES": {
        "visit_rate": "10/m",//頻率設定
    },
}
           
from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
# 重裝了APIView
from rest_framework.viewsets import ModelViewSet
from app001.utils.throttle_classes import VisitThrottle

# book視圖處理類
class Booklist(ModelViewSet):
    throttle_classes = [VisitThrottle, ]

    queryset = models.Book.objects.all()
    serializer_class = BooklistSerializer
           

五、基于RestFramework的視圖處理類的分頁元件

Restframework提供了幾種很好的分頁方式:

  • 基于頁碼的分頁的 PageNumberPagination類
  • 基于limit offset 做分頁的 LimitOffsetPagination類
  • 基于遊标的分頁的 CursorPagination類。但這種方式存在性能問題,如果使用者吧page給改的很大,查詢速度就會很慢。還有一種頁碼加密的方式

1、PageNumberPagination類的使用

  • 方法一、視圖類繼承APIView時

    -- 參考的API:

    GET請求:http://127.0.0.1:8000/booklist/?page=2

from rest_framework.views import APIView
from app001 import models
# rest_framework重裝的response
from rest_framework.response import Response
from app001.serializers.bookserializer import BooklistSerializer
from rest_framework.pagination import PageNumberPagination

class Booklist(APIView):

    def get(self, request):
        class MyPageNumberPagination(PageNumberPagination):
            page_ size = 2    //指定的每頁顯示數
            page_query_param = "page"      //url欄的頁碼參數
            page_size_query_param = "size"     //擷取url參數中設定的每頁顯示資料條數
            max_page_size = 5       //最大支援的每頁顯示的資料條數

        book_obj = models.Book.objects.all()
        # 執行個體化
        pnp = MyPageNumberPagination()
        # 分頁
        paged_book_list = pnp.paginate_queryset(book_obj, request)
        bs = BooklistSerializer(paged_book_list, many=True)
        data = bs.data  # 序列化接口
        return Response(data)
           
  • 方式二、視圖類繼承ModelViewSet時(少用)
from rest_framework.viewsets import ModelViewSet
from rest_framework.pagination import PageNumberPagination
class MyPageNumberPagination(PageNumberPagination):
    page_size = 3

# book視圖處理
class Booklist(ModelViewSet):
    pagination_class = MyPageNumberPagination

    queryset = models.Book.objects.all()
    serializer_class = BooklistSerializer
           

2、LimitOffsetPagination類的使用

  • 這種分頁樣式反映了查找多個資料庫記錄時使用的文法。用戶端包含 “limit” 和 “offset” 查詢參數。limit 表示要傳回的 資料 的最大數量,并且等同于其他樣式中的 page_size。offset 指定查詢的起始位置與完整的未分類 資料 集的關系。多用于傳回的内容是靜态的,或者不用實時傳回資料最新的變化。Google Search 和一些論壇用了這種方式:
  • 參考API:

    GET請求:http://127.0.0.1:8000/booklist/?limit=3&offset=3

    Response響應:{ "count": 7,"next": "http://127.0.0.1:8000/booklist/?limit=3&offset=6", "previous": "http://127.0.0.1:8000/booklist/?limit=3","results": [...]

from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.pagination import LimitOffsetPagination

class MyPageNumberPagination(LimitOffsetPagination):
    max_limit = 3  # 最大限制預設是None
    default_limit = 2  # 設定每一頁顯示多少條
    limit_query_param = 'limit'  # 往後取幾條
    offset_query_param = 'offset'  # 目前所在的位置

class Booklist(APIView):

    def get(self, request):
        book_obj = models.Book.objects.all()
        # 執行個體化
        pnp = MyPageNumberPagination()
        # 分頁
        paged_book_list = pnp.paginate_queryset(book_obj, request)
        bs = BooklistSerializer(paged_book_list, many=True)
        data = bs.data  # 序列化接口
        # return Response(data) #不含上一頁下一頁
        return pnp.get_paginated_response(data)
           
  • LimitOffsetPagination 類包含一些可以被覆寫以修改分頁樣式的屬性。要設定這些屬性,應該繼承 LimitOffsetPagination 類,然後像上面那樣啟用你的自定義分頁類。

    default_limit- 一個數字值,指定用戶端在查詢參數中未提供的 limit 。預設值與 PAGE_SIZE setting key 相同。

    limit_query_param - 一個字元串值,訓示 “limit” 查詢參數的名稱。預設為 'limit'。

    offset_query_param - 一個字元串值,訓示 “offset” 查詢參數的名稱。預設為 'offset'。

    max_limit - 一個數字值,表示用戶端可以要求的最大允許 limit。預設為 None。

- 了解LIMIT / OFFSET的實際工作原理。為此,我們舉例說明,假如有大約1000個符合要求的結果。你若要前100資料,這很容易,這意味着它隻傳回與結果集比對的前100行。但是現在想要900-1000之間的資料。資料庫現在必須周遊前900個結果才能開始傳回(因為它沒有指針告訴它如何獲得結果900)。總之,LIMIT / OFFSET在大型結果集上非常慢。

3、CursorPagination類的使用

  • 現在很多場景,查詢結果在使用者浏覽過程中是變化的,例如微網誌時間線,使用者看的時候,可能後一頁的某些微網誌會被删除,而前一頁又增添了新的微網誌。這種情況就不适合用 limitoffset分頁方式了。Facebook 和 Twitter 都采用了基于遊标的分頁方法。
  • 這種方式有以下兩個特點:

    -- 1. 查詢的結果流可能是動态變化的,例如: 時間線裡出現了新的資料,或者删除了資料,這些變化都可以在 “前一頁” 或者 “後一頁” 上展現出來。

    -- 2. Cursor 展現了排序,是持久化的。一般情況下 Cursor 的順序是和時間有關。如果你的實體(例如:微網誌)可能展現給使用者多種可能的排序(例如:建立時間或者修改時間),那麼則需要建立不同的 Cursor。

    具體實作時,Cursor 可能分别建立自 建立使用者 或者 修改使用者 字段。Facebook Relay 用了查詢名稱 + 時間戳 的 Base64 形式來做 Cursor。

  • CursorPagination 類包含一些可以被覆寫以修改分頁樣式的屬性。

    要設定這些屬性,你應該繼承 CursorPagination 類,然後像上面那樣啟用你的自定義分頁類。

    -- page_size = 指定頁面大小的數字值。如果設定,則會覆寫 PAGE_SIZE 設定。預設值與 PAGE_SIZE setting key 相同。

    -- cursor_query_param = 一個字元串值,指定 “遊标” 查詢參數的名稱。預設為 'cursor'.

    -- ordering = 這應該是一個字元串或字元串清單,指定将應用基于遊标的分頁的字段。例如: ordering = 'slug'。預設為 -created。該值也可以通過在視圖上使用 OrderingFilter 來覆寫。

-關于限制偏移量和遊标分頁的分析 請參考: http://cra.mr/2011/03/08/building-cursors-for-the-disqus-api

六、基于RestFramework的視圖處理類響應器

  • restframework可以根據url的不同來源做出不同的響應,比如說,當來自浏覽器端的請求,會傳回一個頁面,當請求來自于postman等請求軟體時,傳回的是json資料,這樣的效果是restframework的響應器在控制,settings中可以按需配置:
REST_FRAMEWORK={
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',  //響應json
        'rest_framework.renderers.BrowsableAPIRenderer',   //響應頁面
    ),
}
           

七、基于RestFramework的視圖處理類的url注冊器

url注冊器隻是适用于視圖類繼承ModelViewSet類的寫法:

url注冊器會生成四條url:

^ ^booklist/$ [name='book-list']

^ ^booklist\.(?P<format>[a-z0-9]+)/?$ [name='book-list'] //可以指定資料類型

^ ^booklist/(?P<pk>[^/.]+)/$ [name='book-detail']

^ ^booklist/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='book-detail'] //可以指定單個資料資料類型

urls.py

from rest_framework import routers
router = routers.DefaultRouter() //執行個體化
router.register("booklist", views.Booklist)   //注冊

urlpatterns = [
    url(r'^', include(router.urls)),
]
           
from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
# 重裝了APIView
from rest_framework.viewsets import ModelViewSet

# book視圖處理類
class Booklist(ModelViewSet):

    queryset = models.Book.objects.all()
    serializer_class = BooklistSerializer