3.学城项目 登入&&注册接口

0. 删除默认的导航条

3.学城项目 登入&&注册接口_第1张图片

2022-05-18_00817

删除后

3.学城项目 登入&&注册接口_第2张图片

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

看模态框的效果
1.1 组件代码
在src下的components目录下创建一个模态框子组件.

<template>
  
  <div class="login">
    
    <span @click="close_modal">Xspan>
  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>
1.2 使用组件(省略代码)
在Head.vue中导入模态框子组件, 点击登入触发模态框.

<template>
  <div class="header">

      
      <span @click="open_modal">登录span>

      
      <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>
1.3 使用组件(完整代码)

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

3.学城项目 登入&&注册接口_第3张图片

3.学城项目 登入&&注册接口_第4张图片

2. 登入/注册模态框

2.1 登入模态框组件
在src下的components目录下LoginModal.vue
<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">登录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>
2.2 注册模态框组件
在src下的components目录下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">注册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: 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>
2.3 使用登入注册组件
<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>

          <span>注册span>
        div>
      div>
      
      <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 {
      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() {
    sessionStorage.url_path = this.$route.path;
    this.url_path = this.$route.path;
  },
  components: {
    LoginModal,
    Register
  }
}
script>
2.4 效果展示

3.学城项目 登入&&注册接口_第5张图片

3. 多方式登入-密码登入

账户名/手机号/邮箱
需要校验的字段:
用户名(重新定义, 不走模型表的校验, 自定义校验规则)
用户密码(继承了内置用户表, 使用check_password加密在作密码对比)
需要返回:
id 
用户名
token

image-20220519235806689

手机号码手动输入
在注册账户的时候最好避免手机号就账户名, 一些网站检验输入的账户名, 必有有字母!
3.1 模型序列化器
在user apps中新建serializer.py 文件
# 导入序列化模型类
from rest_framework import serializers

# 导入模型层
from user import models


# 用户表模型序列化器
class UserModelSerializer(serializers.ModelSerializer):
    # 定义username字段, 不定义的话会走模型表的字段类型校验
    username = serializers.CharField()

    # 定义Meta类
    class Meta:
        # 使用的表
        model = models.User
        # 转换的字段
        fields = ['username', 'password', ]

        # 额外的配置
        extra_kwargs = {
            # 密码 只写
            '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 = models.User.objects.filter(phone=username).first()

        # 邮箱   . 除换行符外所有字符, + 一次或多次
        elif re.match('^.+@.+$', username):
            user_obj = models.User.objects.filter(email=username).first()

        # 否则就是用户名
        else:
            user_obj = models.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
在dev.py配置文件中配置token的过期时间(一般设置为7)
import datetime

JWT_AUTH = {
    # 过期时间配置
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7)}  # 七天过期
3.2 视图类
apps/user的视图层中编辑返回的信息
# 导入APIView
from rest_framework.views import APIView
# 导入 ViewSet
from rest_framework.viewsets import ViewSet

# 导入自定义响应
from luffy.utils.api_response import ResponseDataFormat

# 导入 action装饰器
from rest_framework.decorators import action


# 1. 异常&日志测试
...

# 2. 登入视图类

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)

3.3 路由层
项目名目录下的主路由
# 项目名文件夹下的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.*)', 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 = [
    # 1. 异常&日志测试路由
    re_path('exception_log/', views.ExceptionLog.as_view()),
    # 2. 登入获取 token
    path('', include(router.urls)),

]

# 生成路由 (设置为空, app名/请求方法名,   生成的路由:http://127.0.0.1:8000/user/login/)
router.register('', views.LoginView, 'login')
# 将路由添加到路由类别的方式
path('', include(router.urls)),
3.4 Postman中测试
* 1. 正常测试 
     访问地址: http://127.0.0.1:8000/user/login/
     输入一个存在的账户(可以是 手机号/邮箱/用户名)

3.学城项目 登入&&注册接口_第6张图片

* 2. 错误测试
     访问地址: http://127.0.0.1:8000/user/login/
     输入一个不存在的账户

