vue_drf實(shí)現(xiàn)短信驗(yàn)證碼
一、需求
1,需求
我們?cè)谧鼍W(wǎng)站開發(fā)時(shí),登錄頁(yè)面很多情況下是可以用手機(jī)號(hào)接收短信驗(yàn)證碼,然后實(shí)現(xiàn)登錄的,那我們今天就來(lái)做一做這一功能。

偽代碼:
進(jìn)入登錄頁(yè)面,點(diǎn)擊短信登錄
輸入手機(jī)號(hào)碼,點(diǎn)擊獲取驗(yàn)證碼,后端在redis里保存驗(yàn)證碼
用戶把手機(jī)收到的驗(yàn)證碼輸入,點(diǎn)擊登錄,會(huì)把手機(jī)號(hào)和驗(yàn)證碼發(fā)往后端,然后進(jìn)行驗(yàn)證
要想發(fā)送短信,讓用戶收到短信,我們的借助一個(gè)容聯(lián)云的接口,注冊(cè)一個(gè)賬號(hào)。

使用時(shí)需要的一些參數(shù):

下載sdk
1.。。。。。。。

2.。。。。。。

3.。。。。。。。

下載完成后,解壓。放入我們drf項(xiàng)目的apps里的libs里
二、sdk參數(shù)配置
1,目錄結(jié)構(gòu)

