Day87 用户登录认证,用户注册认证

 

一、用户的登陆认证

1. 前端:

1.1前端首页实现登陆状态的判断

Header.vue组件代码:

<script>
  export default {
    name: "Header",
    data(){
      return {
        // 设置一个登录标识,表示是否登录
        token: sessionStorage.token || localStorage.token,
        user_name: sessionStorage.user_name || localStorage.user_name,
        user_id: sessionStorage.user_id || localStorage.user_id,
        nav_list:[],
      };
    },
    。。。
  }
</script>

 

1.2头部组件中实现退出登录

实现的思路:头部子组件是通过token值进行判断登录状态,所以当用户点击"退出登录",则需要移出token的值,并使用elementUI里面的弹窗组件进行提示。

Header.vue组件代码:

 methods:{
      check(link){
        return link==window.location.pathname
      },
      logout(){

        this.token = false;
        this.user_id=false;
        this.user_name=false;

        sessionStorage.removeItem("token");
        sessionStorage.removeItem("user_id");
        sessionStorage.removeItem("user_name");

        localStorage.removeItem("token");
        localStorage.removeItem("user_id");
        localStorage.removeItem("user_name");

        this.$alert('退出登录成功!', '路飞学城', {
          confirmButtonText: '确定'
        });
      }
    }

 

2.在登录认证中接入极验验证

官网: https://www.geetest.com/first_page/

注册登录以后,即进入登录后台,选择行为验证。

 

 

接下来,就可以根据官方文档,把验证码集成到项目中了

 

 

''文档地址:https://docs.geetest.com/install/overview/start/

2.1 后端接口

下载和安装验证码模块包。

git clone https://github.com/GeeTeam/gt3-python-sdk.git

安装依赖模块

pip install requests

把验证码模块放置到在libs目录中

 

users/views.py文件下方:

from rest_framework.views import APIView
from luffy.libs.geetest import GeetestLib
from django.conf import settings
import random
from rest_framework.response import Response

class CaptchaAPIView(APIView):
    """极验验证码"""
    def get(self,request):
        """提供生成验证码的配置信息"""
        user_id = '%06d' % random.randint(1,9999)
        gt = GeetestLib(settings.PC_GEETEST_ID, settings.PC_GEETEST_KEY)
        status = gt.pre_process(user_id)
        print(status)

        # 把这两段数据不要保存在session里面, 保存到redis里面
        request.session[gt.GT_STATUS_SESSION_KEY] = status
        request.session["user_id"] = user_id

        response_str = gt.get_response_str()
        return Response(response_str)

    def post(self,request):
        """进行二次验证"""
        pass

users/urls.py路由注册:

path(r'captcha/', views.CaptchaAPIView.as_view() ),

配置文件settings/dev.py代码:

PC_GEETEST_ID = '5f4ab1914455506edffaffd4da37fea5'
PC_GEETEST_KEY ='460e13a49d687e5e44e25c383f0473a6'

 

2.2 前端接口

2.2.1前端获取显示并校验验证码

把下载好的图的验证码模块包中的gt.js放置到前端项目中,并在main.js中引入

// 导入gt极验
import '../static/globals/gt.js'

显示验证码

<script>
export default {
  name: 'Login',
  data(){
    return {
        login_type: 0,
        username:"",
        password:"",
        remember:"",
    }
  },
  mounted(){
    // 请求后端获取生成验证码的流水号
    this.$axios.get(this.$settings.Host + "/users/captcha/",{
      responseType: 'json', // 希望返回json数据
    }).then(response => {
      let data = response.data;

      // 验证初始化配置
      initGeetest({
        gt: data.gt,
        challenge: data.challenge,
        product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
        offline: !data.success
      },this.handlerPopup)
    }).catch(error => {
      console.log(error.response);
    });
  },
  methods:{
    // 用户登录
    loginhander(){
      。。。。
    },
    // 验证码的成功验证事件方法
    handlerPopup(captchaObj){
       // 把验证码添加到模板中制定的页面
       captchaObj.appendTo("#geetest1");
      
    },
  },

};
</script>

 login.vue 代码:

