lun先生

导航

用户注册登录功能

表结构

UserProfile

class UserProfile(AbstractUser):
    name = models.CharField(max_length=30,null=True,blank=True,verbose_name="姓名")
    birthday = models.DateField(null=True,blank=True,verbose_name="出生年月")
    mobile = models.CharField(blank=True,null=True,max_length=11,verbose_name="手机号")
    gender = models.CharField(max_length=6,choices=(("male","男"),("female","女")),default="male")
    email = models.EmailField(max_length=100,null=True,blank=True,verbose_name="邮箱")
    class Meta:
        verbose_name = "用户"
        verbose_name_plural=verbose_name
    def __str__(self):
        return self.username

这个表是继承了Django的auth认证的user表

具体实现方式

  • 导包

    • from django.contrib.auth.models import AbstractUser
  • 让自己的UserProfile类继承AbstractUser,并添加自己的扩展字段

  • 在settings里配置: AUTH_USER_MODEL = "users.UserProfile"

VerifyCode

class VerifyCode(models.Model):
    code = models.CharField(max_length=10,verbose_name="验证码")
    mobile = models.CharField(max_length=11, verbose_name="手机号")
    add_time = models.DateTimeField(default=datetime.now,verbose_name="添加时间")
    class Meta:
        verbose_name="短信验证码"
        verbose_name_plural=verbose_name
    def __str__(self):
        return self.code

主要存的是code,mobil,用来存验证码的

准备工作

Django有一个自带的登录认证方式,默认是验证username和password是否正确,但是我们是也要通过手机号来注册的

所以我们要重新配置

在 users/views

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q
​
# 自定义用户验证,需要继承ModelBackend,并重新写authenticate方法
class CustomBackend(ModelBackend):
    """
    自定义用户验证
    """
    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        if username is None or password is None:
            return
        try:
            # 在这里我把获取user的验证改了,用Q函数,可以通过username=username或mobile=username的方式获取user
            user = UserModel.objects.get(Q(username=username) | Q(mobile=username))
            print(user)
            #user = UserModel._default_manager.get_by_natural_key(username)
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a nonexistent user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

在settings配置

# 自定义用户验证认证类
AUTHENTICATION_BACKENDS = ('users.views.CustomBackend',) # 元组,里面是类的路径(是一个元组,后边的逗号一定要加,不然报错)

接口

/code/?mobile=手机号--->get

serializers

class CodeSerializers(serializers.Serializer):
    mobile = serializers.CharField(max_length=11,required=True,error_messages={
        "max_length":"手机号不合法",
        "required":"请输入手机号"
    })
​
    def validate_mobile(self, data):
        RE = '^[1]([3-9])[0-9]{9}$'
        # 验证手机是否注册
        if UserModel.objects.filter(mobile=data).count():
            raise serializers.ValidationError("手机号码已经注册" ,code="Phone aready exsit")
        flag = re.match(RE,data)
        if not flag:
            raise serializers.ValidationError("手机号码输入不合法",code="No valid")
​
        one_min = datetime.datetime.now() - datetime.timedelta(hours=0,minutes=1,seconds=0)
​
        # 限制频率
        if VerifyCode.objects.filter(add_time__gt=one_min,mobile=data):
            raise serializers.ValidationError(detail="一分钟之内不能重复发送..",code="No repeat send")
        return data

views

# 验证码
class CodeView(APIView):
    def seed_random_code(self):
        seeds = list('0123456789')
        random_code = ""
        for i in range(4):
            seed = random.choice(seeds)
            random_code = random_code+seed
        return random_code
​
    def get(self,request):
        # 获取mobile
        mobile = request.query_params.get("mobile",None)
        # 获取序列化器
        ser = CodeSerializers(data={"mobile":mobile})
        # 验证
        ser.is_valid(raise_exception=True)
        # 发送验证码
        code = self.seed_random_code()
        print(f"----{mobile}----{code}")
        CodeCls = alicode.AliCode(mobile,code=code)
        res = CodeCls.send_code()
        print(res.body.message)
        # 入库
        if res.body.code=='OK':
            instance = VerifyCode.objects.create(code=code,mobile=mobile)
            instance.save()
        return Response(data=res.body.message,status=status.HTTP_200_OK)

简单的描述: 前端携带mobile参数请求,后端验证,最后存入数据库

 

/reg/

post

参数:

  • unsername

  • password

  • code

后端逻辑: 拿到这三个参数,但是你要知道,这里的username存的是手机号,所以我们后端要在序列化器哪里,将username存入mobile字段里

同时username字段也是手机号,并把password加密,然后在存入数据库

serializers


