用戶的注冊認證
前端顯示注冊頁面並調整首頁頭部和登陸頁面的注冊按鈕的鏈接。
注冊頁面Register,主要是通過登錄頁面進行改成而成.
<template>
<div class="box">
<img src="../../static/image/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">
<input v-model = "password" type="password" placeholder="登錄密碼" class="user">
<input v-model = "sms_code" type="text" placeholder="輸入驗證碼" class="user">
<button class="register_btn" >注冊</button>
<p class="go_login" >已有賬號 <router-link to="/user/login">直接登錄</router-link></p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Register',
data(){
return {
sms_code:"",
mobile:"",
password:"",
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: -120px;
}
.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>
前端注冊路由:
import Register from "../components/Register"
// 配置路由列表
export default new Router({
mode:"history",
routes:[
// 路由列表
...
{
name:"Register",
path: "/register",
component:Register,
}
]
})
修改首頁頭部的連接:
# Header.vue
<span class="header-register"><router-link to="/user/reg">注冊</router-link></span>
#Login.vue
<p class="go_login" >沒有賬號 <router-link to="/user/reg">立即注冊</router-link></p>
注冊功能的實現
后台api實現
后端視圖提供注冊功能的api接口,視圖代碼:
from rest_framework.generics import CreateAPIView
from .models import User
from .serializers import UserModelSerializer
class UserAPIView(CreateAPIView):
"""用戶信息視圖接口"""
queryset = User.objects.all()
serializer_class = UserModelSerializer
序列化器代碼:
from rest_framework import serializers
from .models import User
import re
from .utils import get_user_by_account
from django.contrib.auth.hashers import make_password
from rest_framework_jwt.settings import api_settings
class UserModelSerializer(serializers.ModelSerializer):
'''
注冊用戶
需要數據:手機號,密碼,驗證碼
'''
sms_code = serializers.CharField(min_length=4, max_length=6, required=True, help_text='短信驗證碼', write_only=True,
error_messages={'min_length': '請正確輸入驗證碼!', 'max_length': '請正確輸入驗證碼!',
'required': '驗證碼不能為空!', })
token = serializers.CharField(max_length=1024, read_only=True, help_text='token認證字符串')
class Meta:
model = User
fields = ['id', 'username', 'mobile', 'password', 'sms_code', 'token']
extra_kwargs = {
'id': {
'read_only': True,
},
'username': {
'read_only': True,
},
'password': {
'write_only': True,
},
'mobile': {
'write_only': True,
},
}
def validate(self, attrs):
mobile = attrs.get('mobile')
sms_code = attrs.get('sms_code')
password = attrs.get('password')
# 校驗手機號
if not re.match(r'1[3-9]\d{9}$', mobile):
raise serializers.ValidationError('手機號格式有誤,請重新輸入!')
user = get_user_by_account(mobile)
if user is not None:
raise serializers.ValidationError('手機號已被注冊')
return attrs
def create(self, validated_data):
validated_data.pop('sms_code')
raw_password = validated_data.get('password')
hash_password = make_password(raw_password)
mobile = validated_data.get('mobile')
user = User.objects.create(
mobile=mobile,
password=hash_password,
username=mobile
)
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
user.token = token
return user
路由,代碼:
from django.urls import path,re_path
from . import views
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path(r'login/', obtain_jwt_token), #jwt:post username,pwd
path(r'captcha/',views.CaptchaAPIView.as_view()),
path(r'register/',views.UserAPIView.as_view()),
re_path(r'mobile/(?P<mobile>1[3-9]\d{9})/',views.MobileAPIView.as_view()),
re_path(r'sms/(?P<mobile>1[3-9]\d{9})/',views.MSMAPIView.as_view())
]
使用postman訪問測試
客戶端實現注冊功能
register.vue,代碼:
<template>
<div class="box">
<img src="../../static/image/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">
<input v-model = "password" type="password" placeholder="登錄密碼" class="user">
< <div class="sms-box">
<input v-model="sms_code" type="text" maxlength='6' placeholder="輸入驗證碼" class="user">
<div class="sms-btn" @click="smsHandle">點擊發送短信</div>
</div>
<div id="geetest"></div>
<button class="register_btn" @click="registerHander">注冊</button>
<p class="go_login" >已有賬號 <router-link to="/user/login">直接登錄</router-link></p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Register',
data(){
return {
sms_code:"",
mobile:"",
password: "",
}
},
created(){
},
methods:{
registerHander(){
// 用戶注冊
this.$axios.post(`${this.$settings.Host}/users/register/`, {
mobile: this.mobile,
sms_code: this.sms_code,
password: this.password,
})
.then((res) => {
//默認是不需要記住密碼的功能的,所以存到sessionStorage中
localStorage.removeItem('user_token');
localStorage.removeItem('id');
localStorage.removeItem('username');
sessionStorage.user_token = res.data.token;
sessionStorage.id = res.data.id;
sessionStorage.username = res.data.username;
// 頁面跳轉
let self = this;
this.$alert("恭喜注冊成功",{
callback(){
self.$router.push("/");
}
});
}).catch(error=>{
let data = error.response.data;
let message = "";
for(let key in data){
message = data[key][0];
}
this.$message.error(message);
});
}
},
};
</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>
雖然已經實現了注冊功能,但是需要針對當前的安全性和用戶體驗要進行優化.
新增用戶輸入完成手機號以后,要及時進行手機號的唯一性驗證,對於這個功能,需要單獨實現。
手機號的唯一性驗證
后端視圖,代碼:
class MobileAPIView(APIView):
def get(self,request,mobile):
user = get_user_by_account(mobile)
if user is not None:
return Response({'msg':'該手機號已被注冊'},status=status.HTTP_400_BAD_REQUEST)
return Response({'msg':'OK'})
后端路由,代碼:
from django.urls import path,re_path
from rest_framework_jwt.views import obtain_jwt_token
from . import views
urlpatterns = [
path(r'login/', obtain_jwt_token), #jwt:post username,pwd
path(r'captcha/',views.CaptchaAPIView.as_view()),
path(r'register/',views.UserAPIView.as_view()),
re_path(r'mobile/(?P<mobile>1[3-9]\d{9})/',views.MobileAPIView.as_view()),
]
客戶端用戶輸入完手機號碼以后,使用失去焦點事件,然后發送手機號到后端進行唯一性驗證
<template>
<div class="box">
<img src="../../static/image/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" @blur="checkMobile" placeholder="手機號碼" class="user">
<input v-model = "password" type="password" placeholder="登錄密碼" class="user">
<div class="sms-box">
<input v-model="sms_code" type="text" maxlength='6' placeholder="輸入驗證碼" class="user">
<div class="sms-btn" @click="smsHandle">點擊發送短信</div>
</div>
<div id="geetest"></div>
<button class="register_btn" @click="registerHander">注冊</button>
<p class="go_login" >已有賬號 <router-link to="/user/login">直接登錄</router-link></p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Register',
data(){
return {
sms_code:"",
mobile:"",
password: "",
}
},
created(){
},
methods:{
checkMobile(){
// 檢查手機號的合法性[格式和是否已經注冊]
this.$axios.get(`${this.$settings.HOST}/user/mobile/${this.mobile}/`).catch(error=>{
this.$message.error(error.response.data.message);
});
},
registerHander(){
// 用戶注冊
this.$axios.post(`${this.$settings.HOST}/user/`,{
mobile: this.mobile,
sms_code:this.sms_code,
password:this.password,
}).then(response=>{
console.log(response.data);
localStorage.removeItem("user_token");
localStorage.removeItem("user_id");
localStorage.removeItem("user_name");
sessionStorage.user_token = response.data.token;
sessionStorage.user_id = response.data.id;
sessionStorage.user_name = response.data.username;
// 頁面跳轉
let self = this;
this.$alert("注冊成功!","路飛學城",{
callback(){
self.$router.push("/");
}
});
}).catch(error=>{
let data = error.response.data;
let message = "";
for(let key in data){
message = data[key][0];
}
this.$message.error(message);
});
}
},
};
</script>
看一下短信發送過程:
接下來,把注冊過程中一些注冊信息(例如:短信驗證碼)和其他的緩存數據保存到redis數據庫中。
安裝django-redis。封裝的pyredis類,這個django-redis是與django結合的更緊密一些,它安裝之后有兩個模塊,一個是和django配合的,一個是python操作redis的。
pip install django-redis
在settings.py配置中添加一下代碼:
# 設置redis緩存
CACHES = {
# 默認緩存
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379", # 安裝redis的主機的 IP 和 端口
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {
"max_connections": 1000,
"encoding": 'utf-8'
},
"PASSWORD": "foobared" # redis密碼
}
},
# "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",
"CONNECTION_POOL_KWARGS": {
"max_connections": 1000,
"encoding": 'utf-8'
},
"PASSWORD": "foobared" # redis密碼
}
},
# 提供存儲短信驗證碼
"sms_code":{
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/2",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {
"max_connections": 1000,
"encoding": 'utf-8'
},
"PASSWORD": "foobared" # redis密碼
}
}
}
關於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")
通過連接對象,就能操作文檔中的所有方法了
使用雲通訊發送短信
在登錄后的平台上面獲取一下信息:[https://www.yuntongxun.com/]
ACCOUNT SID:8aaf07086f17620f016f308d0d2c0fa9
AUTH TOKEN : be8d96030fca44ffaf958062e6c658e8
AppID(默認):8aaf07086f17620f016f308d0d850faf
Rest URL(生產): app.cloopen.com:8883 [項目上線時使用真實短信發送服務器]
Rest URL(開發): sandboxapp.cloopen.com:8883 [項目開發時使用沙箱短信發送服務器]
找到sdkdemo進行下載
https://www.yuntongxun.com/doc/ready/demo/1_4_1_2.html
在開發過程中,為了節約發送短信的成本,可以把自己的或者同事的手機加入到測試號碼中.
把雲通訊的sdk保存到libs目錄下, 並修改里面的基本配置信息,以后項目上線,用的是公司已經經過實名認證並且購買短信包的,需要更改。
sms.py,代碼:把上面開發使用的一些參數配置到settings/dev.py中
# -*- 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']
# be8d96030fca44ffaf958062e6c658e8
# 請使用管理控制台首頁的APPID或自己創建應用的APPID
_appId = settings.SMS['_appId']
# 8aaf07086f17620f016f308d0d850faf
# 說明:請求地址,生產環境配置成app.cloopen.com
_serverIP = settings.SMS['_serverIP']
# sandboxapp.cloopen.com沙箱環境地址
# 說明:請求端口 ,生產環境為8883
_serverPort = settings.SMS['_serverPort']
# 說明:REST API版本號保持不變
_softVersion = settings.SMS['_softVersion']
# 雲通訊官方提供的發送短信代碼實例
# # 發送模板短信
# # @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
# ccp.send_template_sms('15914397060', ['1234',"5"], 1)
配置文件,代碼:
SMS = {
"_accountSid":'8aaf07086f17620f016f308d0d2c0fa9',
# 說明:主賬號Token,登陸雲通訊網站后,可在控制台-應用中看到開發者主賬號AUTH TOKEN
"_accountToken":'be8d96030fca44ffaf958062e6c658e8',
# be8d96030fca44ffaf958062e6c658e8
# 請使用管理控制台首頁的APPID或自己創建應用的APPID
"_appId":'8aaf07086f17620f016f308d0d850faf',
# 8aaf07086f17620f016f308d0d850faf
# 說明:請求地址,生產環境配置成app.cloopen.com
"_serverIP":'sandboxapp.cloopen.com',
# sandboxapp.cloopen.com沙箱環境地址
# 說明:請求端口 ,生產環境為8883
"_serverPort":"8883",
# 說明:REST API版本號保持不變
"_softVersion":'2013-12-26'
}
發送短信驗證碼
視圖代碼:
class MSMAPIView(APIView):
def get(self,request,mobile):
# 首先查看一下該手機號是否已經在60秒內發送過短信了
redis_conn = get_redis_connection('sms_code')
check_ret = redis_conn.get('mobile_%s' % mobile)
# 找不到默認返回的是None
if check_ret is not None:
return Response({'msg': '60秒內已經發送過短信了,請稍后嘗試!'})
try:
# 生成驗證碼
sms_code = "%06d"%random.randint(0,999999)
# 保存驗證碼5分鍾
redis_conn = get_redis_connection('sms_code')
redis_conn.setex('sms_%s' % mobile,SMS_EXPIRE_TIME,sms_code)
# 短信發送時間間隔60s
redis_conn.setex('mobile_%s' % mobile,SMS_INTERVAL_TIME,'_')
# 調用短信發送的SDK來發送短信
ccp = CCP()
# to, datas, temp_id
# '15914397060', ['1234',"5"], 1 #"5"是分鍾數
# print(mobile,'測試手機號')
ret = ccp.send_template_sms(mobile, [sms_code, SMS_EXPIRE_TIME // 60], 1)
# print('ret返回的數據>>>', ret)
if not ret:
return Response({'msg': '短信發送失敗'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({'msg': '發送短信成功!'})
except:
return Response({'msg': '內部錯誤'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
路由代碼:
re_path(r'sms/(?P<mobile>1[3-9]\d{9})/',views.MSMAPIView.as_view())
配置文件settings/dev.py,代碼:
# 短信接口配置
SMS_ACCOUNTSID = "8a216da86b8863a1016b899599ee0190"
SMS_ACCOUNTTOKEN = "01baf2eb09ca4dca86bb47dde351cc8a"
SMS_APPID = "8a216da86b8863a1016b89959a4e0197"
SMS_SERVERIP = "sandboxapp.cloopen.com"
配置常量settings/constant.py,代碼:
# 輪播的顯示數量
BANNER_LENGTH = 7
# 導航的顯示數量
NAV_LENGTH = 7
# 短信發送有效期[單位:s]
SMS_EXPIRE_TIME = 5 * 60
# 短信的模板ID
SMS_TEMPLATE_ID = 1
# 短信發送的間隔時間
SMS_INTERVAL_TIME = 60
保存用戶注冊信息
查詢用戶對象
def get_user_by_account(account):
"""
根據帳號獲取user對象
:param account: 賬號,可以是用戶名,也可以是手機號
:return: User對象 或者 None
"""
try:
if re.match('^1[3-9]\d{9}$', account):
user = models.User.objects.get(mobile=account)
else:
user = models.User.objects.get(username=account)
except models.User.DoesNotExist:
return None
else:
return user
視圖代碼:
from rest_framework.generics import CreateAPIView
from .serializers import UserModelSerializer
class UserAPIView(CreateAPIView):
queryset = User.objects.all()
serializer_class = UserModelSerializer
序列化器,代碼:
from rest_framework import serializers
from .models import User
import re
from django_redis import get_redis_connection
from rest_framework_jwt.settings import api_settings
import logging
log = logging.getLogger("django")
class UserModelSerializer(serializers.ModelSerializer):
sms_code = serializers.CharField(min_length=4, max_length=6, required=True, help_text='短信驗證碼', write_only=True,
error_messages={'min_length': '請正確輸入驗證碼!', 'max_length': '請正確輸入驗證碼!',
'required': '驗證碼不能為空!', })
token = serializers.CharField(max_length=1024, read_only=True, help_text='token認證字符串')
class Meta:
model = User
fields = ["mobile","password","sms_code","token","id","username"]
extra_kwargs = {
"id":{
"read_only":True,
},
"username":{
"read_only":True,
},
"password":{
"write_only":True,
},
"mobile":{
"write_only":True,
}
}
def validate(self,attrs):
# 接受數據
mobile = attrs.get('mobile')
sms_code = attrs.get('sms_code')
password = attrs.get('password')
# 校驗手機號格式
if not re.match(r'1[3-9]\d{9}$', mobile):
raise serializers.ValidationError('手機號格式有誤,請重新輸入!')
# 驗證手機短信
try:
redis = get_redis_connection("sms_code")
except:
log.error("redis連接失敗!")
raise serializers.ValidationError("服務器出錯,請聯系客服工作人員!")
# 驗證短信是否有效
try:
real_sms_code = redis.get("sms_%s" % mobile).decode()
except:
raise serializers.ValidationError("手機短信不存在或已過期!")
# 驗證短信是否正確
if sms_code != real_sms_code:
raise serializers.ValidationError("手機短信錯誤!")
# 驗證手機是否注冊了
user = get_user_by_account(mobile)
if user is not None:
raise serializers.ValidationError('手機號已被注冊')
# 驗證密碼長度
if len(password) < 6 or len(password) > 16:
raise serializers.ValidationError("密碼必須保持在6-16位字符長度之間!")
# 必須返回數據
return data
def create(self, validated_data):
mobile = validated_data.get("mobile")
password = validated_data.get("password")
try:
user = User.objects.create(
mobile=mobile,
password=hash_password,
username=mobile
# is_active=0, # 如果要做郵件激活才能登錄,則可以加上這個字段
)
except:
log.error("創建用戶失敗!mobile=%s" % mobile)
raise serializers.ValidationError("注冊用戶失敗!請聯系客服工作人員!")
# 注冊成功以后,默認當前用戶為登錄狀態,返回返回登錄的jwt token值
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
路由代碼:
re_path(r'sms/(?P<mobile>1[3-9]\d{9})/',views.MSMAPIView.as_view())
前端請求發送短信
調整前端的頁面,添加一個發送短信功能,
html代碼:
<div class="sms-box">
<input v-model = "sms" type="text" maxlength='8' 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((res)=>{
let data = res.data
if( data.result == '-1' ){
this.$alert("發送短信失敗!","錯誤");
}else{
this.$alert("發送短信成功了!","成功");
}
}).catch((error)=>{
console.log(error.response.data.msg)
})
},
// 提交注冊信息
....
前端實現倒計時顯示
<script>
export default {
name: 'Register',
data() {
return {
sms_code: "",
mobile: "",
password: "",
validateResult: false,
sms_msg: '點擊發送短信',
is_send_sms: true,//發送短信的標記
flag: false,
}
},
created() {
},
methods: {
checkMobile() {
//校驗手機號唯一性
this.$axios.get(`${this.$settings.Host}/users/mobile/${this.mobile}/`)
.then((res) => {
})
.catch((error) => {
this.$message.error(error.response.data.msg)
})
},
smsHandle() {
// 判斷是否填寫了手機
if (!/^\d{11}$/.test(this.mobile)) {
this.$alert('手機號碼格式有誤!', '警告');
return false;
}
this.$axios.get(`${this.$settings.Host}/users/sms/${this.mobile}`)
.then((res) => {
let interval_time = 60; // 發送短信的間隔
if (this.flag === false) {
let t = setInterval(() => {
if (interval_time <= 1) {
clearInterval(t);
this.sms_msg = '點擊發送短信';
this.flag = false;
} else {
this.is_send_sms = false;
this.sms_msg = `${interval_time}秒重新發送`;
interval_time--
}
}, 1000);
this.flag = true;
} else {
return false
}
})
.catch((error) => {
this.$message.error(error.response.data.msg)
})
},
},
};
</script>
后端實現短信發送間隔的判斷
視圖代碼:
class MSMAPIView(APIView):
def get(self,request,mobile):
# 首先查看一下該手機號是否已經在60秒內發送過短信了
redis_conn = get_redis_connection('sms_code')
check_ret = redis_conn.get('mobile_%s' % mobile)
# 找不到默認返回的是None
if check_ret is not None:
return Response({'msg': '60秒內已經發送過短信了,請稍后嘗試!'})
try:
# 生成驗證碼
sms_code = "%06d"%random.randint(0,999999)
# 保存驗證碼5分鍾
redis_conn = get_redis_connection('sms_code')
redis_conn.setex('sms_%s' % mobile,SMS_EXPIRE_TIME,sms_code)
# 短信發送時間間隔60s
redis_conn.setex('mobile_%s' % mobile,SMS_INTERVAL_TIME,'_')
# 調用短信發送的SDK來發送短信
ccp = CCP()
# to, datas, temp_id
# '15914397060', ['1234',"5"], 1 #"5"是分鍾數
# print(mobile,'測試手機號')
ret = ccp.send_template_sms(mobile, [sms_code, SMS_EXPIRE_TIME // 60], 1)
# print('ret返回的數據>>>', ret)
if not ret:
return Response({'msg': '短信發送失敗'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({'msg': '發送短信成功!'})
except:
return Response({'msg': '內部錯誤'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
后端保存用戶注冊信息
創建序列化器對象
from rest_framework import serializers
from .models import User
import re
from django_redis import get_redis_connection
from rest_framework_jwt.settings import api_settings
import logging
log = logging.getLogger("django")
class UserModelSerializer(serializers.ModelSerializer):
sms_code = serializers.CharField(min_length=4, max_length=6, required=True, help_text='短信驗證碼', write_only=True,
error_messages={'min_length': '請正確輸入驗證碼!', 'max_length': '請正確輸入驗證碼!',
'required': '驗證碼不能為空!', })
token = serializers.CharField(max_length=1024, read_only=True, help_text='token認證字符串')
class Meta:
model = User
fields = ["mobile","password","sms_code","token","id","username"]
extra_kwargs = {
"id":{
"read_only":True,
},
"username":{
"read_only":True,
},
"password":{
"write_only":True,
},
"mobile":{
"write_only":True,
}
}
def validate(self,attrs):
# 接受數據
mobile = attrs.get('mobile')
sms_code = attrs.get('sms_code')
password = attrs.get('password')
# 校驗手機號格式
if not re.match(r'1[3-9]\d{9}$', mobile):
raise serializers.ValidationError('手機號格式有誤,請重新輸入!')
# 驗證手機短信
try:
redis = get_redis_connection("sms_code")
except:
log.error("redis連接失敗!")
raise serializers.ValidationError("服務器出錯,請聯系客服工作人員!")
# 驗證短信是否有效
try:
real_sms_code = redis.get("sms_%s" % mobile).decode()
except:
raise serializers.ValidationError("手機短信不存在或已過期!")
# 驗證短信是否正確
if sms_code != real_sms_code:
raise serializers.ValidationError("手機短信錯誤!")
# 驗證手機是否注冊了
user = get_user_by_account(mobile)
if user is not None:
raise serializers.ValidationError('手機號已被注冊')
# 驗證密碼長度
if len(password) < 6 or len(password) > 16:
raise serializers.ValidationError("密碼必須保持在6-16位字符長度之間!")
# 必須返回數據
return data
def create(self, validated_data):
mobile = validated_data.get("mobile")
password = validated_data.get("password")
try:
user = User.objects.create(
mobile=mobile,
password=hash_password,
username=mobile
# is_active=0, # 如果要做郵件激活才能登錄,則可以加上這個字段
)
except:
log.error("創建用戶失敗!mobile=%s" % mobile)
raise serializers.ValidationError("注冊用戶失敗!請聯系客服工作人員!")
# 注冊成功以后,默認當前用戶為登錄狀態,返回返回登錄的jwt token值
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()),
]
客戶端發送注冊信息時附帶發送短信