1. 程式人生 > >實現手機註冊——使用Python(DRF應用)接通騰訊雲簡訊服務介面

實現手機註冊——使用Python(DRF應用)接通騰訊雲簡訊服務介面

本篇以註冊或者登陸的單次單人傳送場景為例,使用Python進行簡訊介面的除錯,從而實現DRF應用的使用者註冊功能,至於群發或者語音等簡訊的場景,合此類似,本篇不做完整介紹。

一、申請騰訊雲簽名並建立模板 


 首先在騰訊雲服務中找到簡訊服務,點選開通,填寫基本資訊,最後生成如下:

其中SDK AppID和App key是在https請求傳送是的必要引數。

然後需要建立一個簽名,簽名相當於是簡訊服務的標題,提交後大概稽核1-2個小時就可以了。我這裡申請的是小程式的服務,所以只需要提供小程式的後臺截圖就可以了。

 要注意的是簽名的內容必須和小程式或者公眾號的名稱一樣,不然會稽核不通過。

建立成功以後就會顯示已通過,並且建立簽名的ID

同理再建立簡訊正文的模板:

 我們可以看到在正文的內容當中,我設定了兩個變數{1}和{2},所以在接下來的傳參過程中,僅正文內容的傳參就需要兩個,另外這裡要注意正文模板的ID號,待會在傳送請求的過程中也是需要的。

 

二、建立並封裝Python簡訊請求檔案tengxun.py


首先我們還是在我們的專案中建立一個tengxun.py檔案用於封裝簡訊傳送的程式碼。

以我的專案為例,我使用的DRF,將這個檔案放在了app目錄下的utils資料夾中。

為了方便進行請求,我們使用的是騰訊簡訊的SDK,對於Python而言,可以直接使用pip安裝:

pip install qcloudsms_py

如下圖所示安裝成功:

對於單條簡訊的傳送有其特定的傳送引數,至於其他型別的傳送,大家可以參考騰訊官方的SDK文件,託管到了Github上,所以我直接給出Github的地址,下發都是有說明的:

 https://github.com/qcloudsms/qcloudsms_py

如下建立tengxun.py檔案:

#!/usr/bin/env python
# encoding: utf-8
'''
@author: ZhonghangAlex
@contact: [email protected]
@software: pycharm @file: tengxun.py @time: 2018/11/28 12:43 @desc: ''' from qcloudsms_py import SmsSingleSender from qcloudsms_py.httpclient import HTTPError class TengXun(object): def __init__(self, appkey): self.appkey = appkey def send_sms(self, code, mobile): # 簡訊應用SDK AppID appid = 1400xx3806 # SDK AppID是1400開頭 # 簡訊應用SDK AppKey appkey = self.appkey # 需要傳送簡訊的手機號碼 phone_numbers = "{mobile}".format(mobile=mobile) # 簡訊模板ID,需要在簡訊應用中申請 template_id = 2393xx # NOTE: 這裡的模板ID`7839`只是一個示例,真實的模板ID需要在簡訊控制檯中申請 # 簽名 sms_sign = "Enet車聯" # NOTE: 這裡的簽名"騰訊雲"只是一個示例,真實的簽名需要在簡訊控制檯中申請,另外簽名引數使用的是`簽名內容`,而不是`簽名ID` ssender = SmsSingleSender(appid, appkey) params = ["{code}".format(code=code),"3"] # 當模板沒有引數時,`params = []` try: result = ssender.send_with_param(86, phone_numbers, template_id, params, sign=sms_sign, extend="", ext="") # 簽名引數未提供或者為空時,會使用預設簽名傳送簡訊 except HTTPError as e: print(e) except Exception as e: print(e) print(result) return result # 指令碼自測 if __name__ == "__main__": teng_xun = TengXun("a067bbcc3xx1c4b924xxc04bef7dc926") teng_xun.send_sms("2018", "15927093114")

 上面的很多引數,我都使用了xx做了替換,大家在寫的過程中填寫自己的相關引數就好。

可以看到在程式碼中我們封裝了一個Tengxun類用於接收引數以及進行簡訊的傳送。

程式碼下方的三行是指令碼自測,先例項化,傳入appkey,2018是要生產的驗證碼,15927093114是要傳送的手機號。

這個時候直接執行這個檔案就可以完成對簡訊的傳送。

可以看到返回的引數:

說明發送成功,此時手機上也接收到了簡訊: 

如果你僅僅是想使用一個指令碼進行簡訊傳送那麼就完成了,接下來我會結合DRF的相關操作,實現一個完整的使用者註冊功能。

 

三、DRF實現使用者註冊


為了程式碼的標準化,我們將一些全域性的引數放在settings中:

# 手機號碼正則表示式
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"

# 騰訊簡訊設定
APIKEY = "a067bbcxx1f1c4b924xxc04bef7dc926"

由於註冊功能面向的是使用者,所以我們就在user這個app中進行實現:

 首先我們要建立簡訊驗證的資料庫表,在models.py中編輯如下:

from datetime import datetime

from django.db import models
from django.contrib.auth.models import AbstractUser

# Create your models here.


class VerifyCode(models.Model):
    """
    簡訊驗證
    """
    code = models.CharField(max_length=10, verbose_name="驗證碼")
    mobile = models.CharField(max_length=11, verbose_name="電話")

    add_time = models.DateTimeField(default=datetime.now, verbose_name="新增時間")

    class Meta:
        verbose_name = "簡訊驗證碼"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.code

接著在serserializer.py中設定序列化格式,並進行檢驗:

