图片验证码和短信验证码开发
tip :前后端分离,先开发后端,后完善前端
一、图片验证码流程
1、引入captcha包放入utils
不是独立的第三方包放入utils,独立的包放入libs里面
- captcha.py 里的生成验证码方法
captcha.generate_captcha()
- response_code.py 是各种的错误返回说明
- commons.py 是定义的正则表达转换器
from werkzeug.routing import BaseConverter
# 定义正则转换器
class ReConverter(BaseConverter):
""""""
def __init__(self, url_map, regex):
# 调用父类的初始化方法
super(ReConverter, self).__init__(url_map)
# 保存正则表达式
self.regex = regex
2.定义验证码api路由 verify.py
GET 127.0.0.1/api/v1.0/image_codes/<image_code_id>
, 保存到redis 数据库,但是redis数据类型 redis: 字符串 列表 哈希 set
的键值对,不能用列表,[列表里只能是字符串,不能放{键值对}],哈希可以用,但是使用哈希维护有效期的时候只能整体设置,想要单条维护因此选用字符串。连接redis保存数据和设置有效期redis_store.setex("image_code_%s" % image_code_id, 180, text)
其中180s 是常量,可以放置在一个单独的constants.py
constants.py
# coding:utf-8
# 图片验证码的redis有效期, 单位:秒
IMAGE_CODE_REDIS_EXPIRES = 180
# 短信验证码的有效期
SMS_CODE_REDIS_EXPIRES = 300
# 短信验证码的间隔
SEND_SMS_CODE_INYERVAL = 6
verify.py
# GET 127.0.0.1/api/v1.0/image_codes/<image_code_id>
@api.route("/image_codes/<image_code_id>")
def get_image_code(image_code_id):
"""
获取图片验证码
: params image_code_id: 图片验证码编号
:return: 正常:验证码图片 异常:返回json
"""
# 业务逻辑处理
# 生成验证码图片
# 名字,真实文本, 图片数据
name, text, image_data = captcha.generate_captcha()
# 将验证码真实值与编号保存到redis中, 设置有效期
# redis: 字符串 列表 哈希 set
# "key": xxx
# 使用哈希维护有效期的时候只能整体设置
# "image_codes": {"id1":"abc", "":"", "":""} 哈希 hset("image_codes", "id1", "abc") hget("image_codes", "id1")
# 单条维护记录,选用字符串
# "image_code_编号1": "真实值"
# "image_code_编号2": "真实值"
# redis_store.set("image_code_%s" % image_code_id, text)
# redis_store.expire("image_code_%s" % image_code_id, constants.IMAGE_CODE_REDIS_EXPIRES)
# 记录名字 有效期 记录值
try:
redis_store.setex("image_code_%s" % image_code_id, constants.IMAGE_CODE_REDIS_EXPIRES, text)
except Exception as e:
# 记录日志
current_app.logger.error(e)
# return jsonify(errno=RET.DBERR, errmsg="save image code id failed")
return jsonify(errno=RET.DBERR, errmsg="保存图片验证码失败")
# 返回图片
resp = make_response(image_data)
resp.headers["Content-Type"] = "image/jpg"
return resp
3.前端的完善 register.js + register.html
前端生成图片验证码的编号UUID 函数register.js
register.js
// 保存图片验证码编号
// 定义的全局变量,编号后边会使用
var imageCodeId = "";
function generateUUID() {
var d = new Date().getTime();
if(window.performance && typeof window.performance.now === "function"){
d += performance.now(); //use high-precision timer if available
}
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
}
形成图片验证码的后端地址, 设置到页面中,让浏览请求验证码图片
register.js
function generateImageCode() {
// 形成图片验证码的后端地址, 设置到页面中,让浏览请求验证码图片
// 1. 生成图片验证码编号
imageCodeId = generateUUID();
// 是指图片url
var url = "/api/v1.0/image_codes/" + imageCodeId;
$(".image-code img").attr("src", url);
}
register.html 连接
<input type="text" class="form-control" name="imagecode" id="imagecode" placeholder="图片验证码" required>
<div class="input-group-addon image-code" onclick="generateImageCode();"><img src=""></div>
二、短信验证码
1、 get 请求 api/v1.0/sms_codes/
- 要先验证图片的验证码的编号和验证码是否完整,
获取参数前端页面用户填写的图片验证码 + 前端生成 编号
校验参数参数是否完整
从redis数据库取出真实验证码 real_image_code网络连接有可能出现异常,抛出数据库异常
判断验证码是否过期只要判断redis的真实的验证码是否为None - 及时删除redis的图片验证码,防止用户使用同一个图片验证码多次登陆手机号
比较验证码的值real_image_code.lower() != image_code.lower() - 判断手机号是否发过短信验证码 设置send_flag 获取redis的标志,并在前端
.js
里定义60s 后才可以操作
判断手机号是否存在导入db ,models, 用User.query.filter_by().first() 是否为空,is not None 手机号存在 - 随机数用格式化字符
%06d,
手机号不存在,则生成短信验证码 - 保存短信验证码+验证码的send标识 redis_store.setex() 连接数据库的要抛出异常
- 发送短信 第三方连接可能出现错误,抛出异常
- 返回值判断
# get 请求 api/v1.0/sms_codes/<mobile>
@api.route("/sms_codes/<re(r'1[34578]\d{9}'):mobile>")
def get_sms_code(mobile):
# 获取参数
image_code = request.args.get("image_code")
image_code_id = request.args.get("image_code_id")
# 校验参数
if not all([image_code_id, image_code]):
return jsonify(errno=RET.PARAMERR, errmsg="参数不完整")
# 业务逻辑处理
# 1. 从redis 取出真实验证码
try:
real_image_code = redis_store.get("image_code_%s" % image_code_id)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DATAERR, errmsg="数据库异常")
# 2.判断是否过期
if real_image_code is None:
return jsonify(errno=RET.NODATA, errmsg="验证码过期")
# 删除redis 中的图片验证码 ,防止用户使用同一个图片验证码多次
try :
redis_store.delete("image_code_%s " % image_code_id)
except Exception as e:
current_app.logger.error(e)
# 3. 比较验证码的值
if real_image_code.lower() != image_code.lower():
return jsonify(errno=RET.DATAERR, errmsg="图片验证码错误")
# 手机号是否处理过,对于这个手机号的操做在60s之内是否有记录
try:
send_flag = redis_store.get("send_sms_code_%s " % mobile)
except Exception as e:
current_app.logger.error(e)
else:
if send_flag is not None:
return jsonify(errno=RET.REQERR, errmsg="请求过于频繁,请在60 s之后再操作")
# 4. 判断手机号是否存在
try:
user = User.query.filter_by(mobile=mobile).first()
except Exception as e:
current_app.logger.error(e)
else:
if user is not None:
return jsonify(errno=RET.DATAEXIST, errmsg="手机号已存在")
# 5. 随机数用格式化字符,手机号不存在,则生成短信验证码
sms_code = "%06d" % random.randint(0, 999999)
# ^6. 保存短信验证码
try:
redis_store.setex("sms_code_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES,sms_code)
redis_store.setex("send_sms_code_%s" % mobile,constants.SEND_SMS_CODE_INYERVAL, 1)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DATAERR, errmsg="保存验证码错误")
# 7.发送短信
try:
ccp = CCP()
result = ccp.send_template_sms(mobile,[sms_code,int(constants.SMS_CODE_REDIS_EXPIRES/60)],1)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.THIRDERR, errmsg="失败")
#返回值
print(result)
if result == 0:
return jsonify(errno= RET.OK, errmsg= "成功")
else:
return jsonify(errno= RET.THIRDERR,errmsg ="发送失败")
2、前端的完善
向后端构造请求的参数 向后端发送请求,resp 是后端返回的响应值,因为后端返回的时json字符串
//register.js
function sendSMSCode() {
$(".phonecode-a").removeAttr("onclick");
var mobile = $("#mobile").val();
if (!mobile) {
$("#mobile-err span").html("请填写正确的手机号!");
$("#mobile-err").show();
$(".phonecode-a").attr("onclick", "sendSMSCode();");
return;
}
var imageCode = $("#imagecode").val();
if (!imageCode) {
$("#image-code-err span").html("请填写验证码!");
$("#image-code-err").show();
$(".phonecode-a").attr("onclick", "sendSMSCode();");
return;
}
// 向后端构造请求的参数
var reg_data = {
image_code: imageCode ,
image_code_id: imageCodeId,
};
// 像后端发送请求
$.get("/api/v1.0/sms_codes/"+ mobile,reg_data,function(resp){
//resp 是后端返回的响应值,因为后端返回的时json字符串
// 所以ajax帮助我们把这个json字符串转换为js对象,resp是转换后的对象
if (resp.errno =="0"){
var num = 60;
var timer = setInterval(function(){
if (num > 1){
$(".phonecode-a").html(num + "秒");
num -= 1
} else {
$(".phonecode-a").html("获取验证码");
$("..phonecode-a").attr("onclick","sendSMSCode();");
clearInterval(timer)
}
}, 1000, 60)
} else {
alert(resp.errmsg);
}
} );
}