图片验证码和短信验证码开发

tip :前后端分离,先开发后端,后完善前端

一、图片验证码流程

1、引入captcha包放入utils

不是独立的第三方包放入utils,独立的包放入libs里面

  1. captcha.py 里的生成验证码方法captcha.generate_captcha()
  2. response_code.py 是各种的错误返回说明
  3. 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);
       }

    } );

}

11-22 19:17