Blog项目——找回密码
一、 分析
业务处理流程:
- 判断手机号是否为空,是否已注册
- 判断短信验证码是否为空,是否格式正确,是否与真实的短信验证码相同
- 判断密码是否为空,格式是否正确
- 判断确认密码与密码是否相同
请求方法:POST
url定义:/user/findpwd/
请求参数:url路径参数
参数 | 类型 | 前端是否必须传 | 描述 |
---|---|---|---|
password | 字符串 | 是 | 用户输入的密码 |
password_repeat | 字符串 | 是 | 用户输入的重复密码 |
mobile | 字符串 | 是 | 用户输入的手机号 |
sms_code | 字符串 | 是 | 用户输入的短信验证码 |
注:由于是post请求,在向后端发起请求时,需要附带csrf token
二、步骤
- 用户输入手机号(js判断是否为空,是否注册)
- 用户输入图形验证码
- 点击发送短信验证码(查看手机是否合法,验证码是否合法)
- 获取验证码,输入密码与重复密码,点击修改密码
- 获取每个值
- 判断每个值是否为空,是否合法
- 通过ajax往后台传json数据
- 后台接收数据
- 判断是否为空
- 将数据放入form表单进行数据清洗
- 如果数据正确,则将密码修改
- 如果不正确则返回错误信息
- 前台获取后台传来的响应码
- 如果是0,则重定向到登陆页面
- 如果不是0,则显示错误原因
三、代码
1. 前端
前端的代码与之前用户注册差不多,函数部分也是用到了类似的几个(拼接图片url,uuid,检查miobile是否注册,发送短信和修改密码)
接下来就来看看:
// @Auther:Summer
$(function () {
let $img = $(".form-item .captcha-graph-img img"); // get img
let $mobile = $("#mobile");
let $smsCodeBtn = $(".form-item .sms-captcha"); //get sms btn
let $mobileRetuenVal = ""; // this is run mobile_fun return val
let $findPwsBtn = $(".findpsw-btn");
generate();
$img.click(generate);
$mobile.blur(function () {
$mobileRetuenVal = fn_check_mobile();
})
// set make img fun
function generate() {
imageUUID = generateUUID();
let imageUrl = "/image_code/" + imageUUID +"/";
$img.attr("src", imageUrl)
}
// the fun to create uuid_code
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;
}
// to check mobile whether register
function fn_check_mobile() {
let sRetuenVal = "";
let sMobile = $mobile.val(); // get mobile val
// check mobile whether null
if(sMobile === ""){
message.showError("手机号不能为空");
return sRetuenVal;
}
// use re to check mobile
if (!(/^1[3-9]\d{9}$/).test(sMobile)){
message.showError("手机号格式错误,请重新输入");
return sRetuenVal;
}
// send ajax
$.ajax({
url: "/mobiles/" + sMobile + "/",
type: "GET",
dataType: "json",
async: false
}).done(function (res) {
if(res.data.count !== 1){
message.showError("输入的手机号有误,请重新输入");
}else{
message.showSuccess("手机号输入正确");
sRetuenVal = "success";
}
}).fail(function () {
message.showError("服务器超时,请重试");
})
return sRetuenVal;
}
// send sms
$smsCodeBtn.click(function () {
// check moile
if($mobileRetuenVal !== "success"){
message.showError("请输入正确的手机号");
return;
}
// get val
let text = $("#input_captcha").val();
// check code
if(!text){
message.showError("请输入验证码");
return;
}
// check uuid of img
if(!imageUUID){
message.showError("图片uuid码为空");
return;
}
// set data
let sDataParm = {
"mobile": $mobile.val(),
"text": text,
"image_code_id": imageUUID
}
// send ajax
$.ajax({
url: "/sms_code/",
type: "POST",
data: JSON.stringify(sDataParm),
dataType: "json"
}).done(function (res) {
if(res.errno === "0"){
message.showSuccess("短信验证码发送成功")
$smsCodeBtn.attr('disabled',true);
let num = 60;
let t = setInterval(function () {
if(num === 1){
// 如果计时器到最后, 清除计时器对象
clearInterval(t);
$smsCodeBtn.attr('disabled',false);
// 将点击获取验证码的按钮展示的文本恢复成原始文本
$smsCodeBtn.html("重新发送验证码");
}else {
num -= 1;
$smsCodeBtn.html(num + "秒");
}
},1000
)}else{
message.showError(res.errmsg);
}}).fail(
message.showError("服务器超时,请重试")
)
})
// change psw
$findPwsBtn.click(function (e) {
// prevent default submit
e.preventDefault();
// get all 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();
// check moile
if($mobileRetuenVal !== "success"){
message.showError("请输入正确的手机号");
return;
}
// check password
if((!sPassword) || (!sPasswordRepeat)){
message.showError("密码与确认密码不能为空")
return;
}
// check passwd len
if((sPassword.length < 6 || sPassword.length > 20) ||
(sPasswordRepeat.length < 6 || sPasswordRepeat.length > 20)){
message.showError("密码和确认密码的长度需在6-20位")
return;
}
// check pass and pass_re whether same
if(sPassword !== sPasswordRepeat){
message.showError("密码和确认密码不一致")
return;
}
// check smscode len
if(!(/^\d{6}$/).test(sSmsCode)){
message.showError("短信验证码格式不正确,必须是6位数字!")
return;
}
// request
// set request data
let SdataParams = {
"password": sPassword,
"password_repeat": sPasswordRepeat,
"mobile": sMobile,
"sms_code": sSmsCode
};
// send ajax
$.ajax({
url: "/user/findpwd/",
type:"POST",
data: JSON.stringify(SdataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
dataType: "json",
}).done(function (res) {
if(res.errno === "0"){
message.showSuccess("修改密码成功");
setTimeout(() =>{
// redirect to login page
window.location.href = '/user/login/';
},1500)
}else{
message.showError(res.errmsg);
}
}).fail(function () {
message.showError("服务器超时,请重试")
})
})
});
注意,上面的代码原来是
$findPwsBtn.submit(function (e) {
// prevent default submit
e.preventDefault();
这里变成了click,应为尝试了无数次,始终无法解开:
当使用submit的时候,无法接触默认操作,而只有在click的时候才能执行这个操作。如果有知道为什么会出现这样错误的,也欢迎留言区留下宝贵意见。
2. 后端——form
form用来进行数据清洗,主要的代码也与上次的form类似:
class ChangePswForm(forms.Form):
password = forms.CharField(label="密码", max_length=20, min_length=6,error_messages={
"max_length": "密码长度必须小于20位",
"min_length": "密码长度必须大于6位",
"required": "密码不能为空"
})
password_repeat = forms.CharField(label="确认密码", max_length=20, min_length=6,
error_messages={
"max_length": "密码长度必须小于20位",
"min_length": "密码长度必须大于6位",
"required": "密码不能为空"
})
mobile = forms.CharField(label="手机号", max_length=11, min_length=11,
error_messages={
"max_length": "手机号长度有误",
"min_length": "手机号长度有无",
"required": "手机号不能为空"})
sms_code = forms.CharField(label="验证码", max_length=6, min_length=6,
error_messages={"max_length": "验证码长度有误",
"min_length": "验证码长度有误",
"required": "验证码不能为空"})
# 检查手机号是否存在
def clean_mobile(self):
tel = self.cleaned_data.get('mobile')
if not Users.objects.filter(mobile=tel).exists():
raise forms.ValidationError("手机号输入有误,请重新输入!")
return tel
def clean(self):
cleaned_data = super().clean()
passwd = cleaned_data.get('password')
passwd_repeat = cleaned_data.get('password_repeat')
if passwd != passwd_repeat:
raise forms.ValidationError("两次密码不一致")
# 校验短信验证码
tel = cleaned_data.get('mobile')
sms_text = cleaned_data.get('sms_code')
# 建立redis连接
redis_conn = get_redis_connection(alias='verify_codes')
sms_fmt = "sms_{}".format(tel).encode('utf8')
real_sms = redis_conn.get(sms_fmt)
if (not real_sms) or (sms_text != real_sms.decode('utf-8')):
raise forms.ValidationError("短信验证码错误")
3. 后端——view
view里需要注意的一个点就是,如果直接获取user实例,然后通过user.password=password是无法进行加密处理的,这里我使用了AbstractUser类自带的set_password函数
class FindPswView(View):
def get(self, request):
return render(request, 'users/findpsw.html')
def post(self, request):
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR, errmsg=[error_map[Code.PARAMERR]])
data_dict = json.loads(json_data.decode("utf8"))
form = ChangePswForm(data=data_dict)
if form.is_valid():
mobile = form.cleaned_data.get("mobile")
password = form.cleaned_data.get("password")
user = Users.objects.get(mobile=mobile)
user.set_password(password)
user.save()
return to_json_data(errmsg="密码修改成功")
else:
error_map_list = []
for item in form.errors.values():
error_map_list.append(item)
err_str = "/".join(error_map_list)
return to_json_data(errno=Code.PARAMERR, errmsg=err_str)
更加别忘了,要保存!!!
4. url
# -*- coding: utf-8 -*-
# @Author : summer
from django.urls import path
from . import views
app_name = "user"
urlpatterns = [
path("login/", views.LoginView.as_view(), name="login"),
path("register/", views.RegistarView.as_view(), name="register"),
path("findpwd/", views.FindPswView.as_view(), name="findpwd"),
]