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>
实现的思路:头部子组件是通过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: '确定' }); } }
官网: 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极验 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>
修改验证码框的样式位置。
.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>
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提供了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 前端请求发送短信
调整前端的页面,添加一个发送短信功能,
<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,代码:
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()
# 子应用路由 urls.py urlpatterns=[ ... path(r'register/', views.UserAPIView.as_view() ), ]
客户端发送注册信息时附带发送短信


浙公网安备 33010602011771号