3.学城项目 登入&&注册接口_第7张图片

3.5 前端登入
* 1. 在登入模态框LoginModal.vue中绑定登入事件

<el-button type="primary" @click="password_login">登录el-button>
* 2. 点击事件中使用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,
        })
      }
    },

3.学城项目 登入&&注册接口_第8张图片

3.学城项目 登入&&注册接口_第9张图片

3.学城项目 登入&&注册接口_第10张图片

* 3. 登入之后立刻修改登入状态
     登入|注册  改为-->  用户名|注销
     通过子传父将触发父组件, 父组件去cookies中获取用户名, 修改值之后动态刷新页面.
// 修改登入状态
this.$emit('login_status')
在Head.vue组件中定义一个username变量, 通过判断username的是否有值切换右侧导航条的显示
username通过登入模态框子组件触发.

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

<script>
	data() {
    return {
         ...
      username: '',
      token: '',
    },
    methods: {
        ...
      // 从cookies中获取用户名, 刷新左侧导航条
      loginStatus(){
      this.username = this.$cookies.get('username')
      this.token = this.$cookies.get('token')
    }
}
script>

image-20220521033459059

* 4. 以后在打开页面, 先去cookies中获用户的信息, 如果有数据, 直接为登入状态.
     在生命周期钩子函数中created中获取.
created() {
    ...
    // 从cookies中获取数据
    this.username = this.$cookies.get('username')
    this.token = this.$cookies.get('token')
},
* 5. 注销登入
     为注销绑定点击事件
     将cookies中的用户数据清除, 再该变username变量的值, 自动刷新页面.
<span @click="logout">注销span>
<script>
   methods: {
       // 注销
       logout() {
           this.$cookies.remove('username')
           this.$cookies.remove('token')
           this.username = ''
           this.token = ''
    }
}
script>

4. 多方式登入-手机号登入

4.1 判断手机号是否存在
* 1. 判断手机号是否在数据库中
# 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)
* 2. Psetman中测试访问地址
     http://127.0.0.1:8000/user/inquire_phone/?phone=18123456789

3.学城项目 登入&&注册接口_第11张图片

4.2 腾讯云短行服务
腾讯云短行服务地址: https://cloud.tencent.com/search/%E7%9F%AD%E4%BF%A1/1_1
1. 申请开通短信服务
* 1. 免费使用

3.学城项目 登入&&注册接口_第12张图片

* 2. 同意协议, 开始接入

3.学城项目 登入&&注册接口_第13张图片

3.学城项目 登入&&注册接口_第14张图片

2. 创建签名
* 1. 创建签名

3.学城项目 登入&&注册接口_第15张图片

3.学城项目 登入&&注册接口_第16张图片

* 2. 签名选择微信公众号(不需要营业执照)

3.学城项目 登入&&注册接口_第17张图片

* 3. 申请一个公众号, 复制公众号设置页面

3.学城项目 登入&&注册接口_第18张图片

3.学城项目 登入&&注册接口_第19张图片

* 4. 等待审核...

3.学城项目 登入&&注册接口_第20张图片

* 5. 复制签名内容: Python梦想空间
3. 创建短信模板
* 1. 创建短信模板

3.学城项目 登入&&注册接口_第21张图片

3.学城项目 登入&&注册接口_第22张图片

* 2. 等待审核...

3.学城项目 登入&&注册接口_第23张图片

* 3. 复制模板id: 1410883
4. 创建应用
* 1. 创建短信模板自动创建一个应用.
* 2. 应用列表中复制 SDK AppID  App Key
     SDK AppID: 14006828197:
     App Key: 87971852ee0862096105de36b05cb1d1d2d3

3.学城项目 登入&&注册接口_第24张图片

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

3.学城项目 登入&&注册接口_第25张图片

4.3 示例代码
* 1. 安装qcloudsms_py模块
   pip install qcloudsms_py
* 2. 示例代码
     在script下新建test_send_mes.py
# 1. 准备必要参数

# 短信应用 SDK AppID
appid = 1400682819  # SDK AppID 以1400开头
# 短信应用 SDK AppKey
appkey = "87971852ee0862096105de36b05cb1d1"
# 需要发送短信的手机号码
phone_numbers = ["自己的手机号码", ]
# 短信模板ID,需要在短信控制台中申请
template_id = 1410883  # NOTE: 这里的模板 ID`7839` 只是示例,真实的模板 ID 需要在短信控制台中申请
# 签名
sms_sign = "Python梦想空间"  # NOTE: 签名参数使用的是`签名内容`,而不是`签名ID`。这里的签名"腾讯云"只是示例,真实的签名需要在短信控制台中申请

# 2. 指定模板 ID 单发短信
from qcloudsms_py import SmsSingleSender
from qcloudsms_py.httpclient import HTTPError
ssender = SmsSingleSender(appid, appkey)
params = ["1234", ]  # 当模板没有参数时,`params = []`
try:
  result = ssender.send_with_param(86, phone_numbers[0],
      template_id, params, sign=sms_sign, extend="", ext="") 
except HTTPError as e:
  print(e)
print(result)
验证码为:{1},您正在登录,若非本人操作,请勿泄露。
params = ["1234"] 中的列表元素会依次替换 模板内容中的{}.
* 3. 运行程序测试
     发送成功之后会提示:
     {'result': 0, 'errmsg': 'OK', 'ext': '', 'sid': '2640:241303963116531302631655411', 'fee': 1, 'isocode': 'CN'}

image-20220521185719502

* 4. 手机收到短信(有时很墨迹)

3.学城项目 登入&&注册接口_第26张图片

4.4 短信服务封装
* 1. 在项目名目录/lib下新建tx_send_mes包
* 2. 在项目名目录/lib/tx_send_mes包下新建 settings.py文件, 将必要参数写在这个文件中.
# 1. 准备必要参数

# 短信应用 SDK AppID
appid = 1400682819  # SDK AppID 以1400开头
# 短信应用 SDK AppKey
appkey = "87971852ee0862096105de36b05cb1d1"
# 需要发送短信的手机号码
phone_numbers = ["自己的手机号码", ]
# 短信模板ID,需要在短信控制台中申请
template_id = 1410883  # NOTE: 这里的模板 ID`7839` 只是示例,真实的模板 ID 需要在短信控制台中申请
# 签名
sms_sign = "Python梦想空间"  # NOTE: 签名参数使用的是`签名内容`,而不是`签名ID`。这里的签名"腾讯云"只是示例,真实的签名需要在短信控制台中申请
* 3. 在项目名目录/lib/tx_send_mes包下新建 send_mes.py文件, 将必要参数写在这个文件中.
# 2. 指定模板 ID 单发短信
from qcloudsms_py import SmsSingleSender
from qcloudsms_py.httpclient import HTTPError
from .settings import *
from random import randint
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):
    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 HTTPError as e:
        log.error(f'当前手机号{phone}, 错误提示: {e}.')

