登入&&注册接口
登入模态框组件(伪代码)
-
组件代码
<!--src/components/LoginModal.vue --> <template> <!--使用模态框 --> <div class="login"> <!--显示一个x 点击x 触发点击事件 关闭模态框--> <span @click="close_modal">X</span> </div> </template> <script> export default { name: "LoginModal", methods: { // 将事件传递给父组件, 父组件中v-if 关闭模态框 close_modal() { // 触发父组件的自定义事件 this.$emit('close') } } } </script> <style scoped> .login { /* 宽高铺满页面 */ width: 100vw; height: 100vw; /* 固定定位 左上角开始 */ position: fixed; left: 0; top: 0; /* 模态框 层级 */ z-index: 666; /* 背景颜色 */ background-color: rgba(0, 255, 0, 0.3); } span { /* 字体颜色 */ font-size: 30px; /* cursor:pointer属性是在计算机中将光标呈现为指示链接的指针(一只手) */ cursor: pointer; } </style>
-
使用组件(简化版)
在Head.vue中导入模态框子组件, 点击登入触发模态框.
<!-- Head.vue 省略代码--> <template> <div class="header"> <!--点击登入 打开模态框--> <span @click="open_modal">登录</span> <!--使用模态框组件 v-if 控制模态框的开启和关闭 接收子组件触发close事件--> <LoginModal v-if="is_show" @close="close_modal"></LoginModal> </div> </template> <script> // 导入模态框 import LoginModal from './LoginModal' export default { name: "Header", data() { return { // 模态框的开关 is_show: false, } }, // 注册组件 components: { LoginModal, }, methods: { // 开启模态框 open_modal() { this.is_show = true }, // 关闭模态框 close_modal(){ this.is_show = false } }, } </script>
-
使用组件(完整版)
点击查看代码
<!-- Head.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> <!--点击登入 打开模态框--> <span @click="open_modal">登录</span> <span class="line">|</span> <span>注册</span> </div> </div> <!--使用模态框组件 v-if 控制模态框的开启和关闭 接收子组件触发close事件--> <LoginModal v-if="is_show" @close="close_modal"></LoginModal> </div> </div> </template> <script> // 导入模态框 import LoginModal from './LoginModal' export default { name: "Header", data() { return { // 当sessionStorage.url_path有值, 就将值赋值给url_path, 否则就将/根路径赋值给url_path url_path: sessionStorage.url_path || '/', // 模态框的开关 is_show: false, } }, // 注册组件 components: { LoginModal, }, methods: { // 路由跳转 goPage(url_path) { // 已经是当前路由就没有必要重新跳转 if (this.url_path !== url_path) { this.$router.push(url_path); } // 更新sessionStorage对的url_path属性值 sessionStorage.url_path = url_path; }, // 开启模态框 open_modal() { this.is_show = true }, // 关闭模态框 close_modal(){ this.is_show = false } }, created() { // $route属性是内置的属性, $route.path获取当前页面的路径, sessionStorage.url_path = this.$route.path; // 将当前路径分别赋值给 Vue对象 与 sessionStorage 对象 的 url_path属性 this.url_path = this.$route.path; } } </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>
-
效果展示
登入/注册模态框
-
登入模态框组件
在src下的components目录下LoginModal.vue
点击查看代码
<template> <div class="login"> <div class="box"> <!-- x 关闭模态框 --> <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">登录</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">登录</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; }, // 手机号验证1开头第二位3-9任意一位, 后9位0-9任意一位 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.is_send = true; }, // 获取验证码 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); } } } </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>
-
注册模态框组件
在src下的components目录下Register.vue
点击查看代码
<template> <div class="register"> <div class="box"> <!-- x 关闭注册模态框 --> <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">注册</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') }, // 手机号验证1开头第二位3-9任意一位, 后9位0-9任意一 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.is_send = true; }, // 获取验证码 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); } } } </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: 20px; 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>
-
使用登入注册组件
Head.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> <span @click="put_login">登录</span> <span class="line">|</span> <span @click="put_register">注册</span> </div> </div> <!--使用模态框组件 v-if 控制模态框的开启和关闭 接收子组件触发close事件--> <LoginModal v-if="is_login" @close="close_login" @go="put_register"/> <Register v-if="is_register" @close="close_register" @go="put_login"/> </div> </div> </template> <script> import LoginModal from './LoginModal' import Register from './Register' export default { name: "Header", data() { return { // 当sessionStorage.url_path有值, 就将值赋值给url_path, 否则就将/根路径赋值给url_path url_path: sessionStorage.url_path || '/', is_login: false, is_register: false, } }, 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; }, close_register() { this.is_register = false; } }, created() { // $route属性是内置的属性, $route.path获取当前页面的路径, sessionStorage.url_path = this.$route.path; // 将当前路径分别赋值给 Vue对象 与 sessionStorage 对象 的 url_path属性 this.url_path = this.$route.path; }, components: { LoginModal, 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>
-
效果展示
多方式登入-账号密码登入
-
需求分析
账户名/手机号/邮箱 需要校验的字段: 用户名(重新定义, 不走模型表的校验, 自定义校验规则) 用户密码(继承了内置用户表, 使用check_password加密在作密码对比) 需要返回: id 用户名 token
-
模型序列化器
在user apps中新建serializer.py 文件
# 导入序列化模型类 from rest_framework import serializers # 导入模型层 from user.models import User # 用户表模型序列化器 class UserModelSerializer(serializers.ModelSerializer): # 定义username字段, 不定义的话会走模型表的字段类型校验 username = serializers.CharField() password = serializers.CharField() # 定义Meta类 class Meta: # 使用的表 model = User # 转换的字段 fields = ['username', 'password', 'id'] # 额外的配置 extra_kwargs = { # 密码 只写 'id': {'read_only': True}, 'password': {'write_only': True}, } # 全局钩子 def validate(self, attrs): # 获取用户对象 user_obj = self._get_user_obj(attrs) # 签发token token = self._get_token(user_obj) # 使用context属性保存token, 用户名 可以在视图类中取出 self._context['token'] = token self._context['username'] = user_obj.username # 全局钩子返回数据 return attrs # 定义检验方法 (在方法中不需要使用对象, 设置为静态方法) @staticmethod def _get_user_obj(attrs): # 导入re 正则模块 import re # 导入异常 from rest_framework.exceptions import ValidationError username = attrs.get('username') password = attrs.get('password') # 判断username的类型 (手机号 / 邮箱 / 用户名, ) # 手机 if re.match('^1[3-9][0-9]{9}$', username): user_obj = User.objects.filter(phone=username).first() # 邮箱 . 除换行符外所有字符, + 一次或多次 elif re.match('^.+@.+$', username): user_obj = User.objects.filter(email=username).first() # 否则就是用户名 else: user_obj = User.objects.filter(username=username).first() # 判读user_obj是否有值, 没有值则说明账户不符合要求 抛出异常 if not user_obj: raise ValidationError('账户输入有误!') # 如果有值, 则检验密码 if not user_obj.check_password(password): raise ValidationError('密码输入有误!') # 密码正确返回用户对象 return user_obj # 签发token @staticmethod def _get_token(user_obj): # pip install djangorestframework-jwt 安装一下 # 导入 djangorestframework-jwt 的内置token生成模块 from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler # 返回token payload = jwt_payload_handler(user_obj) # 通过对象获取payload token = jwt_encode_handler(payload) # payload获得token return token
-
配置JWT过期时间
在dev.py配置文件中配置token的过期时间(一般设置为7天)
JWT_AUTH = { # 过期时间配置 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7) # 七天过期 }
-
视图类
apps/user的视图层中编辑返回的信息
from rest_framework.viewsets import ViewSet # 导入自定义响应 from utils.response import ResponseDataFormat # 导入 action装饰器 from rest_framework.decorators import action # 登入视图类 class LoginView(ViewSet): @action(methods=['POST'], detail=False) def login(self, request, *args, **kwargs): # 导入模型序列化器类 from user.serializer import UserModelSerializer ser = UserModelSerializer(data=request.data) # 判断检验的结果 # 校验不通过直接返回 ser.is_valid(raise_exception=True) # 如果校验成功将用户信息和 token返回 token = ser.context['token'] username = ser.context['username'] return ResponseDataFormat(username=username, token=token)
-
路由层
-
项目名目录下的主路由
# 项目名文件夹下的urls.py from django.urls import path, re_path, include # 导入serve视图类 from django.views.static import serve # 导入内置配置文件, 会自动到dev.py配置文件中去查询 from django.conf import settings # xversion模块自动注册需要版本控制的 Model from xadmin.plugins import xversion # xadmin的依赖 import xadmin # 注册模型 xversion.register_models() # 自动获取 xadmin.autodiscover() urlpatterns = [ # 修改为访问的页面为xadmin的页面 re_path('^xadmin/', xadmin.site.urls), re_path('^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}), # 路由分发到user app path('home/', include('home.urls')), path('user/', include('user.urls')), ]
-
apps/user下的子路由
视图类继承了ViewSet, 使用routers模块自动生成路由.
from django.urls import path, re_path, include # 导入视图类 from user import views # 导入自动生成路由模块 from rest_framework.routers import SimpleRouter # 实例化得到对象 router = SimpleRouter() # 生成路由 (设置为空, app名/请求方法名) router.register('', views.LoginView, 'login') urlpatterns = [ 登入获取 token path('', include(router.urls)), ]
# 生成路由 (设置为空, app名/请求方法名, 生成的路由:http://127.0.0.1:8000/user/login/) router.register('', views.LoginView, 'login') # 将路由添加到路由类别的方式 path('', include(router.urls)),
-
-
Postman中测试
- 正常测试
- 错误测试
- 正常测试
-
前端登入
-
在登入模态框LoginModal.vue中绑定登入事件
<!--在密码登入方式中为等入按键绑定点击事件--> <el-button type="primary" @click="password_login">登录</el-button>
-
点击事件中使用axios发送post请求给后端
登入成功之后把用户名和token保存到cookies中 存: this.$cookies.set('键', '值', '过期时间') 过期时间单位为秒, 开业使用'7d'表示七天. 取: this.$cookies.get('键',) 移除: this.$cookies.remove('键')
// 密码登入 password_login() { // 输入不能为空 console.log(this.username && this.password) if (this.username && this.password) { // 通过axios发送请求 this.$axios.post(this.$settings.base_url + '/user/login/', { username: this.username, password: this.password }).then(response => { if (response.data.code === 200) { // 将用户名和token保存到cookies中 this.$cookies.set('username', response.data.username) this.$cookies.set('token', response.data.token) // 关闭模态框, 子传父触发 this.$emit('close') // 修改登入状态 this.$emit('login_status') }else{ this.$message({ message: response.data.data[0], type: 'warning', duration: 2000, }) } }) } else { this.$message({ message: '你有信息没有填写', type: 'warning', // 显示时间 duration: 2000, }) } },
-
登入之后立刻修改登入状态
登入|注册 改为--> 用户名|注销
<!-- LoginModal.vue --> // 修改登入状态 this.$emit('login_status')
<!-- 右侧导航条Head.vue --> <div class="right-part"> <div v-if="!username"> <!--点击登入 打开模态框--> <span @click="put_login">登录</span> <span class="line">|</span> <span @click="put_register">注册</span> </div> <div v-if="username"> <!--点击登入 打开模态框--> <span>{{username}}</span> <span class="line">|</span> <span>注销</span> </div> </div> <LoginModal v-if="is_login" @close="close_login" @go="put_register" @login_status="loginStatus"/> <script> data() { return { ... username: '', token: '', }, methods: { ... // 从cookies中获取用户名, 刷新左侧导航条 loginStatus(){ this.username = this.$cookies.get('username') this.token = this.$cookies.get('token') } } </script>
-
以后在打开页面, 先去cookies中获用户的信息, 如果有数据, 直接为登入状态.
在生命周期钩子函数中created中获取.
created() { ... // 从cookies中获取数据 this.username = this.$cookies.get('username') this.token = this.$cookies.get('token') },
-
注销登入
将cookies中的用户数据清除, 再该变username变量的值, 自动刷新页面.
<span @click="logout">注销</span> <script> methods: { // 注销 logout() { this.$cookies.remove('username') this.$cookies.remove('token') this.username = '' this.token = '' } } </script>
-
多方式登入-手机号登入
-
判断手机号是否存在
- 登入视图类
# 2. 登入视图类 class LoginView(ViewSet): # 判断手机号是否存在 @action(methods=['GET'], detail=False) def inquire_phone(self, request): # 提交的是get请求 phone = request.query_params.get('phone') # 判断手机号码是否正确, 提示手机号码不合法 is_exist = {'is_exist': False} if not re.match('^1[3-9][0-9]{9}$', phone): return ResponseDataFormat(code=400, msg='手机号码不合法!', data=is_exist) # 手机号码正确去数据库中查询 user_obj = models.User.objects.filter(phone=phone).first() # 查询用户对象不存在 if not user_obj: return ResponseDataFormat(code=400, msg='手机号码没有注册!', data=is_exist) is_exist = {'is_exist': True} return ResponseDataFormat(data=is_exist)
- Psetman中测试访问地址
- 登入视图类
-
腾讯云短行服务
-
申请开通短信服务
-
同意协议, 开始接入
-
创建签名
-
等待审核...
-
创建短信模板
-
等待审核...
-
创建应用
-
TX—API帮助文档
帮助文档中有API的接口文档, 还提供了SDK的文档. SDK: 软件开发工具包, 辅助开发某一类软件的相关文档. SDK 2.0 地址: https://cloud.tencent.com/document/product/382/11672
-
根据帮助文档配置代码
-
-
SDK2.0错误示范
# 2. 指定模板 ID 单发短信 from qcloudsms_py import SmsSingleSender from random import randint from utils.conf_setting import get_conf_setting from utils.logger import log # 生成四位数的随机验证码(纯数字) def get_code(num): code = '' for i in range(num): code += str(randint(0, 9)) return code # 发送短信模块 def send_mes(phone, code): # 短信应用 SDK AppID SDK AppID 以1400开头 appid = get_conf_setting("MSG_API", "appid") # 短信应用 SDK AppKey appkey = get_conf_setting("MSG_API", "appkey") # 短信模板ID,需要在短信控制台中申请,NOTE: 这里的模板 ID`7839` 只是示例,真实的模板 ID 需要在短信控制台中申请 template_id = get_conf_setting("MSG_API", "template_id") # 签名,NOTE: 签名参数使用的是`签名内容`,而不是`签名ID`。这里的签名"腾讯云"只是示例,真实的签名需要在短信控制台中申请 sms_sign = get_conf_setting("MSG_API", "sms_sign") ssender = SmsSingleSender(appid, appkey) params = [code, 5] # 当模板没有参数时,`params = []` try: result = ssender.send_with_param(86, phone, template_id, params, sign=sms_sign, extend="", ext="") # 短信发送成功之后result的值是0 if result.get('result') != 0: # 发送成功返回False return False # 发送不成功返回True return True except Exception as err: log.error(f'当前手机号{phone}, 错误提示: {err}.') if __name__ == '__main__': send_mes("182xxxxxxxxx", "1234")
-
报错一:[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed
-
解决方案
Python 从 2.7.9版本开始,就默认开启了服务器证书验证功能,如果证书校验不通过,则拒绝后续操作;这样可以防止中间人攻击,并使客户端确保服务器确实是它声称的身份。如果是自签名证书,由于一般系统的CA证书中不存在在自签名的CA证书内容,从而导致证书验证不通过。
# 在文件开始部分,加入如下代码: import ssl ssl._create_default_https_context = ssl._create_unverified_context
-
报错二:init() got an unexpected keyword argument 'encoding'.
这是因为qcloudsms_py中的某个地方使用json.loads时,传入了encoding参数
-
解决方案
点我查看参考博客
结论,,,python3.7及以上的版本就用sdk3.0吧
-
-
SDK3.0示范(短信服务封装)
from tencentcloud.common import credential # 导入对应产品模块的client models。 from tencentcloud.sms.v20210111 import sms_client, models # 导入可选配置类 from tencentcloud.common.profile.client_profile import ClientProfile from tencentcloud.common.profile.http_profile import HttpProfile from random import randint from utils.conf_setting import get_conf_setting from utils.logger import log # 生成四位数的随机验证码(纯数字) def get_code(num): code = '' for i in range(num): code += str(randint(0, 9)) return code # 发送短信模块 def send_msg(phone, code): try: # 必要步骤: # SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi cred = credential.Credential(get_conf_setting("MSG_API", "secretId"), get_conf_setting("MSG_API", "secretKey")) # 实例化一个http选项,可选的,没有特殊需求可以跳过。 httpProfile = HttpProfile() # 如果需要指定proxy访问接口,可以按照如下方式初始化hp(无需要直接忽略) # httpProfile = HttpProfile(proxy="http://用户名:密码@代理IP:代理端口") httpProfile.reqMethod = "POST" # post请求(默认为post请求) httpProfile.reqTimeout = 30 # 请求超时时间,单位为秒(默认60秒) httpProfile.endpoint = "sms.tencentcloudapi.com" # 指定接入地域域名(默认就近接入) # 非必要步骤: # 实例化一个客户端配置对象,可以指定超时时间等配置 clientProfile = ClientProfile() clientProfile.signMethod = "TC3-HMAC-SHA256" # 指定签名算法 clientProfile.language = "en-US" clientProfile.httpProfile = httpProfile # 实例化要请求产品(以sms为例)的client对象 # 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 client = sms_client.SmsClient(cred, "ap-nanjing", clientProfile) # 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数 # 你可以直接查询SDK源码确定SendSmsRequest有哪些属性可以设置 # 属性可能是基本类型,也可能引用了另一个数据结构 # 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 req = models.SendSmsRequest() # 基本类型的设置: # 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 # 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看 req.SmsSdkAppId = get_conf_setting("MSG_API", "appid") # 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 # 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看 req.SignName = get_conf_setting("MSG_API", "sms_sign") # 模板 ID: 必须填写已审核通过的模板 ID # 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看 req.TemplateId = get_conf_setting("MSG_API", "template_id") req.TemplateParamSet = [code, "5"] req.PhoneNumberSet = [f"+86{phone}"] # 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息,server 会原样返回 req.SessionContext = "" # 短信码号扩展号(无需要可忽略): 默认未开通,如需开通请联系 [腾讯云短信小助手] req.ExtendCode = "" # 国际/港澳台短信 senderid(无需要可忽略): 国内短信填空,默认未开通,如需开通请联系 [腾讯云短信小助手] req.SenderId = "" resp = client.SendSms(req) code = resp.SendStatusSet[0].Code log.info(resp.to_json_string(indent=2)) if code != "Ok": # 发送成功返回False return False # 发送不成功返回True return True except Exception as err: log.error(f'当前手机号{phone}, 错误提示: {err}.')
-
短信服务接口
Django的缓存写入数据:
cache.set('键', 值, 过期时间/秒)
-
定义Django缓存手机验证码的键(前缀)
完成格式: 前缀_手机号 settings.PHONE_CACHE_KEY % phone
-
短信服务接口发送成功, 放回发送成功的提示信息, 并将验证码写入到Django缓存中
-
短信接口
# 3. 短信接口 class SMSInterface(ViewSet): # 短信服务接口 @action(methods=['GET'], detail=False) def sms_interface(self, request): # 先判断手机号是否正确, 不用判断是否存在, 在注册的时候也要使用 phone = request.query_params.get('phone') is_exist = {'is_send': False} if not re.match('^1[3-9][0-9]{9}$', phone): return ResponseDataFormat(code=400, msg='手机号码不合法!', data=is_exist) # 生成验证码(验证码的位数) code = get_code(4) # 发送通过腾讯发送短信模块发送短信, 接收一个布尔值 is_send = send_mes(phone, code) if not is_send: return ResponseDataFormat(code=400, msg='验证码发送不成功!', data=is_exist) # 将验证码写入到django缓存中 cache.set('键', 值, 过期时间/秒) cache.set(settings.PHONE_CACHE_KEY % phone, code, 300) is_exist = {'is_send': True} return ResponseDataFormat(code=200, msg='验证码发送成功!', data=is_exist)
-
短信接口频率限制, 在apps/user/下新建throttling.py 文件
# 导入内置的权限限制模块, 重新方法即可, 以返回的值作为限制的依据 from rest_framework.throttling import SimpleRateThrottle # 发送短信限次 class SMSThrottling(SimpleRateThrottle): scope = "sms" def get_cache_key(self, request, view): phone = request.query_params.get('phone') # 一般不会直接使用手机号, 作为依据, 而是拼接一个前缀 # SimpleRateThrottle对象有一个cache_format属性 # cache_format = 'throttle_%(scope)s_%(ident)s' # throttle_sms_手机号 return self.cache_format % {'scope': 'sms', 'ident': phone}
-
短信接口视图类中添加限次
# 3. 短信接口视图类中添加限次 # 导入频率限制 from .throttling import SMSThrottling class SMSInterface(ViewSet): # 频率限制 throttle_classes = [SMSThrottling, ] ....
# 在配置文件dev.py中添加配置短信频率限制 # REST_FRAMEWORK的配置字段 REST_FRAMEWORK = { ... # 短信接口一分值只能发一条短信 'DEFAULT_THROTTLE_RATES': {'sms': '1/m'} # key对应类中的scope属性的值 }
-
路由配置
只需要添加 router.register('', views.SMSInterface, 'send') 即可
from django.urls import path, re_path, include # 导入视图类 from user import views # 导入自动生成路由模块 from rest_framework.routers import SimpleRouter # 实例化得到对象 router = SimpleRouter() # 生成路由 (设置为空, app名/请求方法名) router.register('', views.LoginView, 'login') router.register('', views.SMSInterface, 'send') urlpatterns = [ # 1. 异常&日志测试路由 re_path('exception_log/', views.ExceptionLog.as_view()), # 2. 登入获取 token / 发送短信接口 path('', include(router.urls)), ]
-
接口测试
http://127.0.0.1:8000/user/sms_interface/?phone=182xxxxxxxxxx
-