目录
一、短信验证码逻辑分析
二、关于短信平台:
三、短信验证码后端逻辑
四、短信验证码前端逻辑
一、短信验证码逻辑分析
要点
1. 保存短信验证码是为注册做准备的。
2. 为避免用户使用图形验证码恶意测试,后端提取了图形验证码后,立即删除图形验证码。
3. Django不具备发送短信的功能,所以我们借助第三方的短信平台来帮助我们发送短信验证码。
三、短信验证码后端逻辑
3.1 短信验证码接口设计
1. 请求方式
选项方案请求方法GET请求地址/sms_codes/(?P<mobile>1[3-9]\d{9})/
2. 请求参数:路径参数和查询字符串
参数名类型是否必传说明mobilestring是手机号image_codestring是图形验证码uuidstring是唯一编号
3. 响应结果:JSON
字段说明code状态码errmsg错误信息
3.2 短信验证码接口定义(verifications\views.py)
class SMSCodeView(View):
"""短信验证码"""
def get(self, request, mobile):
"""
:param mobile: 手机号
:return: JSON
"""
# 接受参数
image_code_client = request.GET.get('image_code')
uuid = request.GET.get('uuid')
# 校验参数
if not all([image_code_client, uuid]):
return http.HttpResponseForbidden('缺少必传参数')
# 创建连接到redis的对象
redis_conn = get_redis_connection('verify_code')
# 判断用户是否频繁发送短信验证码
send_flag = redis_conn.get('send_flag_%s' % mobile)
if send_flag:
return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '发送短信过于频繁'})
# 提取图形验证码
image_code_server = redis_conn.get('img_%s' % uuid)
if image_code_server is None:
return http.JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '图形验证码已失效'})
# 删除图形验证码
redis_conn.delete('img_%s' % uuid)
# 对比图形验证码
image_code_server = image_code_server.decode() # 将bytes转字符串,再比较
if image_code_client.lower() != image_code_server.lower(): # 转小写,再比较
return http.JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '输入图形验证码有误'})
# 生成短信验证码:随机6位数字,000007
sms_code = '%06d' % random.randint(0, 999999)
logger.info(sms_code) # 手动的输出日志,记录短信验证码
# # 保存短信验证码
# redis_conn.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
# # 保存发送短信验证码的标记
# redis_conn.setex('send_flag_%s' % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)
# 创建redis管道
pl = redis_conn.pipeline()
# 将命令添加到队列中
# 保存短信验证码
pl.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
# 保存发送短信验证码的标记
pl.setex('send_flag_%s' % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)
# 执行
pl.execute()
# 发送短信验证码
这里调用发送短信的方法如:阿里云api.send(参数,,,)
# 响应结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '发送短信成功'})
四、短信验证码前端逻辑
4.1 Vue绑定短信验证码界面
1.register.html
<li>
<label>短信验证码:</label>
<input type="text" v-model="sms_code" @blur="check_sms_code" name="sms_code" id="msg_code" class="msg_input">
<a @click="send_sms_code" class="get_msg_code">[[ sms_code_tip ]]</a>
<span class="error_tip" v-show="error_sms_code">[[ error_sms_code_message ]]</span>
{% if sms_code_errmsg %}
<span class="error_tip">{{ sms_code_errmsg }}</span>
{% endif %}
</li>
2.register.js
check_sms_code(){
if(this.sms_code.length != 6){
this.error_sms_code_message = '请填写短信验证码';
this.error_sms_code = true;
} else {
this.error_sms_code = false;
}
},
4.2 axios请求短信验证码
1.发送短信验证码事件处理
// 发送短信验证码
send_sms_code() {
// 避免恶意用户频繁的点击获取短信验证码的标签
if (this.send_flag == true) { // 先判断是否有人正在上厕所
return; // 有人正在上厕所,退回去
}
this.send_flag = true; // 如果可以进入到厕所,立即关门
// 校验数据:mobile,image_code
this.check_mobile();
this.check_image_code();
if (this.error_mobile == true || this.error_image_code == true) {
this.send_flag = false;
return;
}
let url = '/sms_codes/' + this.mobile + '/?image_code=' + this.image_code + '&uuid=' + this.uuid;
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 展示倒计时60秒效果
let num = 60;
let t = setInterval(() => {
if (num == 1) { // 倒计时即将结束
clearInterval(t); // 停止回调函数的执行
this.sms_code_tip = '获取短信验证码'; // 还原sms_code_tip的提示文字
this.generate_image_code(); // 重新生成图形验证码
this.send_flag = false;
} else { // 正在倒计时
num -= 1; // num = num - 1;
this.sms_code_tip = num + '秒';
}
}, 1000)
} else {
if (response.data.code == '4001') { // 图形验证码错误
this.error_image_code_message = response.data.errmsg;
this.error_image_code = true;
} else { // 4002 短信验证码错误
this.error_sms_code_message = response.data.errmsg;
this.error_sms_code = true;
}
this.send_flag = false;
}
})
.catch(error => {
console.log(error.response);
this.send_flag = false;
})
},
register.js完整代码:
// 我们采用的时ES6的语法
// 创建Vue对象 vm
let vm = new Vue({
el: '#app', // 通过ID选择器找到绑定的HTML内容
// 修改Vue读取变量的语法
delimiters: ['[[', ']]'],
data: { // 数据对象
// v-model
username: '',
password: '',
password2: '',
mobile: '',
allow: '',
image_code_url: '',
uuid: '',
image_code: '',
sms_code_tip: '获取短信验证码',
send_flag: false, // 类比上厕所,send_flag就是锁,false表示门开,true表示门关
sms_code: '',
// v-show
error_name: false,
error_password: false,
error_password2: false,
error_mobile: false,
error_allow: false,
error_image_code: false,
error_sms_code: false,
// error_message
error_name_message: '',
error_mobile_message: '',
error_image_code_message: '',
error_sms_code_message: '',
},
mounted() { // 页面加载完会被调用的
// 生成图形验证码
this.generate_image_code();
},
methods: { // 定义和实现事件方法
// 发送短信验证码
send_sms_code() {
// 避免恶意用户频繁的点击获取短信验证码的标签
if (this.send_flag == true) { // 先判断是否有人正在上厕所
return; // 有人正在上厕所,退回去
}
this.send_flag = true; // 如果可以进入到厕所,立即关门
// 校验数据:mobile,image_code
this.check_mobile();
this.check_image_code();
if (this.error_mobile == true || this.error_image_code == true) {
this.send_flag = false;
return;
}
let url = '/sms_codes/' + this.mobile + '/?image_code=' + this.image_code + '&uuid=' + this.uuid;
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 展示倒计时60秒效果
let num = 60;
let t = setInterval(() => {
if (num == 1) { // 倒计时即将结束
clearInterval(t); // 停止回调函数的执行
this.sms_code_tip = '获取短信验证码'; // 还原sms_code_tip的提示文字
this.generate_image_code(); // 重新生成图形验证码
this.send_flag = false;
} else { // 正在倒计时
num -= 1; // num = num - 1;
this.sms_code_tip = num + '秒';
}
}, 1000)
} else {
if (response.data.code == '4001') { // 图形验证码错误
this.error_image_code_message = response.data.errmsg;
this.error_image_code = true;
} else { // 4002 短信验证码错误
this.error_sms_code_message = response.data.errmsg;
this.error_sms_code = true;
}
this.send_flag = false;
}
})
.catch(error => {
console.log(error.response);
this.send_flag = false;
})
},
// 生成图形验证码的方法:封装的思想,代码复用
generate_image_code() {
this.uuid = generateUUID();
this.image_code_url = '/image_codes/' + this.uuid + '/';
},
// 校验用户名
check_username() {
// 用户名是5-20个字符,[a-zA-Z0-9_-]
// 定义正则
let re = /^[a-zA-Z0-9_-]{5,20}$/;
// 使用正则匹配用户名数据
if (re.test(this.username)) {
// 匹配成功,不展示错误提示信息
this.error_name = false;
} else {
// 匹配失败,展示错误提示信息
this.error_name_message = '请输入5-20个字符的用户名';
this.error_name = true;
}
// 判断用户名是否重复注册
if (this.error_name == false) { // 只有当用户输入的用户名满足条件时才回去判断
let url = '/usernames/' + this.username + '/count/';
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.count == 1) {
// 用户名已存在
this.error_name_message = '用户名已存在';
this.error_name = true;
} else {
// 用户名不存在
this.error_name = false;
}
})
.catch(error => {
console.log(error.response);
})
}
},
// 校验密码
check_password() {
let re = /^[0-9A-Za-z]{8,20}$/;
if (re.test(this.password)) {
this.error_password = false;
} else {
this.error_password = true;
}
},
// 校验确认密码
check_password2() {
if (this.password != this.password2) {
this.error_password2 = true;
} else {
this.error_password2 = false;
}
},
// 校验手机号
check_mobile() {
let re = /^1[3-9]\d{9}$/;
if (re.test(this.mobile)) {
this.error_mobile = false;
} else {
this.error_mobile_message = '您输入的手机号格式不正确';
this.error_mobile = true;
}
// 判断手机号是否重复注册
if (this.error_mobile == false) {
let url = '/mobiles/' + this.mobile + '/count/';
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.count == 1) {
this.error_mobile_message = '手机号已存在';
this.error_mobile = true;
} else {
this.error_mobile = false;
}
})
.catch(error => {
console.log(error.response);
})
}
},
// 校验图形验证码吗
check_image_code() {
if (this.image_code.length != 4) {
this.error_image_code_message = '请输入图形验证码';
this.error_image_code = true;
} else {
this.error_image_code = false;
}
},
// 校验短信验证码
check_sms_code() {
if (this.sms_code.length != 6) {
this.error_sms_code_message = '请填写短信验证码';
this.error_sms_code = true;
} else {
this.error_sms_code = false;
}
},
// 校验是否勾选协议
check_allow() {
if (!this.allow) {
this.error_allow = true;
} else {
this.error_allow = false;
}
},
// 监听表单提交事件
on_submit() {
this.check_username();
this.check_password();
this.check_password2();
this.check_mobile();
this.check_sms_code();
this.check_allow();
// 在校验之后,注册数据中,只要有错误,就禁用掉表单的提交事件
if (this.error_name == true || this.error_password == true || this.error_password2 == true || this.error_mobile == true || this.error_sms_code == true || this.error_allow == true) {
// 禁用掉表单的提交事件
window.event.returnValue = false;
}
},
}
});