Article Directory
analysis
- Business processes
- If the username is empty, has been registered
- Determine the phone number is empty, has been registered
- Determine whether the password is empty, in the correct format
- Judgment and confirm the password is the same password
- Determining whether the message authentication code is empty, whether the correct format, whether the message authentication code of the same real
- Request methods: POST
- url definition:
/users/register/
- Request Parameters: url path parameter
parameter | Types of | Whether the front must pass | description |
---|---|---|---|
username | String | Yes | The user enters a user name |
password | String | Yes | The password entered by the user |
password_repeat | String | Yes | Repeat password entered by the user |
mobile | String | Yes | User input phone number |
sms_code | String | Yes | Message authentication code entered by the user |
Because it is post request, when initiating a request to the back-end, it needs to be accompanied csrf_token
The back-end code implementation
- users/views.py
from django.shortcuts import render
from django.views import View #导入类视图
from utils.json_fun import to_json_data
from utils.res_code import Code,error_map
from apps.users.forms import RegisterForm
from apps.users.models import Users
from django.contrib.auth import login,logout
import json
#用到渲染模板和提交数据用类视图比较多
class RegisterView(View):
"""
#一般会注释url
/users/register
"""
def get(self,request):
return render(request,'users/register.html')
#需要的字段:用户名,密码,确认密码,手机号,短信验证码
#请求方式post
#提交: form表单,ajax
#获取,验证
#步骤:
# 1.获取参数
# 2.校验参数
# 3.保存参数到数据库
# 4.返回给前端
#1.获取参数
def post(self,request):
json_data = request.body #从前端收到的json类型数据
if not json_data:
return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf8')) #将json类型转成字典
#2.校验参数,用form表单方式校验
form = RegisterForm(data=dict_data)
if form.is_valid():
#3.保存数据到数据库
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
mobile = form.cleaned_data.get('mobile')
user = Users.objects.create_user(username=username,password=password,mobile=mobile)
login(request,user)
#4.返回给前端
return to_json_data(errmsg='恭喜您,注册成功')
else:
#定义一个错误信息列表
err_msg_list = []
for item in form.errors.get_json_data().values():
err_msg_list.append(item[0].get('message'))
err_msg_str = '/'.join(err_msg_list) #拼接错误信息为一个字符串
return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)
- New forms.py file in the users directory, as follows:
from django import forms
from django_redis import get_redis_connection
import re
from apps.verifications.constants import SMS_CODE_NUMS
from apps.users.models import Users
class RegisterForm(forms.Form):
"""
#1.单个字段格式验证
# 2.手机号,格式,是否已注册
# 3.密码是否一致
# 4.短信验证码是否跟数据不一致
"""
#1.单个字段格式验证
username = forms.CharField(label='用户名',
max_length=20,
min_length=5,
error_messages={'min_length':'用户名长度要大于5',
'max_length':'用户名长度要小于20',
'required':'用户名不能为空'}
)
password = forms.CharField(label='密码',
max_length=20,
min_length=6,
error_messages={'min_length':'密码长度要大于6',
'max_length':'密码长度要小于20',
'required':'密码不能为空'}
)
password_repeat = forms.CharField(label='确认密码',
max_length=20,
min_length=6,
error_messages={'min_length':'密码长度要大于6',
'max_length':'密码长度要小于20',
'required':'确认密码不能为空'}
)
mobile = forms.CharField(label='手机号',
max_length=11,
min_length=11,
error_messages={'min_length':'手机号长度有误',
'max_length':'手机号长度有误',
'required':'手机号不能为空'}
)
sms_code = forms.CharField(label='短信验证码',
max_length=SMS_CODE_NUMS,
min_length=SMS_CODE_NUMS,
error_messages={'min_length':'短信验证码长度有误',
'max_length':'短信验证码长度有误',
'required':'短信验证码不能为空'}
)
def clean_mobile(self):
"""
# 2.手机号,格式,是否已注册
:return:
"""
tel = self.cleaned_data.get('mobile') #从前端获取
#验证输入的手机号格式
if not re.match(r'^1[3-9]\d{9}$',tel): #效果同verifications的forms.py中的RegexValidator
raise forms.ValidationError('手机号码格式不正确')
#验证输入的手机号在数据库中是否存在
if Users.objects.filter(mobile=tel).exists():
raise forms.ValidationError('手机号已注册!请重新输入!')
return tel
def clean(self):
"""
# 3.密码是否一致
# 4.短信验证码是否跟数据不一致
:return:
"""
#cleaned_data 就是读取前端form表单返回的值,返回类型为字典dict型
cleaned_data = super().clean() #继承clean的方法,重写
# 3.密码是否一致
password = cleaned_data.get('password') #从那个字典中获取键password对应的值
password_repeat = cleaned_data.get('password_repeat')
if password != password_repeat: #判断密码和确认密码是否一致
raise forms.ValidationError('两次输入密码不一致')
sms_text = cleaned_data.get('sms_code')
tel = cleaned_data.get('mobile')
# 4.短信验证码是否跟数据不一致
#连接redis数据库
redis_conn = get_redis_connection('verity_codes')
#构建短信验证码
sms_fmt = 'sms_{}'.format(tel)
#获取数据库中的短信验证码
real_sms = redis_conn.get(sms_fmt)
if (not real_sms) or (sms_text != real_sms.decode('utf-8')): #判断结果为总是抛出异常
raise forms.ValidationError('短信验证码错误')
Front-end code implementation
$(function () {
let $username = $('#user_name'); // 选择id为user_name的网页元素,需要定义一个id为user_name
let $img = $(".form-item .captcha-graph-img img"); // 获取图像标签
let sImageCodeId = ""; // 定义图像验证码ID值
let $mobile = $('#mobile'); // 选择id为mobile的网页元素,需要定义一个id为mobile
let $smsCodeBtn = $('.form-item .sms-captcha'); // 获取短信验证码按钮元素,需要定义一个id为input_smscode
let $imgCodeText = $('#input_captcha'); // 获取用户输入的图片验证码元素,需要定义一个id为input_captcha
let $register = $('.form-contain'); // 获取注册表单元素
// 1、图片验证码逻辑
// 通过uuid生成验证码编号
// 拼接验证码地址
// 设置验证码图片标签的src
generateImageCode(); // 生成图像验证码图片
$img.click(generateImageCode); // 点击图片验证码生成新的图片验证码图片
// 2、用户名验证逻辑
$username.blur(function () {
fn_check_usrname();
});
// 3、手机号验证逻辑
// 判断用户手机号是否注册
$mobile.blur(function () {
fn_check_mobile();
});
// 4、发送短信验证码逻辑
$smsCodeBtn.click(function () {
// 判断手机号是否输入
if (fn_check_mobile() !== "success") {
return
}
// 判断用户是否输入图片验证码
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
};
// for test
// let SdataParams = {
// "mobile": "1806508", // 获取用户输入的手机号
// "text": "ha3d", // 获取用户输入的图片验证码文本
// "image_code_id": "680a5a66-d9e5-4c3c-b8ea" // 获取图片UUID
// };
// 向后端发送请求
$.ajax({
// 请求地址
url: "/sms_codes/",
// 请求方式
type: "POST",
// 向后端发送csrf token
// headers: {
// // 根据后端开启的CSRFProtect保护,cookie字段名固定为X-CSRFToken
// "X-CSRFToken": getCookie("csrf_token")
// },
// data: JSON.stringify(SdataParams),
data: JSON.stringify(SdataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
async: false
})
.done(function (res) {
if (res.errno === "0") {
// 倒计时60秒,60秒后允许用户再次点击发送短信验证码的按钮
message.showSuccess('短信验证码发送成功');
let num = 60;
// 设置一个计时器
let t = setInterval(function () {
if (num === 1) {
// 如果计时器到最后, 清除计时器对象
clearInterval(t);
// 将点击获取验证码的按钮展示的文本恢复成原始文本
$smsCodeBtn.html("获取验证码");
} else {
num -= 1;
// 展示倒计时信息
$smsCodeBtn.html(num + "秒");
}
}, 1000);
} else {
message.showError(res.errmsg);
}
})
.fail(function(){
message.showError('服务器超时,请重试!');
});
});
// 5、注册逻辑
$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 (fn_check_usrname() !== "success") {
return
}
// 判断手机号是否为空,是否已注册
if (fn_check_mobile() !== "success") {
return
}
// 判断用户输入的密码是否为空
if ((!sPassword) || (!sPasswordRepeat)) {
message.showError('密码或确认密码不能为空');
return
}
// 判断用户输入的密码和确认密码长度是否为6-20位
if ((sPassword.length < 6 || sPassword.length > 20) ||
(sPasswordRepeat.length < 6 || sPasswordRepeat.length > 20)) {
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返回给后台
$.ajax({
// 请求地址
url: "/users/register/", // url尾部需要添加/ url要和后台的一致
// 请求方式
type: "POST",
data: JSON.stringify(SdataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
.done(function (res) {
if (res.errno === "0") {
// 注册成功
message.showSuccess('恭喜你,注册成功!');
setTimeout(function () {
// 注册成功之后重定向到主页
window.location.href = document.referrer;
}, 1000)
} else {
// 注册失败,打印错误信息
message.showError(res.errmsg);
}
})
.fail(function(){
message.showError('服务器超时,请重试!');
});
});
// 生成一个图片验证码的编号,并设置页面中图片验证码img标签的src属性
function generateImageCode() {
// 1、生成一个图片验证码随机编号
sImageCodeId = generateUUID();
// 2、拼接请求url /image_codes/<uuid:image_code_id>/
let imageCodeUrl = "/image_codes/" + sImageCodeId + "/";
// 3、修改验证码图片src地址
$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;
}
// 判断用户名是否已经注册
function fn_check_usrname() {
let sUsername = $username.val(); // 获取用户名字符串
let sReturnValue = "";
if (sUsername === "") {
message.showError('用户名不能为空!');
return
}
if (!(/^\w{5,20}$/).test(sUsername)) {
message.showError('请输入5-20个字符的用户名');
return
}
// 发送ajax请求,去后端查询用户名是否存在
$.ajax({
url: '/usernames/' + sUsername + '/',
type: 'GET',
dataType: 'json',
async: false
})
.done(function (res) {
if (res.data.count !== 0) {
message.showError(res.data.username + '已注册,请重新输入!');
sReturnValue = ""
} else {
message.showInfo(res.data.username + '能正常使用!');
sReturnValue = "success"
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
sReturnValue = ""
});
return sReturnValue
}
// 判断手机号是否注册
function fn_check_mobile() {
let sMobile = $mobile.val(); // 获取用户输入的手机号码字符串
let sReturnValue = "";
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 !== 0) {
message.showError(res.data.mobile + '已注册,请重新输入!');
sReturnValue = ""
} else {
message.showSuccess(res.data.mobile + '能正常使用!');
sReturnValue = "success"
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
sReturnValue = ""
});
return sReturnValue
}
// 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;
}
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
// Setting the token on the AJAX request
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
});