jwt多方式登陆

一、基于jwt的多方式登陆

1、要求:手机、邮箱和用户名都能登陆
2、流程分析(post请求):

-路由:自动生成
-视图类:ViewSet(ViewSetMixin, views.APIView)
-序列化类:重写validate方法,在这里面对用户名和密码进行校验

3、代码实现

###路由
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'}))
]
#自动生成路由无法实现。因为action中有对应关系,只要把视图函数中的post函数改成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来进行与序列化类的数据传递
        ser = serializer.LoginSerializer(data=request.data,context={'request':request})
#序列化类对象的校验,字段自己的校验、局部钩子校验以及全局钩子校验
        if ser.is_valid():
#如果通过校验,表示登陆成功,返回手动签发的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有数据库唯一字段约束的限制
    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转码
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            # self.context.get('request')
            #视图类和序列化类之间通过context这个字典传递数据,将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表,签发token,认证类

1、不借助auth表,自定义user表,视图函数继承APIView,不借助序列器
2、代码实现

###路由
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))  # 正常情况下通过认证,得到当前登陆用户(需要每次访问数据库),出于效率考虑,我们没有查询数据库,自己构造了一个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,是个字典,内部有user_id
        #后续要查询该用户的所有订单,直接根据user_id查询即可
        return APIResponse(msg='查询订单成功')

三、表关系以及抽象表的建立

:以后所有的数据删除,尽量用软删除,使用一个字段标志是否删除,而不是真正的从数据库中删除
优点:1、这样删除数据不会影响索引,不会导致索引失效
2、之前存的用户数据还在,以备以后使用

#表模型
#抽象出一个基表(不在数据库生成,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就不会完成数据库迁移以及建表
        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)
    # 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键都是级联
    # ManyToManyField字段不提供设置on_delete,如果想设置关系表级联,只能手动定义关系表
    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)
    # 有作者可以没有详情,删除作者,详情一定会被级联删除
    # 外键字段为正向查询字段,related_name是反向查询字段
    author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE)
    # 二、表断关联
    # 1、表之间没有外键关联,但是有外键逻辑关联(有充当外键的字段)
    # 2、断关联后不会影响数据库查询效率,但是会极大提高数据库增删改效率(不影响增删改查操作)
    # 3、断关联一定要通过逻辑保证表之间数据的安全
    # 4、断关联
    # 5、级联关系
    #       作者没了,详情也没: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)
            # 内部如何实现的?
            # 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('没有要删除的数据')
            return APIResponse(code=999, msg='没有要删除的数据')

###序列化类
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)  # 列表套字典,是要修改的数据
        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()),
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容