Django项目--路飞学城项目05
2022/2/19 6:13:23
本文主要是介绍Django项目--路飞学城项目05,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1 腾讯云短信二次封装
tx_sms __init__.py settings.py sms.py
1.1 init.py
from .sms import get_code, send_sms
1.2 settings.py
# 短信应用 SDK AppID APPPID = # SDK AppID 以1400开头 # 短信应用 SDK AppKey APPKEY = "" # 短信模板ID,需要在短信控制台中申请 TEMPLATE_ID = # NOTE: 这里的模板 ID`7839` 只是示例,真实的模板 ID 需要在短信控制台中申请 # 签名 SMS_SIGN = ""
1.3 sms.py
import random from qcloudsms_py import SmsSingleSender from qcloudsms_py.httpclient import HTTPError from .settings import * from utils.logging import get_logger logger = get_logger() # 获取验证码的函数(4位数字) def get_code(): code_str = '' for i in range(4): number = str(random.randint(0, 9)) code_str += number return code_str # 发送短信的函数 def send_sms(phone, code): ssender = SmsSingleSender(APPPID, APPKEY) params = [code, '1'] # 当模板没有参数时,`params = []` try: result = ssender.send_with_param(86, phone, TEMPLATE_ID, params, sign=SMS_SIGN, extend="", ext="") except Exception as e: # 记录日志 logger.error('%s手机号,发送短信失败,错误信息是%s' % (phone,str(e))) if result['result'] == 0: return True else: logger.warning('%s手机号,发送短信失败,失败原因是%s' % (phone, result['errmsg'])) return False
2 短信发送接口
# 前端给我手机号,get请求
2.1 视图
# action装饰器的拓展使用--给同一个视图view下,不同的接口,添加不同的类设置 可用action装饰器 参数:throttle_classes=[SMSThrottle] class MobileView(ViewSet): @action(methods=['GET'],detail=False,url_path='sms',throttle_classes=[SMSThrottle]) def send_sms(self, request): mobile = request.query_params.get('mobile') if mobile and re.match('^1[3-9][0-9]{9}$', mobile): # 发送短信 # 获取验证码,记录验证码(放到缓存中,默认还是缓存到内存) code = tx_sms.get_code() cache.set(settings.CACHE_SMS % mobile, code) # 调用发送短信函数 res = tx_sms.send_sms(mobile, code) if res: return APIResponse(msg='短信发送成功') else: raise APIException('短信发送失败') else: raise APIException('手机号不合法')
2.2 路由
# view 采用的自动生成路由 # 这是自己写路由的形式 path('sms/', views.LoginView.as_view({'get':'send_sms'})),
2.3 配合一个频率类
# 限制一个手机号,一分钟只能发一次验证码 # 频率类全局配置 REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES': { 'sms': '1/m', }, }
from rest_framework.throttling import SimpleRateThrottle class SMSThrottle(SimpleRateThrottle): scope = 'sms' def get_cache_key(self, request, view): mobile = request.query_params.get('mobile') return mobile
3 短信登录接口
# post, 手机号,验证码
3.1 视图
class LoginView(ViewSet): def mobile_login(self, request): # 逻辑写在序列化类中 ser = serializer.MobileLoginSerializer(data=request.data) ser.is_valid(raise_exception=True) # 执行序列化类字段自己的校验规则,局部钩子,全局钩子 token = ser.context.get('token') username = ser.context.get('username') return APIResponse(msg='登录成功', username=username, token=token)
3.2 序列化类
class MobileLoginSerializer(serializers.ModelSerializer): # 重写它的原因,unique=True mobile = serializers.CharField() #code 由于不是表的字段,所有必须重写 code=serializers.CharField() class Meta: model = models.User fields = ['code', 'mobile'] # 用户校验和签发token def validate(self, attrs): ''' 1 校验code是否正确:从缓存中根据手机号获取code,比较 2 根据手机号,拿到当前用户, 3 根据用户,签发token 4 把token放到序列化类的对象中 ''' user = self._get_user(attrs) token = self._get_token(user) # 上下文,是个字典 self.context['token'] = token self.context['username'] = user.username return attrs def _get_user(self, attrs): # 不是私有,但尽量只给内部用 code = attrs.get('code') mobile = attrs.get('mobile') # 校验code cache_code = cache.get(settings.CACHE_SMS % mobile) # code == "111111" 是防止验证码发送频繁,留的后门 if cache_code and code == cache_code or code == "111111": user = models.User.objects.filter(mobile=mobile).first() if not user: raise ValidationError({'detail': '手机号不存在'}) return user else: raise ValidationError({'detail': '验证码错误'}) def _get_token(self, user): payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return token
3.3 路由
path('mobile_login/', views.LoginView.as_view({'post':'mobile_login'})),
4 短信注册接口
# 手机号 验证码 密码
4.1 视图类
class RegisterView(GenericViewSet, CreateModelMixin): queryset = User.objects.all() serializer_class = serializer.RegisterUserSerializer def create(self, request, *args, **kwargs): super().create(request, *args, **kwargs) return APIResponse(msg='注册成功')
4.2 序列化类
class RegisterUserSerializer(serializers.ModelSerializer): # code 不是User表的字段,必须重写 code = serializers.CharField(max_length=4, min_length=4,write_only=True) class Meta: model = models.User fields = ['mobile', 'code', 'password'] extra_kwargs = {'mobile': {'write_only': True}, 'password': {'write_only': True} } # 如果想让密码比较复杂,可以给密码写一个局部钩子,使用正则去匹配 # 校验字段 def validate(self, attrs): ''' 1 校验code是否正确 2 把code从attrs中剔除 3 使用手机号作为用户名,后期可以改 ''' code = attrs.get('code') mobile = attrs.get('mobile') # 校验code cache_code = cache.get(settings.CACHE_SMS % mobile) # code == "111111" 是防止验证码发送频繁,留的后门 if cache_code and code == cache_code or code == "111111": # 把code剔除,code不是User表的字段,不能用来保存 attrs.pop('code') # 创造一个username attrs['username'] = mobile else: raise ValidationError({'detail': '验证码错误'}) return attrs # 为什么要重写create? 因为密码是明文的,需要使用create_user创建用户 def create(self, validated_data): user = models.User.objects.create_user(**validated_data) return user
5 前端登录注册
# 多方式登录接口 # 校验手机号是否存在接口 # 发送验证码接口(腾讯短信平台) # 验证码登录接口 # 验证码注册接口
5.1 Login.vue
<template> <div class="header"> <div class="slogan"> <p>老男孩IT教育 | 帮助有志向的年轻人通过努力学习获得体面的工作和生活</p> </div> <div class="nav"> <ul class="left-part"> <li class="logo"> <router-link to="/"> <img src="../assets/img/head-logo.svg" alt=""> </router-link> </li> <li class="ele"> <span @click="goPage('/free-course')" :class="{active: url_path === '/free-course'}">免费课</span> </li> <li class="ele"> <span @click="goPage('/actual-course')" :class="{active: url_path === '/actual-course'}">实战课</span> </li> <li class="ele"> <span @click="goPage('/light-course')" :class="{active: url_path === '/light-course'}">轻课</span> </li> </ul> <div class="right-part"> <div v-if="!token"> <span @click="put_login">登录</span> <span class="line">|</span> <span @click="put_register">注册</span> </div> <div v-else> <span>{{username}}</span> <span class="line">|</span> <span @click="logout">注销</span> </div> </div> <Login v-if="is_login" @close="close_login" @go="put_register"></Login> <Register v-if="is_register" @close="close_register" @go="put_login"></Register> </div> </div> </template> <script> import Login from "./Login"; import Register from "./Register"; export default { name: "Header", data() { return { url_path: sessionStorage.url_path || '/', is_login: false, is_register: false, token:'', username:'' } }, methods: { goPage(url_path) { // 已经是当前路由就没有必要重新跳转 if (this.url_path !== url_path) { this.$router.push(url_path); } sessionStorage.url_path = url_path; }, put_login() { this.is_login = true; this.is_register = false; }, put_register() { this.is_login = false; this.is_register = true; }, close_login() { this.is_login = false; this.token=this.$cookies.get('token') this.username=this.$cookies.get('username') }, close_register() { this.is_register = false; }, logout(){ this.$cookies.remove('token') this.$cookies.remove('username') this.token='' this.username='' } }, created() { // 前端存数据的地方:cookie中:过期时间,借助第三方, sessionStorage:临时存储,页面关闭就没了 localStorage:永久存储,除非客户清理 // sessionStorage.name = 'lqz'; // localStorage.age='19' sessionStorage.url_path = this.$route.path; this.url_path = this.$route.path; // 取token和username this.token=this.$cookies.get('token') this.username=this.$cookies.get('username') }, components: { Login, Register } } </script> <style scoped> .header { background-color: white; box-shadow: 0 0 5px 0 #aaa; } .header:after { content: ""; display: block; clear: both; } .slogan { background-color: #eee; height: 40px; } .slogan p { width: 1200px; margin: 0 auto; color: #aaa; font-size: 13px; line-height: 40px; } .nav { background-color: white; user-select: none; width: 1200px; margin: 0 auto; } .nav ul { padding: 15px 0; float: left; } .nav ul:after { clear: both; content: ''; display: block; } .nav ul li { float: left; } .logo { margin-right: 20px; } .ele { margin: 0 20px; } .ele span { display: block; font: 15px/36px '微软雅黑'; border-bottom: 2px solid transparent; cursor: pointer; } .ele span:hover { border-bottom-color: orange; } .ele span.active { color: orange; border-bottom-color: orange; } .right-part { float: right; } .right-part .line { margin: 0 10px; } .right-part span { line-height: 68px; cursor: pointer; } </style>
5.2 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; if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) { this.$message({ message: '手机号有误', type: 'warning', duration: 1000, onClose: () => { this.mobile = ''; } }); return false; } // 手机号合法的情况,向后端发送请求,检查手机号是否注册过 this.$http.get(this.$settings.base_url + 'user/mobile/?mobile=' + this.mobile).then(res => { if (res.data.code == 100) { // 表示用户已经注册了,不需要注册了 this.$message({ message: '该手机号已经注册过,请直接登录!!', type: 'warning', duration: 3000, onClose: () => { this.mobile = '' // 手机号没有注册过,不能发验证码 this.is_send = false; } }); } else { this.is_send = true; // 通过is_send来标志,能否发送验证码 } }) }, send_sms() { if (!this.is_send) return; this.is_send = false; let sms_interval_time = 60; this.sms_interval = "发送中..."; let timer = setInterval(() => { if (sms_interval_time <= 1) { clearInterval(timer); this.sms_interval = "获取验证码"; this.is_send = true; // 重新回复点击发送功能的条件 } else { sms_interval_time -= 1; this.sms_interval = `${sms_interval_time}秒后再发`; } }, 1000); // 真正的发送验证码 this.$http.get(this.$settings.base_url + 'user/sms/?mobile=' + this.mobile).then(res => { this.$message({ message: res.data.msg, type: 'info', duration: 3000, }) }) }, register() { if (this.mobile && this.sms && this.password) { this.$http.post(this.$settings.base_url + 'user/register/', { mobile: this.mobile, code: this.sms, password: this.password }).then(res => { console.log(res.data) if (res.data.code == 100) { // 直接跳到取登录 this.$emit('go') } else { this.$message({ message: res.data.msg, type: 'warning', duration: 3000, }); } }) } } } } </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>
5.3 Header.vue
<template> <div class="header"> <div class="slogan"> <p>老男孩IT教育 | 帮助有志向的年轻人通过努力学习获得体面的工作和生活</p> </div> <div class="nav"> <ul class="left-part"> <li class="logo"> <router-link to="/"> <img src="../assets/img/head-logo.svg" alt=""> </router-link> </li> <li class="ele"> <span @click="goPage('/free-course')" :class="{active: url_path === '/free-course'}">免费课</span> </li> <li class="ele"> <span @click="goPage('/actual-course')" :class="{active: url_path === '/actual-course'}">实战课</span> </li> <li class="ele"> <span @click="goPage('/light-course')" :class="{active: url_path === '/light-course'}">轻课</span> </li> </ul> <div class="right-part"> <div v-if="!token"> <span @click="put_login">登录</span> <span class="line">|</span> <span @click="put_register">注册</span> </div> <div v-else> <span>{{username}}</span> <span class="line">|</span> <span @click="logout">注销</span> </div> </div> <Login v-if="is_login" @close="close_login" @go="put_register"></Login> <Register v-if="is_register" @close="close_register" @go="put_login"></Register> </div> </div> </template> <script> import Login from "./Login"; import Register from "./Register"; export default { name: "Header", data() { return { url_path: sessionStorage.url_path || '/', is_login: false, is_register: false, token:'', username:'' } }, methods: { goPage(url_path) { // 已经是当前路由就没有必要重新跳转 if (this.url_path !== url_path) { this.$router.push(url_path); } sessionStorage.url_path = url_path; }, put_login() { this.is_login = true; this.is_register = false; }, put_register() { this.is_login = false; this.is_register = true; }, close_login() { this.is_login = false; this.token=this.$cookies.get('token') this.username=this.$cookies.get('username') }, close_register() { this.is_register = false; }, logout(){ this.$cookies.remove('token') this.$cookies.remove('username') this.token='' this.username='' } }, created() { // 前端存数据的地方:cookie中:过期时间,借助第三方, sessionStorage:临时存储,页面关闭就没了 localStorage:永久存储,除非客户清理 // sessionStorage.name = 'lqz'; // localStorage.age='19' sessionStorage.url_path = this.$route.path; this.url_path = this.$route.path; // 取token和username this.token=this.$cookies.get('token') this.username=this.$cookies.get('username') }, components: { Login, Register } } </script> <style scoped> .header { background-color: white; box-shadow: 0 0 5px 0 #aaa; } .header:after { content: ""; display: block; clear: both; } .slogan { background-color: #eee; height: 40px; } .slogan p { width: 1200px; margin: 0 auto; color: #aaa; font-size: 13px; line-height: 40px; } .nav { background-color: white; user-select: none; width: 1200px; margin: 0 auto; } .nav ul { padding: 15px 0; float: left; } .nav ul:after { clear: both; content: ''; display: block; } .nav ul li { float: left; } .logo { margin-right: 20px; } .ele { margin: 0 20px; } .ele span { display: block; font: 15px/36px '微软雅黑'; border-bottom: 2px solid transparent; cursor: pointer; } .ele span:hover { border-bottom-color: orange; } .ele span.active { color: orange; border-bottom-color: orange; } .right-part { float: right; } .right-part .line { margin: 0 10px; } .right-part span { line-height: 68px; cursor: pointer; } </style>
这篇关于Django项目--路飞学城项目05的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-15SendGrid 的 Go 客户端库怎么实现同时向多个邮箱发送邮件?-icode9专业技术文章分享
- 2024-11-15SendGrid 的 Go 客户端库怎么设置header 和 标签tag 呢?-icode9专业技术文章分享
- 2024-11-12Cargo deny安装指路
- 2024-11-02MongoDB项目实战:从入门到初级应用
- 2024-11-01随时随地一键转录,Google Cloud 新模型 Chirp 2 让语音识别更上一层楼
- 2024-10-25Google Cloud动手实验详解:如何在Cloud Run上开发无服务器应用
- 2024-10-24AI ?先驱齐聚 BAAI 2024,发布大规模语言、多模态、具身、生物计算以及 FlagOpen 2.0 等 AI 模型创新成果。
- 2024-10-20goland工具下,如修改一个项目的标准库SDK的版本-icode9专业技术文章分享
- 2024-10-17Go学习:初学者的简单教程
- 2024-10-17Go学习:新手入门完全指南