luffy-簡訊介面頻率限制,以及前端傳送,和註冊
阿新 • • 發佈:2020-07-23
簡訊介面頻率限制
# throttlings.py from rest_framework.throttling import SimpleRateThrottle class SMSThrottling(SimpleRateThrottle): scope = 'sms' def get_cache_key(self, request, view): # 限制手機號 # 取手機號 telephone = request.query_params.get("telephone") # 父類SimpleRateThrottle中內建了cache_format = 'throttle_%(scope)s_%(ident)s'return self.cache_format %{'scope':self.scope,'ident':telephone} # views.py from .throttlings import SMSThrottling class SendSmSView(ViewSet): throttle_classes = [SMSThrottling, ] @action(methods=['GET'], detail=False) def send(self, request, *args, **kwargs): """ 傳送驗證碼介面 路由:http://127.0.0.1:8000/user/send/?telephone=13861015437 :return:""" import re from luffyapi.libs.tx_sms import send_message, get_code from django.core.cache import cache from django.conf import settings telephone = request.query_params.get("telephone") if not re.match('^1[3-9][0-9]{9}', telephone): returnAPIResponse(code=0, msg='手機號不合法') code = get_code() result = send_message(telephone, code) # 儲存驗證碼 # sms_cache_%s cache.set(settings.PHONE_CACHE_KEY % telephone, code, 180) if result: return APIResponse(code=1, msg='驗證碼傳送成功') else: return APIResponse(code=0, msg='驗證碼傳送失敗') # urls.py from django.urls import path,re_path,include from . import views from rest_framework.routers import SimpleRouter router = SimpleRouter() router.register('', views.LoginView, 'login') router.register('', views.SendSmSView, 'send') urlpatterns = [ path('', include(router.urls)), ]
驗證碼登入介面
# views.py # 登入介面 class LoginView(ViewSet): @action(methods=['POST'], detail=False) def login(self, request, *args, **kwargs): """ 路由:http://127.0.0.1:8000/user/login/ :param request: :param args: :param kwargs: :return: """ # 1 需要 有個序列化的類 login_ser = serializers.LoginModelSerializer(data=request.data) # 2 生成序列化類物件 # 3 呼叫序列號物件的is_validad # result = login_ser.is_valid() if not login_ser.is_valid(): return APIResponse(status=101, msg='登入失敗', errors=login_ser.errors) token = login_ser.context.get('token') user = login_ser.context.get('user') # 4 return return APIResponse(status=100, msg='登入成功', token=token, username=user.username) @action(methods=['GET'], detail=False) def check_telephone(self, request, *args, **kwargs): """ 路由:http://127.0.0.1:8000/user/check_telephone/?telephone=13861015437 :param request: :param args: :param kwargs: :return: """ import re telephone = request.query_params.get("telephone") if not re.match('^1[3-9][0-9]{9}', telephone): return APIResponse(code=0, msg='手機號不合法') try: models.User.objects.get(telephone=telephone) return APIResponse(code=1) except Exception as e: return APIResponse(code=0, msg='手機號不存在') @action(methods=['POST'], detail=False) def code_login(self, request, *args, **kwargs): """ 驗證碼登入介面 路由:http://127.0.0.1:8000/user/code_login/ :param request: :param args: :param kwargs: :return: """ login_ser = serializers.CodeLoginModelSerializer(data=request.data) if not login_ser.is_valid(): return APIResponse(status=101, msg='登入失敗', errors=login_ser.errors) token = login_ser.context.get('token') user = login_ser.context.get('user') return APIResponse(status=100, msg='登入成功', token=token, username=user.username) # serializers.py # 驗證碼登入的序列化器 class CodeLoginModelSerializer(serializers.ModelSerializer): code = serializers.CharField(max_length=4, min_length=4) class Meta: model = models.User fields = ['telephone', 'code'] def validate(self, attrs): user = self._get_user(attrs) # 簽發token token = self._get_token(user) self.context['token'] = token self.context['user'] = user return attrs def _get_user(self, attrs): from django.core.cache import cache from django.conf import settings import re telephone = attrs.get('telephone') # 使用者名稱方式 code = attrs.get('code') # 取出之前存在快取中的code cache_code = cache.get(settings.PHONE_CACHE_KEY % telephone) if re.match('^1[3-9][0-9]{9}$', telephone): if code == cache_code: # 驗證碼通過 user = models.User.objects.filter(telephone=telephone).first() if user: # 驗證碼通過一次後,清除cache中的快取 cache.set(settings.PHONE_CACHE_KEY % telephone, "") return user else: raise ValidationError("使用者不存在") else: raise ValidationError("驗證碼錯誤") else: raise ValidationError("手機號不合法") def _get_token(self, user): from rest_framework_jwt.serializers import jwt_encode_handler, jwt_payload_handler payload = jwt_payload_handler(user) # 把user傳入,得到payload token = jwt_encode_handler(payload) # 把payload傳入,得到token return token
前臺傳送驗證碼,以及登入
程式碼
<template> <div class="login"> <div class="box"> <i class="el-icon-close" @click="close_login"></i> <div class="content"> <div class="nav"> <span :class="{active: login_method === 'is_pwd'}" @click="change_login_method('is_pwd')">密碼登入</span> <span :class="{active: login_method === 'is_sms'}" @click="change_login_method('is_sms')">簡訊登入</span> </div> <el-form v-if="login_method === 'is_pwd'"> <el-input placeholder="使用者名稱/手機號/郵箱" prefix-icon="el-icon-user" v-model="username" clearable> </el-input> <el-input placeholder="密碼" prefix-icon="el-icon-key" v-model="password" clearable show-password> </el-input> <el-button type="primary" @click="login_password">登入</el-button> </el-form> <el-form v-if="login_method === 'is_sms'"> <el-input placeholder="手機號" prefix-icon="el-icon-phone-outline" v-model="mobile" clearable @blur="check_mobile"> </el-input> <el-input placeholder="驗證碼" prefix-icon="el-icon-chat-line-round" v-model="sms" clearable> <template slot="append"> <span class="sms" @click="send_sms">{{ sms_interval }}</span> </template> </el-input> <el-button type="primary" @click="code_login">登入</el-button> </el-form> <div class="foot"> <span @click="go_register">立即註冊</span> </div> </div> </div> </div> </template> <script> export default { name: "Login", data() { return { username: '', password: '', mobile: '', sms: '', login_method: 'is_pwd', sms_interval: '獲取驗證碼', is_send: false, } }, methods: { close_login() { this.$emit('close') }, go_register() { this.$emit('go') }, change_login_method(method) { this.login_method = method; }, check_mobile() { if (!this.mobile) return; //字串.match(/正則表示式/) if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) { this.$message({ message: '手機號有誤', type: 'warning', duration: 1000, onClose: () => { this.mobile = ''; } }); return false; } //傳送axios請求 // this.$axios.get(this.$settings.base_url+'/user/check_telephone/telephone='+this.mobile}) this.$axios.get(this.$settings.base_url + '/user/check_telephone/', {params: {telephone: this.mobile}}).then(response => { if (response.data.code) { //手機號存在,允許傳送驗證碼 this.is_send = true; } else { this.$message({ message: '手機號不存在', type: 'warning', duration: 1000, onClose: () => { this.mobile = ''; } }); } }).catch(error => { console.log(error) }) }, send_sms() { if (!this.is_send) return; this.is_send = false; let sms_interval_time = 60; this.sms_interval = "傳送中..."; this.$axios.get(this.$settings.base_url + '/user/send/', {params: {'telephone': this.mobile}}) .then(response => { if (response.data.code) { this.$message({ message: '傳送驗證碼成功', type: 'success', duration: 1000, }); } }) // setInterval(()=>{},100) //定時器:每隔一秒種,把數字減一,當減到小於1,按鈕又能點了,顯示獲取驗證碼 let timer = setInterval(() => { if (sms_interval_time <= 1) { clearInterval(timer); //如果小於等於1,把定時器清除 this.sms_interval = "獲取驗證碼"; this.is_send = true; // 重新回覆點擊發送功能的條件 } else { sms_interval_time -= 1; this.sms_interval = `${sms_interval_time}秒後再發`; } }, 1000); }, login_password() { if (this.username && this.password) { //傳送請求 this.$axios.post(this.$settings.base_url + '/user/login/', { username: this.username, password: this.password }).then(response => { console.log(response.data) //把使用者資訊儲存到cookie中 // this.$cookies.set('key','value','過期時間,按s計') this.$cookies.set('token', response.data.token, '7d') this.$cookies.set('username', response.data.username, '7d') //關閉登入視窗(子傳父) this.$emit('close') //給父元件,Head傳遞一個事件,讓它從cookie中取出token和username this.$emit('loginsuccess') }).catch(errors => { }) } else { this.$message({ message: '使用者名稱或密碼必須填哦', type: 'warning', }); } }, code_login() { if (this.mobile && this.sms) { //傳送請求 this.$axios.post(this.$settings.base_url + '/user/code_login/', { telephone: this.mobile, code: this.sms }).then(response => { console.log(response.data) //把使用者資訊儲存到cookie中 // this.$cookies.set('key','value','過期時間,按s計') this.$cookies.set('token', response.data.token, '7d') this.$cookies.set('username', response.data.username, '7d') //關閉登入視窗(子傳父) this.$emit('close') //給父元件,Head傳遞一個事件,讓它從cookie中取出token和username this.$emit('loginsuccess') }).catch(errors => { }) } else { this.$message({ message: '手機號或驗證碼必填', type: 'warning', }); } }, } } </script> <style scoped> .login { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; z-index: 10; background-color: rgba(0, 0, 0, 0.3); } .box { width: 400px; height: 420px; background-color: white; border-radius: 10px; position: relative; top: calc(50vh - 210px); left: calc(50vw - 200px); } .el-icon-close { position: absolute; font-weight: bold; font-size: 20px; top: 10px; right: 10px; cursor: pointer; } .el-icon-close:hover { color: darkred; } .content { position: absolute; top: 40px; width: 280px; left: 60px; } .nav { font-size: 20px; height: 38px; border-bottom: 2px solid darkgrey; } .nav > span { margin: 0 20px 0 35px; color: darkgrey; user-select: none; cursor: pointer; padding-bottom: 10px; border-bottom: 2px solid darkgrey; } .nav > span.active { color: black; border-bottom: 3px solid black; padding-bottom: 9px; } .el-input, .el-button { margin-top: 40px; } .el-button { width: 100%; font-size: 18px; } .foot > span { float: right; margin-top: 20px; color: orange; cursor: pointer; } .sms { color: orange; cursor: pointer; display: inline-block; width: 70px; text-align: center; user-select: none; } </style>程式碼
後臺註冊介面
# urls.py router.register('register', views.RegisterView, 'register') # /user/register post請求就是新增 # views.py class RegisterView(GenericViewSet,CreateModelMixin): queryset = models.User.objects.all() serializer_class = serializer.UserRegisterSerilaizer def create(self, request, *args, **kwargs): response=super().create(request, *args, **kwargs) username=response.data.get('username') return APIResponse(code=1,msg='註冊成功',username=username) # serializer.py class UserRegisterSerilaizer(serializers.ModelSerializer): code=serializers.CharField(max_length=4,min_length=4,write_only=True) class Meta: model = models.User fields = ['telephone', 'code','password','username'] extra_kwargs = { 'password': {'max_length': 18,'min_length':8}, 'username': {'read_only':True} } def validate(self, attrs): telephone = attrs.get('telephone') code = attrs.get('code') # 取出原來的code cache_code = cache.get(settings.PHONE_CACHE_KEY % telephone) if code == cache_code: # 驗證碼通過 if re.match('^1[3-9][0-9]{9}$', telephone): attrs['username']=telephone # 把使用者的名字設成手機號 attrs.pop('code') return attrs else: raise ValidationError('手機號不合法') else: raise ValidationError('驗證碼錯誤') # 重寫create方法 def create(self, validated_data): user=models.User.objects.create_user(**validated_data) return user
前臺註冊功能
# Register.vue <template> <div class="register"> <div class="box"> <i class="el-icon-close" @click="close_register"></i> <div class="content"> <div class="nav"> <span class="active">新使用者註冊</span> </div> <el-form> <el-input placeholder="手機號" prefix-icon="el-icon-phone-outline" v-model="mobile" clearable @blur="check_mobile"> </el-input> <el-input placeholder="密碼" prefix-icon="el-icon-key" v-model="password" clearable show-password> </el-input> <el-input placeholder="驗證碼" prefix-icon="el-icon-chat-line-round" v-model="sms" clearable> <template slot="append"> <span class="sms" @click="send_sms">{{ sms_interval }}</span> </template> </el-input> <el-button type="primary" @click="register">註冊</el-button> </el-form> <div class="foot"> <span @click="go_login">立即登入</span> </div> </div> </div> </div> </template> <script> export default { name: "Register", data() { return { mobile: '', password: '', sms: '', sms_interval: '獲取驗證碼', is_send: false, } }, methods: { close_register() { this.$emit('close', false) }, go_login() { this.$emit('go') }, check_mobile() { if (!this.mobile) return; //字串.match(/正則表示式/) if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) { this.$message({ message: '手機號有誤', type: 'warning', duration: 1000, onClose: () => { this.mobile = ''; } }); return false; } this.$axios.get(this.$settings.base_url + '/user/check_telephone/', {params: {telephone: this.mobile}}).then(response => { if (response.data.code) { this.$message({ message: '您已經註冊過了,快去登入把', type: 'warning', duration: 1000, onClose: () => { this.go_login() } }); } else { this.is_send = true; this.$message({ message: '該使用者沒有註冊過,歡迎註冊我們的平臺', type: 'success', duration: 1000, }); } }).catch(error => { console.log(error) }) }, send_sms() { if (!this.is_send) return; this.is_send = false; let sms_interval_time = 60; this.sms_interval = "傳送中..."; this.$axios.get(this.$settings.base_url + '/user/send/', {params: {'telephone': this.mobile}}) .then(response => { if (response.data.code) { this.$message({ message: '傳送驗證碼成功', type: 'success', duration: 1000, }); } }) // setInterval(()=>{},100) //定時器:每隔一秒種,把數字減一,當減到小於1,按鈕又能點了,顯示獲取驗證碼 let timer = setInterval(() => { if (sms_interval_time <= 1) { clearInterval(timer); //如果小於等於1,把定時器清除 this.sms_interval = "獲取驗證碼"; this.is_send = true; // 重新回覆點擊發送功能的條件 } else { sms_interval_time -= 1; this.sms_interval = `${sms_interval_time}秒後再發`; } }, 1000); }, register() { if (this.mobile && this.sms && this.password) { this.$axios.post(this.$settings.base_url + '/user/register/', { telephone: this.mobile, code: this.sms, password: this.password }).then(response => { if (response.data.code) { //註冊成功,來個提示,跳轉到登入 this.$message({ message: '註冊成功', type: 'success', duration: 1000, onClose: () => { this.go_login() } }); } else { this.$message({ message: '未知錯誤', type: 'error', duration: 1000, onClose: () => { this.mobile = '' this.sms = '' this.password = '' } }); } }) } else { this.$message({ message: '你有沒填的資訊', type: 'error', duration: 1000, }); } }, } } </script> <style scoped> .register { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; z-index: 10; background-color: rgba(0, 0, 0, 0.3); } .box { width: 400px; height: 480px; background-color: white; border-radius: 10px; position: relative; top: calc(50vh - 240px); left: calc(50vw - 200px); } .el-icon-close { position: absolute; font-weight: bold; font-size: 20px; top: 10px; right: 10px; cursor: pointer; } .el-icon-close:hover { color: darkred; } .content { position: absolute; top: 40px; width: 280px; left: 60px; } .nav { font-size: 20px; height: 38px; border-bottom: 2px solid darkgrey; } .nav > span { margin-left: 90px; color: darkgrey; user-select: none; cursor: pointer; padding-bottom: 10px; border-bottom: 2px solid darkgrey; } .nav > span.active { color: black; border-bottom: 3px solid black; padding-bottom: 9px; } .el-input, .el-button { margin-top: 40px; } .el-button { width: 100%; font-size: 18px; } .foot > span { float: right; margin-top: 20px; color: orange; cursor: pointer; } .sms { color: orange; cursor: pointer; display: inline-block; width: 70px; text-align: center; user-select: none; } </style>程式碼