1. 程式人生 > 其它 >02-簡訊驗證碼完成登入註冊功能

02-簡訊驗證碼完成登入註冊功能

小程式登入註冊頁面

該頁面只有一個按鈕,如果使用者沒有註冊過的話,會直接完成註冊,首先使用者點選獲取驗證碼按鈕,在小程式前端會對手機號做一個簡單的校驗,然後將手機號傳送到Django後端,後端對此手機號傳送簡訊驗證碼,使用者在規定的時間內輸入驗證碼,點選登入註冊按鈕,小程式前端將手機號和驗證碼再次傳送到後端進行校驗,校驗通過的話,本次登入註冊功能完成。


本文每個細節處只展現關聯程式碼,文末會展現整個專案的檔案結構,以及檔案完整內容。


介面設計

獲取驗證碼

url: "http://127.0.0.1:8000/api/login/"
data: { phone: this.data.phone }
method: 'GET',
dataType: 'json'

登入與註冊

url: "http://127.0.0.1:8000/api/login/",
data: { phone: this.data.phone, code: this.data.code },
method: 'POST',
dataType: 'json',

後端技術:

Django DRF Redis 騰訊雲

業務流程

該功能採用Restful介面設計風格,獲取驗證碼和登入註冊使用同一個檢視類的不同函式響應。

  1. 小程式傳送獲取驗證碼的請求

  2. 傳送簡訊。

Django接受到了請求,先校驗手機號是否合法
校驗失敗則返回{"status":False,"message":"手機號格式錯誤"}


校驗通過隨機生成一個驗證碼則通過騰訊雲傳送簡訊,
如果傳送失敗,則向前端返回{"status":False,"message":"簡訊傳送失敗"}
如果傳送成功,則以電話為鍵,驗證碼為值存入redis,向前端傳送{"status":True,"message":"簡訊傳送成功"}

  1. 小程式傳送登入註冊請求

  2. 完成登入註冊

Django接收請求,校驗手機號和驗證碼是否正確
校驗失敗返回{"status": False, 'message': '手機號或者驗證碼錯誤'}
校驗成功,返回{"status": True, "data": {"token": token, "phone": phone}}

傳送簡訊驗證碼

傳送簡訊驗證碼之前要先校驗手機號格式,在這裡我們只是校驗手機號格式是否正確,屬於輕量級校驗,我們使用DRF的Serializer類來實現,使用雖然程式碼量變多了,但是為了保證統一的程式設計風格。

1. 建立序列化類

建立檔案serializer.py專門用來存放序列化類,在該檔案中建立類MessageSerializer

MessageSerializer

lass MessageSerializer(serializers.Serializer):
    '''
    用於給手機發送簡訊時驗證手機號是否正確的序列化類
    '''

    phone = serializers.CharField(label='手機號', validators=[phone_validator, ])

因為後面的登入註冊介面也需要用到手機號校驗,因此這裡把手機號校驗寫成了一個函式提取出來,然後將其放到了MessageSerializer的手機號欄位校驗規則中

phone_validator

def phone_validator(value):
    if not re.match(r"^(1[3|4|5|6|7|8|9])\d{9}$", value):
        raise ValidationError('手機號格式錯誤')

2. 檢視中引用序列化類的校驗功能

在views.py中建立登入註冊的檢視處理類

LoginOrRegistView

class LoginOrRegistView(APIView):
    '''
    首先使用簡訊驗證碼請求校驗類校驗手機號是否合格
    合格則生成隨機驗證碼,嘗試傳送簡訊,
    傳送失敗,就返回,傳送成功就將手機號和簡訊驗證碼放入到redis中然後返回
    '''
    def get(self, request, *args, **kwargs):
        # print(request.query_params)
        ser = serializer.MessageSerializer(data=request.query_params)
        if not ser.is_valid():
            return Response({"status": False, "message": "手機號格式錯誤"})

        phone = ser.validated_data.get('phone')
        code = str(random.randint(100000, 999999))

        result = send_message(phone, code)
        if not result:
            return Response({"status": False, "message": "簡訊傳送失敗"})

        conn = get_redis_connection()
        conn.set(phone, code, ex=60*int(settings.TENCENT_LIMIT_TIME))

        return Response({"status": True, "message": "簡訊傳送成功"})

上面的程式碼可以看到,我將騰訊雲的傳送簡訊功能提煉成了一個函式,直接呼叫即可,這裡有我的關於如何使用騰訊雲傳送簡訊

3. 使用騰訊雲傳送簡訊

騰訊雲傳送簡訊函式可以寫在專案根目錄下的utils中。傳送簡訊只需要一個手機號和驗證碼,其餘的資訊可以配置

tencent_sms.py

import json
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.sms.v20210111 import sms_client, models
from paimai import settings # paimai是我本次專案的名字,請按照自己的修改


def send_message(phone, code):
    try:
        cred = credential.Credential(
            settings.TENCENT_SECRET_ID, settings.TENCENT_SECRET_KEY)

        httpProfile = HttpProfile()
        httpProfile.endpoint = settings.TENCENT_ENDPOINT

        clientProfile = ClientProfile()
        clientProfile.httpProfile = httpProfile
        client = sms_client.SmsClient(
            cred, settings.TENCENT_CITY, clientProfile)

        req = models.SendSmsRequest()
        params = {
            "PhoneNumberSet": [settings.TENCENT_CHINA + phone, ],
            "SmsSdkAppId": settings.TENCENT_APP_ID,
            "SignName": settings.TENCENT_SIGN,
            "TemplateId": settings.TENCENT_TEMPLATED_ID,
            "TemplateParamSet": [code, settings.TENCENT_LIMIT_TIME]
        }
        req.from_json_string(json.dumps(params))

        resp = client.SendSms(req)
        if resp.SendStatusSet[0].Code == "Ok":
            return True
        # print(resp.to_json_string(indent=2))

    except TencentCloudSDKException as err:
        # print(err)
        pass

