luffy-短信接口频率限制,以及前端发送,和注册
短信接口频率限制
# 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): return APIResponse(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>