八、用户注册功能实现
1.分析
业务处理流程:
- 判断用户名是否为空,是否已注册
- 判断手机号是否为空,是否已注册
- 判断密码是否为空,格式是否正确
- 判断确认密码与密码是否相同
- 判断短信验证码是否为空,是否格式正确,是否与真实的短信验证码相同
请求方法:POST
url定义:/user/register/
请求参数:url路径参数
参数 | 类型 | 前端是否必须传 | 描述 |
---|---|---|---|
username | 字符串 | 是 | 用户输入的用户名 |
password | 字符串 | 是 | 用户输入的密码 |
password_repeat | 字符串 | 是 | 用户输入的重复密码 |
mobile | 字符串 | 是 | 用户输入的手机号 |
sms_code | 字符串 | 是 | 用户输入的短信验证码 |
注:由于是post请求,在向后端发起请求时,需要附带csrf token
1.前台代码实现
js
$(function () {
let $username = $('#user_name'); //获取用户名
let $img = $(".form-item .captcha-graph-img img"); // 获取图像
let sImageCodeId = ""; //uuid
let $mobile = $('#mobile'); // 选择id为mobile的网页元素,需要定义一个id为mobile手机号
// 校验功能
// 定义一些状态变量
let isUsernameReady = false,
isPasswordReady = false,
isMobileReady = false;
send_flag = true; // 短信标记
// let $imgCodeText = $('#input_captcha');
// console.log(img);
genreate();
$img.click(genreate);
function genreate() {
sImageCodeId = generateUUID();
let imageCodeUrl = '/image_code/' + sImageCodeId + '/';
$img.attr('src', imageCodeUrl)
}
// 生成图片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;
}
// 2、用户名验证逻辑
// blur,触发失去焦点事件
$username.blur(function () {
fn_check_username();
});
// 判断用户名是否已经注册
function fn_check_username() {
// 校验用户名
isUsernameReady = false;
let sUsername = $username.val(); // 获取用户名字符串
if (sUsername === "") {
message.showError('用户名不能为空!');
return
}
if (!(/^\w{5,20}$/).test(sUsername)) {
message.showError('请输入5-20个字符的用户名');
return
}
// 发送ajax请求,去后端查询用户名是否存在
$.ajax({
url: '/username/' + sUsername + '/',
type: 'GET',
dataType: 'json',
})
.done(function (res) {
if (res.data.count === 1) {
message.showError(res.data.username + '已注册,请重新输入!');
} else {
message.showSuccess(res.data.username + '能正常使用!');
isUsernameReady = true
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
//3、手机号验证
// 手机号校验,光标离开手机号输入框就校验
$mobile.blur(fn_check_mobile);
// 判断手机号是否注册
function fn_check_mobile() {
//检验手机号
isMobileReady = false;
let sMobile = $mobile.val(); // 获取用户输入的手机号码字符串
if (sMobile === "") {
message.showError('手机号不能为空!');
return
}
if (!(/^1[345789]\d{9}$/).test(sMobile)) {
message.showError('手机号码格式不正确,请重新输入!');
return
}
$.ajax({
url: '/mobiles/' + sMobile + '/',
type: 'GET',
dataType: 'json',
async: false
})
.done(function (res) {
if (res.data.count === 1) {
message.showError(res.data.mobile + '已注册,请重新输入!');
} else {
message.showSuccess(res.data.mobile + '能正常使用!');
isMobileReady = true;
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
// 4、发送短信验证码逻辑
let $smsCodeBtn = $('.form-item .sms-captcha'); // 获取短信验证码按钮元素,需要定义一个id为input_smscode
let $imgCodeText = $('#input_captcha'); // 获取用户输入的图片验证码元素,需要定义一个id为input_captcha
$smsCodeBtn.click(function () {
// 判断手机号是否输入
if(send_flag){
send_flag = false;
// 判断手机号码是否准备好
if(!isMobileReady){
fn_check_mobile();
return
}
// 13535353535
// 判断用户是否输入图片验证码
let text = $imgCodeText.val(); // 获取用户输入的图片验证码文本
if (!text) {
message.showError('请填写验证码!');
return
}
if (!sImageCodeId) {
message.showError('图片UUID为空');
return
}
// 正常
let SdataParams = {
"mobile": $mobile.val(), // 获取用户输入的手机号
"text": text, // 获取用户输入的图片验证码文本
"image_code_id": sImageCodeId // 获取图片UUID
};
// 向后端发送请求
$.ajax({
// 请求地址
url: "/sms_code/",
// 请求方式
type: "POST",
// 向后端发送csrf token
headers: {
// 根据后端开启的CSRFProtect保护,cookie字段名固定为X-CSRFToken
"X-CSRFToken": getCookie("csrftoken")
},
data: JSON.stringify(SdataParams),
// data: JSON.stringify(SdataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
.done(function (res) {
console.log(res);
if (res.errno === "0") {
// 倒计时60秒,60秒后允许用户再次点击发送短信验证码的按钮
message.showSuccess('短信验证码发送成功');
let num = 60;
// 设置一个计时器
let t = setInterval(function () {
if (num === 1) {
// 如果计时器到最后, 清除计时器对象
clearInterval(t);
// 将点击获取验证码的按钮展示的文本恢复成原始文本
$smsCodeBtn.html("获取验证码");
send_flag = true;
} else {
num -= 1;
// 展示倒计时信息
$smsCodeBtn.html(num + "秒");
}
}, 1000);
}
else {
message.showError(res.errmsg);
send_flag = true;
}
})
.fail(function(){
message.showError('服务器超时,请重试!');
});
}
});
// 5、注册逻辑
let $register = $('.form-contain'); // 获取注册表单元素
$register.submit(function (e) {
// 阻止默认提交操作
e.preventDefault();
// 获取用户输入的内容
let sUsername = $username.val(); // 获取用户输入的用户名字符串
let sPassword = $("input[name=password]").val();
let sPasswordRepeat = $("input[name=password_repeat]").val();
let sMobile = $mobile.val(); // 获取用户输入的手机号码字符串
let sSmsCode = $("input[name=sms_captcha]").val();
// 判断用户名是否已注册
if (!isUsernameReady) {
fn_check_username();
return
}
// 判断手机号是否为空,是否已注册
if (!isMobileReady) {
fn_check_mobile();
return
}
// 判断用户输入的密码是否为空
if ((!sPassword) || (!sPasswordRepeat)) {
message.showError('密码或确认密码不能为空');
return
}
// const reg = /^(?![^A-Za-z]+$)(?![^0-9]+$)[\x21-x7e]{6,18}$/
// 以首字母开头,必须包含数字的6-18位
// 判断用户输入的密码和确认密码长度是否为6-20位
if (!(/^[0-9A-Za-z]{6,20}$/).test(sPassword)){
message.showError('请输入6到20位密码');
return
}
// 判断用户输入的密码和确认密码是否一致
if (sPassword !== sPasswordRepeat) {
message.showError('密码和确认密码不一致');
return
}
// 判断用户输入的短信验证码是否为6位数字
if (!(/^\d{6}$/).test(sSmsCode)) {
message.showError('短信验证码格式不正确,必须为6位数字!');
return
}
// 发起注册请求
// 1、创建请求参数
let SdataParams = {
"username": sUsername,
"password": sPassword,
"password_repeat": sPasswordRepeat,
"mobile": sMobile,
"sms_code": sSmsCode
};
// 2、创建ajax请求
$.ajax({
// 请求地址
url: "/user/register/", // url尾部需要添加/
// 请求方式
type: "POST",
data: JSON.stringify(SdataParams),
headers: {
// 根据后端开启的CSRFProtect保护,cookie字段名固定为X-CSRFToken
"X-CSRFToken": getCookie("csrftoken")
},
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
.done(function (res) {
if (res.errno === "0") {
// 注册成功
message.showSuccess('恭喜你,注册成功!');
setTimeout(() => {
// 注册成功之后重定向到主页
window.location.href = '/user/login/';
}, 1500)
} else {
// 注册失败,打印错误信息
message.showError(res.errmsg);
}
})
.fail(function(){
message.showError('服务器超时,请重试!');
});
});
// get cookie using jQuery
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
let cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
});
2.后端代码实现
views.py
# 在users目录下的views.py文件中定义如下类视图:
import json
import re
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseForbidden
from django.views import View
from django_redis import get_redis_connection
from django.contrib.auth import login
from dj_blog.utils.res_code import res_json
from users.models import User
class RegisterView(View):
"""
username
password
password_ret
mobile
sms_code
"""
def get(self, request):
return render(request, 'users/register.html')
def post(self, request):
"""
"username": sUsername,
"password": sPassword,
"password_repeat": sPasswordRepeat,
"mobile": sMobile,
"sms_code": sSmsCode
:param request:
:return:
"""
data_dict = request.body
data_dict = json.loads(data_dict)
username = data_dict.get('username')
password = data_dict.get('password')
password2 = data_dict.get('password_repeat')
mobile = data_dict.get('mobile')
sms_code = data_dict.get('sms_code')
# 1.非空
if not all([username, password, password2, mobile, sms_code]):
return HttpResponseForbidden('填写数据不完整')
# 2.用户名
if not re.match('^[\u4e00-\u9fa5\w]{5,20}$', username):
return HttpResponseForbidden('用户名为5-20个字符')
if Users.objects.filter(username=username).count() > 0:
return HttpResponseForbidden('用户名已经存在')
# 密码
if not re.match('^[0-9A-Za-z]{6,20}$', password):
return HttpResponseForbidden('密码为6-20个字符')
# 确认密码
if password != password2:
return HttpResponseForbidden('两个密码不一致')
# 手机号
if not re.match('^1[3456789]\d{9}$', mobile):
return HttpResponseForbidden('手机号错误')
if Users.objects.filter(mobile=mobile).count() > 0:
return HttpResponseForbidden('手机号存在')
# 短信验证码
# 1.读取redis中的短信验证码
redis_cli = get_redis_connection('verify_codes')
sms_code_redis = redis_cli.get('sms_{}'.format(mobile))
# 2.判断是否过期
if sms_code_redis is None:
return HttpResponseForbidden('短信验证码已经过期')
# 3.删除短信验证码,不可以使用第二次
redis_cli.delete('sms_' + mobile)
redis_cli.delete('send_flag_' + mobile)
# 4.判断是否正确
if sms_code_redis.decode() != sms_code:
return HttpResponseForbidden('短信验证码错误')
# 处理
# 1.创建用户对象
user = Users.objects.create_user(
username=username,
password=password,
mobile=mobile
)
# 2.状态保持
login(request, user)
# 向cookie中写用户名,用于客户端显示
response = res_json(errmsg='恭喜你,注册成功!')
return response
urls.py
from django.urls import path
from . import views
app_name = 'users'
urlpatterns = [
path('register/',views.RegisterView.as_view(), name='register'),
]
总结:
1、使用AJAX提交表单,注册功能完成。
2、因为自定义的user是继承的django自带的用户类,所以可以使用它的create_user创建普通用户
3、使用内置auth认证系统进行登录login(request,user)