* 4.  在项目名目录/lib/tx_send_mes包下 __init__.py文件中导入模块
from .send_mes import get_code
from .send_mes import send_mes

4.5 短信服务接口
Django的缓存写入数据
cache.set('键', , 过期时间/)
* 1. 在定义配置文件settings/const.py 中定义Django缓存手机验证码的键(前缀)
     完成格式: 前缀_手机号  settings.PHONE_CACHE_KEY % phone
# 缓存手机验证键的前缀
PHONE_CACHE_KEY = 'sms_cache_%s'
* 2. 短信服务接口发送成功, 放回发送成功的提示信息, 并将验证码写入到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)
    
* 3. 短信接口频率限制, 在apps/user/下新建throttling.py 文件
# 导入内置的权限限制模块, 重新方法即可, 以返回的值作为限制的依据
from rest_framework.throttling import SimpleRateThrottle


# 发送短信限次
class SMSThrottling(SimpleRateThrottle):
    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属性的值
}
* 4. 路由配置
     只需要添加 router.register('', views.LoginView, '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)),

]
* 5. 接口测试
     http://127.0.0.1:8000/user/sms_interface/?phone=18177

3.学城项目 登入&&注册接口_第27张图片

3.学城项目 登入&&注册接口_第28张图片

4.6 短信登入接口
使用Navicat将手机改为自己的手机号码
1. 定义模型序列化器
在apps/user/serializer.py 中写模型序列化类
# 验证码登入校验
class CodeModelSerializer(serializers.ModelSerializer):
    # 模型表中没有这个字段, 需要自己定义
    code = serializers.CharField()

    # 定义code字段
    class Meta:
        # 定义使用的表模型
        model = models.User
        # 需要校验的字段
        fields = ['phone', 'code']

    # 全局钩子
    def validate(self, attrs):
        # 获取提交的数据
        phone = attrs.get('phone')
        code = attrs.get('code')

        user_obj = self._get_user_obj(phone, code)
        token = self._get_token(user_obj)
        self._context['token'] = token
        self._context['username'] = user_obj.username

        return attrs

    @staticmethod
    def _get_user_obj(phone, code):
        # 判断手机格式
        if not re.match('^1[3-9][0-9]{9}$', phone):
            raise ValidationError('手机号码格式不正确!')

        # 验证码校验
        load_code = cache.get(settings.PHONE_CACHE_KEY % phone)

        if load_code != code:
            raise ValidationError('验证码不正确!')

        # 通过手机号获取用户对象
        user_obj = models.User.objects.filter(phone=phone).first()

        # 判断用户对象是否存在
        if not user_obj:
            raise ValidationError('该手机号没有注册!')

        # 用户存在返回用户对象
        return user_obj

    # 签发token
    @staticmethod
    def _get_token(user_obj):

        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

