jwt多方式登陸

一鬼廓、基于jwt的多方式登陸

1肿仑、要求:手機、郵箱和用戶名都能登陸
2、流程分析(post請求):

-路由:自動生成
-視圖類:ViewSet(ViewSetMixin, views.APIView)
-序列化類:重寫validate方法尤慰,在這里面對用戶名和密碼進行校驗

3馏锡、代碼實現(xiàn)

###路由
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/',views.LoginView.as_view({'post':'post'}))
]
#自動生成路由無法實現(xiàn)。因為action中有對應關(guān)系伟端,只要把視圖函數(shù)中的post函數(shù)改成create就行了
#post:create
#get:list
#get:retrieve
#put:update
#delete:destroy

###表模型
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    phone = models.CharField(max_length=32,unique=True)

###視圖
from rest_framework.viewsets import ViewSet
from app01 import serializer
from app01.utils import APIResponse
class LoginView(ViewSet):
    def post(self,request,*args,**kwargs):
#實例化得到一個序列化類的對象杯道,里面加上context來進行與序列化類的數(shù)據(jù)傳遞
        ser = serializer.LoginSerializer(data=request.data,context={'request':request})
#序列化類對象的校驗,字段自己的校驗责蝠、局部鉤子校驗以及全局鉤子校驗
        if ser.is_valid():
#如果通過校驗党巾,表示登陸成功,返回手動簽發(fā)的token
            token = ser.context.get('token')
            username = ser.context.get('username')
            return APIResponse(token=token,username=username)
        else:
            return APIResponse(code=101,msg='用戶名或密碼錯誤')
        #方式二
        #try:
            #ser = serializer.LoginSerializer(data=request.data,context={'request':request})
            #ser.is_valid(raise_exception=true)
            #token = ser.context.get('token')
            #username = ser.context.get('username')
            #return APIResponse(token=token,username=username)
        #except Exception as e:
            #return APIResponse(code=101,msg='用戶名或密碼錯誤') 

