第11次:用户注册(完整版)

第一步:定义用户模型类

class User(AbstractUser):
    mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')

    class Meta:
        db_table = 'tb_user'
        verbose_name = '用户'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.username

第二步:在配置文件中配置自定义User模型类,用自定义的User模型类代替Django自带的User模型类

#dev.py
# 指定本项目用户模型类,让Django自带的User类不起作用
AUTH_USER_MODEL = 'users.User'

第三步:生成数据表

python .\manage.py makemigrations
python .\manage.py migrate

第四步:定义视图类,处理注册时请求

import re

from django.db import DatabaseError
from django.http import HttpResponseForbidden
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views import View

# from xiaoyu_mall.apps.users.models import User    应用目录变更后不要使用这种方式来导包
# 推荐使用如下的相对路径来导包
from .models import User


class RegisterView(View):
    # get请求,
    def get(self, request):
        return render(request, 'register.html')

    def post(self, request):
        # 第一步:接收请求参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        mobile = request.POST.get('mobile')
        allow = request.POST.get('allow')
        # 第二步:判断参数是否完整
        if not all([username, password, mobile, allow]):
            return HttpResponseForbidden('缺少必要参数')
        # 用户名长度校验
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return HttpResponseForbidden('请输入5-20个字符的用户名')
        # 检验密码格式
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return HttpResponseForbidden('请输入8-20位的密码')
        # 检验两次密码一致
        if password != password2:
            return HttpResponseForbidden('两次密码不一致')
        # 校验手机号
        if not re.match(r'^1[3-9]{9}$', mobile):
            return HttpResponseForbidden('请输入正确的手机号')
        # 提醒勾选协议
        if allow != 'on':
            return HttpResponseForbidden('请勾选协议')
        # 第三步:保存注册数据
        try:
            User.objects.create_user(username=username, password=password, mobile=mobile)
        except DatabaseError:
            return render(request, 'register.html', {'register_errmsg': '注册失败'})
        # 返回注册结果
        return redirect(reverse('contents:index'))

第五步:定义根路由和子路由

#根路由
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    # 导入
    path('', include('users.urls', namespace='users')),
]
from django.urls import path
# 变更了应用所在目录之后,不要使用这种方式导包
# from xiaoyu_mall.apps.users import views
# 推荐使用如下的相对路径来导包
from . import views
# 设置命名空间
app_name = 'users'
urlpatterns = [
    path('register/', views.RegisterView.as_view(), name='register'),
]

第六步:注册过程中的前端校验与后端校验

  • 前端校验使用vue-2.5.16.js实现,预先校验表单数据的合法性,定义在register.js中
  1. 用户名与手机号先要经过前端正则校验后,再向后端发送查询请求,确定数据库中没有重复数据之后再保存。
  2. 密码要在先在前端正则检验
  3. 要校验确认密码与密码是否一致
  4. 要校验手机号的格式
  5. 要校验是否勾选协议

在static/js/目录下创建register.js