2. 短信登录接口
# 2. 登入视图类

class LoginView(ViewSet):
    ...
    # 短信登入
    @action(methods=['POST'], detail=False)
    def code_login(self, request):
        # 导入模型序列化器类
        from user.serializer import CodeModelSerializer
        # 校验提交的数据
        ser = CodeModelSerializer(data=request.data)

        # 判断校验的结果
        # 判断检验的结果
        # 校验不通过直接返回
        ser.is_valid(raise_exception=True)
        # 如果校验成功将用户信息和 token返回
        token = ser.context['token']
        username = ser.context['username']

        return ResponseDataFormat(username=username, token=token)
3. 接口测试
使用Postman测试, 
获取code: http://127.0.0.1:8000/user/sms_interface/?phone=18177 获取验证码
登入测试: http://127.0.0.1:8000/user/code_login/ 在body体中填写表单
* 1. 输入错误的验证码(验证码过期)

3.学城项目 登入&&注册接口_第29张图片

* 2. 输入错误的手机号

3.学城项目 登入&&注册接口_第30张图片

* 3. 输入正确的手机号与验证码

3.学城项目 登入&&注册接口_第31张图片

4.7 前端登入
1.校验手机号
输入手机号绑定失去焦点事件, 前端校验手机号的格式是否正确.
前端校验成功之后, 再将手机提交到后端, 再次校验手机号码.
>
<el-input
	placeholder="手机号"
	...
    @blur="check_mobile">
el-input>
// 进入手机信息框, 失去焦点时触发
check_mobile() {
    // 手机号为空直接返回
    if (!this.mobile) return;
    // 如果写入手机号, 检验手机号 1开头第二位3-9任意一位, 后9位0-9任意一位
    if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {
        // 校验不成功弹框提示错误
        this.$message({
            message: '手机号有误',
            type: 'warning',
            // 显示时间
            duration: 2000,
            // 弹窗关闭时触发, 将手机号清空
            onClose: () => {
                this.mobile = '';
            }
        });
        // 前端手机号码直接返回
        return false
    }

    // 前端校验之后通过之后, 后端对手机号码进行校验 https:127.0.0.1:8000/user/inquire_phone/?phone=xxxx
    // this.$axios.get(this.$settings.base_url + '/user/inquire_phone/?phone=', + this.mobile)
    this.$axios.get(this.$settings.base_url + '/user/inquire_phone/', {params: {phone: this.mobile}}).then(response => {
        // 判断返回值
        if (response.data.data.is_exist) {
            // 后端手机号码校验成功之后可以点击发送验证码
            this.is_send = true;
        } else {
            // 错误提示
            this.$message({
                message: response.data.msg,
                type: 'warning',
                duration: 2000,
            })
        }
    })
},
url路由拼接成
get(this.$settings.base_url + '/user/inquire_phone/?phone=', + this.mobile)

