一、获取短信验证码
1.业务流程分析
- 校验手机号码
- 校验图形验证码
- 校验有效时间内有送记录
- 生成短信验证码
- 发送短信
- 保存短信验证码(redis)
- 保存发送记录
2、接口设计
2.1 接口说明
GET
POST
其实功能上没什么区别,一般情况下:
GET
,是为了获取资源,查询资源数据
POST
,是生成或者新建数据,数据相对安全
条目 | 说明 |
---|---|
请求方法 | POST |
url定义 | /sms_code/ |
参数格式 | 表单(form) |
2.2 参数说明
参数名字 | 类型 | 是否必须 | 描述 |
---|---|---|---|
mobile | 字符串 | 是 | 用户输入的手机号码 |
captcha | 字符串 | 是 | 用户输入的图形验证码 |
2.3 返回的结果:
返回的结果:
{
"errno":"0",
"errmsg":"验证码发送成功",
}
3、后端代码(一)
由于至二级写校验手机号码图形验证码会让代码看起来不太爽,所以在 verification 里面新建 forms.py ,先放一放,先写完views
#由于要进行表单验证,此时应导入表单模块
from django import forms
#检验图形验证码
class CheckImageForm(forms.Form):
'''
1 校验手机号码
2 校验图形验证码
3 校验有效时间内有送记录
'''
pass
verification_views:
#由于要写类视图,这行必须要写进去
from django.views import View
from utils.res_code import json_response,Code,error_map
from .forms import CheckImageForm
class SmsCodeView(View):
'''
发送短信验证码,
url:/sms_code/
'''
def post(self,request):
'''
4 生成短信验证码
5 发送短信
6 保存短信验证码(redis)
7 保存发送记录
:param request:
:return:
'''
form = CheckImageForm(request.POST)
if form.is_valid():
pass
else:
#将失败信息进行拼接
#定义一个空列表,接收错误信息
err_msg_list = []
#将失败信息迭代放进空列表
for item in form.errors.values():
#item 也是一个列表,所以把错误信息放在item的第一位
err_msg_list.append(item[0])
#错误提示的拼接
err_msg_str = '/'.join(err_msg_list)
return json_response(errno=Code.PARAMERR,errmsg=err_msg_str)
然后切换至前端先验证功能是否能正常使用~~
4、前端代码(一)
//5.发送短信验证码
let $smsBurtton = $('.sms-captcha');
$smsBurtton.click(()=>{
//首先拿到图形验证码
let sCaptcha = $('input[name="captcha_graph"]').val();
if (sCaptcha === ''){
message.showError('孩子 请输入图形验证码!!');
return
}
//判断手机号码是否准备好
if (!isMobileReady){
fnCheckMobile();
return
}
//若都校验好了则发送ajax
$
.ajax({
url:'/sms_code/',
type:'POST',
data:{
mobile:$mobile.val(), //注意这些值是要找到你自己设置的变量
captcha:sCaptcha,
},
dataType:'json',
})
// 相当于success:function(res){}
.done((res)=>{
if (res.errno !== '0'){
message.showError(res.errmsg)
}else {
message.showSuccess(res.errmsg)
}
})
.fail(()=>{
message.showError('服务器请求超时,请重试!')
});
此时进行测试,你会发现会有403错误出现,也就是传说中的,为了防止跨域
攻击
解决方法:
约定俗成,在 /static/js/common.js
里面加上下面代码
/*=== get cookie start ===*/
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
// 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;
}
/*=== get cookie end ===*/
/*=== csrf start ===*/
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
/*=== csrf end ===*/
详解django文档
你以为这样就完了吗? 错!!还需要在 POST的页面,在表单里面加上 {% csrf_token %}
5.后端代码(二)
在敲了这么久之后,前端验证的第一部分算是基本上完成,剩下的稍后再完善,此时再切换回后端代码........
首先在之前落下的 forms.py 里面创建 mobile 、captcha与前端字段对应
from django import forms
#检验图形验证码
class CheckImageForm(forms.Form):
'''
1 校验手机号码
2 校验图形验证码
3 校验有效时间内有送记录
'''
#定义mobile与前端对应
mobile = forms.CharField(max_length=11,min_length=11,error_messages={
'max_length':'手机长度有误,请重新输入',
'min_length':'手机长度有误,请重新输入',
'required':'手机号码不能为空!!',
})
# 定义captcha与前端对应
captcha = forms.CharField(max_length=4,min_length=4,error_messages={
'max_length': '图形验码度有误,请重新输入',
'min_length': '图形验码长度有误,请重新输入',
'required': '图形验码不能为空!!',
})
然后再 contants.py里面声明 短信验证码存时间,与发送间隔
#短信验证码长度
SMS_CODE_LENGTH = 4
#短信验证码发送间隔 单位秒
SMS_CODE_INTERVAL = 60
#短信验证码过期时间 单位分钟
SMS_CODE_EXPIRES = 5
然后回到视图 views.py 里面补回成功信息的流程
if form.is_valid():
#获取手机号码
mobile = form.cleaned_data.get('mobile')
#生成验证码
sms_code = ''.join([random.choice('0123456789') for _ in range(constants.SMS_CODE_LENGTH)])
#发送验证码
#保存发送记录
logger.info('发送短信验证码[正常][mobile: %s sms_code: %s ]'%(mobile,sms_code))
#保存验证码
#创建短信验证码发送记录的key
sms_flags_key = 'sms_flags_%s'%(mobile)
#创建短信验证码内容的key
sms_text_key = 'sms_text_%s'%(sms_code)
#创建redis数据库连接
redis_conn = get_redis_connection(alias='verify_code')
#创建管道
pl = redis_conn.pipeline()
try:
# pl.setex(key,时间,内容)
pl.setex(sms_flags_key,constants.SMS_CODE_INTERVAL,1)
pl.setex(sms_text_key,constants.SMS_CODE_EXPIRES*60,sms_code)
#让管道通知redis执行命令
pl.execute()
return json_response(errmsg='短信验证码发送成功,请留意短信')
except Exception as e:
logger.error('redis 执行异常:%s'%(e))
return json_response(errno=Code.UNKOWNERR,errmsg=error_map[Code.UNKOWNERR])
03 settings.jpg
6.表单校验
在forms.py里面新增手机号码正则校验器
from django.core.validators import RegexValidator
#创建手机号码正则校验器
#mobile_validaor = RegexValidator(正则表达式,错误信息)
mobile_validaor = RegexValidator(r'^1[3-9]\d{9}$','手机号码格式有误!')
- 校验图形验证
- 检验是否60s内发送过验证码
- 校验手机号码
def clean(self):
clean_data = super().clean()
mobile = clean_data.get('mobile')
captcha = clean_data.get('captcha')
if mobile and captcha : # 如果前面的检验有问题就不需要往下执行了
#1.校验图形验证码
#将session中的验证码与用户输入的对比
#因为没有request参数,所以复写__init__
image_code = self.request.session.get('image_code') # 要与views.image_code_view 的字段一致
if not image_code:
raise forms.ValidationError('图形验证码失效')
if image_code.lower() != captcha.lower():
raise forms.ValidationError('验证码错误,请重新输入')
#2.是否60s内发送过验证码
#存在于redis
redis_conn = get_redis_connection(alias='verify_code')
if redis_conn.get('sms_flags_%s'%(mobile)):
raise forms.ValidationError('发送短信验证码过于频繁')
#3.校验手机号码是否注册
if User.objects.filter(mobile=mobile).count():
raise forms.ValidationError('手机号码已注册,请换一个手机号码')
return clean_data
7、最后加上前端倒计时
let $smsBurtton = $('.sms-captcha');
$smsBurtton.click(() => {
//首先拿到图形验证码
let sCaptcha = $('input[name="captcha_graph"]').val();
if (sCaptcha === '') {
message.showError('孩子 请输入图形验证码!!');
return
}
//判断手机号码是否准备好
if (!isMobileReady) {
fnCheckMobile();
return
}
//若都校验好了则发送ajax
$
.ajax({
url: '/sms_code/',
type: 'POST',
data: {
mobile: $mobile.val(),
captcha: sCaptcha,
},
dataType: 'json',
})
// 相当于success:function(res){}
.done((res) => {
if (res.errno !== '0') {
message.showError(res.errmsg)
} else {
message.showSuccess(res.errmsg);
//设置禁用按钮
$smsBurtton.attr('disabled',true);
//倒计时
var a = 60;
function set_time() {
if (a == 1) {
clearInterval(timer2);
//document.getElementById("sms-captcha").innerHTML = "获取短信验证码";
$smsBurtton.html("获取短信验证码");
$smsBurtton.removeAttr('disabled');
} else {
a--;
$smsBurtton.html (a+'(s)');
}
}
var timer2 = setInterval(function () {
set_time()
}, 1000);
}
})
.fail(() => {
message.showError('服务器请求超时,请重试!')
});
})
效果器图
04效果图2.jpg 05效果图3.jpg
完整js
$(() => {
//1.点击刷新验证码
$('.captcha-graph-img img').click(function () {
$(this).attr('src', '/image_code/?rand=' + Math.random())
});
//定义状态变量
//用户名是否准备
let isUsernameReady = false,
//密码是否准备
isPasswordReady = false,
//手机是否准备
isMobileReady = false,
//短信验证码是否准备
isSmsCodeReady = false;
//2. 校验功能
//用户名校验,鼠标离开用户名输入框就校验
let $username = $('#username');
//blur()光标移开事件
$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
$.ajax({
url: '/username/' + sUsername + '/',
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 () {
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 = $('#mobile');
//blur()光标移开事件
$mobile.blur(fnCheckMobile);
function fnCheckMobile() {
isMobileReady = false;
let sMobile = $mobile.val();
if (sMobile === '') {
message.showError('手机号码不能为空,请重新输入!');
return
}
if (!(/^1[3,9]\d{9}$/).test(sMobile)) {
message.showError('请重新输入11位的手机号码!');
return
}
//发送ajax
$.ajax({
url: '/mobile/' + sMobile + '/',
type: 'GET',
dataType: 'json',
success: function (res) {
if (res.data.count !== 0) {
message.showError('手机号码' + res.data.mobile + '已被注册,请重新输入');
} else {
message.showInfo('手机号码' + res.data.mobile + '可以使用');
isMobileReady = true
}
},
error: function () {
message.showError('服务器连接超时,请重试');
}
})
}
//5.发送短信验证码
let $smsBurtton = $('.sms-captcha');
$smsBurtton.click(() => {
//首先拿到图形验证码
let sCaptcha = $('input[name="captcha_graph"]').val();
if (sCaptcha === '') {
message.showError('孩子 请输入图形验证码!!');
return
}
//判断手机号码是否准备好
if (!isMobileReady) {
fnCheckMobile();
return
}
//若都校验好了则发送ajax
$
.ajax({
url: '/sms_code/',
type: 'POST',
data: {
mobile: $mobile.val(),
captcha: sCaptcha,
},
dataType: 'json',
})
// 相当于success:function(res){}
.done((res) => {
if (res.errno !== '0') {
message.showError(res.errmsg)
} else {
message.showSuccess(res.errmsg);
$smsBurtton.attr('disabled',true);
//倒计时
var a = 60;
function set_time() {
if (a == 1) {
clearInterval(timer2);
//document.getElementById("sms-captcha").innerHTML = "获取短信验证码";
$smsBurtton.html("获取短信验证码");
$smsBurtton.removeAttr('disabled');
} else {
a--;
$smsBurtton.html (a+'(s)');
}
}
var timer2 = setInterval(function () {
set_time()
}, 1000);
}
})
.fail(() => {
message.showError('服务器请求超时,请重试!')
});
})
});
最后的最后
$ git add .
$ git commit -m 'update projects'
$ git push origin master
网友评论