let vm = new Vue({
	el: '#app',
	// 修改Vue读取变量的语法
    delimiters: ['[[', ']]'],
	data: {
		username: '',		// 用户名
		password: '', 		// 密码
		password2: '',		// 确认密码
		mobile: '',			// 手机号
		allow: '',			// 同意协议
		uuid: '',
		image_code_url: '',	// 图形验证码
		image_code: '',

		error_name: false,
		error_password: false,
		error_password2: false,
		error_mobile: false,
		error_allow: false,
		error_image_code: false,

		error_name_message: '',		// 用户名错误提示
		error_mobile_message: '',	// 手机错误提示
		error_image_code_message: '',
	},
	mounted(){
		// 界面获取图形验证码
		this.generate_image_code();
	},
	methods: {
		// 生成图形验证码
		generate_image_code(){
			// 生成UUID。generateUUID() : 封装在common.js文件中,需要提前引入
			this.uuid = generateUUID();
			// 拼接图形验证码请求地址
			this.image_code_url = "/image_codes/" + this.uuid + "/";
		},
		// 校验用户名
		check_username(){
			// 准备正则表达式
			let re = /^[a-zA-Z0-9_-]{5,20}$/;
			// 正则表达式匹配用户名
			if (re.test(this.username)) {
				this.error_name = false;
			} else {
				this.error_name_message = '请输入5-20个字符的用户名';
				this.error_name = true;
			}
			// 检查用户名是否重名注册
			if (this.error_name == false) {
				let url = '/usernames/' + this.username + '/count/';
				axios.get(url,{
					responseType: 'json'
				})
					.then(response => {
						if (response.data.count == 1) {
							this.error_name_message = '用户名已存在';
							this.error_name = true;
						} else {
							this.error_name = false;
						}
					})
					.catch(error => {
						console.log(error.response);
					})
			}
		},
		// 校验密码
		check_password(){
			let re = /^[0-9A-Za-z]{8,20}$/;
			if (re.test(this.password)) {
				this.error_password = false;
			} else {
				this.error_password = true;
			}
		},
		// 校验确认密码
		check_password2(){
			// 判断两次密码是否一致
			if(this.password != this.password2) {
				this.error_password2 = true;
			} else {
				this.error_password2 = false;
			}
		},
		// 校验手机号
		check_mobile(){
			let re = /^1[3-9]\d{9}$/;
			if(re.test(this.mobile)) {
				this.error_mobile = false;
			} else {
				this.error_mobile_message = '您输入的手机号格式不正确';
				this.error_mobile = true;
			}
			// 检查手机号是否重复注册
			if (this.error_mobile == false) {
				let url = '/mobiles/'+ this.mobile + '/count/';
				axios.get(url, {
					responseType: 'json'
				})
					.then(response => {
						if (response.data.count == 1) {
							this.error_mobile_message = '手机号已存在';
							this.error_mobile = true;
						} else {
							this.error_mobile = false;
						}
					})
					.catch(error => {
						console.log(error.response);
					})
			}
		},
		// 检查图形验证码
		check_image_code(){
			if(this.image_code.length != 4) {
				this.error_image_code_message = '请填写图片验证码';
				this.error_image_code = true;
			} else {
				this.error_image_code = false;
			}
		},

		// 校验是否勾选协议
		check_allow(){
			if(!this.allow) {
				this.error_allow = true;
			} else {
				this.error_allow = false;
			}
		},

		// 监听表单提交事件
		on_submit(){
			this.check_username();
			this.check_password();
			this.check_password2();
			this.check_mobile();
			this.check_allow();

			if(this.error_name == true || this.error_password == true || this.error_password2 == true
				|| this.error_mobile == true || this.error_allow == true) {
                // 禁用表单的提交
				window.event.returnValue = false;
            }
		},
	}
});

common.js

// 获取cookie
function getCookie(name) {
    let r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return r ? r[1] : undefined;
}

// 提取地址栏中的查询字符串
function get_query_string(name) {
    let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
    let r = window.location.search.substr(1).match(reg);
    if (r != null) {
        return decodeURI(r[2]);
    }
    return null;
}

// 生成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;
}
  • 后端校验通过视图来实现,通过查询数据库校验用户名、手机号在系统中的是否唯一。

users应用下views.py

from xiaoyu_mall.utils.response_code import RETCODE

class MobileCountView(View):
    """手机号唯一性校验"""

    def get(self, request, mobile):
        count = User.objects.filter(mobile=mobile).count()
        return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})

class UsernameCountView(View):
    """判断用户名是否重复注册"""

    def get(self, request, username):
        count = User.objects.filter(username=username).count()
        return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})

上面代码用的状态码定义在xiaoyu_mall/xiaoyu_mall/utils/response_code.py中

class RETCODE:
    OK                  = "0"
    IMAGECODEERR        = "4001"
    THROTTLINGERR       = "4002"
    NECESSARYPARAMERR   = "4003"
    USERERR             = "4004"
    PWDERR              = "4005"
    CPWDERR             = "4006"
    MOBILEERR           = "4007"
    SMSCODERR           = "4008"
    ALLOWERR            = "4009"
    SESSIONERR          = "4101"
    DBERR               = "5000"
    EMAILERR            = "5001"
    TELERR              = "5002"
    NODATAERR           = "5003"
    NEWPWDERR           = "5004"
    OPENIDERR           = "5005"
    PARAMERR            = "5006"
    STOCKERR            = "5007"