(this.$settings.base_url + '/user/inquire_phone/', {params: {phone: this.mobile}})
使用params参数
两种方式都可以拼接路由: https:127.0.0.1:8000/user/inquire_phone/?xx=xx
2. 发送验证码
限制一分钟只能发送一条短信.
<span class="sms" @click="send_sms">{{ sms_interval }}span>
// 获取验证码
send_sms() {
    // 当is_send的值为空时, 点击发送验证码无效
    if (!this.is_send) return;
    // 成功点击发送验证码之后不需要在点(接口幂等性)
    this.is_send = false;

    // 发送axios请求
    this.$axios.get(this.$settings.base_url + '/user/sms_interface/', {params: {phone: this.mobile}}).then(response => {
        // 发送成功之后弹框提示
        if (response.data.data.is_send) {
            this.$message({
                message: response.data.msg,
                type: 'success',
                duration: 2000,
            })
        }
    })


    // 定义一个频率限制时间
    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);
},
3. 验证码登入
为登入按键绑定请求, 登入成功之后通过父传值子修改登入状态. 在将用户名和token写入cookies中.
<el-button type="primary" @click="code_login">登录el-button>
// 验证码登入
code_login() {
    // 手机号和验证都没写弹窗提示
    if (!(this.mobile && this.sms)) {
        this.$message({
            message: '手机号或验证码没有输入!',
            type: 'warning',
            duration: 2000,
        })
    }
    this.$axios.post(this.$settings.base_url + '/user/code_login/', {phone: this.mobile, code: this.sms}).then(
        response => {
            // 判断返回信息
            console.log(response.data)
            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')
            }
        }
    )
}
4. 测试
手机号不正确, 将手机号码清除.

登入验证(type=password 隐藏一下手机号)

4.8 前后端提交到远端

在Terminai中输入git命令
1. 前端
# 添加到暂存区
PS D:\vue\luffy_vue> git add .
# 提交到版本库
PS D:\vue\luffy_vue> git commit -m '完成登入'
# 下拉
PS F:\synchro\Project\luffy> git pull origin dev                        
# 上传到远端仓库
PS F:\synchro\Project\luffy> git push origin dev
2. 后端
# 添加到暂存区
PS F:\synchro\Project\luffy> git add .    
# 提交到版本库
PS F:\synchro\Project\luffy> git commit -m '完成手机登入,验证码登入功能'
# 下拉
PS F:\synchro\Project\luffy> git pull origin dev                        
# 上传到远端仓库
PS F:\synchro\Project\luffy> git push origin dev

5. 注册接口

