Django blog项目《六》:《注册功能1》用户模型、用户名、手机号、图形验证码、短信验证码功能实现

文章目录

    • 一、用户模型设计
          • 1. 对创建超级用户时需要输入email字段
          • 2. 添加mobile和email_active字段
          • 3. 对Users模型进行settings.py配置
    • 二、图形验证码
          • 1. 路由urls.py设置
          • 2. JS前端实现
          • 3. views.py逻辑实现
    • 三、用户名
          • 1. 路由urls.py设置
          • 2. JS前端实现
          • 3. view.py逻辑实现
    • 四、手机号
          • 1. 路由urls.py设置
          • 2. JS前端实现
          • 3. view.py逻辑实现
    • 五、短信验证码
          • 1. 路由urls.py设置
          • 2. JS前端实现
          • 3. view.py逻辑实现

用户模块分析已经完成,那么接下来就是具体的代码实现了,这一章主要完成用户模型设计和图形验证码、用户名和手机号验证的完成。

一、用户模型设计

用户模型使用django自带的auth中的AbstractUser来进行改写,

  1. 添加手机号字段
  2. 添加邮箱状态字段

具体实现过程:在 users app 里面的 models.py 里面进行用户模型的修改

1. 对创建超级用户时需要输入email字段

users/models.py

from django.db import models
from django.contrib.auth.models import UserManager as _UserManager


class UserManager(_UserManager):
    """
    重写创建超级用户时需要输入email字段
    """
    def create_superuser(self, username, password, email=None, **extra_fields):

        return super().create_superuser(username=username, password=password, email=email, **extra_fields)

2. 添加mobile和email_active字段

users/models.py

from django.contrib.auth.models import AbstractUser

class Users(AbstractUser):
    """
    重写users模型
    """
    objects = UserManager()
    mobile = models.CharField(
        max_length=11,
 
        help_text="手机号",
        verbose_name="手机号",
        error_messages={
            "unique": "手机号已被注册"
        })
    email_active = models.BooleanField(default=False, help_text="邮箱状态")

    class Meta:
        db_table = "tb_users"  # 数据库名
        verbose_name = "用户"  # 指明在admin站点中的名字
        verbose_name_plural = verbose_name  # 显示复数名称

    def __str__(self):
        return self.username
3. 对Users模型进行settings.py配置

自定义的Users模型需要进行配置后才能使用。

settings.py

# 配置自定义的用户模型

AUTH_USER_MODEL = "users.Users"

二、图形验证码

在用户注册时很多字段验证在后续的其他项目时还能使用,为了复用这里重新新建一个app专门用来做用户注册的用户名、手机号、图形验证码、短信验证码的验证。

创建1个新的app verifications,拉到apps里面去。

1. 路由urls.py设置
  1. 项目下面的 urls.py

    from django.urls import path, include
    
    urlpatterns = [
        path('', include("verifications.urls")),
    ]
    
  2. 应用 verifications/urls.py配置

    from django.urls import re_path
    from verifications import views
    
    
    app_name = "verifications"
    
    urlpatterns = [
        re_path("image_code//", views.ImageCodes.as_view(), name="image_code"),
    
    ]
    
2. JS前端实现

注册功能的js代码不单独放,都统一放在register.js里面

users/register.js

$(function () {
    let $img = $('.form-item .captcha-graph-img img'); //获取图片信息
    let sImageCodeId='';  //定义一个空字符串用来放uuid字符串

    generateImageCode();
    // 当点击验证码时刷新验证码
    $img.click(generateImageCode);


    // 生成图片UUID验证码
    function generateUUID() {
        let d = new Date().getTime();
        if (window.performance && typeof window.performance.now === "function") {
            d += performance.now(); //use high-precision timer if available
        }
        let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            let r = (d + Math.random() * 16) % 16 | 0;
            d = Math.floor(d / 16);
            return (c==='x' ? r : (r & 0x3 | 0x8)).toString(16);
        });
        return uuid;
    }

    //生成验证码图片
    function generateImageCode() {
        sImageCodeId = generateUUID();  //这里不能有let这相当于对前面的sImageCodeId进行修改
        // console.log("第一个uuid:"+sImageCodeId);
        let imageCodeUrl = '/image_code/'+sImageCodeId+'/';  //拼接图片验证码请求路径
        $img.attr('src',imageCodeUrl);  //给图片添加src链接 路径

    }
});
3. views.py逻辑实现

