用户注册功能实现 四 (完)
目的:
1、短信校验功能实现
2、注册功能实现功能实现(完)
接口校验功能:
前端通过 → js校验
后端接口views通过 → forms校验
应用技术:
1、前端:js(jQuery框架)、ajax(前后端交互)、(用户注册不使用submit提交)
2、后端:django接口设计(json),csrf防跨域攻击,form表单校验功能
一、注册功能
1.业务流程分析
- 判断用户名是否为空,是否已注册
- 判断密码是否为空,格式是否正确
- 判断两次密码是否一致
- 判断手机号码是否为空,格式是否正确
- 判断短信验证码是否为空,格式是否正确,是否与真实短信验证码相同
2.接口设计
接口说明:
类目 | 说明 |
---|---|
请求方法 | POST |
url定义 | /user/register/ (和前面页面get请求类似) |
参数格式 | 表单 |
注意:
1、这里我们发送的是ajax,而不是通过submit方式提交post请求。
2、post请求,前端请求要带上csrf token,注意csrf_token,虽然我们以前写了。
参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
username | 字符串 | 是 | 用户输入的用户名 |
password | 字符串 | 是 | 用户输入的密码 |
password_repeat | 字符串 | 是 | 用户输入的重复密码 |
mobile | 字符串 | 是 | 用户输入的手机号码 |
sms_code | 字符串 | 是 | 用户输入的短信验证码 |
注意:这里我们不需要图片验证码,因为当我们成功发送短信验证码后,这个参数就没用了。
返回结果:
{
"errno": "0",
"errmsg": "恭喜您,注册成功!",
}
3.后端代码
constants代码
#保存设置常量
#图片验证码过期时间,单位秒
IMAGE_CODE_EXPIRES = 300
# 短信验证码长度
SMS_CODE_LENGTH = 4
# 短信验证码发送间隔 秒
SMS_CODE_INTERVAL = 60
# 短信验证码过期时间 分
SMS_CODE_EXPIRES = 5
# 短信发送模板
SMS_CODE_TEMP_ID = 1
- user/views.py代码:
from django.shortcuts import render
from django.views import View
from .forms import RegisterForm
from .models import User
from utils.json_res import json_response
from utils.res_code import Code, error_map
class RegisterView(View):
#注册页面的get请求
def get(self, request):
return render(request, 'user/register.html')
#注册页面的post请求
def post(self, request):
#通过form表单自动校验
form = RegisterForm(request.POST)
#只有form校验成功才会执行。
if form.is_valid():
#通过form的cleaned_data方法得到校验成功后的数据,而不是通过request.POST.get()方法获得。
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
mobile = form.cleaned_data.get('mobile')
#create_user和create的区别:
# create_user方法会把写入到数据库中的密码字段的数据自动加密,而create方法写入数据是明文。
User.objects.create_user(username=username, password=password, mobile=mobile)
return json_response(errmsg='恭喜你,注册成功!')
else:
# 定义一个错误信息列表
err_msg_list = []
for item in form.errors.values():
err_msg_list.append(item[0])
err_msg_str = '/'.join(err_msg_list)
return json_response(errno=Code.PARAMERR, errmsg=err_msg_str)
- user/forms.py代码:
在form的mobile字段我们使用到了以前的手机校验器
import re
from django import forms
from django_redis import get_redis_connection
from .models import User
from verification.constants import SMS_CODE_LENGTH
#导入手机校验器
from verification.forms import mobile_validator
#用来校验表单数据的类
注意:
1、类中规定的字段名,必须和前端html表单中要校验的字段名和js中校验的字段名三者统一,才会起作用。
2、clean方法和clean_字段名:这两个方法胡区别是后者是用来校验单个具体字段,当某个字段有特殊需求时,推荐使用单个字段校验,前者可以校验所有字段,当要校验多个字段,后者两个或两个以上字段有关联时,比如(密码和重复密码,需要判断两个字段值是否相等时,推荐使用clean方法。)
class RegisterForm(forms.Form):
username = forms.CharField(label='用户名', max_length=20, min_length=5,
error_messages={
'max_length': '用户名长度要小于20',
'min_length': '用户名长度要大于4',
'required': '用户名不能为空'
})
password = forms.CharField(label='密码', max_length=20, min_length=6,
error_messages={
'max_length': '密码长度要小于20',
'min_length': '密码长度要大于5',
'required': '用户名不能为空'
})
password_repeat = forms.CharField(label='确认密码', max_length=20, min_length=6,
error_messages={
'max_length': '密码长度要小于20',
'min_length': '密码长度要大于5',
'required': '用户名不能为空'
})
mobile = forms.CharField(label='手机号码', max_length=11, min_length=11,validators=[mobile_validator,], error_messages={
'max_length': '手机号码长度有误',
'min_length': '手机号码长度有误',
'required': '手机号码不能为空'
})
sms_code = forms.CharField(label='短信验证码', max_length=SMS_CODE_LENGTH, min_length=SMS_CODE_LENGTH,
error_messages={
'max_length': '短信验证码长度有误',
'min_length': '短信验证码长度有误长度有误',
'required': '短信验证码不能为空'
})
def clean_username(self):
"""
校验用户名
:return:username
"""
username = self.cleaned_data.get('username')
if User.objects.filter(username=username).exists():
return forms.ValidationError('用户名已存在!')
return username
def clean_mobile(self):
"""
校验手机号
:return:mobile
"""
mobile = self.cleaned_data.get('mobile')
if not re.match(r'^1[3-9]\d{9}$', mobile):
raise forms.ValidationError('手机号码格式不正确')
if User.objects.filter(mobile=mobile).exists():
raise forms.ValidationError('手机号码已注册!')
return mobile
def clean(self):
"""
校验,密码,和短信验证码
:return:
"""
clean_data = super().clean()
# 校验密码是否一致
password = clean_data.get('password')
password_repeat = clean_data.get('password_repeat')
if password != password_repeat:
raise forms.ValidationError('两次密码不一致!')
# 校验短信验证码
sms_code = clean_data.get('sms_code')
moblie = clean_data.get('mobile')
redis_conn = get_redis_connection(alias='verify_code')
real_code = redis_conn.get('sms_text_{}'.format(moblie))
if (not real_code) or (real_code.decode('utf-8') != sms_code):
raise forms.ValidationError('短信验证码错误!')
`4.前端js代码(完整版)
user/register.is代码
$(() => {
// 1、点击刷新图像验证码
$('.captcha-graph-img img').click(function () {
$(this).attr('src', '/image_code/?rand=' + Math.random())
});
//校验功能
//定义一些状态变量
let isUsernameReady = false,
isPasswoedReady = false,
isMobileReady = false,
isSmsCodeReady = false;
// 2.鼠标离开用户名输入框校验用户名
let $username = $('#user_name');
$username.blur(fnCheckUsername);
function fnCheckUsername () {
isUsernameReady = false;
let sUsername = $username.val(); //获取用户字符串
if (sUsername === ''){
message.showError('用户名不能为空!');
return
}
if (!(/^\w{5,20}$/).test(sUsername)){
message.showError('请输入5-20个字符的用户名');
return
}
$.ajax({
url: '/username/' + sUsername + '/', //例如 127.0.0.1:8000/username/zhangsan/ 发送请求
type: 'GET',
dataType: 'json',
success: function (res) {
if(res.data.count !== 0){
message.showError(res.data.username + '已经注册,请重新输入!')
}else {
message.showInfo(res.data.username + '可以正常使用!')
isUsernameReady = true
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
}
// 3.检测密码是否一致
let $passwordRepeat = $('input[name="password_repeat"]');
$passwordRepeat.blur(fnCheckPassword);
function fnCheckPassword () {
isPasswordReady = false;
let password = $('input[name="password"]').val();
let passwordRepeat = $passwordRepeat.val();
if (password === '' || passwordRepeat === ''){
message.showError('密码不能为空');
return
}
if (password !== passwordRepeat){
message.showError('两次密码输入不一致');
return
}
if (password === passwordRepeat){
isPasswordReady = true
}
}
// 4.检查手机号码是否可用
let $mobile = $('input[name="mobile"]');
$mobile.blur(fnCheckMobile);
function fnCheckMobile () {
isMobileReady = true;
let sMobile = $mobile.val();
if(sMobile === ''){
message.showError('手机号码不能为空');
return
}
if(!(/^1[3-9]\d{9}$/).test(sMobile)){
message.showError('手机号码格式不正确');
return
}
$.ajax({
url: '/mobile/' + sMobile + '/',
type: 'GET',
dataType: 'json',
success: function (data) {
if(data.data.count !== 0){
message.showError(data.data.mobile + '已经注册,请重新输入!')
}else {
message.showInfo(data.data.mobile + '可以正常使用!');
isMobileReady = true
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
}
// 5.发送手机验证码
let $smsButton = $('.sms-captcha');
$smsButton.click(function () {
let sCaptcha = $('input[name="captcha_graph"]').val();
if(sCaptcha === ''){
message.showError('请输入验证码');
return
}
if(!isMobileReady){
fnCheckMobile();
return
}
$.ajax({
url: '/sms_code/',
type: 'POST',
data: {
mobile: $mobile.val(),
captcha: sCaptcha
},
dataType: 'json',
success: function (data) {
if(data.errno !== '0'){
message.showError(data.errmsg)
}else {
message.showSuccess(data.errmsg);
//设置禁用
$smsButton.attr('disabled',true);
//倒计时
var num = 60;
//设置计时器
let t = setInterval(function () {
$smsButton.html('倒计时'+num+'秒');
if(num === 1){
clearInterval(t);
$smsButton.removeAttr('disabled');
$smsButton.html('获取短信验证码');
}
num --;
},1000)
}
},
error: function (xhr, msg) {
message.showError('服务器超时,请重试!')
}
});
});
// 6.注册
let $submitBtn = $('.register-btn');
$submitBtn.click(function (e) {
//阻止默认提交
e.preventDefault();
// 1.检查用户名
if(!isUsernameReady){
fnCheckUsername();
return
}
// 2.检查密码
if(!isPasswordReady){
fnCheckPassword();
return
}
// 3.检查电话号码
if(!isMobileReady){
fnCheckMobile();
return
}
// 4.检查短信验证码
let sSmsCode = $('input[name="sms_captcha"]').val();
if(sSmsCode === ''){
message.showError('短信验证码不能为空!');
return
}
if(!(/^\d{4}$/).test(sSmsCode)){
message.showError('短信验证码长度不正确,必须是4位数字!');
return
}
//注意data中表单字段和后端form表单字段保持一致
$.ajax({
url: '/user/register/',
type: 'POST',
data:{
username: $username.val(),
password: $('input[name="password"]').val(),
password_repeat: $passwordRepeat.val(),
mobile: $mobile.val(),
sms_code: sSmsCode
},
dataType: 'json',
success: function (res) {
if(res.errno === '0'){
message.showSuccess('恭喜您,注册成功!');
setTimeout(function () {
//注册成功后重定向到登录页面
window.location.href = '/user/login/'
}, 3000)
}else{
//注册失败
message.showError(res.errmsg)
}
},
error: function () {
message.showError('服务器超时,请重试!')
}
})
});
});
实现登录和注册页面的跳转