1. 程式人生 > >Flask專案之手機端租房網站的實戰開發(六)

Flask專案之手機端租房網站的實戰開發(六)

說明:該篇部落格是博主一字一碼編寫的,實屬不易,請尊重原創,謝謝大家!

接著上一篇部落格繼續往下寫 :https://blog.csdn.net/qq_41782425/article/details/85858348

目錄

一丶補充

二丶簡訊驗證碼前端編寫 

三丶註冊後端介面編寫

四丶註冊後端測試

五丶註冊前端編寫

六丶使用者註冊功能測試


一丶補充

1.之前寫的獲取的圖片驗證碼,有一個地方需要做進一步的處理,需要刪除redis資料庫中的圖片驗證碼,防止使用者使用同一個圖片驗證碼驗證多次,所以將刪除程式碼放在取出redis資料庫中圖片驗證碼的值之後,也就是在real_image_code變數之後進行刪除,這樣即使刪除了redis資料中的資料,也不會影響接下來的判斷校驗,這樣做的目的就是說不管使用者填對填錯,只有一次驗證機會

2.測試以上邏輯是否正確

  • step2 檢視redis資料庫圖片驗證碼的值

  • step3 在Postman工具中傳送簡訊驗證碼路由地址

此時繫結的手機號碼上就會收到我們定義的6位數驗證碼

  • step4 檢視redis資料庫(這個時候並沒有超過180秒),發現圖片驗證碼的key已經被刪除

3.簡訊驗證碼這一塊,大多網站都會在使用者傳送第一次驗證碼後,在前端頁面會出現60s後再試之類的提醒,那麼對於後端來說,也是需要的,比如被人知道了後我埠的介面,1分鐘之類不斷的傳送簡訊,那可就不好了,所以為了做到優化以及安全防範,那麼就需要在後端進行一個禁止60s內傳送第二次簡訊的一個邏輯,不僅僅限於前端

  • step1 邏輯分析,首先當傳送第一次簡訊時,需要記住傳送簡訊記錄,當時間超過60s以上,才可以傳送第二條簡訊,此時就需要想想是在什麼時候將這個傳送簡訊的記錄存到redis資料庫,大多公司是這樣做的,不管傳送成功與否,都是需要等待60s後才能傳送第二次,所以我們這裡也是這樣做的,在傳送簡訊之前就將記錄存到redis資料庫當中去
  • step2 當在儲存簡訊驗證碼的時候,將這個第一次傳送簡訊的記錄存到redis資料庫中,讓redis資料庫去維護記錄的有效期60s,這個記錄的資料隨便起,這裡設定1,那麼當在redis資料庫查詢不到這個記錄時,代表可以記錄過了有效期被刪除了,則代表可以傳送第二條簡訊驗證碼了
redis_store.setex("send_sms_code_%s" % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)
  • step3 然後在判斷手機號是否註冊之前進行校驗判斷,判斷對於此手機號碼在60s以內有沒有之前傳送簡訊的操作,如果有,則不代表使用者操作頻繁,不予處理(注:經測試如果在判斷獲取記錄的值的時候程式碼為if send_flag == 1時,會出現bug,所以建議使用is not None即可)

4.測試以上邏輯是否正常

  • step1 啟動專案程式

  • step2 重新整理http://127.0.0.1:5000/register.html 註冊頁面,生成新的圖片驗證碼,將驗證碼內容以及對應編碼通過傳送簡訊路由傳送到對應介面進行測試

  • step3 此時從redis資料庫中檢視圖片驗證碼的id以及值

  • step4 在Postman工具中,對簡訊介面地址進行訪問,並攜帶image_code以及image_code_id

同時收到手機簡訊驗證碼

  • step5 最關鍵的一步,與此同時,重新整理圖片驗證碼,獲取新的圖片驗證碼,然後在redis資料查詢id以及圖片驗證碼的值後在Postman中進行再次傳送(注:這裡的演示是在60s之內傳送第二次請求給後端簡訊介面),出現如下提示,代表邏輯正確

二丶簡訊驗證碼前端編寫 

1.在register.js中構造請求資料

var req_data = {
      "image_code": imageCode,  //使用者輸入圖片驗證碼的值
      "image_code_id": imageCodeId  //對應圖片驗證碼的編號
    };

2.通過ajax方式想後端去傳送請求