class UserRegSerializers(serializers.ModelSerializer):
    code = serializers.CharField(max_length=4,min_length=4,required=True,write_only=True)
    mobile = serializers.CharField(max_length=11, allow_blank=True,allow_null=True,required=False)
    password = serializers.CharField(
        style={"input_type":'password'}, # input密码框的type=password
        label="密码",
        write_only=True
    )
    def validate_code(self, code):
        # 访问数据库
        recodeCode = VerifyCode.objects.filter(code=code,mobile=self.initial_data['username']).order_by('-add_time')
        print(recodeCode)
        if recodeCode:
            # 取出最后一次验证码
            last_code = recodeCode[0]
            one_five_min_ago = datetime.datetime.now()- datetime.timedelta(hours=0, minutes=5, seconds=0)
            # 如果 验证码超过5分钟
            # 这个是没有办法比的,应为两个时间的格式不一样,详情看后面
            if last_code.add_time < one_five_min_ago:
                raise serializers.ValidationError(detail="验证码超时",code="code timeout")
            else:
                # 比对验证码是否正确
                if last_code.code!=code:
                    raise serializers.ValidationError(detail="验证码错误",code="code not right")
                return code
        else:
            raise serializers.ValidationError(detail="验证失败")
​
    def validate(self, attrs):
        print("validataed")
        attrs['mobile'] = attrs['username']
        del attrs['code']
        print(attrs)
        return attrs
​
    def create(self, validated_data):
        users = super().create(validated_data=validated_data)
        users.set_password(validated_data['password'])
        users.save()
        return users
    class Meta:
        model = UserProfile
        fields = ['username','mobile','code','password']

 

views

# User用户注册
class UserRegVIPViews(CreateModelMixin,GenericViewSet):
    queryset = UserProfile
    serializer_class = UserRegSerializers
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)
​
        # 携带用户名和taken
        res_dict = serializer.data
        payload = jwt_payload_handler(user)
        res_dict['token'] = jwt_encode_handler(payload)
        res_dict['name'] = user.name if user.name else user.username
        # 上面的几行代码很重要,是想前端返回token和name的,要存入浏览器的
​
        headers = self.get_success_headers(serializer.data)
        return Response(res_dict, status=status.HTTP_201_CREATED, headers=headers)
​
    def perform_create(self, serializer):
        return serializer.save()

views的导包:

views导包

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q
from rest_framework.viewsets import GenericViewSet
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework_jwt.serializers import jwt_encode_handler,jwt_payload_handler

from apps.users.models import VerifyCode,UserProfile
from apps.users.serializers import CodeSerializers,UserRegSerializers
from apps.users.utils import alicode
import random

 

关于序列化器code时间不能超过5分钟,否则失效,我们可以时间比较大小,但是这里有坑:问题解决参考网址

Django datetime: can't compare offset-naive and offset-aware datetimes | DeepCode (codeeper.com)

Django datetime: can't compare offset-naive and offset-aware datetimes

问题描述

>>> import datetime
>>> from django.utils import timezone
>>> naive = datetime.datetime.utcnow()
>>> aware = timezone.now()
>>> naive == aware
Traceback (most recent call last):
...
TypeError: can't compare offset-naive and offset-aware datetimes

解决问题

当遇到这种问题,首先要了解一下 offset-naiveoffset-aware 是如何生成的

  • 由Django生成的datetime,例如,从model的字段中读取的值,当Django开启时区支持USE_TZ = True,该值就是aware

  • 由Python原生生成的datetime,就是naive

因此,问题的解决就是改变一下datetime即可,aware=>naive 或者 naive=>aware

创建一个aware datetime

>>> import pytz
>>> aware = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)

这样就生成了一个UTC时区的创建一个 aware datetime

另外一个方法

在Django中一个配置USE_TZ,该配置会影响django.utils.timezone.now()函数的返回,当 USE_TZ = False 返回 naive datetime,当 USE_TZ = True 返回 aware datetime

因此, 配置 USE_TZ = False 后即可与 datetime.timedelta 等进行计算比较等操作

 

 

这里用的是阿里云的短信发送测试功能,我把它封装成一个类

uers/utils/alicode

from typing import List

from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models


class Sample:

    @staticmethod
    def create_client(
        access_key_id: str,
        access_key_secret: str,
    ) -> Dysmsapi20170525Client:
        """
        使用AK&SK初始化账号Client
        @param access_key_id:
        @param access_key_secret:
        @return: Client
        @throws Exception
        """
        config = open_api_models.Config(
            # 您的AccessKey ID,
            access_key_id=access_key_id,
            # 您的AccessKey Secret,
            access_key_secret=access_key_secret
        )
        # 访问的域名
        config.endpoint = f'dysmsapi.aliyuncs.com'
        return Dysmsapi20170525Client(config)

class AliCode:
    def __init__(self,moble,code):
        self.moble = moble
        code_dict = {"code":code}
        self.code = code_dict
    def send_code(self):
        # accessKeyId, accessKeySecret需要自己去阿里云获取
        client = Sample.create_client('accessKeyId', 'accessKeySecret')
        send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
            sign_name='阿里云短信测试',
            template_code='SMS_154950909',
            phone_numbers=self.moble,
            template_param=f'{self.code}'
        )
        # 发送code
        res = client.send_sms(send_sms_request)
        return res

 

posted on 2022-04-27 21:49  lun先生  阅读(132)  评论(0)    收藏  举报