該函式的具體內容請在參照使用騰訊雲傳送簡訊自行揣摩,這裡不再贅述,我將其用到的一些配置資訊寫進了全域性配置檔案中

在專案的全域性配置檔案中新增如下配置

# ############################# 騰訊雲簡訊配置 #############################
TENCENT_SECRET_ID = "你的secretid"
TENCENT_SECRET_KEY = "你的secretkey"
TENCENT_CITY = "ap-guangzhou"
TENCENT_APP_ID = "1400636319"
TENCENT_SIGN = "派森之旅個人公眾號"
TENCENT_TEMPLATED_ID = "1310476"
TENCENT_ENDPOINT = "sms.tencentcloudapi.com"
TENCENT_LIMIT_TIME = "2" # 其中一個配置傳送模板的配置引數,在我的模板中,這個引數填寫之後,是,請與2分鐘之內完成登入和註冊
TENCENT_CHINA = "+86"

4. 配置redis

步驟2中可以看到這條程式碼get_redis_connection這是django提供的redis聯結器,django可以通過配置的形式直接使用redis,而無需我們專門書寫

在專案的全域性配置檔案settings.py中追加這段程式碼,讀者請依據自己的redis配置修改填寫

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        # 需要著重修改的就是這個redis地址
        "LOCATION": "redis://192.168.1.100:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            # "PASSwORD":“密碼",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}
        }
    }
}

登入註冊

使用者收到簡訊驗證碼,填寫之後,向後端傳送登入註冊請求,在這裡我們要先後檢驗手機號和驗證碼是否正確,老規矩,但凡攜帶資料的請求,我們都儘可能得用序列化器來實現校驗,而在核心程式碼區,則只需要呼叫一個is_valid()來判斷即可,可使程式碼更加結構化和簡潔。

1. 登入註冊的序列化類LoginOrRegistSerializer

class LoginOrRegistSerializer(serializers.Serializer):
    '''
    登陸或者註冊時的序列化類,
    首先校驗手機號是否正確
    校驗簡訊驗證碼格式是否正確[長度是否正確,字元是否都是數字]
    和redis中的驗證碼比較,如果redis中取不到值,則驗證碼過期
    如果和redis中的驗證碼不一致,則驗證碼輸入錯誤
    '''
    phone = serializers.CharField(label='手機號', validators=[phone_validator, ])
    code = serializers.CharField(label='簡訊驗證碼')

    def validate_code(self, value):
        if len(value) != 6:
            raise ValidationError('驗證碼格式錯誤')
        if not value.isdecimal():
            raise ValidationError('驗證碼格式錯誤')

        phone = self.initial_data['phone']
        conn = get_redis_connection()
        code = conn.get(phone)

        if not code:
            raise ValidationError('驗證碼已過期')
        if value != code.decode('utf-8'):
            raise ValidationError('驗證碼錯誤')

        return value

2. 登入註冊實現

還是LoginOrRegistView這個檢視類,不過這次我們使用post這個方法來響應請求,因為這個請求本身就是post型別的


class LoginOrRegistView(APIView):

    def post(self, request, *args, **kwargs):
        # print(request.data)
        ser = serializer.LoginOrRegistSerializer(data=request.data)
        if not ser.is_valid():
            print(ser.errors)
            return Response({"status": False, 'message': '手機號或者驗證碼錯誤'})

        phone = ser.validated_data.get('phone')
        user_object, flag = UserInfo.objects.get_or_create(phone=phone)
        user_object.token = str(uuid.uuid4())
        user_object.save()

        return Response({"status": True, "data": {"token": user_object.token, "phone": phone}})

可以看到這裡的post方法內容比之前面的get方法還要少,使用了序列化類以後,程式碼更加精煉簡潔,功能劃分也更清楚了。

上述程式碼中,校驗成功後,後端使用uuid隨機生成一個token,也可以使用jwt,這裡為了方便,我們就使用了比較原始的token驗證,將touke放在使用者表中,在使用者登入以後就通過token來實現使用者請求的身份識別。get_or_create()這個方法,表中有就返回,沒有就創造,也就是我們這裡實現的沒有註冊的話預設註冊。使用者表的涉及到django的model使用,在我的系列部落格中有過講解,這裡不做說明,表字段自行設計

總結

使用小程式+django+騰訊雲完成驗證碼登入註冊的核心程式碼就這麼多,至於小程式端的程式碼為什麼沒有寫?因為這是一個前後端分離的專案,後端只需要處理請求即可,也就是說你就算使用postman也可以完成這段後端程式碼的開發和測試。前端自有邏輯,寫在這裡太冗餘了。本系列部落格並非是從零搭建專案,而是記錄每一個功能模板的實現。

仔細測試後端登入註冊部分,可以發現,有幾種異常情況,我們統統返回了手機號或者驗證碼錯誤,這對使用者來說不友好,例如是手機號錯誤,還是驗證碼錯誤,驗證碼錯誤又分為,驗證碼為空,驗證碼格式錯誤和驗證碼過期。驗證碼過期的時候,是從redis中取不到資料的,但是使用者是否請求過驗證碼呢?這些是否有必要分的那麼細?這就視專案情況而定。這些都有技術手段可以搞定,這裡不過分闡述了。