// 想後端傳送請求
$.get("/api/v1.0/sms_codes/"+mobile, req_data, function (resp) {
    // 回撥函式中的resp是後端返回的json字串,通過ajax將這個字串轉換成js物件
    // 所以這裡的resp為ajax轉換後的物件
    if (resp.error == "0") {
        // 表示傳送成功
        var num = 60;
        var timer = setInterval(function () {
            if (num>1) {
                // 修改倒計時的文字內容
                $(".phonecode-a").html(num + "s");
                num -= 1
            } else {
                $(".phonecode-a").html("獲取驗證碼");
                $(".phonecode-a").attr("onclick", "sendSMSCode();");
                clearInterval(timer)
            }
        }, 1000, 60)
    } else {
        alert(resp.errmsg);
        $(".phonecode-a").attr("onclick", "sendSMSCode();");
    }

});

3.測試

  • step1 啟動程式

  • step3 輸入手機號,圖片驗證碼後點擊獲取簡訊驗證碼,成功出現倒計時,同時手機收到簡訊驗證碼

手機驗證碼

  • step4 在谷歌瀏覽器中檢視介面地址的NetWork

General資料

Response響應資料 

三丶註冊後端介面編寫

1.在專案api_1_0目錄下建立passport.py檔案用作於註冊以及登入的邏輯介面檔案

2.構建register檢視函式,並在ihome/init檔案中匯入passport模組

@api.route("/users", methods=["POST"])
def register():
    pass

3.邏輯編寫

  • step1 獲取前端請求傳送請求中的json資料
req_dict = request.get_json()
mobile = req_dict.get("mobile") #跟前端約定好的
sms_code = req_dict.get("sms_code")
password = req_dict.get("password")
password2 = req_dict.get("password2")
  • step2 校驗引數
if not all([mobile, sms_code, password, password2]):
    return jsonify(errno=RET.PARAMERR, errmsg="請求引數不完整")
  • step3 判斷使用者輸入的手機號碼格式

  • step4 從redis資料庫中取出簡訊驗證碼
try:
    real_sms_code = redis_store.get("sms_code_%s" % mobile)
except Exception as e:
    current_app.logger.error(e)
    return jsonify(errno=RET.DBERR, errmsg="讀取真實簡訊驗證碼異常")
  • step5 判斷簡訊驗證碼是否過期
if real_sms_code is None:
    return jsonify(errno=RET.NODATA, errmsg="簡訊驗證碼失效")
  • step6 判斷使用者輸入的簡訊驗證碼是否正確
if real_sms_code != sms_code:
    return jsonify(errno=RET.DATAERR, errmsg="簡訊驗證碼填寫錯誤")
  • step7 同圖片驗證碼一樣,在redis資料庫中進行刪除,防止一個簡訊驗證碼多次使用
try:
    redis_store.delete("sms_code_%s" % mobile)
except Exception as e:
    current_app.logger.error(e)
  • step8 判斷使用者的手機號是否註冊過

  • step9 將使用者註冊的資料儲存到mysql資料庫中,這時候為了減少資料庫的查詢,不增加程式執行的負荷,所以我們在判斷使用者手機號是否註冊過的時候,不再進行查詢,而是在儲存使用者註冊資料時候,判斷手機號碼是否存在,因之前在models中建立資料庫表時,將mobile欄位的資料設定為唯一約束,所以當資料庫ih_user_profile表中已存在mobile欄位的資料時,再進行插入相同手機號mobile的資料時,mysql資料庫則會丟擲異常,那麼我們就利用資料庫丟擲的異常來判斷手機號碼是否存在,從而減少在檢視函式中資料庫的查詢

資料庫測試mobile欄位異常

  •  step10 註釋step8程式碼重新編寫,大體如下,因密碼需要進行加密後再存到資料庫,所以到最後面再進行處理

說明:這裡的註冊名為手機號,後面會另寫介面讓name為名字暱稱,當出現異常時候,需要進行資料庫回滾操作

  • step11 將登陸狀態儲存到session中,並返回註冊成功結果
session["name"] = mobile
session["mobile"] = mobile
session["user_id"] = user.id

return jsonify(errno=RET.OK, errmsg="註冊成功")
  • step12 將使用者輸入的密碼進行加密

說明:將對使用者輸入的密碼新增salt值(鹽值),再進行加密儲存,說白了就是,在戶輸入的密碼基礎上再新增一個隨機生成的字串,即使使用者1和使用者2密碼一樣,但在進行salt處理後,在資料庫中也不會相同,不存在撞庫說法,這樣做安全性極高,即使密碼洩露,破解出來後也並不是使用者的真是密碼

