1. 程式人生 > >圖片驗證碼和簡訊驗證碼開發

圖片驗證碼和簡訊驗證碼開發

圖片驗證碼和簡訊驗證碼開發

tip :前後端分離,先開發後端,後完善前端

一、圖片驗證碼流程

1539052738611

1、引入captcha包放入utils

不是獨立的第三方包放入utils,獨立的包放入libs裡面

1539053095673

  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);
       }

    } );

}