注册模块——验证逻辑

注册模块——验证逻辑_第1张图片

上图是注册的页面,用户名、手机号、图形验证码、手机短信验证码、密码都是需要验证的接口,有的需要分别验证。关于前端的验证,我在这里就不具体说明了。

用户名验证api

1.前端找到用户输入框标签,获取框中值,通过Ajax传来
2.后端路由匹配获取传来的值,利用user模型过滤方法查看用户名是否已被注册过
3.以json的格式返货给前端,前端通过Ajax方法接收。
  • view函数
from django.views import View
from django.http import JsonResponse

class Check_username(View):
    """

    """
    def get(self,request,username):
        data = {
            'count': models.Users.objects.filter(username=username).count(),
            'username':username
        }
        return JsonResponse(data=data)
  • ajax部分代码
// 发送ajax请求,去后端查询用户名是否存在
        $.ajax({
            type: 'GET',
            url: '/check_username/' + username + '/',
            dataType: 'json',
            async: false
        })
            .done(function (res) {
                if (res.data.count !== 0) {
                    message.showError(res.data.username + '已被注册,请重新输入!');
                    returnvalue = ''
                }
                else {
                    message.showSuccess(res.data.username + '能正常使用');
                    returnvalue = 'success'
                }
            })
            .fail(function () {
                message.showError('服务器请求超时,请重试!');
                returnvalue = ''

            });
  
  • 路由配置
urlpatterns = [
        re_path('check_username/(?P\w{5,20})/',views.Check_username.as_view(),name='check_username'),

        ]
  • 实现功能:
  1. 通过re_path路由匹配,验证用户名是否符合格式,即5~20位的字符或数字的组合。
  2. 通过user模型,验证该用户是否已被注册。

手机号验证api

与验证用户名类似,通过re_path获取得到手机号,并对其检查是否已被注册过。

  • view函数
class Check_mobile(View):
    """
   
    """
    def get(self,request,mobile):
        data = {
            'count': models.Users.objects.filter(mobile=mobile).count(),
            'mobile':mobile
        }
        return JsonResponse(data=data)
  • ajax部分代码
 $.ajax({
            type: 'GET',
            url: '/check_mobile/' + mobile + '/',
            dataType: 'json',
            async: false
        })
            .done(function (res) {
                if (res.data.count !== 0) {
                    message.showError(res.data.mobile + '已被注册,请重新输入!');
                    returnvalue = ''
                }
                else {
                    message.showSuccess(res.data.mobile + '能正常使用');
                    returnvalue = 'success'
                }
            })
            .fail(function () {
                message.showError('服务器请求超时,请重试!');
                returnvalue = ''

            });
  • 路由配置
urlpatterns = [
        re_path('check_mobile/(?P1[3-9]\d{9})/',views.Check_mobile.as_view(),name='check_mobile'),
      
        ]
  • 实现功能:
  1. 通过re_path验证手机号的格式是否正确,即11位的数字,开头为1,第二位为3~9的数字。
  2. 通过user模型查询该手机号是否被注册过。
  • 踩坑记录:
    url路由re匹配手机号,后面加上边界限定符$,就匹配不到,原因是手机号后面还跟了一个/。

手机短信能否发送验证api

1.前端传来手机号、用户名、uuid、图像验证码,通过body获取
2.后端form和视图函数一起检验手机号存在及格式、用户名存在及格式、uuid存在及格式以及对应数据库是否存在、图像验证码存在及是否与数据库中的匹配
3.检验成功后,记录发送的key、发送文本一起存入redis,再记录发送的标志为60秒
4.对于一切的错误,都主动抛出异常并且logger记录
  • form表单
from django_redis import get_redis_connection

from django import forms
from django.core.validators import RegexValidator

from apps.users.models import Users

mobile_validator = RegexValidator(r"^1[3-9]\d{9}$",'手机号格式不正确')
class Send_message_form(forms.Form):
    mobile = forms.CharField(max_length=11,min_length=11,required=True,validators=[mobile_validator,],error_messages={
        'required':'手机号不能为空','max_length':'手机号长度有误','min_length':'手机号长度有误'
    })
    image_uuid = forms.UUIDField(required=True,error_messages={
        'required':'图片uuid不能为空'
    })
    image_text = forms.CharField(required=True,max_length=4,min_length=4,error_messages={
        'required': '图片验证码不能为空', 'max_length': '图片验证码长度有误', 'min_length': '图片验证码长度有误'
    })

    def clean(self):
        #取数据
        cleaned_data = super().clean()
        mobile = cleaned_data.get('mobile')
        image_text = cleaned_data.get('image_text')
        image_uuid = cleaned_data.get('image_uuid')

        #校验手机号
        if Users.objects.filter(mobile=mobile).count():
            raise forms.ValidationError('该手机号已被注册')

        #取出图片文本
        con_reids = get_redis_connection(alias='verify_codes')
        img_key = 'imagecode_{}'.format(image_uuid).encode('utf-8')
        real_image_text_origin = con_reids.get(img_key)
        real_image_text = real_image_text_origin.decode('utf-8') if real_image_text_origin else None
        con_reids.delete(img_key)
        #校验图片验证码
        if (not real_image_text) or (image_text != real_image_text):
            raise forms.ValidationError('图片验证失败')

        #验证短信发送标志是否还在
        sms_flag_key = 'sms_flag_{}'.format(mobile).encode('utf-8')
        sms_flag = con_reids.get(sms_flag_key)
        if sms_flag:
            raise forms.ValidationError("短信发送太频繁")
  • 视图函数