err_msg = {
    RETCODE.OK                 : "成功",
    RETCODE.IMAGECODEERR       : "图形验证码错误",
    RETCODE.THROTTLINGERR      : "访问过于频繁",
    RETCODE.NECESSARYPARAMERR  : "缺少必传参数",
    RETCODE.USERERR            : "用户名错误",
    RETCODE.PWDERR             : "密码错误",
    RETCODE.CPWDERR            : "密码不一致",
    RETCODE.MOBILEERR          : "手机号错误",
    RETCODE.SMSCODERR          : "短信验证码有误",
    RETCODE.ALLOWERR           : "未勾选协议",
    RETCODE.SESSIONERR         : "用户未登录",
    RETCODE.DBERR              : "数据错误",
    RETCODE.EMAILERR           : "邮箱错误",
    RETCODE.TELERR             : "固定电话错误",
    RETCODE.NODATAERR          : "无数据",
    RETCODE.NEWPWDERR          : "新密码数据",
    RETCODE.OPENIDERR          : "无效的openid",
    RETCODE.PARAMERR           : "参数错误",
    RETCODE.STOCKERR           : "库存不足",
}
  • 路由配置,在users应用下urls.py中,
from django.urls import path, re_path
# 变更了应用所在目录之后,不要使用这种方式导包
# from xiaoyu_mall.apps.users import views
# 推荐使用如下的相对路径来导包
from . import views

# 设置命名空间
app_name = 'users'
urlpatterns = [
    path('register/', views.RegisterView.as_view(), name='register'),
    re_path('usernames/(?P<username>[a-zA-Z0-9_-]{5,20})/count/', views.UsernameCountView.as_view()),
    re_path(r'mobiles/(?P<mobile>1[3-9]\d{9})/count/', views.MobileCountView.as_view()),
]

第七步:图形验证

安装依赖库

pip install pillow
pip install captcha

图形验证码要存在redis数据库中,在xiaoyu_mall/dev.py配置使用redis的2号库存图形验证码

CACHES = {
    "default": {  # 默认
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    "session": {  # session
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    "verify_code": {  # 保存验证码
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/2",  # 选择redis2号库
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
}

在verifications应用中创建constants.py,用于设置图形验证码有效期的变量

# 图形验证码有效期,单位:秒
IMAGE_CODE_REDIS_EXPIRES = 300
# 短信验证码有效期,单位:秒
SMS_CODE_REDIS_EXPIRES = 300

在verifications应用中创建verify_pic.py

from captcha.image import ImageCaptcha
import io
import random
import string

# 生成随机验证码
def generate_captcha_text():
    captcha_text = ''.join(random.choices(string.ascii_letters +
                                          string.digits, k=4))
    return captcha_text

# 生成验证码图片
def generate_captcha_image(text):
    image = ImageCaptcha()
    data = image.generate_image(text)

    img_byte_array = io.BytesIO()
    data.save(img_byte_array, format='PNG')
    binary_image = img_byte_array.getvalue()
    return binary_image

在verifications应用下views.py定义生成图形验证码的类视图

from django.views import View
from .verify_pic import generate_captcha_text,generate_captcha_image
from django_redis import get_redis_connection
from . import constants
from django.http import HttpResponse
import logging

# 日志记录器
logger = logging.getLogger('django')


class ImageCodeView(View):
    def get(self, request, uuid):
        text = generate_captcha_text()
        image = generate_captcha_image(text)
        # print(text)  # 输出生成的验证码
        redis_conn = get_redis_connection('verify_code')  # 保存图形验证码
        # setex 保存到redis中 并设置生存时间
        redis_conn.setex('img_%s' % uuid,
                         constants.IMAGE_CODE_REDIS_EXPIRES, text)
        # 响应图形验证码
        return HttpResponse(image, content_type='image/jpg')

配置根路由

from django.contrib import admin
from django.urls import path, include

from xiaoyu_mall.apps import verifications

urlpatterns = [
    path('admin/', admin.site.urls),
    # 导入
    path('', include('users.urls', namespace='users')),
    path('', include('contents.urls', namespace='contents')),
    path('',include('verifications.urls')),
]

配置verifications应用下子路由

from django.urls import path, re_path
# 变更了应用所在目录之后,不要使用这种方式导包
# from xiaoyu_mall.apps.users import views
# 推荐使用如下的相对路径来导包
from . import views

urlpatterns = [
    path('image_codes/<uuid:uuid>', views.ImageCodeView.as_view(), name='register'),
]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值