一、需求
1,需求
我们在做网站开发时,登录页面很多情况下是可以用手机号接收短信验证码,然后实现登录的,那我们今天就来做一做这一功能。
伪代码:
进入登录页面,点击短信登录
输入手机号码,点击获取验证码,后端在redis里保存验证码
用户把手机收到的验证码输入,点击登录,会把手机号和验证码发往后端,然后进行验证
2,依赖
要想发送短信,让用户收到短信,我们的借助一个容联云的接口,注册一个账号。
使用时需要的一些参数:
下载sdk
1.。。。。。。。
2.。。。。。。
3.。。。。。。。
下载完成后,解压。放入我们drf项目的apps里的libs里
二、sdk参数配置
1,目录结构
2,配置sms.py文件
# -*- coding:utf-8 -*- from .CCPRestSDK import REST # 说明:主账号,登陆云通讯网站后,可在"控制台-应用"中看到开发者主账号ACCOUNT SID _accountSid = 'xxxxxxxxxxxxx' # 8a216da863f8e6c20164139687e80c1b # 说明:主账号Token,登陆云通讯网站后,可在控制台-应用中看到开发者主账号AUTH TOKEN _accountToken = 'xxxxxxxxxxxxx' # 6dd01b2b60104b3dbc88b2b74158bac6 # 请使用管理控制台首页的APPID或自己创建应用的APPID _appId = '8aaf0708697b6beb01699f3c645f1766' # 8a216da863f8e6c20164139688400c21 # 说明:请求地址,生产环境配置成app.cloopen.com _serverIP = 'sandboxapp.cloopen.com' # 说明:请求端口 ,生产环境为8883 _serverPort = "8883" # 说明:REST API版本号保持不变 _softVersion = '2013-12-26' #下面的内容不用修改 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 ccp.send_template_sms('15914397060', ['1234', 5], 1)
三、代码实现
1,后端代码
views.py,这是获取验证码请求的处理,也就是后端产生一个随机码,发送给手机用户,然后把随机码存储于redis中,然后给前端返回一个验证码发送成功的信号
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. 通过查询字符串获取手机号码 phone = request.query_params.get("phone") ty=request.query_params.get('type') # 2. 发送短信之前验证码验证一下手机号码 if ty=='register': try: User.objects.get(phone=phone) return Response({"message": "当前手机号已经被注册过"}, status=status.HTTP_400_BAD_REQUEST) except: pass redis = get_redis_connection("sms_code") if redis.get("times_%s" % phone): return Response({"message": "当前手机号已经在一分钟内发送过短信"}, status=status.HTTP_400_BAD_REQUEST) # 3. 使用手机号码发送短信验证码 # 生成一个短信验证码 sms_code = "%04d" % random.randint(0, 9999) ccp = CCP() result = ccp.send_template_sms(phone,[sms_code,"5分钟"],1) if result == 0: # 发送短信成功,保存短信验证码到redis数据库中 # 开启管道操作 pl = redis.pipeline() pl.multi() # 接下来会在管道中执行多条命令 # setex(变量名,有效期[秒],值 ) SMS_EXPIRE_TIME = 5 * 60 # 短信验证码的有效期 SMS_TIMES = 60 # 短信发送的间隔时间 # 把原来立即执行的命令放置到管道 pl.setex("sms_%s" % phone, SMS_EXPIRE_TIME, sms_code) pl.setex("times_%s" % phone, SMS_TIMES, 1) # 统一执行管道中的命令 pl.execute() # 4. 响应数据给客户端 return Response({"message":result},status=status.HTTP_200_OK)
urls.py
from django.urls import path # jwt内部实现的登陆视图 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,这是对用户提交手机验证码后,对手机号和验证码的校对。判断都正确后,返回一个对象,包括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认证成功返回数据 :token 返回的jwt :user 当前登录的用户信息[对象] :request 当前本次客户端提交过来的数据 """ return { 'token': token, 'id': user.id, 'username': user.username, } #实现多功能登录 import re from .models import User
#查找用户名或手机号是否已经是我们的用户 def get_user_by_account(account): """ 根据帐号获取user对象 :param account: 账号,可以是用户名,也可以是手机号 :return: User对象 或者 None """ try: if re.match('^1[3-9]\d{9}$', account): # 帐号为手机号 user = User.objects.get(phone=account) else: # 帐号为用户名 user = User.objects.get(username=account) except User.DoesNotExist: return None else: return user #验证用户提交的短信和我们保存在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): """ 自定义用户名或手机号认证 """ def authenticate(self, request, username=None, password=None, **kwargs): user = get_user_by_account(username)
#当密码长度为4时,我判断其为手机号和短信验证码登录 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,前端代码
<template> <div id="login"> <div class="box"> <p> <img src="../../assets/login_title.png" alt=""> p> <p class="sign">帮助有志向的年轻人通过努力学习获得体面的工作和生活!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="用户名 / 手机号码"> <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"> 没有账号 <span><router-link to="/register">立即注册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="手机号码"> <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"> 没有账号 <span><router-link to="/register">立即注册router-link>span> div> div> div> div> template> <script> export default { name:'login', data:function () { return { num:1, username:'', password:'', remenber:'', status:'', content:'获取验证码', phone:'', sms_code:'', } }, methods:{ //手机号和短信验证码登录 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) }); }, //获取短信验证码 get_sms_code:function(){ let reg = /1[3-9]{2}\d{8}/; if( reg.test(this.phone) ){ if(this.content == "获取验证码"){ this.content=60; let _this=this; let tt=setInterval(function () { if (_this.content>=1){ _this.content-- } else { _this.content='获取验证码'; 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('验证码发送成功') } }).catch(function (error) { console.log(error.response) }) } }else { alert('手机号码有误') } }, //用户名和密码登录 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('用户名或密码错误') } }) .catch(function (error) { alert(error.response.data.non_field_errors[0]); console.log(error.response.data.non_field_errors); }); } else { alert('验证码错误') } }, 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>
前端获取短信验证码:
//获取短信验证码 get_sms_code:function(){ let reg = /1[3-9]{2}\d{8}/;
//当手机号为为真实手机号,才可以触发获取验证码 if( reg.test(this.phone) ){
//当页面上显示为‘获取验证码’时,才可以触发获取验证码请求;当进入倒计时,点击不能触发获取验证码请求 if(this.content == "获取验证码"){
//成功发送获取验证码请求之后开始倒计时60秒 this.content=60; let _this=this; let tt=setInterval(function () { if (_this.content>=1){ _this.content-- } else { _this.content='获取验证码'; 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('验证码发送成功') } }).catch(function (error) { console.log(error.response) }) } }else { alert('手机号码有误') } },
前端用手机号和短信验证码登录:
//手机号和短信验证码登录
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) {
//当验证成功后,接收返回的token放入session里
sessionStorage.token=res.data.token;
_this.$router.go(-1);
}).catch(function (error) {
console.log(error.response)
});
},