5.1 注册序列化器
# 注册序列化类
class ResponseModelSerializer(serializers.ModelSerializer):
    # 定义code字段, 字段只读, 不然在使用res.data会报错
    code = serializers.CharField(write_only=True)

    class Meta:
        model = models.User
        fields = ['phone', 'password', 'code']
        extra_kwargs = {
            # 密码最少6位最长18位
            'password': {'min_length': 6, 'max_length': 18, 'write_only': True},
        }

    def validate(self, attrs):
        phone = attrs.get('phone')
        code = attrs.get('code')
        self._is_register(phone, code)
        # 将手机号作为用户名, 登入的时候被手机号码正则匹配到, 也能登入
        attrs['username'] = phone  # 往校验结果字段中添加数据
        return attrs

    # 先校验手机号与验证码在校验用户是否存在
    @staticmethod
    def _is_register(phone, code):
        # 判断手机号格式是否正确
        if not re.match('^1[3-9][0-9]{9}$', phone):
            raise ValidationError('手机号码格式不正确!')

        # 校验验证码
        load_code = cache.get(settings.PHONE_CACHE_KEY % phone)

        if load_code != code:
            raise ValidationError('验证码不正确!')

        # 方式1 :数据库中手机字段没有设置唯一, 判断手机号是否被注册, 就直接将用户数据写入, 写入不成功就说明被注册了
        # 数据库中没有将手机字段设置唯一, 使用手机号去查用户, 用户存在则说明注册了.
        user_obj = models.User.objects.filter(phone=phone)
        if user_obj:
            raise ValidationError('该手机号以注册!')

    def create(self, validated_data):
        # 将code从字典中移除
        validated_data.pop('code')
        # 使用内置的创建用户方法, 密码是密文的
        user_obj = models.User.objects.create_user(**validated_data)
        return user_obj

5.2 注册视图类
# 4. 手机号注册
class Register(ViewSet):
    @action(methods=['POST', ], detail=False)
    def phone_register(self, request):
        # 导入模型序列化器类
        from user.serializer import ResponseModelSerializer
        ser = ResponseModelSerializer(data=request.data)

        # 判断校验结果
        ser.is_valid(raise_exception=True)

        # 将数据保存, 自动触发create()方法
        user_obj = ser.save()
        # 注册成功返回用户名
        return ResponseDataFormat(msg='注册成功', username=user_obj.username)
5.3 注册路由
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')
router.register('', views.Register, 'register')

urlpatterns = [
    # 1. 异常&日志测试路由
    re_path('exception_log/', views.ExceptionLog.as_view()),
    # 2. 登入获取 token / 发送短信接口 / 手机号注册
    path('', include(router.urls)),

]
5.4 前端注册页面
1. 校验手机号
输入手机号绑定失去焦点事件, 前端校验手机号的格式是否正确.
前端校验成功之后, 再将手机提交到后端, 再次校验手机号码.

<el-input
    placeholder="手机号"
	...
    @blur="check_mobile">
el-input>
// 进入手机信息框, 失去焦点时触发
check_mobile() {
    // 手机号为空直接返回
    if (!this.mobile) return;
    // 如果写入手机号, 检验手机号 1开头第二位3-9任意一位, 后9位0-9任意一位
    if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {
        // 校验不成功弹框提示错误
        this.$message({
            message: '手机号有误',
            type: 'warning',
            // 显示时间
            duration: 2000,
            // 弹窗关闭时触发, 将手机号清空
            onClose: () => {
                this.mobile = '';
            }
        });
        // 前端手机号码直接返回
        return false
    }

    // 前端校验之后通过之后, 后端对手机号码进行校验 https:127.0.0.1:8000/user/inquire_phone/?phone=xxxx
    // this.$axios.get(this.$settings.base_url + '/user/inquire_phone/?phone=', + this.mobile)
    this.$axios.get(this.$settings.base_url + '/user/inquire_phone/', {params: {phone: this.mobile}}).then(response => {
        // 判断返回值, 如果对象存在则说明用户已经被注册了, 直接跳转到登入模态框
        if (response.data.data.is_exist) {
            this.$message({
                message: '此号码已经注册可以登入.',
                type: 'success',
                duration: 2000,
            })
            this.go_login()
        } else {
            // 验证发可以点击发送
            this.is_send = true;
            this.$message({
                message: '此号码可以注册.',
                type: 'success',
                duration: 2000,
            })
        }
    })
},
2. 发送验证码
只要填写了手机号, 手机号码没有问题就点击获取验证码.
密码在点击注册时校验, 必填的.
<span class="sms" @click="send_sms">{{ sms_interval }}span>
// 获取验证码
send_sms() {
    // 当is_send的值为空时, 点击发送验证码无效
    if (!this.is_send) return;
    // 成功点击发送验证码之后不需要在点(接口幂等性)
    this.is_send = false;

    // 发送axios请求
    this.$axios.get(this.$settings.base_url + '/user/sms_interface/', {params: {phone: this.mobile}}).then(response => {
        // 发送成功之后弹框提示
        if (response.data.data.is_send) {
            this.$message({
                message: response.data.msg,
                type: 'success',
                duration: 2000,
            })
        }
    })


    // 定义一个频率限制时间
    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);
},
3. 注册用户
手机号, 密码, 验证码必须填写.
<el-input>
    placeholder="密码 6-18位"  