图形验证码需要使用图形验证码插件,网上有很多现成的代码,这里可以在gitee.com获取captcha图形验证码插件。

在前面settings.py配置redis数据库时只设置了一个为default默认0数据库,将图形验证码和短信验证码的放在为verify_code的1数据库

settings.py中添加验证码存放的数据库

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/0',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient'
        }
    },
    # 新添加存放短信验证码和图形验证码
    'verify_code': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient'
        }
    },
}

verifications/views.py

import logging

from django.views import View
from django.http import HttpResponse
from django_redis import get_redis_connection

from verifications import contains
from utils.captcha.captcha import captcha


logger = logging.getLogger("django")


class ImageCodes(View):
    """
    create image_code view
    """
    def get(self, request, image_code_id):  # 从前端获取到传过来的参数uuid
        verify_text, verify_image = captcha.generate_captcha()  # 生成验证码文本和图片

        # 链接redis步骤 将生成的验证码文本和图片保存到redis中

        # 1.链接redis区
        redis_obj = get_redis_connection(alias="verify_code")

        # 2. 构造键值对
        verify_key = "image_{}".format(image_code_id)

        # 3. 保存数据到redis并设置有效期
        redis_obj.setex(verify_key, contains.IMAGE_CODE_EXPIRE_TIME, verify_text)

        logger.info('verify_text:{}'.format(verify_text))  # 后台显示验证码信息

        return HttpResponse(content=verify_image, content_type='image/jpg')   # 把验证码图片返回到前端

三、用户名

1. 路由urls.py设置

verification/urls.py

from django.urls import re_path
from verifications import views


app_name = "verifications"

urlpatterns = [
    re_path("username/(?P\w{6,18})/", views.CheckUsernameView.as_view(), name="username"),
]
2. JS前端实现

users/register.js

$(function () {
    
    let $username = $('#user_name');  //获取用户名标签

    //鼠标移出用户名输入框时执行判断
    $username.blur(function () {
        fn_check_username();
    });

    // 用户名是否存在
    function fn_check_username() {
        let sUsername = $username.val(); //获取表单数据
        let sReturnValue = '';
        // 前端校验
        if (sUsername === ""){
            // alert("用户名不能为空") ;
            // alert('用户名能为空!');  //这里需要改进
            return
        }else if(!(/\w{6,18}/).test(sUsername)){
            // alert('请输入6~18位字符的用户名!');  //这里需要改进
            return
        }

        //ajax请求判断数据库是否存在  后端校验
        $.ajax({
            url:'/username/' + sUsername +'/',
            type:'GET',
            dataType:'json',
            async: false,  // 关闭异步
            success:function (arg) {
                if(arg.data.count !== 0){  // 不等于0表示已经注册了
                    // alert("用户名已注册,请重新输入")
                    sReturnValue = "";
                }else{
                    sReturnValue="success";
                    alert("用户名可以正常使用")
                }
            },
            error:function(){
                alert("用户名ajax,服务器超时,请重新输入");
                sReturnValue = "";
            }
        });

        return sReturnValue  //返回出该信息
    }
});
3. view.py逻辑实现

verifications/views.py

from django.views import View
from django.http import JsonResponse

from users import models

class CheckUsernameView(View):
    """
    create username verify view

    # 1. 创建一个类
    request: GET
    params: username
    """
    # 2. 创建个get方法来处理逻辑
    def get(self, request, username):

        # 3. 从数据库中查看是否存在该用户
        data = {
            "username": username,
            "count": models.Users.objects.filter(username=username).count()  # 获取数据库中有几条这个信息:无则0
        }

        # 4. 返回到前端
        return JsonResponse(data=data)

四、手机号

1. 路由urls.py设置

verification/urls.py

from django.urls import re_path
from verifications import views


app_name = "verifications"

urlpatterns = [
    re_path("mobile/(?P1[3-9]\d{11})/", views.CheckMobileView.as_view(), name="mobile"),
]
2. JS前端实现