2,配置sms.py文件
# -*- coding:utf-8 -*-
from .CCPRestSDK import REST
# 說(shuō)明:主賬號(hào),登陸云通訊網(wǎng)站后,可在"控制臺(tái)-應(yīng)用"中看到開發(fā)者主賬號(hào)ACCOUNT SID
_accountSid = 'xxxxxxxxxxxxx'
# 8a216da863f8e6c20164139687e80c1b
# 說(shuō)明:主賬號(hào)Token,登陸云通訊網(wǎng)站后,可在控制臺(tái)-應(yīng)用中看到開發(fā)者主賬號(hào)AUTH TOKEN
_accountToken = 'xxxxxxxxxxxxx'
# 6dd01b2b60104b3dbc88b2b74158bac6
# 請(qǐng)使用管理控制臺(tái)首頁(yè)的APPID或自己創(chuàng)建應(yīng)用的APPID
_appId = '8aaf0708697b6beb01699f3c645f1766'
# 8a216da863f8e6c20164139688400c21
# 說(shuō)明:請(qǐng)求地址,生產(chǎn)環(huán)境配置成app.cloopen.com
_serverIP = 'sandboxapp.cloopen.com'
# 說(shuō)明:請(qǐng)求端口 ,生產(chǎn)環(huán)境為8883
_serverPort = "8883"
# 說(shuō)明:REST API版本號(hào)保持不變
_softVersion = '2013-12-26'
#下面的內(nèi)容不用修改
class CCP(object):
"""發(fā)送短信的輔助類"""
def __new__(cls, *args, **kwargs):
# 判斷是否存在類屬性_instance,_instance是類CCP的唯一對(duì)象,即單例
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):
"""發(fā)送模板短信"""
# @param to 手機(jī)號(hào)碼
# @param datas 內(nèi)容數(shù)據(jù) 格式為數(shù)組 例如:{'12','34'},如不需替換請(qǐng)?zhí)?''
# @param temp_id 模板Id
result = self.rest.sendTemplateSMS(to, datas, temp_id)
# 如果云通訊發(fā)送短信成功,返回的字典數(shù)據(jù)result中statuCode字段的值為"000000"
if result.get("statusCode") == "000000":
# 返回0 表示發(fā)送短信成功
return 0
else:
# 返回-1 表示發(fā)送失敗
return -1
if __name__ == '__main__':
ccp = CCP()
# 注意: 測(cè)試的短信模板編號(hào)為1
ccp.send_template_sms('15914397060', ['1234', 5], 1)
三、代碼實(shí)現(xiàn)
1,后端代碼
views.py,這是獲取驗(yàn)證碼請(qǐng)求的處理,也就是后端產(chǎn)生一個(gè)隨機(jī)碼,發(fā)送給手機(jī)用戶,然后把隨機(jī)碼存儲(chǔ)于redis中,然后給前端返回一個(gè)驗(yàn)證碼發(fā)送成功的信號(hào)
from .models import User
from rest_framework import status
from lufei_drf.libs.yuntongxun.sms import CCP
from django_redis import get_redis_connection
class SMSCodeAPIView(APIView):
def get(self,request):
# 1. 通過(guò)查詢字符串獲取手機(jī)號(hào)碼
phone = request.query_params.get("phone")
ty=request.query_params.get('type')
# 2. 發(fā)送短信之前驗(yàn)證碼驗(yàn)證一下手機(jī)號(hào)碼
if ty=='register':
try:
User.objects.get(phone=phone)
return Response({"message": "當(dāng)前手機(jī)號(hào)已經(jīng)被注冊(cè)過(guò)"}, status=status.HTTP_400_BAD_REQUEST)
except:
pass
redis = get_redis_connection("sms_code")
if redis.get("times_%s" % phone):
return Response({"message": "當(dāng)前手機(jī)號(hào)已經(jīng)在一分鐘內(nèi)發(fā)送過(guò)短信"}, status=status.HTTP_400_BAD_REQUEST)
# 3. 使用手機(jī)號(hào)碼發(fā)送短信驗(yàn)證碼
# 生成一個(gè)短信驗(yàn)證碼
sms_code = "%04d" % random.randint(0, 9999)
ccp = CCP()
result = ccp.send_template_sms(phone,[sms_code,"5分鐘"],1)
if result == 0:
# 發(fā)送短信成功,保存短信驗(yàn)證碼到redis數(shù)據(jù)庫(kù)中
# 開啟管道操作
pl = redis.pipeline()
pl.multi() # 接下來(lái)會(huì)在管道中執(zhí)行多條命令
# setex(變量名,有效期[秒],值 )
SMS_EXPIRE_TIME = 5 * 60 # 短信驗(yàn)證碼的有效期
SMS_TIMES = 60 # 短信發(fā)送的間隔時(shí)間
# 把原來(lái)立即執(zhí)行的命令放置到管道
pl.setex("sms_%s" % phone, SMS_EXPIRE_TIME, sms_code)
pl.setex("times_%s" % phone, SMS_TIMES, 1)
# 統(tǒng)一執(zhí)行管道中的命令
pl.execute()
# 4. 響應(yīng)數(shù)據(jù)給客戶端
return Response({"message":result},status=status.HTTP_200_OK)
urls.py
from django.urls import path
# jwt內(nèi)部實(shí)現(xiàn)的登陸視圖
from rest_framework_jwt.views import obtain_jwt_token
from .views import SMSCodeAPIView,
urlpatterns=[
path(r"login/", obtain_jwt_token ),
path('sms/',SMSCodeAPIView.as_view()),
]
utils.py,這是對(duì)用戶提交手機(jī)驗(yàn)證碼后,對(duì)手機(jī)號(hào)和驗(yàn)證碼的校對(duì)。判斷都正確后,返回一個(gè)對(duì)象,包括token,user信息等,
from django.contrib.auth.backends import ModelBackend
from django_redis import get_redis_connection
def jwt_response_payload_handler(token, user=None, request=None):
"""
自定義jwt認(rèn)證成功返回?cái)?shù)據(jù)
:token 返回的jwt
:user 當(dāng)前登錄的用戶信息[對(duì)象]
:request 當(dāng)前本次客戶端提交過(guò)來(lái)的數(shù)據(jù)
"""
return {
'token': token,
'id': user.id,
'username': user.username,
}
#實(shí)現(xiàn)多功能登錄
import re
from .models import User#查找用戶名或手機(jī)號(hào)是否已經(jīng)是我們的用戶
def get_user_by_account(account):
"""
根據(jù)帳號(hào)獲取user對(duì)象
:param account: 賬號(hào),可以是用戶名,也可以是手機(jī)號(hào)
:return: User對(duì)象 或者 None
"""
try:
if re.match('^1[3-9]\d{9}$', account):
# 帳號(hào)為手機(jī)號(hào)
user = User.objects.get(phone=account)
else:
# 帳號(hào)為用戶名
user = User.objects.get(username=account)
except User.DoesNotExist:
return None
else:
return user
#驗(yàn)證用戶提交的短信和我們保存在redis里的信息是否一致
def sms_code_verify(phone,sms_code):
redis = get_redis_connection("sms_code")
value=redis.get('sms_%s'%phone).decode()
if value==sms_code:
return True
return False
class UsernameMobileAuthBackend(ModelBackend):
"""
自定義用戶名或手機(jī)號(hào)認(rèn)證
"""
def authenticate(self, request, username=None, password=None, **kwargs):
user = get_user_by_account(username) #當(dāng)密碼長(zhǎng)度為4時(shí),我判斷其為手機(jī)號(hào)和短信驗(yàn)證碼登錄
if len(password)==4 and user is not None and sms_code_verify(username,password):
return user
elif user is not None and user.check_password(password):
return user
else:
return None
2,前端代碼
login組件
<template>
<div id="login">
<div class="box">
<p>
<img src="../../assets/login_title.png" alt="">
</p>
<p class="sign">幫助有志向的年輕人通過(guò)努力學(xué)習(xí)獲得體面的工作和生活!</p>
<div class="pass" v-show="num==1">
<div class="title2 cursor">
<span @click="num=1" :class="num==1 ? 'show' :''">密碼登錄</span>
<span @click="num=2" :class="num==2 ? 'show' :''">短信登錄</span>
</div>
<input v-model="username" type="text" class="ss" placeholder="用戶名 / 手機(jī)號(hào)碼">
<input v-model="password" type="password" class="ss" placeholder="密碼">
<div id="captcha" class="ss"></div>
<div class="t1">
<div class="left">
<input type="checkbox" class="cursor" v-model="remenber">
<div class="remenber cursor" >記住密碼</div>
</div>
<div class="right cursor">忘記密碼</div>
</div>
<button class="login_btn" @click="login1">登錄</button>
<div class="register">
沒(méi)有賬號(hào)
<span><router-link to="/register">立即注冊(cè)</router-link></span>
</div>
</div>
<div class="messge" v-show="num==2">
<div class="title2 cursor">
<span @click="num=1" :class="num==1 ? 'show' :''">密碼登錄</span>
<span @click="num=2" :class="num==2 ? 'show' :''">短信登錄</span>
</div>
<input v-model="phone" type="text" class="ss" placeholder="手機(jī)號(hào)碼">
<div class="sms">
<input v-model="sms_code" type="text" class="ss">
<div class="content" @click="get_sms_code">{{content}}</div>
</div>
<button class="login_btn" @click="sms_login">登錄</button>
<div class="register">
沒(méi)有賬號(hào)
<span><router-link to="/register">立即注冊(cè)</router-link></span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name:'login',
data:function () {
return {
num:1,
username:'',
password:'',
remenber:'',
status:'',
content:'獲取驗(yàn)證碼',
phone:'',
sms_code:'',
}
},
methods:{
//手機(jī)號(hào)和短信驗(yàn)證碼登錄
sms_login:function(){
let _this=this;
this.$axios.post('http://127.0.0.1:8000/user/login/',{
'username':_this.phone,
'password':_this.sms_code,
},{responseType:'json'})
.then(function (res) {
sessionStorage.token=res.data.token;
_this.$router.go(-1);
}).catch(function (error) {
console.log(error.response)
});
},
//獲取短信驗(yàn)證碼
get_sms_code:function(){
let reg = /1[3-9]{2}\d{8}/;
if( reg.test(this.phone) ){
if(this.content == "獲取驗(yàn)證碼"){
this.content=60;
let _this=this;
let tt=setInterval(function () {
if (_this.content>=1){
_this.content--
}
else {
_this.content='獲取驗(yàn)證碼';
clearInterval(tt)
}
},1000);
this.$axios.get('http://127.0.0.1:8000/user/sms?type=login&phone='+this.phone)
.then(function (res) {
if(res.data.message==0){
alert('驗(yàn)證碼發(fā)送成功')
}
}).catch(function (error) {
console.log(error.response)
})
}
}else {
alert('手機(jī)號(hào)碼有誤')
}
},
//用戶名和密碼登錄
login1:function () {
if (this.status==1){
let _this=this;
this.$axios.post('http://127.0.0.1:8000/user/login/',{
'username':_this.username,
'password':_this.password,
},{responseType:'json'})
.then(function (res) {
if (res.status==200){
if (_this.remenber){
sessionStorage.removeItem('token');
localStorage.token=res.data.token;
}
else {
localStorage.removeItem('token');
sessionStorage.token=res.data.token
}
_this.$router.go(-1);
}
else {
alert('用戶名或密碼錯(cuò)誤')
}
})
.catch(function (error) {
alert(error.response.data.non_field_errors[0]);
console.log(error.response.data.non_field_errors);
});
}
else {
alert('驗(yàn)證碼錯(cuò)誤')
}
},
handlerPopup:function (captchaObj) {
let _this=this;
captchaObj.onSuccess(function () {
var validate = captchaObj.getValidate();
_this.$axios.post("http://127.0.0.1:8000/user/yzm/",{
geetest_challenge: validate.geetest_challenge,
geetest_validate: validate.geetest_validate,
geetest_seccode: validate.geetest_seccode,
},{
responseType:"json",
}).then(function (res) {
_this.status=res.data.status
}).catch(function (error) {
console.log(error)
})
});
captchaObj.appendTo("#captcha");
}
},
created:function () {
let _this=this;
this.$axios.get("http://127.0.0.1:8000/user/yzm")
.then(function (res) {
let data=JSON.parse(res.data);
initGeetest({
width:'350px',
gt: data.gt,
challenge: data.challenge,
product: "popup",
offline: !data.success
}, _this.handlerPopup);
}).catch(function (error) {
console.log(error)
})
}
}
</script>
<style scoped>
#login{
background: url('../../assets/Login.jpg');
background-size: 100% 100%;
height: 100%;
position: fixed;
width: 100%;
}
.box{
width: 500px;
height: 600px;
margin: 0 auto;
margin-top: 200px;
text-align: center;
}
.box img{
width: 190px;
height: auto;
}
.box p{
margin: 0;
}
.sign{
font-size: 18px;
color: #fff;
letter-spacing: .29px;
padding-top: 10px;
padding-bottom: 50px;
}
.pass{
width: 400px;
height: 460px;
margin: 0 auto;
background-color: white;
border-radius: 4px;
}
.messge{
width: 400px;
height: 390px;
margin: 0 auto;
background-color: white;
border-radius: 4px;
}
.title2{
width: 350px;
font-size: 20px;
color: #9b9b9b;
padding-top: 50px;
border-bottom: 1px solid #e6e6e6;
margin: 0 auto;
margin-bottom: 20px;
}
.ss{
width: 350px;
height: 45px;
border-radius: 4px;
border: 1px solid #d9d9d9;
text-indent: 20px;
font-size: 14px;
margin-bottom: 20px;
}
.pass .t1{
width: 350px;
margin: 0 auto;
height: 20px;
line-height: 20px;
font-size: 12px;
text-align: center;
position: relative;
}
.t1 .right{
position: absolute;
right: 0;
}
.remenber{
display: inline-block;
position: absolute;
left: 20px;
}
.left input{
position: absolute;
left:0;
width: 14px;
height: 14px;
}
.login_btn{
width: 350px;
height: 45px;
background: #ffc210;
border-radius: 5px;
font-size: 16px;
color: #fff;
letter-spacing: .26px;
margin-top: 30px;
outline: none;
border:none;
cursor: pointer;
}
.register{
margin-top: 20px;
font-size: 14px;
color: #9b9b9b;
}
.register span{
color: #ffc210;
cursor: pointer;
}
.cursor{
cursor: pointer;
}
.show{
display: inline-block;
padding-bottom: 5px;
border-bottom: 2px solid orange;
color: #4a4a4a;
}
a{
text-decoration: none;
color: #ffc210;
}
#captcha{
margin: 0 auto;
height: 44px;
}
.sms{
position: relative;
width: 350px;
height: 45px;
margin: 0 auto;
line-height: 45px;
}
.sms .content{
position: absolute;
top:0;
right: 10px;
color: orange;
border-left: 1px solid orange;
padding-left: 10px;
cursor: pointer;
}
</style>
前端獲取短信驗(yàn)證碼:
//獲取短信驗(yàn)證碼
get_sms_code:function(){
let reg = /1[3-9]{2}\d{8}/; //當(dāng)手機(jī)號(hào)為為真實(shí)手機(jī)號(hào),才可以觸發(fā)獲取驗(yàn)證碼
if( reg.test(this.phone) ){ //當(dāng)頁(yè)面上顯示為‘獲取驗(yàn)證碼'時(shí),才可以觸發(fā)獲取驗(yàn)證碼請(qǐng)求;當(dāng)進(jìn)入倒計(jì)時(shí),點(diǎn)擊不能觸發(fā)獲取驗(yàn)證碼請(qǐng)求
if(this.content == "獲取驗(yàn)證碼"){ //成功發(fā)送獲取驗(yàn)證碼請(qǐng)求之后開始倒計(jì)時(shí)60秒
this.content=60;
let _this=this;
let tt=setInterval(function () {
if (_this.content>=1){
_this.content--
}
else {
_this.content='獲取驗(yàn)證碼';
clearInterval(tt)
}
},1000);
this.$axios.get('http://127.0.0.1:8000/user/sms?type=login&phone='+this.phone)
.then(function (res) {
if(res.data.message==0){
alert('驗(yàn)證碼發(fā)送成功')
}
}).catch(function (error) {
console.log(error.response)
})
}
}else {
alert('手機(jī)號(hào)碼有誤')
}
},
前端用手機(jī)號(hào)和短信驗(yàn)證碼登錄:
//獲取短信驗(yàn)證碼
get_sms_code:function(){
let reg = /1[3-9]{2}\d{8}/; //當(dāng)手機(jī)號(hào)為為真實(shí)手機(jī)號(hào),才可以觸發(fā)獲取驗(yàn)證碼
if( reg.test(this.phone) ){ //當(dāng)頁(yè)面上顯示為‘獲取驗(yàn)證碼'時(shí),才可以觸發(fā)獲取驗(yàn)證碼請(qǐng)求;當(dāng)進(jìn)入倒計(jì)時(shí),點(diǎn)擊不能觸發(fā)獲取驗(yàn)證碼請(qǐng)求
if(this.content == "獲取驗(yàn)證碼"){ //成功發(fā)送獲取驗(yàn)證碼請(qǐng)求之后開始倒計(jì)時(shí)60秒
this.content=60;
let _this=this;
let tt=setInterval(function () {
if (_this.content>=1){
_this.content--
}
else {
_this.content='獲取驗(yàn)證碼';
clearInterval(tt)
}
},1000);
this.$axios.get('http://127.0.0.1:8000/user/sms?type=login&phone='+this.phone)
.then(function (res) {
if(res.data.message==0){
alert('驗(yàn)證碼發(fā)送成功')
}
}).catch(function (error) {
console.log(error.response)
})
}
}else {
alert('手機(jī)號(hào)碼有誤')
}
},
到此這篇關(guān)于vue_drf實(shí)現(xiàn)短信驗(yàn)證碼的文章就介紹到這了,更多相關(guān)vue_drf短信驗(yàn)證碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue style屬性設(shè)置背景圖片的相對(duì)路徑無(wú)效的解決
這篇文章主要介紹了vue style屬性設(shè)置背景圖片的相對(duì)路徑無(wú)效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
Vue.js + Nuxt.js 項(xiàng)目中使用 Vee-validate 表單校驗(yàn)
vee-validate 是為 Vue.js 量身打造的表單校驗(yàn)框架,允許您校驗(yàn)輸入的內(nèi)容并顯示對(duì)應(yīng)的錯(cuò)誤提示信息。這篇文章給大家?guī)?lái)了Vue.js 使用 Vee-validate 實(shí)現(xiàn)表單校驗(yàn)的相關(guān)知識(shí),感興趣的朋友一起看看吧2019-04-04
vue3實(shí)現(xiàn)移動(dòng)端滑動(dòng)模塊
這篇文章主要為大家詳細(xì)介紹了vue3實(shí)現(xiàn)移動(dòng)端滑動(dòng)模塊,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09
Vue關(guān)于自定義事件的$event傳參問(wèn)題
這篇文章主要介紹了Vue關(guān)于自定義事件的$event傳參問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
vue組件中節(jié)流函數(shù)的失效的原因和解決方法
這篇文章主要介紹了vue組件中節(jié)流函數(shù)的失效和解決方法,幫助大家更好的理解和學(xué)習(xí)vue框架,感興趣的朋友可以了解下2020-12-12
vue-cli3項(xiàng)目配置eslint代碼規(guī)范的完整步驟
這篇文章主要給大家介紹了關(guān)于vue-cli3項(xiàng)目配置eslint代碼規(guī)范的完整步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
從零開始在NPM上發(fā)布一個(gè)Vue組件的方法步驟
這篇文章主要介紹了從零開始在NPM上發(fā)布一個(gè)Vue組件的方法步驟,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12

