luffy_03

03用戶的註冊認證

註冊功能的實現

接下來,咱們把註冊過程當中一些註冊信息(例如:短信驗證碼)和session緩存到redis數據庫中。css

安裝django-redis。html

pip install django-redis

在settings.py配置中添加一下代碼:前端

# 設置redis緩存
CACHES = {
    # 默認緩存
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        # 項目上線時,須要調整這裏的路徑
        "LOCATION": "redis://127.0.0.1:6379/0",

        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    # 提供給xadmin或者admin的session存儲
    "session": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    # 提供存儲短信驗證碼
    "sms_code":{
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/2",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

# 設置xadmin用戶登陸時,登陸信息session保存到redis
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"

關於django-redis 的使用,說明文檔可見http://django-redis-chs.readthedocs.io/zh_CN/latest/vue

django-redis提供了get_redis_connection的方法,經過調用get_redis_connection方法傳遞redis的配置名稱可獲取到redis的鏈接對象,經過redis鏈接對象能夠執行redis命令python

https://redis-py.readthedocs.io/en/latest/ios

使用範例:redis

from django_redis import get_redis_connection
// 連接redis數據庫
redis_conn = get_redis_connection("default")

使用雲通信發送短信

在登陸後的平臺上面獲取一下信息:數據庫

ACCOUNT SID:8aaf0708697b6beb01699f4442911776
AUTH TOKEN : b4dea244f43a4e0f90e557f0a99c70fa
AppID(默認):8aaf0708697b6beb01699f4442e3177c
Rest URL(生產): app.cloopen.com:8883         [項目上線時使用真實短信發送服務器]
Rest URL(開發): sandboxapp.cloopen.com:8883  [項目開發時使用沙箱短信發送服務器]

找到sdkdemo進行下載django

1553677987640

在開發過程當中,爲了節約發送短信的成本,能夠把本身的或者同事的手機加入到測試號碼中.axios

1553678528811

把雲通信的sdk保存到libs目錄下, 並修改裏面的基本配置信息。

# -*- coding:utf-8 -*-

from .CCPRestSDK import REST
from django.conf import settings
# 說明:主帳號,登錄雲通信網站後,可在"控制檯-應用"中看到開發者主帳號ACCOUNT SID
_accountSid = settings.SMS_ACCOUNTSID

# 說明:主帳號Token,登錄雲通信網站後,可在控制檯-應用中看到開發者主帳號AUTH TOKEN
_accountToken = settings.SMS_ACCOUNTTOKEN
# 6dd01b2b60104b3dbc88b2b74158bac6
# 請使用管理控制檯首頁的APPID或本身建立應用的APPID
_appId = settings.SMS_APPID
# 8a216da863f8e6c20164139688400c21
# 說明:請求地址,生產環境配置成app.cloopen.com
_serverIP = settings.SMS_SERVERIP

# 說明:請求端口 ,生產環境爲8883
_serverPort = "8883"

# 說明:REST API版本號保持不變
_softVersion = '2013-12-26'

# 雲通信官方提供的發送短信代碼實例
# # 發送模板短信
# # @param to 手機號碼
# # @param datas 內容數據 格式爲數組 例如:{'12','34'},如不需替換請填 ''
# # @param $tempId 模板Id
#
# def sendTemplateSMS(to, datas, tempId):
#     # 初始化REST SDK
#     rest = REST(serverIP, serverPort, softVersion)
#     rest.setAccount(accountSid, accountToken)
#     rest.setAppId(appId)
#
#     result = rest.sendTemplateSMS(to, datas, tempId)
#     for k, v in result.iteritems():
#
#         if k == 'templateSMS':
#             for k, s in v.iteritems():
#                 print '%s:%s' % (k, s)
#         else:
#             print '%s:%s' % (k, v)


class CCP(object):
    """發送短信的輔助類"""

    def __new__(cls, *args, **kwargs):
        # 判斷是否存在類屬性_instance,_instance是類CCP的惟一對象,即單例
        if not hasattr(CCP, "_instance"):
            cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs)
            cls._instance.rest = REST(_serverIP, _serverPort, _softVersion)
            cls._instance.rest.setAccount(_accountSid, _accountToken)
            cls._instance.rest.setAppId(_appId)
        return cls._instance

    def send_template_sms(self, to, datas, temp_id):
        """發送模板短信"""
        # @param to 手機號碼
        # @param datas 內容數據 格式爲數組 例如:{'12','34'},如不需替換請填 ''
        # @param temp_id 模板Id
        result = self.rest.sendTemplateSMS(to, datas, temp_id)
        # 若是雲通信發送短信成功,返回的字典數據result中statuCode字段的值爲"000000"
        if result.get("statusCode") == "000000":
            # 返回0 表示發送短信成功
            return 0
        else:
            # 返回-1 表示發送失敗
            return -1


if __name__ == '__main__':
    ccp = CCP()
    # 注意: 測試的短信模板編號爲1[之後申請了企業帳號之後能夠有更多的模板]
    # 參數1: 客戶端手機號碼,測試時只能發給測試號碼
    # 參數2: 短信模塊中的數據
    #        短信驗證碼
    #        短信驗證碼有效期提示
    # 參數3: 短信模板的id,開發測試時,只能使用1
    result = ccp.send_template_sms('13928835901', ['1234',5], 1)
    print(result)

配置文件,代碼:

# 短信配置
# 主帳號
SMS_ACCOUNTSID = '8a216da86ab0b4d2016ab3e05fe400b1'

# 主帳號Token
SMS_ACCOUNTTOKEN = '5f0ba4296bbb4e248aa77253ccfe0b31'

# 建立應用的APPID
SMS_APPID = '8a216da86ab0b4d2016ab3e0603900b7'

# 說明:請求地址,生產環境配置成app.cloopen.com
SMS_SERVERIP = 'sandboxapp.cloopen.com'

後端生成短信驗證碼

from luffy.libs.yuntongxun.sms import CCP
from django_redis import get_redis_connection
class SMSAPIView(APIView):
    # url: users/sms/(?P<mobile>1[3-9]\d{9})
    def get(self,request,mobile):
        ccp = CCP()
        sms_code = "%04d" % random.randint(1,9999)
        result = ccp.send_template_sms(mobile,[sms_code, 5],1)

        if not result:
            """發送成功"""
            redis = get_redis_connection("sms_code")
            redis.setex("%s_sms_code" % mobile, 5*60, sms_code)

        return Response({"result":result})

urls.py,代碼:

re_path(r'sms/(?P<mobile>1[3-9]\d{9})/', views.SMSAPIView.as_view() ),

前端請求發送短信

調整前端的頁面,添加一個發送短信功能,

html代碼:

<div class="sms-box">
            <input v-model = "sms" type="text" placeholder="輸入驗證碼" class="user">
            <div class="sms-btn" @click="smsHandle">點擊發送短信</div>
          </div>

css,代碼:

.sms-box{
  position: relative;
}
.sms-btn{
    font-size: 14px;
    color: #ffc210;
    letter-spacing: .26px;
    position: absolute;
    right: 16px;
    top: 10px;
    cursor: pointer;
    overflow: hidden;
    background: #fff;
    border-left: 1px solid #484848;
    padding-left: 16px;
    padding-bottom: 4px;
}

script,代碼:

data裏面的methods中代碼:

methods:{
    // 發送短信
    smsHandle() {
      // 判斷是否填寫了手機
      if( !/^\d{11}$/.test(this.mobile) ){
        this.$alert('手機號碼格式有誤!', '警告');
        return false;
      }

      this.$axios.get(this.$settings.Host+`/users/sms/${this.mobile}/`).then(response=>{
        let data = response.data
        if( data.result == '-1' ){
          this.$alert("發送短信失敗!","錯誤");
        }else{
          this.$alert("發送短信成功了!","成功");
        }
      }).catch(error=>{
          console.log(error.response)
      })

    },
    // 提交註冊信息
    ....

前端實現倒計時顯示

<template>
....
          <div class="sms-box">
            <input v-model = "sms" type="text" placeholder="輸入驗證碼" class="user">
            <div class="sms-btn" @click="smsHandle">{{sms_text}}</div>
          </div>
....
</template>

<script>
export default {
  name: 'Register',
  data(){
    return {
        sms:"",
        mobile:"",
        password:"",
        password2:"",
        validateResult:false,
        is_send:false,    // 是否已經發送短信的狀態
        send_intervel:60, // 發送短信的間隔
        sms_text:"點擊發送短信", // 發送短信的提示
    }
  },
  methods:{
    // 發送短信
    smsHandle() {
      // 判斷是否填寫了手機
      if( !/^\d{11}$/.test(this.mobile) ){
        this.$alert('手機號碼格式有誤!', '警告');
        return false;
      }

      // 判斷是否在60s內有發送太短信,若是有則,不能點擊發送
      if(this.is_send){
        this.$alert('60s內不能頻繁發送短信!', '警告');
        return false;
      }


      let _this = this;

      _this.$axios.get(_this.$settings.Host+`/users/sms/${_this.mobile}/`).then(response=>{
        let data = response.data;
        if( data.result == '-1' ){
          _this.$alert("發送短信失敗!","錯誤");
        }else{
          _this.is_send = true;
          _this.$alert("發送短信成功了!","成功",{
            callback(){
              let num = _this.send_intervel
              let timer = setInterval(()=>{
                if(num<1){
                  clearInterval(timer);
                  _this.sms_text = "點擊發送短信";
                  _this.is_send = false;
                }else{
                  num--;
                  _this.sms_text = num+"後可繼續點擊發送";
                }
              },1000)
            }
          });
        }
      }).catch(error=>{
          console.log(error.response)
      })

    },
    // 提交註冊信息
    registerHander(){
        ....
    }
  },

};
</script>

後端實現短信發送間隔的判斷

視圖代碼:

class SMSAPIView(APIView):
    # url: users/sms/(?P<mobile>1[3-9]\d{9})
    def get(self,request,mobile):
        redis = get_redis_connection("sms_code")
        # 獲取短信發送間隔
        try:
            interval = redis.get("%s_interval" % mobile)
            if interval:
                print(interval)
                return Response({"result":"-1"})
        except:
            pass

        ccp = CCP()
        sms_code = "%04d" % random.randint(1,9999)
        result = ccp.send_template_sms(mobile,[sms_code, 5],1)

        if not result:
            """發送成功"""

            redis.setex("%s_sms_code" % mobile, 5*60, sms_code)
            # 這裏的值不重要,重要的是這個變量是否在redis被查找到
            redis.setex("%s_interval" % mobile, 60, 1)

        return Response({"result":result})

後端保存用戶註冊信息

建立序列化器對象[暫時不涉及到手機驗證碼功能]

from rest_framework import serializers
from .models import User
import re
from django_redis import get_redis_connection
class UserModelSerializer(serializers.ModelSerializer):
    sms_code = serializers.CharField(write_only=True, max_length=4,min_length=4,required=True,help_text="短信驗證碼")
    password2 = serializers.CharField(write_only=True,help_text="確認密碼")
    token = serializers.CharField(read_only=True,help_text="jwt token值")
    class Meta:
        model = User
        fields = ["mobile","id","token","password","password2","username","sms_code"]
        extra_kwargs = {
            "id":{"read_only":True},
            "username":{"read_only":True},
            "password":{"write_only":True},
            "mobile":{"write_only":True}
        }


    def validate_mobile(self, mobile):
        # 驗證格式
        result = re.match('^1[3-9]\d{9}$', mobile)
        if not result:
            raise serializers.ValidationError("手機號碼格式有誤!")

        # 驗證惟一性
        try:
            user = User.objects.get(mobile=mobile)
            if user:
                raise serializers.ValidationError("當前手機號碼已經被註冊!")

        except User.DoesNotExist:
            pass

        return mobile


    def validate(self, attrs):

        # 判斷密碼長度
        password = attrs.get("password")
        if not re.match('^.{6,16}$', password):
            raise serializers.ValidationError("密碼長度必須在6-16位之間!")

        # 判斷密碼和確認密碼是否一致
        password2 = attrs.get("password2")
        if password != password2:
            raise serializers.ValidationError("密碼和確認密碼不一致!")

        # 驗證短信驗證碼
        mobile = attrs.get("mobile")
        redis = get_redis_connection("sms_code")
        try:
            real_sms_code = redis.get("%s_sms_code" % mobile).decode()
        except:
            raise serializers.ValidationError("驗證碼不存在,或已通過期!")

        if real_sms_code != attrs.get("sms_code"):
            raise serializers.ValidationError("驗證碼不存在,或錯誤!")

        # 刪除本次使用的驗證碼
        try:
            redis.delete("%s_sms_code" % mobile)
        except:
            pass

        return attrs

    def create(self, validated_data):
        """保存用戶"""
        mobile = validated_data.get("mobile")
        password = validated_data.get("password")

        try:
            user = User.objects.create(
                mobile=mobile,
                username=mobile,
                password=password,
            )

            # 密碼加密
            user.set_password(user.password)
            user.save()

        except:
            raise serializers.ValidationError("註冊用戶失敗!")

        # 生成一個jwt
        from rest_framework_jwt.settings import api_settings

        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

        payload = jwt_payload_handler(user)
        user.token = jwt_encode_handler(payload)

        return user

視圖代碼:

from .serializers import UserModelSerializer
from rest_framework.generics import CreateAPIView
from .models import User
class UserAPIView(CreateAPIView):
    serializer_class = UserModelSerializer
    queryset = User.objects.all()

設置路由

# 子應用路由 urls.py
urlpatterns=[
    ...
    path(r'register/', views.UserAPIView.as_view() ),
]

客戶端發送註冊信息時附帶發送短信

1557801292782

本站公眾號
   歡迎關注本站公眾號,獲取更多信息