users/register.js

$(function () {
   
    let $mobile = $("#mobile"); //获取手机号
    
    //鼠标移出手机号输入框时执行判断
    $mobile.blur(function() {
        fn_check_mobile();
    });

    // 手机号是否存在
    function fn_check_mobile(){
        let sMobile = $mobile.val();
        let sReturnMobileValue = "";
        // 前端校验
        if (sMobile === ""){
            // alert("手机号输入为空");
            alert("手机号为空");

        }else if(!(/^1[3-9]\d{9}/).test(sMobile)){
            alert("请输入正确的手机号格式") ;

        }
        //ajax请求判断数据库是否存在  后端校验
        $.ajax({
            url:"/mobile/"+sMobile+'/',
            type:"GET",
            dataType: "json",
            async:false,  // 关闭异步
            success:function (arg) {
                if(arg.data.count !== 0){
                    alert(sMobile+"手机号已被注册,请重新输入");
                    sReturnMobileValue = "";
                }else{
                    alert(sMobile+"手机号能正常使用");
                    sReturnMobileValue = "success";
                }
            },
            error:function () {
                alert("手机号ajax,服务器超时,请重新输入");
                sReturnMobileValue = "";
            }
        });
        return sReturnMobileValue  //返回出该信息
    }
});
3. view.py逻辑实现

verifications/views.py

from django.views import View
from django.http import HttpResponse, JsonResponse

from users import models

class CheckMobileView(View):
    """
    create mobile verify view

    # 1.创建一个类
    request: GET
    params: mobile
    """
    # 2. 创建个get方法来处理逻辑
    def get(self, request, mobile):
        # 3. 从数据库中查看是否存在该用户
        data = {
            "mobile": mobile,
            "count": models.Users.objects.filter(mobile=mobile).count()
        }
        # 5. 返回到前端

        return JsonResponse(data=data)

五、短信验证码

1. 路由urls.py设置

verification/urls.py

from django.urls import re_path
from verifications import views


app_name = "verifications"

urlpatterns = [
    re_path("sms_code/", views.SmsCodeView.as_view(), name="sms_code"),
]
2. JS前端实现

users/register.js

$(function () {
    let sImageCodeId='';  //定义一个空字符串用来放uuid字符串
    let $mobile = $("#mobile"); //获取手机号
    let $smsCodeBtn = $(".form-item .sms-captcha");  //发送短信验证按钮
    let $smsCodeText = $(".form-item .form-captcha");    //图形验证码输入框文本
   


    //短信验证
    $smsCodeBtn.click(function(){
        // 判定手机号是否有输入
        if ( fn_check_mobile() !== "success"){
            alert("手机号为空");
            return
        }
        
        // 判断用户是否输入图形验证码
        let text = $smsCodeText.val();
        if (!text){
            alert("请输入图形验证码");
            return
        }

        // 判断UUID
        if(!sImageCodeId){
            console.log("第2个"+sImageCodeId);
            alert("图形UUID不对");
            return
        }
        
        let dataParams={
            'mobile':$mobile.val(),
            'image_text':text,
            "image_code_id":sImageCodeId,
        };
        
        $.ajax({
            url:"/sms_code/",
            type:"POST",
            data:JSON.stringify(dataParams),
            success:function(arg){
                console.log(arg);
                if(arg.errno==="200"){   // errno: "200", errmsg: "短信发送正常", data: null
                    alert("短信验证码发送成功");
                    //倒计时功能
                    let num = 60;
                    // 设置计时器
                    let t = setInterval(function () {
                        if (num===1){
                            clearInterval(t);
                            $smsCodeBtn.html("重新发送验证码")
                        }else{
                            num -= 1;
                            //展示倒计时信息
                            $smsCodeBtn.html(num+"秒");
                        }

                    },1000)
                }else{
                    alert(arg.errmsg);
                }
            },
            error:function(){
                alert("短信验证ajax,服务器超时,请重试")
            }
        })
    });
});
3. view.py逻辑实现

在写代码时需要很多错误代码和错误代码的意思,这里单独建一个文件用来放这个错误代码,同时后续在写代码时需要很多时候传递给前端一个json格式的文件,包含错误代码,这样前端才能做好判断。