el-input>
<el-button type="primary" @click="register">注册el-button>
// 手机号码注册
register() {
    // 手机号码 验证码 密码 都必须填写, 你填写直接提示
    if (!(this.mobile && this.sms && this.password)) {
        this.$message({
            message: '你有信息没有填写.',
            type: 'warning',
            duration: 2000,
        })
    }

    // 向后端发送请求
    this.$axios.post(this.$settings.base_url + '/user/phone_register/', {
        phone: this.mobile,
        code: this.sms,
        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')
        }
    })
}

<Register v-if="is_register" @close="close_register" @login_status="loginStatus" @go="put_login"/>
4.测试
修改一下短信接口, 发短信要钱的, 注册发送短信接口, 等项目写完了在把注释取消.
# 3.短信接口
class SMSInterface(ViewSet):
    # 频率限制
    throttle_classes = [SMSThrottling, ]
    # 短信服务接口
    @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)
        print(code)
        # 发送通过腾讯发送短信模块发送短信, 接收一个布尔值
        # 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)

5.5 前后端提交到远端
1. 前端
# 添加到暂存区
PS D:\vue\luffy_vue> git add .
warning: LF will be replaced by CRLF in src/App.vue.
The file will have its original line endings in your working directory

# 提交到版本库
PS D:\vue\luffy_vue> git commit -m '注册接口完成'
[dev bea5a2f] 注册接口完成
 3 files changed, 82 insertions(+), 8 deletions(-)
 
# 下拉
PS D:\vue\luffy_vue> git pull origin dev         
From gitee.com:python_21/luffy_vue
 * branch            dev        -> FETCH_HEAD
Already up to date.

# 上传
PS D:\vue\luffy_vue> git push origin dev         
Enumerating objects: 13, done.
Counting objects: 100% (13/13), done.
Delta compression using up to 12 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 1.94 KiB | 995.00 KiB/s, done.
Total 7 (delta 5), reused 0 (delta 0), pack-reused 0
remote: Powered by GITEE.COM [GNK-6.3]
To gitee.com:python_21/luffy_vue.git
   2c798be..bea5a2f  dev -> dev
2. 后端
# 添加到暂存区
PS F:\synchro\Project\luffy> git add .

# 提交到版本库
PS F:\synchro\Project\luffy> git commit -m '注册接口完成'
[dev 13d5a8c] 注册接口完成
 11 files changed, 162 insertions(+), 15 deletions(-)
 rewrite luffy/apps/home/__pycache__/views.cpython-38.pyc (63%)
 rewrite luffy/apps/user/__pycache__/serializer.cpython-38.pyc (61%)
 rewrite luffy/apps/user/__pycache__/urls.cpython-38.pyc (91%)
 
# 下拉
PS F:\synchro\Project\luffy> git pull origin dev         
From gitee.com:python_21/luffy
 * branch            dev        -> FETCH_HEAD
Already up to date.

# 上传
PS F:\synchro\Project\luffy> git push origin dev         
Enumerating objects: 40, done.
Counting objects: 100% (40/40), done.
Delta compression using up to 12 threads
Compressing objects: 100% (21/21), done.
Writing objects: 100% (22/22), 8.27 KiB | 529.00 KiB/s, done.
Total 22 (delta 16), reused 0 (delta 0), pack-reused 0
remote: Powered by GITEE.COM [GNK-6.3]
To gitee.com:python_21/luffy.git
   1d20d39..13d5a8c  dev -> dev

你可能感兴趣的:(8.学城项目,vue.js,javascript,前端)