###序列化類
from rest_framework import serializers
from rest_framework_jwt.utils import jwt_encode_handler,jwt_payload_handler
from app01 import models
from rest_framework.exceptions import ValidationError
import re
class LoginSerializer(serializers.ModelSerializer):
# 覆蓋霜医,避免login校驗username有數(shù)據(jù)庫唯一字段約束的限制
    username = serializers.CharField()
    class Meta:
        model = models.UserInfo
        fields = ['username','password']
    def validate(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
#分三種情況齿拂,取出對應的user對象
        if re.match('^1[3-9]\d{9}$',username):
            user = models.UserInfo.objects.filter(phone=username).first()
        elif re.match('^.+@.+$',username):
            user = models.UserInfo.objects.filter(email=username).first()
        else:
            user = models.UserInfo.objects.filter(username=username).first()
#基于auth組件校驗密碼
        if user and user.check_password(password):
#登陸成功,生成token
#drf-jwt中有通過user對象生成token的方法支子,中間借助payload轉(zhuǎn)碼
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            # self.context.get('request')
            #視圖類和序列化類之間通過context這個字典傳遞數(shù)據(jù)创肥,將token和username傳給視圖
            self.context['token'] = token
            self.context['username'] = user.username
            return attrs
        else:
            raise ValidationError('用戶名以及密碼錯誤')

###自定義APIResponse以及全局異常
from rest_framework.response import Response
#自己封裝的response對象
class APIResponse(Response):
    def __init__(self,code=100,msg='登陸成功', data=None,
                 status=None, headers=None,
                 content_type=None,**kwargs):
        dic = {'code':code,'msg':msg}
        if data:
            dic['data'] = data
        dic.update(kwargs)
        super().__init__(data=dic,status=status,headers=headers,content_type=content_type)

#全局異常的捕獲
from rest_framework.views import exception_handler
from rest_framework import status
def common_exception_handler(exc, context):
        response = exception_handler(exc, context)
        if response is None:
            response = Response({'code':999,'detail': '未知錯誤'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        return response
#全局異常捕獲需在settings.py中設置
REST_FRAMEWORK = {
            'EXCEPTION_HANDLER':'app01.utils.common_exception_handler'
        }

###注意點:1达舒、全局鉤子中要有返回值attrs值朋。2、封裝的response對象中傳入的data是字典巩搏。3昨登、在序列化中要重寫username,因為本身auth表中就帶有username贯底,不重寫相當于保存一樣的就會報錯丰辣。

二、自定義user表禽捆,簽發(fā)token笙什,認證類

1、不借助auth表胚想,自定義user表琐凭,視圖函數(shù)繼承APIView,不借助序列器
2浊服、代碼實現(xiàn)

###路由
path('login2/',views.MyLoginView.as_view())
path('order/',views.OrderAPIView.as_view())

###表模型
class MyUser(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    phone = models.CharField(max_length=32)
    email = models.EmailField()

###自定義jwt認證
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication, jwt_decode_handler
from rest_framework_jwt.utils import jwt_decode_handler
import jwt
class JwtAuthentication(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        token = request.META.get('HTTP_Authorization'.upper())
        try:
            payload = jwt_decode_handler(token)
        except jwt.ExpiredSignature:
            raise AuthenticationFailed('過期了')
        except jwt.DecodeError:
            raise AuthenticationFailed('解碼錯誤')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('不合法的token')
        print(payload)  #{'user_id': 1, 'username': 'egon', 'exp': 1605532596, 'email': '11@qq.com'}
#user = models.MyUser.objects.get(id = payload('user_id))  # 正常情況下通過認證统屈,得到當前登陸用戶(需要每次訪問數(shù)據(jù)庫),出于效率考慮,我們沒有查詢數(shù)據(jù)庫牙躺,自己構(gòu)造了一個user實例化對象
#user = models.MyUser(id = payload['user_id'],username = payload['username'])
        user = payload
        return (user,token)

###視圖
from rest_framework.views import APIView
import re
from app01 import models
from rest_framework_jwt.utils import jwt_encode_handler,jwt_payload_handler
class MyLoginView(APIView):
    def post(self,request,*args,**kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        if re.match('^1[3-9]\d{9}$',username):
            user = models.MyUser.objects.filter(phone=username).first()
        elif re.match('^.+@.+$',username):
            user = models.MyUser.objects.filter(email=username).first()
        else:
            user = models.MyUser.objects.filter(username=username).first()
        if user and user.password == password:
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return APIResponse(token=token,username=user.username)
        else:
            return APIResponse(code=101,msg='用戶名或密碼錯誤')

from app01 import auth
from app01.auth import JwtAuthentication
class OrderAPIView(APIView):
    authentication_classes = [auth.JwtAuthentication, ]
    def get(self,request):
        print(request.user)
        #user是jwt認證中的payload愁憔,是個字典,內(nèi)部有user_id
        #后續(xù)要查詢該用戶的所有訂單孽拷,直接根據(jù)user_id查詢即可
        return APIResponse(msg='查詢訂單成功')

三吨掌、表關(guān)系以及抽象表的建立

:以后所有的數(shù)據(jù)刪除,盡量用軟刪除,使用一個字段標志是否刪除思犁,而不是真正的從數(shù)據(jù)庫中刪除
優(yōu)點:1代虾、這樣刪除數(shù)據(jù)不會影響索引,不會導致索引失效
2激蹲、之前存的用戶數(shù)據(jù)還在棉磨,以備以后使用

#表模型
#抽象出一個基表(不在數(shù)據(jù)庫生成,abstract=True)学辱,只用來繼承
class BaseModel(models.Model):
    is_delete = models.BooleanField(default=False)
    create_time = models.DateTimeField(auto_now_add=True)
    class Meta:
        #基表必須設置abstract = True,基表就是給普通Model類繼承使用的乘瓤,設置了abstract就不會完成數(shù)據(jù)庫遷移以及建表
        abstract = True
class Book(BaseModel):
    name = models.CharField(max_length=16)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publisher = models.ForeignKey(to='Publisher', db_constraint=False, on_delete=models.DO_NOTHING)
    # 重點:多對多外鍵實際在關(guān)系表中,ORM默認關(guān)系表中兩個外鍵都是級聯(lián)
    # ManyToManyField字段不提供設置on_delete策泣,如果想設置關(guān)系表級聯(lián)衙傀,只能手動定義關(guān)系表
    authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
    @property
    def publish_name(self):
        return self.publish.name

    @property
    def author_list(self):
        # ll=[]
        # for author in self.authors.all():
        #     ll.append({'name':author.name,'sex':author.get_sex_display()})
        return [{'name': author.name, 'sex': author.get_sex_display()} for author in self.authors.all()]

class Publish(BaseModel):
    name = models.CharField(max_length=16)
    address = models.CharField(max_length=64)

class Author(BaseModel):
    name = models.CharField(max_length=16)
    sex = models.IntegerField(choices=[(0, '男'), (1, '女')], default=0)

class AuthorDetail(BaseModel):
    mobile = models.CharField(max_length=11)
    # 有作者可以沒有詳情,刪除作者萨咕,詳情一定會被級聯(lián)刪除
    # 外鍵字段為正向查詢字段统抬,related_name是反向查詢字段
    author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE)
    # 二、表斷關(guān)聯(lián)
    # 1危队、表之間沒有外鍵關(guān)聯(lián)聪建,但是有外鍵邏輯關(guān)聯(lián)(有充當外鍵的字段)
    # 2、斷關(guān)聯(lián)后不會影響數(shù)據(jù)庫查詢效率茫陆,但是會極大提高數(shù)據(jù)庫增刪改效率(不影響增刪改查操作)
    # 3金麸、斷關(guān)聯(lián)一定要通過邏輯保證表之間數(shù)據(jù)的安全
    # 4、斷關(guān)聯(lián)
    # 5簿盅、級聯(lián)關(guān)系
    #       作者沒了挥下,詳情也沒:on_delete=models.CASCADE
    #       出版社沒了,書還是那個出版社出版:on_delete=models.DO_NOTHING
    #       部門沒了桨醋,員工沒有部門(空不能):null=True, on_delete=models.SET_NULL
    #       部門沒了棚瘟,員工進入默認部門(默認值):default=0, on_delete=models.SET_DEFAULT

四、批量操作

###book表的單增與群增
class BookView(APIView):
    def post(self, request, *args, **kwargs):
        if isinstance(request.data, dict):
            # 增一條
            ser = serializer.BookSerializer(data=request.data)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(data=ser.data)
        elif isinstance(request.data, list):
            # 增多條
            ser = serializer.BookSerializer(data=request.data, many=True)
            # 內(nèi)部如何實現(xiàn)的喜最?
            # many=True,ser不是BookSerializer對象偎蘸,而是ListSerializer對象,套了一個個的BookSerializer
            print(type(ser))
            ser.is_valid(raise_exception=True)
            #
            from rest_framework.serializers import ListSerializer
            ser.save()  # ListSerializer的save
            return APIResponse(msg='增加%s條成功' % len(request.data))

###book表的單查與群查
class BookView(APIView):
    def get(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        if pk:
            # 單查
            # 方式一
            # book=models.Book.objects.filter(id=pk).filter(is_delete=False).first()
            # if not book:
            #     raise Exception('要查詢的不存在')

            # 方式二
            book = models.Book.objects.get(id=pk, is_delete=False)
            ser = serializer.BookSerializer(instance=book)

        else:
            # 查所有
            book_list = models.Book.objects.all().filter(is_delete=False)
            ser = serializer.BookSerializer(instance=book_list, many=True)
        return APIResponse(data=ser.data)

###book表的單改與群改
class BookView(APIView):
    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        if pk:
            # 單條修改
            book = models.Book.objects.get(id=pk, is_delete=False)
            ser = serializer.BookSerializer(instance=book, data=request.data)
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg='修改成功')
        else:
            # 分析:ListSerializer的update方法沒有寫返顺,需要我們自己寫
            from rest_framework.serializers import ListSerializer
            # pks=[item['id'] for item in request.data]

            # 如果不重寫ListSerializer的update方法禀苦,這是存不進去的
            pks = []
            for item in request.data:
                pks.append(item['id'])
                item.pop('id')

            print(request.data)
            book_list = models.Book.objects.filter(id__in=pks, is_delete=False)
            ser = serializer.BookSerializer(instance=book_list, data=request.data, many=True)
            print(type(ser))
            ser.is_valid(raise_exception=True)
            ser.save()
            return APIResponse(msg='修改%s條成功')

            # 你們能想到的方法
            # pks = []
            # for item in request.data:
            #     pks.append(item['id'])
            #     item.pop('id')
            # book_list = models.Book.objects.filter(id__in=pks, is_delete=False)
            #
            # for i,book in enumerate(book_list):
            #     ser = serializer.BookSerializer(instance=book, data=request.data[i])
            #     ser.is_valid(raise_exception=True)
            #     ser.save()
            # return APIResponse(msg='修改%s條成功'%len(book_list))

###book表的單刪與群刪
class BookView(APIView):

    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk', None)
        pks = []
        if pk:
            # 單條刪除
            # res=models.Book.objects.filter(id=pk).update(is_delete=True)
            # print(res)
            # return APIResponse(msg='刪除成功')
            pks.append(pk)
        else:
            pks = request.data

        res = models.Book.objects.filter(id__in=pks).update(is_delete=True)
        if res >= 1:
            return APIResponse(msg='刪除%s條成功' % res)
        else:
            # raise Exception('沒有要刪除的數(shù)據(jù)')
            return APIResponse(code=999, msg='沒有要刪除的數(shù)據(jù)')

###序列化類
from app01 import models

class ListBookSerializer(serializers.ListSerializer):
    # def create(self, validated_data):
    #     print('=======',validated_data)
    #     return '1'
    def update(self, instance, validated_data):
        print(instance)  # book_list:是一堆圖書對象
        print(validated_data)  # 列表套字典,是要修改的數(shù)據(jù)
        return [self.child.update(book, validated_data[i]) for i, book in enumerate(instance)]

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        list_serializer_class = ListBookSerializer  # 指定many=True的時候遂鹊,生成的ListBookSerializer的對象了
        fields = ['name', 'price', 'publish', 'authors', 'publish_name', 'author_list']
        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True},
            'publish_name': {'read_only': True},
            'author_list': {'read_only': True},
        }
    # def create(self, validated_data):
    #     print(validated_data)

###路由
path('books/', views.BookView.as_view()),
re_path('books/(?P<pk>\d+)', views.BookView.as_view()),
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末振乏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子秉扑,更是在濱河造成了極大的恐慌慧邮,老刑警劉巖调限,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異误澳,居然都是意外死亡耻矮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門忆谓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裆装,“玉大人,你說我怎么就攤上這事倡缠∩诿猓” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵昙沦,是天一觀的道長琢唾。 經(jīng)常有香客問我,道長盾饮,這世上最難降的妖魔是什么采桃? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮丘损,結(jié)果婚禮上普办,老公的妹妹穿的比我還像新娘。我一直安慰自己号俐,他們只是感情好泌豆,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布定庵。 她就那樣靜靜地躺著吏饿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蔬浙。 梳的紋絲不亂的頭發(fā)上猪落,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機與錄音畴博,去河邊找鬼笨忌。 笑死,一個胖子當著我的面吹牛俱病,可吹牛的內(nèi)容都是我干的官疲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼亮隙,長吁一口氣:“原來是場噩夢啊……” “哼途凫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起溢吻,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤维费,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犀盟,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡而晒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了阅畴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倡怎。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贱枣,靈堂內(nèi)的尸體忽然破棺而出诈胜,到底是詐尸還是另有隱情,我是刑警寧澤冯事,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布焦匈,位于F島的核電站,受9級特大地震影響昵仅,放射性物質(zhì)發(fā)生泄漏缓熟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一摔笤、第九天 我趴在偏房一處隱蔽的房頂上張望够滑。 院中可真熱鬧,春花似錦吕世、人聲如沸彰触。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽况毅。三九已至,卻和暖如春尔艇,著一層夾襖步出監(jiān)牢的瞬間尔许,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工终娃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留味廊,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓棠耕,卻偏偏與公主長得像余佛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子窍荧,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容