<template>
    <div class="login-box">
        <img src="../../static/img/Loginbg.3377d0c.jpg" alt="">
        <div class="login">
            <div class="login-title">
                <img src="../../static/img/Logotitle.1ba5466.png" alt="">
                <p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p>
            </div>
            <div class="login_box">
                <div class="title">
                    <span @click="login_type=0">密码登录</span>
                    <span @click="login_type=1">短信登录</span>
                </div>
                <div class="inp" v-if="login_type==0">
                    <input v-model = "username" type="text" placeholder="用户名 / 手机号码" class="user">
                    <input v-model = "password" type="password" name="" class="pwd" placeholder="密码">
                    <div id="geetest1"></div>
                    <div class="rember">
                        <p>
                            <input type="checkbox" class="no" v-model="remember"/>
                            <span>记住密码</span>
                        </p>
                        <p>忘记密码</p>
                    </div>
                    <button class="login_btn" @click="loginhander">登录</button>
                    <p class="go_login" >没有账号 <router-link to="/reg">立即注册</router-link></p>
                </div>
                <div class="inp" v-show="login_type==1">
                    <input v-model = "username" type="text" placeholder="手机号码" class="user">
                    <input v-model = "password"  type="text" class="pwd" placeholder="短信验证码">
          <button id="get_code">获取验证码</button>
                    <button class="login_btn">登录</button>
                    <p class="go_login" >没有账号 <router-link to="/reg">立即注册</router-link></p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
export default {
  name: 'Login',
  data(){
    return {
        login_type: 0,
        username:"",
        password:"",
        remember:"",
        is_geek:false,
    }
  },
  mounted(){
    // 请求后端获取生成验证码的流水号
    this.$axios.get(this.$settings.Host + "/users/captcha/",{
      responseType: 'json', // 希望返回json数据
    }).then(response => {
      let data = response.data;

      // 验证初始化配置
      initGeetest({
        gt: data.gt,
        challenge: data.challenge,
        product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
        offline: !data.success
      },this.handlerPopup)
    }).catch(error => {
      console.log(error.response);
    });
  },
  methods:{
    // 用户登录
    loginhander(){
      // 判断用户是否已经通过了极验验证
      if(!this.is_geek){
        return false;
      }

      this.$axios.post(this.$settings.Host+"/users/login/",{
        username:this.username,
        password:this.password,
      }).then(response=>{
        let data = response.data
        // 根据用户是否勾选了记住密码来保存用户认证信息
        if(this.remember){
          // 记住密码
          localStorage.token = data.token;
          localStorage.user_id = data.id;
          localStorage.user_name = data.username;

        }else{
          // 不需要记住密码
          sessionStorage.token = data.token;
          sessionStorage.user_id = data.id;
          sessionStorage.user_name = data.username;
        }

        // 登录成功以后,跳转会上一个页面
        this.$router.go(-1);

      }).catch(error=>{
        console.log(error.response)
      })
    },
    // 验证码的成功验证事件方法
    handlerPopup(captchaObj){
       // 把验证码添加到模板中制定的页面
       captchaObj.appendTo("#geetest1");

       // 记录vue对象
       let _this = this;

       // 监听用户对于验证码的操作是否成功了
       captchaObj.onSuccess(()=>{
          var validate = captchaObj.getValidate();

          _this.$axios.post(_this.$settings.Host+"/users/captcha/",{
              geetest_challenge: validate.geetest_challenge,
              geetest_validate: validate.geetest_validate,
              geetest_seccode: validate.geetest_seccode
          }).then(response=>{
              // 在用户成功添加数据以后,可以允许点击登录按钮
              _this.is_geek = true;

          }).catch(error=>{
              console.log(error.response)
          })

        });

    },
  },

};
</script>
<style scoped>
.login-box{
    width: 100%;
    height: 100%;
    position: relative;
    overflow: hidden;
    margin-top: -80px;
}
.login-box img{
    width: 100%;
    min-height: 100%;
}
.login-box .login {
    position: absolute;
    width: 500px;
    height: 400px;
    left: 0;
    margin: auto;
    right: 0;
    bottom: 0;
    top: -220px;
}
.login .login-title{
     width: 100%;
    text-align: center;
}
.login-title img{
    width: 190px;
    height: auto;
}
.login-title p{
    font-size: 18px;
    color: #fff;
    letter-spacing: .29px;
    padding-top: 10px;
    padding-bottom: 50px;
}
.login_box{
    width: 400px;
    height: auto;
    background: #fff;
    box-shadow: 0 2px 4px 0 rgba(0,0,0,.5);
    border-radius: 4px;
    margin: 0 auto;
    padding-bottom: 40px;
}
.login_box .title{
    font-size: 20px;
    color: #9b9b9b;
    letter-spacing: .32px;
    border-bottom: 1px solid #e6e6e6;
    display: flex;
    justify-content: space-around;
    padding: 50px 60px 0 60px;
    margin-bottom: 20px;
    cursor: pointer;
}
.login_box .title span:nth-of-type(1){
    color: #4a4a4a;
    border-bottom: 2px solid #84cc39;
}

