Loading

登入&&注册接口

登入模态框组件(伪代码)

  • 组件代码

    <!--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>
    
  • 效果展示

    image

    image

登入/注册模态框

  • 登入模态框组件

    在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>
    
  • 效果展示

    image

多方式登入-账号密码登入

  • 需求分析

    账户名/手机号/邮箱
    需要校验的字段:
        用户名(重新定义, 不走模型表的校验, 自定义校验规则)
        用户密码(继承了内置用户表, 使用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中测试

    访问地址: http://127.0.0.1:8000/user/login/

    • 正常测试
      image
    • 错误测试
      image
  • 前端登入

    • 在登入模态框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,
          })
        }
      },
      

      image

      image

      image

    • 登入之后立刻修改登入状态

      登入|注册 改为--> 用户名|注销

      <!-- 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>
      

      image

多方式登入-手机号登入

  • 判断手机号是否存在

    • 登入视图类
      # 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中测试访问地址
      image
  • 腾讯云短行服务

    https://cloud.tencent.com/product/sms

    • 申请开通短信服务

      image
    • 同意协议, 开始接入

      image
    • 创建签名

      image
    • 等待审核...

      image
    • 创建短信模板

      image
    • 等待审核...

      image
    • 创建应用

      image
    • TX—API帮助文档

      帮助文档中有API的接口文档, 还提供了SDK的文档.
      SDK: 软件开发工具包, 辅助开发某一类软件的相关文档.
      SDK 2.0 地址: https://cloud.tencent.com/document/product/382/11672
      
      image
    • 根据帮助文档配置代码

      image
  • 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

      image

    • 解决方案

      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参数

      image

    • 解决方案

      点我查看参考博客
      结论,,,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

      image

      image

posted @ 2022-12-05 21:23  爱learn  阅读(145)  评论(0编辑  收藏  举报