class Check_send_message(View):
    """
    """
    def post(self,request):
        data_json = request.body
        if not data_json:
            return to_json_data(errno= Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        data_dict = json.loads(data_json.decode('utf-8'))
        form = Send_message_form(data = data_dict)
        if form.is_valid():
            mobile = form.cleaned_data.get('mobile')
            sms_text = ''.join(random.sample(string.digits,constants.MESSAGE_NUM))
            con_reids = get_redis_connection(alias='verify_codes')
            #创建一个短信发送标志,60秒有效期
            p1 = con_reids.pipeline()
            sms_flag_key = 'sms_flag_{}'.format(mobile).encode('utf-8')
            sms_text_key = 'sms_text_{}'.format(mobile).encode('utf-8')
            try:
                p1.setex(sms_flag_key,constants.SMS_FLAG,1)
                p1.setex(sms_text_key,constants.SMS_CODE_REDIS_EXPIRES,sms_text)
                p1.execute()
            except Exception as e:
                logger.debug("redis存放短信验证码失败:{}".format(e))
                return to_json_data(errno=Code.UNKOWNERR,errmsg=error_map[Code.UNKOWNERR])
            logger.info('短信验证码为:{}'.format(sms_text))

            #发送短信验证码
            try:
                ccp = CCP()
                result = ccp.send_template_sms(mobile, [sms_text, constants.SMS_CODE_REDIS_EXPIRES], constants.SMS_CODE_TEMP_ID)
            except Exception as e:
                logger.error("短信验证码发送异常,mobile:{}-error:{}".format(mobile,e))
                return to_json_data(errno=Code.SMSERROR,errmsg=error_map[Code.SMSFAIL])
            if result != 0:
                logger.warning("发送短信验证码失败,mobile:{}".format(mobile))
                return to_json_data(errno=Code.SMSFAIL,errmsg=error_map(Code.SMSFAIL))
            else:
                logger.info("{}手机号发送验证码成功".format(mobile))
                return to_json_data(errno=Code.OK,errmsg="手机验证码发送成功")

        else:
            error_list = []
            for item in form.errors.get_json_data().values():
                # error_list = error_list.append(item[0].get('message'))
                error_list.append(item[0].get('message'))
            err_msg_str = '/'.join(error_list)  # 拼接错误信息为一个字符串
            return to_json_data(errno=Code.PARAMERR, errmsg=err_msg_str)
  • 路由配置
urlpattern = [
        path('send_message/',views.Check_send_message.as_view(),name = 'send_message'),
        ]
  • ajax部分代码
 $.ajax({
      // 请求地址
      url: "/send_message/",
      // 请求方式
      type: "POST",
      data: JSON.stringify(SdataParams),
      // 请求内容的数据类型(前端发给后端的格式)
      contentType: "application/json; charset=utf-8",
      // 响应数据的格式(后端返回给前端的格式)
      dataType: "json",
      async: false	// 关掉异步功能
    })
      .done(function (res) {
        if (res.errno === "0") {
          // 倒计时60秒,60秒后允许用户再次点击发送短信验证码的按钮
           message.showSuccess('短信验证码发送成功');
          let num = 60;
          // 设置一个计时器
          let t = setInterval(function () {
            if (num === 1) {
              // 如果计时器到最后, 清除计时器对象
              clearInterval(t);
              // 将点击获取验证码的按钮展示的文本恢复成原始文本
              $smsCodeBtn.html("获取验证码");
              // // 将点击按钮的onclick事件函数恢复回去
              // $(".get_code").attr("onclick", "sendSMSCode();");
            } else {
              num -= 1;
              // 展示倒计时信息
              $smsCodeBtn.html(num + "秒");
            }
          }, 1000);
        } else {
          message.showError(res.errmsg);
        }
      })
      .fail(function(res){
        message.showError(res.errmsg);
      });
  • 实现功能:
  1. ​​​​​​​验证手机号、图片uuid、图片验证码,只有这些都验证成功。才能发短信。
  2. 发送短信,随机生成六位数字,并保存于redis中,有效期60秒,并且保存了相应的发送标志,即限制不同的发送。
  3. logger记录每次发送验证码的手机号和其内容
  • 注意事项
  1. 前后端传来传去的是json格式,故要用json模块相互对dict、json转换
  2. 前后端传的的数据也是byte形式,对于传来的要主动解码
  3. redis的数据存放都是byte格式,故要记得编码,解码

注册接口api

最后的注册接口,我们除了图片验证码不需要验证,其他都要重复验证一遍。

1、接收前端传来的参数,用户名、电话、密码、确认密码、短信验证码
2、form表单的构造,并实现相应的验证逻辑
3、验证成功后,将用户名、密码、电话保存在数据库中,用到用户模型的creat_user方法
4、注册成功后,自动跳转到已登陆状态
  • form表单
class User_form(forms.Form):
    username = forms.CharField(max_length=20,min_length=5,required=True,error_messages={
        'max_length':'用户名长度不能低于5位','min_length':'用户名长度不能大于20位','required':'用户名不能为空'
    })
    password = forms.CharField(max_length=20,min_length=6,required=True,error_messages={
        'max_length':'密码长度不能低于6位','min_length':'密码名长度不能大于20位','required':'密码不能为空'
    })
    password_repeat = forms.CharField(max_length=20,min_length=6,required=True,error_messages={
        'max_length':'密码长度不能低于6位','min_length':'密码名长度不能大于20位','required':'密码不能为空'
    })
    mobile = forms.CharField(max_length=11,min_length=11,required=True,error_messages={
        'max_length':'手机号长度不符合','min_length':'手机号长度不符合','required':'手机号不能为空'
    })
    sms_code = forms.CharField(max_length=6,min_length=6,required=True,error_messages={
        'max_length': '验证码长度不符合', 'min_length': '验证码长度不符合', 'required': '验证码不能为空'
    })

    def clean_mobile(self):
        tel = self.cleaned_data.get('mobile')
        if not re.match(r'^1[3-9]\d{9}$',tel):
            raise forms.ValidationError('手机号格式不正确')
        if Users.objects.filter(mobile=tel).count():
            raise forms.ValidationError('该手机号已被注册')
        return tel
    def clean(self):
        clean_data = super().clean()
        passwd = clean_data.get('password')
        passwd_re = clean_data.get('password_repeat')
        tel = clean_data.get('mobile')
        sms = clean_data.get('sms_code')

        #验证密码
        if passwd != passwd_re:
            raise forms.ValidationError('两次密码不一致')

        #验证手机验证码
        con_redis = get_redis_connection(alias='verify_codes')
        sms_key = 'sms_text_{}'.format(tel).encode('utf-8')

        sms_text = con_redis.get(sms_key)
        if not sms_text or sms_text.decode('utf-8') != sms:
            raise forms.ValidationError('短信验证码错误')
  • 视图函数
class Register(View):
    """
    """
    def get(self,request):
        return render(request, 'users/register.html')
    def post(self,request):
        json_data = request.body
        if not json_data:
            return to_json_data(errno= Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        dict_data = json.loads(json_data.decode('utf-8'))
        form = User_form(data=dict_data)
        if form.is_valid():
            #将用户名、密码保存到数据库中
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            mobile = form.cleaned_data.get('mobile')
            user = Users.objects.create_user(username=username,password=password,mobile=mobile)
            user.save()
            login(request, user)
            return to_json_data(errmsg="恭喜您,注册成功!")
        else:
            error_list = []
            for item in form.errors.get_json_data().values():
                # error_list = error_list.append(item[0].get('message'))
                error_list.append(item[0].get('message'))
            err_msg_str = '/'.join(error_list)  # 拼接错误信息为一个字符串
            return to_json_data(errno=Code.PARAMERR, errmsg=err_msg_str)
  • 路由设置
urlpatterns = [
        path('register/',views.Register.as_view(),name='register'),  
        ]
  • Ajax部分代码
 // 1、创建请求参数
    let SdataParams = {
      "username": sUsername,
      "password": sPassword,
      "password_repeat": sPasswordRepeat,
      "mobile": sMobile,
      "sms_code": sSmsCode
    };

    // 2、创建ajax请求
    $.ajax({
      // 请求地址
      url: "/users/register/",  // url尾部需要添加/
      // 请求方式
      type: "POST",
      data: JSON.stringify(SdataParams),
      // 请求内容的数据类型(前端发给后端的格式)
      contentType: "application/json; charset=utf-8",
      // 响应数据的格式(后端返回给前端的格式)
      dataType: "json",
    })
      .done(function (res) {
        if (res.errno === "0") {
          // 注册成功
          message.showSuccess('恭喜你,注册成功!');
          setTimeout(function () {
            // 注册成功之后重定向到主页
            window.location.href = document.referrer;
          }, 1000)
        } else {
          // 注册失败,打印错误信息
          message.showError(res.errmsg);
        }
      })
      .fail(function(){
        message.showError('服务器超时,请重试!');
      });
  • 实现功能:
  1. ​​​​​​​除了图片及其验证码不需要验证,其他的都要再验证码一遍。因为图片验证码的目的是为了限制短信发送的门槛,防止无限发送短信。
  2. 将注册信息保存到数据库中。
  3. 注册成功后,自动实现登陆。
  • 踩坑记录:​​​​​​​
    1、reids所有的有效期的数字单位是秒
    2、保存在redis的key,value都是编码后的,所有取出来要解码,取值要对key编码

 

你可能感兴趣的:(注册模块——验证逻辑)