.inp{
    width: 350px;
    margin: 0 auto;
}
.inp input{
    outline: 0;
    width: 100%;
    height: 45px;
    border-radius: 4px;
    border: 1px solid #d9d9d9;
    text-indent: 20px;
    font-size: 14px;
    background: #fff !important;
}
.inp input.user{
    margin-bottom: 16px;
}
.inp .rember{
    display: flex;
    justify-content: space-between;
    align-items: center;
    position: relative;
    margin-top: 10px;
}
.inp .rember p:first-of-type{
    font-size: 12px;
    color: #4a4a4a;
    letter-spacing: .19px;
    margin-left: 22px;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-align: center;
    align-items: center;
    /*position: relative;*/
}
.inp .rember p:nth-of-type(2){
    font-size: 14px;
    color: #9b9b9b;
    letter-spacing: .19px;
    cursor: pointer;
}

.inp .rember input{
    outline: 0;
    width: 30px;
    height: 45px;
    border-radius: 4px;
    border: 1px solid #d9d9d9;
    text-indent: 20px;
    font-size: 14px;
    background: #fff !important;
}

.inp .rember p span{
    display: inline-block;
    font-size: 12px;
    width: 100px;
  /*position: absolute;*/
/*left: 20px;*/

}
#geetest{
    margin-top: 20px;
}
.login_btn{
     width: 100%;
    height: 45px;
    background: #84cc39;
    border-radius: 5px;
    font-size: 16px;
    color: #fff;
    letter-spacing: .26px;
    margin-top: 30px;
}
.inp .go_login{
    text-align: center;
    font-size: 14px;
    color: #9b9b9b;
    letter-spacing: .26px;
    padding-top: 20px;
}
.inp .go_login span{
    color: #84cc39;
    cursor: pointer;
}
</style>
View Code

修改验证码框的样式位置。

static/css/reset.css,代码:

.geetest_holder{
    padding-top: 15px;
    width: 100%!important;
}

 

3. 后端接口实现登录验证

from django.shortcuts import render

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


from rest_framework.views import APIView
from luffy.libs.geetest import GeetestLib
from django.conf import settings
import random
from rest_framework.response import Response

class CaptchaAPIView(APIView):
    """极验验证码"""
    gt = GeetestLib(settings.PC_GEETEST_ID, settings.PC_GEETEST_KEY)
    def get(self,request):
        """提供生成验证码的配置信息"""
     pass
  def post(self,request): """进行二次验证""" challenge = request.data.get(self.gt.FN_CHALLENGE, '') validate = request.data.get(self.gt.FN_VALIDATE, '') seccode = request.data.get(self.gt.FN_SECCODE, '') status = request.session.get(self.gt.GT_STATUS_SESSION_KEY) user_id = request.session.get("user_id") if status: result = self.gt.success_validate(challenge, validate, seccode, user_id) else: result = self.gt.failback_validate(challenge, validate, seccode) # 返回一个随机字符串,在用户登录提供数据时一并发送到后端,进行验证 # 后面可以使用redis保存 return Response({"message":result})

 

二、用户注册认证

前端显示注册页面并调整首页头部和登陆页面的注册按钮的链接。

注册页面Register,主要是通过登录页面进行改成而成./

 1.前端创建Register.vue:

<template>
    <div class="box">
        <img src="https://www.luffycity.com/static/img/Loginbg.3377d0c.jpg" alt="">
        <div class="register">
            <div class="register_box">
        <div class="register-title">注册路飞学城</div>
                <div class="inp">
                    <input v-model = "mobile" type="text" placeholder="手机号码" class="user">
                    <div id="geetest"></div>
                    <input v-model = "sms" type="text" placeholder="输入验证码" class="user">
                    <button class="register_btn" >注册</button>
                    <p class="go_login" >已有账号 <router-link to="/login">直接登录</router-link></p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
export default {
  name: 'Register',
  data(){
    return {
        sms:"",
        mobile:"",
        validateResult:false,
    }
  },
  created(){
  },
  methods:{},

};
</script>

<style scoped>
.box{
    width: 100%;
  height: 100%;
    position: relative;
  overflow: hidden;
}
.box img{
    width: 100%;
  min-height: 100%;
}
.box .register {
    position: absolute;
    width: 500px;
    height: 400px;
    top: 0;
    left: 0;
  margin: auto;
  right: 0;
  bottom: 0;
  top: -338px;
}
.register .register-title{
    width: 100%;
    font-size: 24px;
    text-align: center;
    padding-top: 30px;
    padding-bottom: 30px;
    color: #4a4a4a;
    letter-spacing: .39px;
}
.register-title img{
    width: 190px;
    height: auto;
}
.register-title p{
    font-family: PingFangSC-Regular;
    font-size: 18px;
    color: #fff;
    letter-spacing: .29px;
    padding-top: 10px;
    padding-bottom: 50px;
}
.register_box{
    width: 400px;
    height: auto;
    background: #fff;
    box-shadow: 0 2px 4px 0 rgba(0,0,0,.5);
    border-radius: 4px;
    margin: 0 auto;
    padding-bottom: 40px;
}
.register_box .title{
    font-size: 20px;
    color: #9b9b9b;
    letter-spacing: .32px;
    border-bottom: 1px solid #e6e6e6;
     display: flex;
        justify-content: space-around;
        padding: 50px 60px 0 60px;
        margin-bottom: 20px;
        cursor: pointer;
}
.register_box .title span:nth-of-type(1){
    color: #4a4a4a;
        border-bottom: 2px solid #84cc39;
}

