第一步:定义用户模型类
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中
- 用户名与手机号先要经过前端正则校验后,再向后端发送查询请求,确定数据库中没有重复数据之后再保存。
- 密码要在先在前端正则检验
- 要校验确认密码与密码是否一致
- 要校验手机号的格式
- 要校验是否勾选协议
在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'),
]