在utlis下面创建一个res_code文件夹,创建一个 res_code.py文件用来放错误代码, json_function.py用来放置转化json格式的插件:

utils/res_code/res_code.py

class Code:
    OK = "200"
    DBERR = "4001"
    NODATA = "4002"
    DATAEXIST = "4003"
    DATAERR = "4004"
    METHERR = "4005"
    SMSERROR = "4006"
    SMSFAIL = "4007"

    SESSIONERR = "4101"
    LOGINERR = "4102"
    PARAMERR = "4103"
    USERERR = "4104"
    ROLEERR = "4105"
    PWDERR = "4106"

    SERVERERR = "4500"
    UNKOWNERR = "4501"


error_map = {
    Code.OK: "成功",
    Code.DBERR: "数据库查询错误",
    Code.NODATA: "无数据",
    Code.DATAEXIST: "数据已存在",
    Code.DATAERR: "数据错误",
    Code.METHERR: "方法错误",
    Code.SMSERROR: "发送短信验证码异常",
    Code.SMSFAIL: "发送短信验证码失败",

    Code.SESSIONERR: "用户未登录",
    Code.LOGINERR: "用户登录失败",
    Code.PARAMERR: "参数错误",
    Code.USERERR: "用户不存在或未激活",
    Code.ROLEERR: "用户身份错误",
    Code.PWDERR: "密码错误",

    Code.SERVERERR: "内部错误",
    Code.UNKOWNERR: "未知错误",
}

utils/res_code/json_funciton.py

from django.http import JsonResponse

from utils.res_code.res_code import Code


# 处理json格式转化,并加入异常码和异常信息
def to_json_data(errno=Code.OK, errmsg='', data=None, **kwargs):
    """
    返回给前端 json数据以及错误信息
    :param errno: 错误代码
    :param errmsg: 错误信息
    :param data: 参数
    :param kwargs: 不定长数据
    :return:
    """
    json_dict = {'errno': errno, 'errmsg': errmsg, 'data': data}

    if kwargs:
        json_dict.update(kwargs)

    return JsonResponse(json_dict)

verifications/contains.py

# -*- coding: utf-8 -*-
"""
@Time    : 2020/2/26 21:29
@Author  : 半纸梁
@File    : contains.py
"""

IMAGE_CODE_EXPIRE_TIME = 5*60  # 图形验证码有效时间

SMS_CODE_EXPIRE_TIME = 5*60   # 短信验证码有效时间

SMS_REPEAT_EXPIRE_TIME = 60   # 可重复发送短信的时间

SMS_TEMPLATE = 1   # 短信发送模板

SMS_CCP_EXPIRE_TIME = 5   # 云通讯短信有效时间 以分钟计算的

verifications/views.py

import logging
import json
import random

from django.views import View
from django_redis import get_redis_connection

from verifications import contains
from verifications.forms import SmsCodeForm
from utils.yuntongxun.sms import CCP
from utils.res_code.json_function import to_json_data
from utils.res_code.res_code import Code, error_map

logger = logging.getLogger("django")