#!/usr/bin/env python
# encoding: utf-8
'''
@author: ZhonghangAlex
@contact: [email protected]
@software: pycharm
@file: serializer.py
@time: 2018/11/28 13:15
@desc:
'''
import re
from rest_framework import serializers
from django.contrib.auth import get_user_model
from datetime import datetime
from datetime import timedelta
from .models import VerifyCode

from VueShop.settings import REGEX_MOBILE


User = get_user_model()


class SmsSerializer(serializers.Serializer):
    mobile = serializers.CharField(max_length=11)

    def validate_mobile(self, mobile):
        """
        驗證手機號碼
        :param data:
        :return:
        """

        # 手機是否註冊
        if User.objects.filter(mobile=mobile).count() != 0:
            raise serializers.ValidationError("使用者已經存在")

        # 驗證手機號碼是否合法
        if not re.match(REGEX_MOBILE, mobile):
            raise serializers.ValidationError("手機號碼非法")

        # 驗證傳送頻率
        one_min_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
        if VerifyCode.objects.filter(add_time__gt=one_min_ago, mobile=mobile).count():
            raise serializers.ValidationError("距離上一次傳送未超過60s")

        return mobile

接下來就要構建views.py的程式碼,我們在Tengxun.py檔案中封裝類TengXun這個類,所以我們可以在view中進行呼叫,其中用於單條簡訊傳送的class如下(採用CBV方式):

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from random import choice
from rest_framework.mixins import CreateModelMixin
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from VueShop.settings import APIKEY
from .serializer import SmsSerializer
from utils.tengxun import TengXun
from .models import VerifyCode

class SmsCodeViewset(CreateModelMixin, viewsets.GenericViewSet):
    """
    傳送簡訊驗證碼
    """
    serializer_class = SmsSerializer

    def generate_code(self):
        """
        生成四位數字的驗證碼
        :return:
        """
        seeds = "1234567890"
        random_str = []
        for i in range(4):
            random_str.append(choice(seeds))
        return "".join(random_str)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        mobile = serializer.validated_data["mobile"]

        teng_xun = TengXun(APIKEY)

        code = self.generate_code()

        sms_status = teng_xun.send_sms(code=code, mobile=mobile)

        if sms_status["result"] != 0:
            return Response({
                "mobile":sms_status["errmsg"]
            }, status=status.HTTP_400_BAD_REQUEST)
        else:
            code_record = VerifyCode(code=code, mobile=mobile)
            code_record.save()
            return Response({
                "mobile":mobile
            }, status=status.HTTP_201_CREATED)

然後在url配置中加入code配置:

# 配置code的url
router.register(r'code', SmsCodeViewset, base_name="code")

就完成了DRF應用對簡訊介面的呼叫,生成了如下的API:

最後要進行的就是登錄檔單的驗證 

在serializer.py中新增類

class UserRegSerializer(serializers.ModelSerializer):
    code = serializers.CharField(required=True, write_only=True, allow_blank=False, max_length=4, min_length=4,label="驗證碼",
                                 error_messages={
                                     "blank":"請輸入驗證碼",
                                     "required":"請輸入驗證碼",
                                     "max_length":"驗證碼格式錯誤",
                                     "min_length":"驗證碼格式錯誤"
                                 }, help_text="驗證碼")
    username = serializers.CharField(required=True, allow_blank=False, label="使用者名稱", validators=[UniqueValidator(queryset=User.objects.all(), message="使用者已經存在")])
    password = serializers.CharField(
        write_only=True,
        style={'input_type':'password'},
        label="密碼"
    )

    # 二次加密密碼,儲存資料庫時生成不能反解的密碼
    def create(self, validated_data):
        user = super(UserRegSerializer, self).create(validated_data=validated_data)
        user.set_password(validated_data["password"])
        user.save()
        return user

    def validate_code(self, code):
        # 為什麼不使用get方法
        # 因為使用get方法需要做異常的捕獲
        # try:
        #     verify_records = VerifyCode.objects.get(mobile=self.initial_data["userame"], code=code)
        # except VerifyCode.DoesNotExist as e:
        #     return e
        # except VerifyCode.MultipleObjectsReturned as e:
        #     return e
        verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
        if verify_records:
            last_records = verify_records[0]
            three_min_ago = datetime.now() - timedelta(hours=0, minutes=3, seconds=0)
            if three_min_ago > last_records.add_time:
                raise serializers.ValidationError("驗證碼過期")

            if last_records.code != code:
                raise serializers.ValidationError("驗證碼錯誤")

        else:
            raise serializers.ValidationError("驗證碼錯誤")

    def validate(self, attrs):
        attrs["mobile"] = attrs["username"]
        del attrs["code"]
        return attrs

    class Meta:
        model = User
        fields = ("username", "code", "mobile", "password")

在View中新增檢視類,並且通過重寫create和perform_create實現token定製化,實現註冊後自動登入:

class UserViewSet(CreateModelMixin, viewsets.GenericViewSet):
    """
    使用者
    """
    serializer_class = UserRegSerializer
    queryset = User.objects.all()

    # 重新定義create函式 實現註冊後自動登入 及定製化Token
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)

        re_dict = serializer.data
        payload = jwt_payload_handler(user)
        re_dict["token"] = jwt_encode_handler(payload)
        re_dict["name"] = user.name if user.name else user.username

        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        return serializer.save()

同樣在urls.py中進行路由的配置:

# 配置user的url
router.register(r'user', UserViewSet, base_name="users")

 這樣以來就完整地實現了通過簡訊註冊的邏輯。