.inp{
    width: 350px;
    margin: 0 auto;
}
.inp input{
    border: 0;
    outline: 0;
    width: 100%;
    height: 45px;
    border-radius: 4px;
    border: 1px solid #d9d9d9;
    text-indent: 20px;
    font-size: 14px;
    background: #fff !important;
}
.inp input.user{
    margin-bottom: 16px;
}
.inp .rember{
     display: flex;
    justify-content: space-between;
    align-items: center;
    position: relative;
    margin-top: 10px;
}
.inp .rember p:first-of-type{
    font-size: 12px;
    color: #4a4a4a;
    letter-spacing: .19px;
    margin-left: 22px;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-align: center;
    align-items: center;
    /*position: relative;*/
}
.inp .rember p:nth-of-type(2){
    font-size: 14px;
    color: #9b9b9b;
    letter-spacing: .19px;
    cursor: pointer;
}

.inp .rember input{
    outline: 0;
    width: 30px;
    height: 45px;
    border-radius: 4px;
    border: 1px solid #d9d9d9;
    text-indent: 20px;
    font-size: 14px;
    background: #fff !important;
}

.inp .rember p span{
    display: inline-block;
  font-size: 12px;
  width: 100px;
  /*position: absolute;*/
/*left: 20px;*/

}
#geetest{
    margin-top: 20px;
}
.register_btn{
     width: 100%;
    height: 45px;
    background: #84cc39;
    border-radius: 5px;
    font-size: 16px;
    color: #fff;
    letter-spacing: .26px;
    margin-top: 30px;
}
.inp .go_login{
    text-align: center;
    font-size: 14px;
    color: #9b9b9b;
    letter-spacing: .26px;
    padding-top: 20px;
}
.inp .go_login span{
    color: #84cc39;
    cursor: pointer;
}
</style>
View Code

1.1前端注册路由:

import Register from "../components/Register"

// 配置路由列表
export default new Router({
  mode:"history",
  routes:[
    // 路由列表
    ...
    {
      name:"Register",
      path: "/register",
      component:Register,
    }
  ]
})

1.2 修改首页头部的连接:

# Header.vue
<span class="header-register"><router-link to="/register">注册</router-link></span>
#Login.vue
<p class="go_login" >没有账号 <router-link to="/register">立即注册</router-link></p>

2.注册功能的实现

接下来,我们把注册过程中一些注册信息(例如:短信验证码)和session缓存到redis数据库中。

2.1 安装django-redis。

pip install django-redis

2.2 在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/

django-redis提供了get_redis_connection的方法,通过调用get_redis_connection方法传递redis的配置名称可获取到redis的连接对象,通过redis连接对象可以执行redis命令

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

使用范例:

from django_redis import get_redis_connection
// 链接redis数据库
redis_conn = get_redis_connection("default")

 

3.使用云通讯发送短信

3.1 云通讯基本配置

在登录后的平台上面获取一下信息:

ACCOUNT SID:8aaf0708697b6beb01699f4442911776
AUTH TOKEN : b4dea244f43a4e0f90e557f0a99c70fa
AppID(默认):8aaf0708697b6beb01699f4442e3177c
Rest URL(生产): app.cloopen.com:8883         [项目上线时使用真实短信发送服务器]
Rest URL(开发): sandboxapp.cloopen.com:8883  [项目开发时使用沙箱短信发送服务器]

找到sdkdemo进行下载

 

 

把云通讯的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'

 

3.2 后端代码实现

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() ),

 

3.3 前端请求发送短信

调整前端的页面,添加一个发送短信功能,

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)
      })

    },
    // 提交注册信息
    ....

 

3.4 前端倒计时 实现

<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>

 

3.5 后端实现短信发送间隔的判断

视图代码:

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})

 

3.6 后端保存用户信息

创建序列化器对象[暂时不涉及到手机验证码功能]

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()

 

3.7 设置路由

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

客户端发送注册信息时附带发送短信

 

posted @ 2019-05-18 10:39  addit  Views(283)  Comments(0)    收藏  举报