class SmsCodeView(View):
    """
    # 1. 创建一个SmsCodeView类
    param: mobile、image_text、image_code_id
    """
    # 2. 创建一个post方法用来处理逻辑
    def post(self, request):
        # 3. 获取前端传来的数据
        json_data = request.body

        # 4. 将数据转化为字典
        dict_data = json.loads(json_data)

        # 5. 将数据传递给SmsCodeForm表单进行校验
        form = SmsCodeForm(data=dict_data)

        # 6. 校验成功处理方式
        if form.is_valid():
            # 7. 获取校验后的数据
            mobile = form.cleaned_data.get("mobile")

            # 8. 生成短信验证码
            sms_text = "%06d" % random.randint(0, 999999)

            # 9. 将短信验证码和和过期时间保存到redis中
            redis_obj = get_redis_connection("verify_code")
            sms_text_key = "sms_code_{}".format(mobile).encode("utf8")
            sms_repeat_key = "sms_sixty_{}".format(mobile).encode("utf8")

            redis_obj.setex(sms_text_key, contains.SMS_CODE_EXPIRE_TIME, sms_text)  # key, expire_time, value
            redis_obj.setex(sms_repeat_key, contains.SMS_CODE_EXPIRE_TIME, contains.SMS_REPEAT_EXPIRE_TIME)

            # logging.info("发送短信正常[mobile:%s sms_num:%s]" % (mobile, sms_num))  # 调试代码时候用,在控制台显示

            # 9. 使用用通讯插件发送短信
            try:
                result = CCP().send_Template_sms(mobile, [sms_text, contains.SMS_CCP_EXPIRE_TIME], contains.SMS_TEMPLATE)
            except Exception as e:
                logger.error("短信发送异常[mobile:{},error:{}]".format(mobile, e))
                return to_json_data(errno=Code.SMSERROR, errmsg=error_map[Code.SMSERROR])  # 短信发送异常
            else:
                if result == 0:  # 发送成功
                    logger.info("短信发送成功[mobile:{},sms_code:{}]".format(mobile, sms_text))
                    return to_json_data(errmsg="短信发送正常")
                else:  # 发送失败
                    logger.warning("短信发送失败[mobile:{}]".format(mobile))
                    return to_json_data(errno=Code.SMSFAIL, errmsg=error_map[Code.SMSFAIL])

        # 校验未通过
        else:
            err_msg_list = []

            for item in form.errors.values():
                err_msg_list.append(item[0])

            err_info = '/'.join(err_msg_list)
            return to_json_data(errno=Code.PARAMERR, errmsg=err_info)



verifications/forms.py

# -*- coding: utf-8 -*-
"""
@Time    : 2020/2/27 9:49
@Author  : 半纸梁
@File    : forms.py
"""
from django import forms
from django_redis import get_redis_connection
from django.core.validators import RegexValidator


mobile_reg = RegexValidator(r"^1[3-9]\d{9}","手机号格式不正确")


class SmsCodeForm(forms.Form):
    """
    check sms_code field
    字段一定要和js中传递过来的保持一样
    """
    mobile = forms.CharField(
        max_length=11,
        min_length=11,
        validators=[mobile_reg],
        error_messages={
            "max_length": "手机号格式不正确",
            "min_length": "手机号格式不正确",
            "required": "手机号不能为空"
        }
    )

    image_code_id = forms.UUIDField(error_messages={"required": "UUID不能为空"})  # 
    image_text = forms.CharField(
        max_length=4,
        min_length=4,
        error_messages={
            "max_length": "图形验证码格式不正确",
            "min_length": "图像验证码格式不正确",
            "required": "图形验证码不能为空"
        }
    )

    def clean(self):
        # 1. 获取清洗后的数据
        cleaned_data = super().clean()
        mobile = cleaned_data.get("mobile")
        image_code_uuid = cleaned_data.get("image_code_id")
        image_text = cleaned_data.get("image_text")

        # 2. 建立redis链接
        try:
            redis_obj = get_redis_connection("verify_code")
        except Exception as e:
            raise forms.ValidationError("redis数据库连接错误")

        # 3. 构建image_code查询的键
        image_code_key = "image_{}".format(image_code_uuid).encode("utf8")

        # 4. 获取数据库中的image_code的值
        redis_byte_image_text = redis_obj.get(image_code_key)

        # 5. 删除redis中该uuid的键
        redis_obj.delete(image_code_key)  #

        # 6. 将拿到的值化为utf8字符:redis中拿到的值都是二进制
        redis_image_text = redis_byte_image_text.decode("utf8") if redis_byte_image_text else None

        # 7. 判断现在的image_text和redis_image_text是否一致
        if image_text.upper() != redis_image_text:
            raise forms.ValidationError("图形验证码校验失败")

        # 8. 判断60秒内是否有重复发送短信
        redis_sms_repeat_key = "mobile_{}".format(mobile).encode("utf8")  # 构建短信60秒内发送查询的键
        if redis_obj.get(redis_sms_repeat_key):
            raise forms.ValidationError("短信发送太频繁,请稍后再试")

    

用户注册功能点击注册放在users app里面进行完成。

你可能感兴趣的:(Django,框架)