目錄
register.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" name="a" v-model="remember"/> <span>記住密碼</span> </p> <p>忘記密碼</p> </div> <button class="login_btn" @click="loginHandle">登入</button> <p class="go_login" >沒有賬號 <router-link to="/user/register">立即註冊</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" >沒有賬號 <span>立即註冊</span></p> </div> </div> </div> </div> </template> <script> export default { name: 'Login', data(){ return { login_type: 0, username:"", password:"", remember:false, } }, methods:{ loginHandle(){ var captcha1 = new TencentCaptcha('2095582704', (res) =>{ if (res.ret === 0){ this.$axios.post(`${this.$settings.Host}/users/login/`,{ username:this.username, password:this.password, ticket:res.ticket, randstr:res.randstr, }).then((res)=>{ console.log(res); // console.log(this.remember); if (this.remember){ localStorage.token = res.data.token; localStorage.username = res.data.username; localStorage.id = res.data.id; sessionStorage.removeItem('token'); sessionStorage.removeItem('username'); sessionStorage.removeItem('id'); }else { sessionStorage.token = res.data.token; sessionStorage.username = res.data.username; sessionStorage.id = res.data.id; localStorage.removeItem('token'); localStorage.removeItem('username'); localStorage.removeItem('id'); } this.$confirm('下一步想去哪消費!', '提示', { confirmButtonText: '去首頁', cancelButtonText: '去個人中心', type: 'success' }).then(() => { this.$router.push('/'); }).catch(() => { this.$router.push('/person'); }); }).catch((error)=>{ this.$alert('使用者名稱或者密碼錯誤', '登入失敗', { confirmButtonText: '確定', }); }) } }); captcha1.show(); // 顯示驗證碼 } }, }; </script> <style scoped> .box{ width: 100%; height: 100%; position: relative; overflow: hidden; } .box img{ width: 100%; min-height: 100%; } .box .login { position: absolute; width: 500px; height: 400px; top: 0; left: 0; margin: auto; right: 0; bottom: 0; top: -338px; } .login .login-title{ width: 100%; text-align: center; } .login-title img{ width: 190px; height: auto; } .login-title p{ font-family: PingFangSC-Regular; 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{ 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; } .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>
1.前端和後端對於手機號的驗證
1.將註冊的幾個輸入框和js中的資料設定好資料驅動檢視 :v-model
// 修改input框中的相關的值 <input v-model = "mobile" type="text" placeholder="手機號碼" class="user" @blur="checkPhone"> <input v-model = "password" type="password" placeholder="密碼" class="user"> <input v-model = "r_password" type="password" placeholder="確認密碼" class="user"> // 修改js中data的相關值 data(){ return { sms:"", mobile:"", password:"", r_password:"", validateResult:false, }
2.前端對輸入的手機號長度做校驗
給手機號的輸入框繫結一個滑鼠離開的事件,當使用者輸入手機號之後離開輸入框,會對手機號格式進行校驗
<!-- html --> <input v-model = "mobile" type="text" placeholder="手機號碼" class="user" @blur="checkPhone">
// js methods:{ checkPhone(){ let phoneNumber = this.mobile; // 前端 let reg = /^1[3-9][0-9]{9}$/; if (!reg.test(phoneNumber)){ // 前端檢測手機號長度是否符合要求 this.$message.error('手機號 格式 error'); return false; } // 如果前端驗證手機號長度沒問題,就向後端發起請求 this.$axios.get(`${this.$settings.Host}/users/check_phone/?phone=${phoneNumber}`) // request.GET.get(phone) .then((res)=>{ console.log(res); }).catch((error)=>{ this.$message.error(error.response.data.error_msg); }) },
比如:你想驗證手機號是不是已經存在 這種邏輯就沒有辦法放到前端去做,因為手機號都儲存在資料庫中,所以如果想驗證手機號是否存在,肯定是後端要做相關邏輯。
也就是:
前端驗證手機號格式
後端驗證手機號格式加手機號是否已經存在
3.後端設定介面:驗證手機號格式和手機號唯一性
users/urls.py
# urls.py from rest_framework_jwt.views import obtain_jwt_token,verify_jwt_token from django.urls import path from . import views urlpatterns = [ ...... path(r'/check_phone/',views.CheckPhoneNumber.as_view()) ]
users/views.py
# views.py import re from rest_framework.views import APIView from rest_framework.response import Response from .utils import get_user_obj ...... class CheckPhoneNumber(APIView): def get(self,request): phone_number = request.GET.get('phone') # 驗證手機號格式 if not re.match('^1[3-9][0-9]{9}$', phone_number): # 格式不對 return Response({'error_msg':'手機號格式有誤,請重新輸入!'}, status=status.HTTP_400_BAD_REQUEST) # 驗證手機號唯一性 ret = get_user_obj(phone_number) if ret: return Response({'error_msg': '手機號已被註冊,請換手機號'}, status=status.HTTP_400_BAD_REQUEST) return Response({'msg': 'ok'})
現在前端和後端的校驗都已經編寫完畢,讓我們看一下效果
2.實現基本的註冊功能-不包括驗證碼
1.註冊功能的流程
2.註冊-前端
點選註冊按鈕,將輸入框中的資料傳送到後端
<!-- html --> <button class="register_btn" @click="registerHandler">註冊</button>
// js methods:{ ...... registerHandler(){ this.$axios.post(`${this.$settings.Host}/`,{ // 將輸入框的四項一併提交到後臺 sms:this.sms, mobile:this.mobile, password:this.password, r_password:this.r_password, }).then((res)=>{ }).catch((error)=>{ }) },
3.註冊-後端介面
users/urls.py
# urls.py from rest_framework_jwt.views import obtain_jwt_token,verify_jwt_token from django.urls import path from . import views urlpatterns = [ ...... path(r'register/', views.RegisterView.as_view()), ]
users/views.py
# views.py class RegisterView(CreateAPIView): queryset = models.User.objects.all() serializer_class = RegisterModelSerializer
user/serializers.py
序列化需要的欄位:id token phone
反序列化之後需要存到資料庫的欄位:phone password
# serializers.py class RegisterModelSerializer(serializers.ModelSerializer): ''' sms,r_password這兩個欄位只需要write_only=True 意思是:反序列化校驗的時候,這兩個欄位必須要傳遞給我 但是序列化返回資料的時候,這兩個欄位是不序列化出來的 id,token這兩個欄位只需要read_only=True 意思是:反序列化校驗的時候,這些欄位是無需校驗的 但是序列化返回資料的時候,必須要返回這兩個資料 ''' id = serializers.IntegerField(read_only=True) # sms和r_password這兩個欄位資料庫並沒有,但是反序列化的時候還需要校驗這兩個欄位,所以需要在這寫這兩個欄位 # 將兩個欄位設定為write_only=True的原因是因為:驗證碼和確認密碼這兩個欄位只做反序列化校驗功能,不需要序列化返回到前端 sms = serializers.CharField(max_length=6, min_length=4, write_only=True) # '3333' r_password = serializers.CharField(write_only=True) token = serializers.CharField(read_only=True) # 後端需要給前端傳遞token class Meta: model = models.User fields = ['id', 'phone', 'password', 'r_password', 'sms', 'token'] extra_kwargs = { 'password': {'write_only': True}, } # 校驗密碼和確認密碼 def validate(self, attrs): # 校驗手機號 phone_number = attrs.get('phone') # 後端校驗手機號格式是否正確 if not re.match('^1[3-9][0-9]{9}$', phone_number): raise serializers.ValidationError('手機號格式不對') # 驗證手機號是否已經存在 ret = get_user_obj(phone_number) if ret: raise serializers.ValidationError('手機號已經存在!!') p1 = attrs.get('password') p2 = attrs.get('r_password') # 驗證密碼和確認密碼是否一致 if p1 != p2: raise serializers.ValidationError('兩次密碼不一致,請核對') # todo 校驗驗證碼[待做] return attrs ''' 重寫create方法:因為序列化器中有的欄位是不需要反序列化存到資料庫中 ''' def create(self, validated_data): # 確認密碼和驗證碼不需要存到資料庫中,所以要剔除它們 validated_data.pop('r_password') validated_data.pop('sms') # 密碼加密 hash_password = make_password(validated_data['password']) validated_data['password'] = hash_password # 以上處理好的資料存到資料庫中 user = models.User.objects.create( **validated_data ) user.token = '123' # 硬加上一個token return user # 返回user物件時,就會有token這個屬性了
3.點選獲取驗證碼
1.點選獲取驗證碼的原理
2.點選獲取驗證碼-配置
1.安裝django-redis
pip install django-redis
2.dev.py檔案做相關配置
# dev.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"
3.點選獲取驗證碼-前端
<!-- html --> <button style="width: 34%;height: 41px;" @click="getSmsCode">點選獲取驗證碼</button>
// js getSmsCode(){ this.$axios.get(`${this.$settings.Host}/`) }
4.點選獲取驗證碼-後端介面
users/urls.py
# urls.py from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token from . import views from django.urls import path urlpatterns = [ ...... path(r'sms_code/(?P<phone>^1[3-9][0-9]{9}$)/', views.GetSMSCodeView.as_view()), ]
users/views.py
# views.py from django_redis import get_redis_connection class GetSMSCodeView(APIView): def get(self,request,phone): # 驗證是否已經傳送過簡訊了 conn = get_redis_connection('sms_code') ret = conn.get('mobile_interval_%s'%phone) if ret: return Response({'msg':'60秒內已經傳送過了,別瞎搞'}, status=status.HTTP_400_BAD_REQUEST) # 生成驗證碼 sms_code = "%06d" % random.randint(0,999999) # 儲存驗證碼 ''' 驗證碼是存到redis裡的,因為驗證碼有時限,所以用setex將驗證碼存入 存入方式是以鍵值對存入的: key:手機號 value:驗證碼 ''' conn.setex('mobile_%s'%phone, sms_code, contains.SMS_CODE_EXPIRE_TIME) # 設定有效期 # conn.setex('mobile_interval_%s'%phone, sms_code, contains.SMS_CODE_INTERVAL_TIME) # 設定傳送簡訊的時間間隔 # todo 傳送驗證碼 return Response({'msg':'ok'})
兩個常量放到constant.py中
settings/constant.py
# settings/constant.py SMS_CODE_EXPIRE_TIME = 60*10 # 有效時間 SMS_CODE_INTERVAL_TIME = 60 # 間隔時間
4.解決登入不上Xadmin的bug
登入不上Xadmin的原因是:
之前我們設定了Xadmin使用我們們自己建立的使用者表,而不是使用Xadmin原先自帶的表:傳送門:Xadmin使用自己建立的使用者表
而在昨天我們又重寫了jwt程式碼,讓其除了驗證使用者名稱和密碼還要驗證ticket票據:傳送門:重寫jwt程式碼來實現對滑動成功的認證
這樣的話就會導致我們在登入Xadmin的時候,也會驗證ticket票據,所以會登入失敗。
所以我們做一個if判斷:
當ticket有值的時候才走滑動成功的認證:驗證使用者名稱 密碼 ticket randstr
當ticket沒有值的時候,就做常規認證,只驗證使用者名稱和密碼即可
users/utils.py
# users/utils.py class CustomeModelBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): try: user_obj = get_user_obj(username) ''' 看是否有票據資訊,如果沒有,直接走下面的程式碼(Xadmin使用者名稱密碼登入) 如果有票據資訊,走if裡面的程式碼 對票據進行驗證 這樣的話xadmin就不會因為走票據資訊這套邏輯導致登入失敗了 ''' if kwargs.get('ticket'): ticket = kwargs.get('ticket') userip = request.META['REMOTE_ADDR'] randstr = kwargs.get('randstr') print('userip:', userip) params = { "aid": settings.FSQ.get('appid'), "AppSecretKey": settings.FSQ.get('app_serect_key'), "Ticket": ticket, "Randstr": randstr, "UserIP": userip } params = urlencode(params).encode() url = settings.FSQ.get('URL') f = urlopen(url, params) content = f.read() res = json.loads(content) print(res) # {'response': '1', 'evil_level': '0', 'err_msg': 'OK'} if res.get('response') != '1': return None if user_obj: if user_obj.check_password(password): return user_obj else: return None except Exception: logger.error('驗證過程程式碼有誤,請聯絡管理員') return None
5.redis
1.redis簡要介紹
memcache 一款軟體,可以使用鍵值對的格式,儲存資料到記憶體中.
redis是義大利的工程師開發的開源\免費的高速記憶體快取資料庫.需要注意的是,作者本身是隻開發了linux版本的redis資料庫.
官方原版: https://redis.io/
中文官網:http://www.redis.cn
2.redis的下載和安裝
使用以下命令啟動redis服務端[配置檔案路徑根據自己的擺放位置改動]
redis-server C:/tool/redis/redis.windows.conf
redis-server --service-install redis.windows.conf
NOSQL:not only sql,泛指非關係型資料庫。
關係型資料庫: (mysql, oracle, sql server, sqlite, db2)
1. 資料存放在表中,表之間有關係。 2. 通用的SQL操作語言。 3. 大部分支援事務。
非關係型資料庫:[ redis,hadoop,mangoDB]:
1. 沒有資料表的概念,不同的nosql資料庫存放資料位置不同。 2. nosql資料庫沒有通用的操作語言。 3. 基本不支援事務。 redis支援簡單事務
redis是一款基於CS架構的資料庫,所以redis有客戶端,也有服務端。
其中,客戶端可以使用python等程式語言,也可以終端命令列工具
redis客戶端連線伺服器:
redis-cli -h `redis伺服器ip` -p `redis伺服器port` #6379
4.redis資料型別
# 1. string型別: 字串型別是 Redis 中最為基礎的資料儲存型別,它在 Redis 中是二進位制安全的,也就是byte型別 最大容量是512M。 key: string
# 2. hash型別: hash用於儲存物件,物件的結構為屬性、值,值的型別為string。 key:{ 域:值[這裡的值只能是字串], 域:值, 域:值, 域:值, ... }
# 3. list型別: 列表的元素型別為string。 key:[ 值1,值2,值3..... ]
# 4. set型別: 無序集合,元素為string型別,元素唯一不重複,沒有修改操作。 key: {值1,值4,值3,值5,....}
# 5. zset型別[sortset]: 有序集合,元素為string型別,元素唯一不重複,有修改操作。 key:{ 值: 權重值, 值: 權重值, }
5.String
-
設定鍵值
set key value
-
例1:設定鍵為
name
值為xiaoming
的資料set name xiaoming
-
設定鍵值及過期時間,以秒為單位
setex key seconds value
-
例2:設定鍵為
aa
值為aa
過期時間為3秒的資料setex name 20 xiaoming
關於設定儲存資料的有效期
# setex 新增儲存資料到redis,同時設定有效期 格式: setex key time value # expire 給已有的資料重新設定有效期 格式: expire key time
-
設定多個鍵值
mset key1 value1 key2 value2 ...
-
例3:設定鍵為
a1
值為python
、鍵為a2
值為java
、鍵為a3
值為c
mset a1 python a2 java a3 c
-
追加值
append key value
-
例4:向鍵為
a1
中追加值haha
,a1='xx'append a1 haha
a1= 'xxhaha'
-
獲取:根據鍵獲取值,如果不存在此鍵則返回
nil
get key
-
例5:獲取鍵
name
的值get name
-
根據多個鍵獲取多個值
mget key1 key2 ...
-
例6:獲取鍵
a1、a2、a3
的值mget a1 a2 a3
6.鍵操作
-
keys pattern
-
例1:檢視所有鍵
keys *
-
例2:檢視名稱中包含
a
的鍵keys a*
-
判斷鍵是否存在,如果存在返回
1
,不存在返回0
exists key1
-
例3:判斷鍵
a1
是否存在exists a1
-
檢視鍵對應的
value
的型別type key
-
例4:檢視鍵
a1
的值型別,為redis⽀持的五種型別中的⼀種type a1
-
刪除鍵及對應的值
del key1 key2 ...
-
例5:刪除鍵
a2、a3
del a2 a3
-
檢視有效時間,以秒為單位
ttl key
-
例7:檢視鍵
bb
的有效時間ttl bb
7.hash
# 結構: 鍵key:{ 域field:值value }
-
設定單個屬性
hset key field value
-
例1:設定鍵
user
的屬性name
為xiaohong
hset user name xiaohong
-
設定多個屬性
hmset key field1 value1 field2 value2 ...
-
例2:設定鍵
u2
的屬性name
為xiaohong
、屬性age
為11
hmset u2 name xiaohong age 11
-
獲取指定鍵所有的屬性
hkeys key
-
例3:獲取鍵u2的所有屬性
hkeys u2
-
獲取⼀個屬性的值
hget key field
-
例4:獲取鍵
u2
屬性name
的值hget u2 name
-
獲取多個屬性的值
hmget key field1 field2 ...
-
例5:獲取鍵
u2
屬性name
、age
的值hmget u2 name age
-
獲取所有屬性的值
hvals key
-
例6:獲取鍵
u2
所有屬性的值hvals u2
-
刪除屬性,屬性對應的值會被⼀起刪除
hdel key field1 field2 ...
-
例7:刪除鍵
u2
的屬性age
hdel u2 age
8.set
-
新增元素
sadd key member1 member2 ...
-
例1:向鍵
a3
的集合中新增元素zhangsan
、lisi
、wangwu
sadd a3 zhangsan sili wangwu
-
返回所有的元素
smembers key
-
例2:獲取鍵
a3
的集合中所有元素smembers a3
-
刪除指定元素
srem key value
-
例3:刪除鍵
a3
的集合中元素wangwu
srem a3 wangwu
9.list
按照插⼊順序排序
-
在左側插⼊資料
lpush key value1 value2 ...
-
例1:從鍵為
a1
的列表左側加⼊資料a 、 b 、c
lpush a1 a b c
-
在右側插⼊資料
rpush key value1 value2 ...
-
例2:從鍵為
a1
的列表右側加⼊資料0、1
rpush a1 0 1
-
在指定元素的前或後插⼊新元素
linsert key before或after 現有元素 新元素
-
例3:在鍵為
a1
的列表中元素b
前加⼊3
linsert a1 before b 3
設定指定索引位置的元素值
-
索引從左側開始,第⼀個元素為0
-
索引可以是負數,表示尾部開始計數,如
-1
表示最後⼀個元素lset key index value
-
例5:修改鍵為
a1
的列表中下標為1
的元素值為z
lset a 1 z
-
刪除指定元素
-
將列表中前
count
次出現的值為value
的元素移除 -
count > 0: 從頭往尾移除
-
count < 0: 從尾往頭移除
-
count = 0: 移除所有
lrem key count value
-
lrem a2 -2 b
-
例6.3:檢視列表
a2
的所有元素lrange a2 0 -1