問題:反過來當用戶註冊成功後,進行使用者登入時,那麼這個密碼是如何進行判斷?是將使用者輸入的密碼進行加密後再和資料庫加密密碼進行對比,可是當初在註冊時對使用者輸入的密碼進行加密時候添加了隨機的salt值,所以這裡需要注意思考一下,我們這裡的想法是通過在註冊時候將salt值進行儲存在資料庫中另起一個欄位,這是其一方法,其二是將這個salt值與加密後的密碼中間以某個分隔符進行分隔,這樣做的話就不用再建立表字段另存salt值,如[email protected],@符前為salt值,後為使用者加密後的密碼

:MD5已被攻破,利用暴力測試以及演算法公式成功反推出加密前的密碼,sh1估計等不了幾年就會被破解,我們這裡使用的是sha256進行密碼的加密

①在models檔案中通過匯入werkzeug.security類中的generate_password_hash方法將密碼進行加密,並存到資料庫中

def generate_password_hash(self, origin_password):
    self.password_hash = generate_password_hash(origin_password)

②在passport檔案中,在構建User物件的時候,通過呼叫models檔案中的generate_password_hash方法,將前端使用者輸入的password傳參給此方法,並儲存到User模型類的passwor_hash欄位中

user.generate_password_hash(password)

4.property裝飾器的使用

  • step1 將函式呼叫轉換為物件屬性設定(在passport檔案中)
user.password = password
  • step2 在models中註釋之前的generate_password_hash方法的呼叫,使用property裝飾器對新建立的password函式裝飾為password屬性(注,在passport檔案中user.password物件的password屬性名要與models檔案中的方法同名),再通過password屬性的setter方法,設定類屬性的值,這個值就是我們在passport檔案中通過user.password = password這個password值

四丶註冊後端測試

  • step1 因測試users註冊後端介面使用的POST請求方式,而此時還沒有編寫註冊前端埠測試,所以需要在ihome/init檔案中將csrf後端機制關閉,再進行測試
# 為flask補充csrf防護
# CSRFProtect(app)

  • step3 在註冊頁面輸入錯誤手機號,正確的圖片驗證碼,當點選獲取簡訊驗證碼時候,會提示傳送簡訊失敗,無所謂,我們從redis資料庫直接讀取sms_code即可

  • step4 在redis資料庫中獲取簡訊驗證碼(因為在上一步輸入的是錯誤手機號,導致無法傳送簡訊,測試無所謂嘛)

  • step5 在測試工具Postman中,新增127.0.0.1:5000/api/v1.0/users地址,構造json格式的body體資料,就是我們註冊頁面所需要的資料,成功返回註冊成功響應資料,代表註冊後端介面沒問題

  • step6 響應資料表示註冊成功後,檢視資料庫ih_user_profile表資訊,第一條資料是之前測試使用的

五丶註冊前端編寫

1.前端註冊HTML程式碼(不展示)

2.前端註冊JS程式碼(不展示)

3.在register.js檔案中通過ajax方式向後端介面傳送註冊請求(在callback for submit())

// 呼叫ajax向後端傳送註冊請求
        var req_data = {
            mobile:mobile,
            sms_code:phoneCode,
            password:passwd,
            password2:passwd2

        };
        var req_json = JSON.stringify(req_data)
        $.ajax({
            url:"/api/v1.0/users",
            type:"post",
            data:req_json,
            contentType:"application/json",
            dataType: "json",
            headers:{
              "X-CSRFToken":getCookie("csrf_token")
            },
            success: function (resp) {
                if (resp.errno== "0"){
                    // 註冊成功,即跳轉到主頁
                    location.href = "/index.html";
                }else {
                    alert(resp.errmsg);
                }
                
            }

        })

4.在js構建一個getCookie的方法來獲取前端名為csrf_token的cookie的值

function getCookie(name) {
    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return r ? r[1] : undefined;
}

 5.將獲取的cookie的值,放在請求頭中的X-CSRFToken鍵中,方便後端進行csrf驗證(需將ihome/init中的CSRF驗證開啟)

headers:{
          "X-CSRFToken":getCookie("csrf_token")
        },

六丶使用者註冊功能測試

1.進行註冊測試,輸入不存在的號碼是為了演示給大家,雖然提示傳送簡訊驗證碼失敗,但我可以從redis資料庫去拿取簡訊驗證碼,註冊成功後,即跳轉到登入頁面

2.使用者註冊成功後,檢視資料庫中ih_user_profile表的內容,成功將使用者註冊資訊儲存到資料庫