Java前后端分离实战Auto2.0用户登录注册--前端页面

162 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

前言

这部分的内容可能比较多,因此比较难一次性说清楚,那么咱们本次的任务就是说完成,咱们整个项目里面最重要的部分之一的用户登录注册,首先咱们的前端还是Vue + ElementUI。后端是咱们的微服务架构,咱们要在这个场景下完成一个完整的登录注册+第三方的验证登录,方便用户的体验。同时咱们为了实现多端的一个功能实现,所以的话,咱们的就是说使用这个token去完成。

那么咱们现在要完成的就是咱们最最基本的登录和注册模块。那么现在最先需要完成的就是在咱们的平台本身的登录与注册模块,这个非常重要,因为这个每做好的话,咱们的这个Auto2.0结合第三方的登录也是很难做好的。因为逻辑其实都是一样的,就是细节可能不一样。

由于时间和联调的问题,那么咱们这个也是分好几个part,咱们尽可能详细一点,先把前端的事情说清楚,然后咱们再把后端的事情说清楚。

然后咱们的这个博文是分这几个part,前端的页面,基本的用户登录,注册,黑名单安全机制。Auto2.0 微信,QQ登录,账号绑定。之后的话,咱们的这个部分就完成了,之后的话其实就很简单了,其他的内容我可能连博客都懒得写了,当然除了用户聊天模块,我还会好好写,因为这个也是咱们的一个大头。

页面

我们先来看看最基本的前端页面 在这里插入图片描述

在这里插入图片描述 在这里插入图片描述

前端验证码

咱们的这个验证码的话是前端来生成的,一方面是减少咱们的这个后端的压力,减少这个请求次数。这个咋说呢,其实对于不知道的用户,会老老实实去填写,对于懂的用户,会抓包的同学,用户账号密码不对还是过不了,欸,没啥用,你说过拦截器是吧,不好意思,只要请求到了服务器基本上都会浪费资源,区别是啥,前者校验验证码,不过就不查询,后者直接查,对于恶意脚本来说,会把压力给我mysql,对于这个恶意脚本的话,我们直接在网关,或者是拦截器,搞个黑名单,直接拉黑ip但是存在多个用户用一个ip的情况,会误杀这个太直接不太好,或者搞一个特征请求,只要你不知道我请求的特征,你的脚本模拟不出来,并且此ip不满足特征请求的情况下的次数过多,基本上是脚本,直接拉黑即可。

那么回到咱们这个,这个的前端验证码的话,其实就是一个组件。 在这里插入图片描述


<template>
  <div class="s-canvas">
    <canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas>
  </div>
</template>

<script>
export default {
  name: "SIdentify",
  props: {
    identifyCode: {
      type: String,
      default: '1234'
    },
    fontSizeMin: {
      type: Number,
      default: 25
    },
    fontSizeMax: {
      type: Number,
      default: 30
    },
    backgroundColorMin: {
      type: Number,
      default: 255
    },
    backgroundColorMax: {
      type: Number,
      default: 255
    },
    colorMin: {
      type: Number,
      default: 0
    },
    colorMax: {
      type: Number,
      default: 160
    },
    lineColorMin: {
      type: Number,
      default: 100
    },lineColorMax: {
      type: Number,
      default: 255
    },
    dotColorMin: {
      type: Number,
      default: 0
    },
    dotColorMax: {
      type: Number,
      default: 255
    },
    contentWidth: {
      type: Number,
      default: 112
    },
    contentHeight: {
      type: Number,
      default: 31
    }
  },
  methods: {
    // 生成一个随机数
    randomNum(min, max) {
      return Math.floor(Math.random() * (max - min) + min)
    },
    // 生成一个随机的颜色
    randomColor(min, max) {
      let r = this.randomNum(min, max)
      let g = this.randomNum(min, max)
      let b = this.randomNum(min, max)
      return 'rgb(' + r + ',' + g + ',' + b + ')'
    },
    drawPic() {
      let canvas = document.getElementById('s-canvas')
      let ctx = canvas.getContext('2d')
      ctx.textBaseline = 'bottom'
      // 绘制背景
      ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)
      ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)
      // 绘制文字
      for (let i = 0; i < this.identifyCode.length; i++) {
        this.drawText(ctx, this.identifyCode[i], i)
      }
      this.drawLine(ctx)
      this.drawDot(ctx)
    },
    drawText(ctx, txt, i) {
      ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)
      ctx.font = this.randomNum(this.fontSizeMin, this.fontSizeMax) + 'px SimHei'
      let x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1))
      let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)
      var deg = this.randomNum(-45, 45)
      // 修改坐标原点和旋转角度
      ctx.translate(x, y)
      ctx.rotate(deg * Math.PI / 180)
      ctx.fillText(txt, 0, 0)
      // 恢复坐标原点和旋转角度
      ctx.rotate(-deg * Math.PI / 180)
      ctx.translate(-x, -y)
    },
    drawLine(ctx) {
      // 绘制干扰线
      for (let i = 0; i < 5; i++) {
        ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)
        ctx.beginPath()
        ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
        ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
        ctx.stroke()
      }
    },
    drawDot(ctx) {
      // 绘制干扰点
      for (let i = 0; i < 80; i++) {
        ctx.fillStyle = this.randomColor(0, 255)
        ctx.beginPath()
        ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI)
        ctx.fill()
      }
    }
  },
  watch: {
    identifyCode() {
      this.drawPic()
    }
  },
  mounted() {
    this.drawPic()
  }
}
</script>

<style scoped>
.s-canvas {
  height: 38px;

}
.s-canvas canvas{
  margin-top: 1px;
  margin-left: 8px;
}
</style>


哪里要用,哪里导入。

登录页面

在这里插入图片描述

咱们来一个一个看看。

Login

<template>
  <div>
    <vue-particles
      class="login-background"
      color="#97D0F2"
      :particleOpacity="0.7"
      :particlesNumber="200"
      shapeType="circle"
      :particleSize="4"
      linesColor="#97D0F2"
      :linesWidth="1"
      :lineLinked="true"
      :lineOpacity="0.8"
      :linesDistance="150"
      :moveSpeed="3"
      :hoverEffect="true"
      hoverMode="grab"
      :clickEffect="true"
      clickMode="push">
    </vue-particles>
    <el-tabs v-model="activeName" class="login-bok">

      <el-tab-pane label="本账号登录" name="first">
        <div style="width: 80%;height: 80%;margin: 100px auto">
          <loginby-user-name></loginby-user-name>
        </div>
      </el-tab-pane>
      <el-tab-pane label="第三方登录" name="second">
        <login-third></login-third>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>

<script>
import loginbyUserName from "./loginbyUserName";
import LoginThird from "./LoginThird";
export default {
  components:{
    loginbyUserName,
    LoginThird
  },
  name: "login",
  data() {
    return {
      activeName: 'first'
    };
  },
}
</script>

<style scoped>
.login-bok{
  width: 30%;
  height: 450px;
  margin: 0 auto;
  border: 1px solid #DCDFE6;
  padding: 20px;
  border-radius: 10px;
  box-shadow: 0 0 30px #DCDFE6;
}

.login-background {
  width: 80%;
  margin: 0 auto;
  height: 500px; /**宽高100%是为了图片铺满屏幕 */
  z-index: 0;
  position: absolute;
}

</style>

这个的话是咱们的主页面。 然后是咱们的平台登录。

LoginbyUserName

<template>
  <div>
    <el-form :model="formLogin" :rules="rules" ref="ruleForm" label-width="0px" >
      <el-form-item prop="username">
        <el-input v-model="formLogin.username" placeholder="账号">
          <i slot="prepend" class="el-icon-s-custom"/>
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input type="password" placeholder="密码" v-model="formLogin.password">
          <i slot="prepend" class="el-icon-lock"/>
        </el-input>
      </el-form-item>
      <el-form-item prop="code">
        <el-row :span="24">
          <el-col :span="12">
            <el-input v-model="formLogin.code" auto-complete="off"  placeholder="请输入验证码" size=""></el-input>
          </el-col>
          <el-col :span="12">
            <div class="login-code" @click="refreshCode">
              <!--验证码组件-->
              <s-identify :identifyCode="identifyCode"></s-identify>
            </div>
          </el-col>
        </el-row>
      </el-form-item>
      <el-form-item>
        <div class="login-btn">
          <el-button type="primary" @click="submitForm()" style="margin-left: auto;width: 35%">登录</el-button>
          <el-button type="primary" @click="goRegister" style="margin-left: 27%;width: 35%" >注册</el-button>
        </div>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import SIdentify from "../../components/SIdentify/SIdentify";
export default {
  name: "loginbyUserName",
  components: { SIdentify },
  data() {
    return{
      formLogin: {
        username: "",
        password: "",
        code: ""
      },
      identifyCodes: '1234567890abcdefjhijklinopqrsduvwxyz',//随机串内容
      identifyCode: '',
      // 校验
      rules: {
        username:
          [
            { required: true, message: "请输入用户名", trigger: "blur" }
          ],
        password: [
          { required: true, message: "请输入密码", trigger: "blur" }
        ],
        code: [
          { required: true, message: "请输入验证码", trigger: "blur" }
        ]
      }

    }
  },
  mounted () {
    // 初始化验证码
    this.identifyCode = ''
    this.makeCode(this.identifyCodes, 4)
  },
  methods:{
    refreshCode () {
      this.identifyCode = ''
      this.makeCode(this.identifyCodes, 4)
    },
    makeCode (o, l) {
      for (let i = 0; i < l; i++) {
        this.identifyCode += this.identifyCodes[this.randomNum(0, this.identifyCodes.length)]
      }
    },
    randomNum (min, max) {
      return Math.floor(Math.random() * (max - min) + min)
    },

    submitForm(){

      if (this.formLogin.code.toLowerCase() !== this.identifyCode.toLowerCase()) {
        this.$message.error('请填写正确验证码')
        this.refreshCode()

      }
      else {
        //这边后面做一个提交,服务器验证,通过之后获得token
        sessionStorage.setItem("loginToken","123456")
        console.log("code is right")
        this.$router.push("/userinfo")
        console.log(this.formLogin.password)
        console.log(this.formLogin.username)
      }
    },
    goRegister(){
      this.$router.push("/register")
    }
  },
}
</script>

<style scoped>
</style>

第三方登录

<template>
<div>
  <el-tabs v-model="activeName" class="login-bok">
    <el-tab-pane label="微信" name="first">
      <div style="margin: 50px auto">
        <loginby-wx></loginby-wx>
      </div>
    </el-tab-pane>
    <el-tab-pane label="QQ" name="second">
      <loginby-q-q></loginby-q-q>
    </el-tab-pane>
  </el-tabs>
</div>
</template>

<script>
import LoginbyQQ from "./LoginbyQQ";
import LoginbyWx from "./LoginbyWx";
export default {
  components:{
    LoginbyQQ,
    LoginbyWx,
  },
  name: "LoginThird",
  data() {
    return {
      activeName: 'first'
    };
  },
}
</script>

<style scoped>
.login-bok{
  margin: 0 auto;
  text-align: center;
}

</style>

然后里面有两个导航,一个是QQ的一个是微信的,我们这边推荐的其实还是使用第三方登录,并不是说减缓我们的服务器压力,其实这玩意只会增加压力,但是我们可以拿到更多的数据,后面还可以基于微信,或者QQ给我们提供的一些数据,来做更加细致的推荐。

这个里展示QQ的,微信的现在的页面是一样的,现在没啥区别,到咱们后面做Auto2.0的时候才有,这个到时候细说。

<template>
  <div>
    <el-image
      style="width: 60%; height: 60%;margin: 0 auto"
      src="static/image/QQ.jpg"
      fit="fit"></el-image>
    <p>
      <el-tooltip content="勾选同意后将自动创建账号并进入账号设置绑定页面" placement="bottom" effect="light">
        <el-checkbox v-model="checked" label="1">使用QQ登录时,自动创建账号</el-checkbox>
      </el-tooltip>
    </p>
  </div>
</template>

<script>
export default {
  name: "LoginbyQQ",
  data () {
    return {
      checked: true
    };
  },
}
</script>

<style scoped>

</style>

注册页面

这个注册的话,就是单纯的在我们的平台进行注册,这里的区别就是多了一个邮箱验证。然后在这里做接口幂等。

<template>
  <div>
  <vue-particles
    class="login-background"
    color="#97D0F2"
    :particleOpacity="0.7"
    :particlesNumber="200"
    shapeType="circle"
    :particleSize="4"
    linesColor="#97D0F2"
    :linesWidth="1"
    :lineLinked="true"
    :lineOpacity="0.8"
    :linesDistance="150"
    :moveSpeed="3"
    :hoverEffect="true"
    hoverMode="grab"
    :clickEffect="true"
    clickMode="push">
  </vue-particles>
    <el-container>
    <div style="width: 30%;margin: 0 auto">
      <el-form :model="formRegister" :rules="rules" ref="formRegister" label-width="0px" >
        <el-form-item prop="nickname">
          <el-input v-model="formRegister.nickname" placeholder="昵称">
            <i slot="prepend" class="el-icon-user"/>
          </el-input>
        </el-form-item>
        <el-form-item prop="username">
          <el-input v-model="formRegister.username" placeholder="账号">
            <i slot="prepend" class="el-icon-s-custom"/>
          </el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input type="password" placeholder="密码" v-model="formRegister.password">
            <i slot="prepend" class="el-icon-lock"/>
          </el-input>
        </el-form-item>
        <el-form-item>
          <el-input type="phone" placeholder="手机(选填,建议填写)" v-model="formRegister.phone">
            <i slot="prepend" class="el-icon-phone"/>
          </el-input>
        </el-form-item>
        <el-form-item prop="email">
          <el-input type="email" placeholder="邮箱(必填)" v-model="formRegister.email">
            <i slot="prepend" class="el-icon-s-promotion"/>
          </el-input>
        </el-form-item>
        <el-form-item prop="code">
          <el-row :span="24">
            <el-col :span="12">
              <el-input v-model="formRegister.code" auto-complete="off"  placeholder="请输入验证码" size=""></el-input>
            </el-col>
            <el-col :span="12">
              <div class="login-code" @click="refreshCode">
                <!--验证码组件-->
                <s-identify :identifyCode="identifyCode"></s-identify>
              </div>
            </el-col>
          </el-row>
        </el-form-item>
        <el-form-item prop="emailCode">
          <el-row :span="24">
            <el-col :span="12">
              <el-input v-model="formRegister.emailCode" auto-complete="off"  placeholder="请输入邮箱验证码" size=""></el-input>
            </el-col>
            <el-col :span="12">
              <div class="login-code" @click="refreshCode">
                <!--发送邮箱验证码-->
                <el-button @click="getEmailCode()" :disabled="!show"
                           style="margin-left: 26%;width: 70%"
                           type="primary"
                >
                  <span v-show="show">发送验证码</span>
                  <span v-show="!show" class="count">{{count}} s</span>
                </el-button>
              </div>
            </el-col>
          </el-row>
        </el-form-item>
        <el-form-item>
          <div class="login-btn">
            <el-button type="primary" @click="goLogin" style="margin-left: auto;width: 35%">登录</el-button>
            <el-button type="primary" @click="submitForm()" style="margin-left: 27%;width: 35%" >注册</el-button>
          </div>
        </el-form-item>
      </el-form>
    </div>
    </el-container>
  </div>
</template>

<script>
import SIdentify from "../../components/SIdentify/SIdentify";
export default {
  name: "register",
  components: { SIdentify },
  data() {
    return{
      TIME_COUNT:60,
      show: true,
      count: 60,
      formRegister: {
        nickname: "",
        phone: "",
        username: "",
        password: "",
        code: "",
        email:"",
        emailCode:""
      },
      identifyCodes: '1234567890abcdefjhijklinopqrsduvwxyz',//随机串内容
      identifyCode: '',
      // 校验
      rules: {
        phone: [
          { min: 11, max: 11, message: '请输入11位手机号码', trigger: 'blur' },
          {
            pattern: /^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/,
            message: '请输入正确的手机号码'
          }
        ],
        nickname:
          [
            { required: true, message: "请输入用户昵称", trigger: "blur" }
          ],
        username:
          [
            { required: true, message: "请输入账号(用于登录)", trigger: "blur" }
          ],
        password: [
          { required: true, message: "请输入密码", trigger: "blur" }
        ],
        code: [
          { required: true, message: "请输入验证码", trigger: "blur" }
        ],
        emailCode: [
          { required: true, message: "请输入邮箱验证码", trigger: "blur" }
        ]

      }

    }
  },
  mounted () {
    // 初始化验证码
    this.identifyCode = ''
    this.makeCode(this.identifyCodes, 4)
  },
  methods:{
    getEmailCode () {
      const that = this
      if (this.formRegister.email === '') {
        this.$message.error('请先输入邮箱再点击获取验证码')
      } else {
        let regEmail = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
        if (!regEmail.test(this.formRegister.email)) {
          this.$message({showClose: true, message: '请输入格式正确有效的邮箱号!', type: 'error'})
        } else {
        //  这部分是发送邮箱验证码的玩意

          // 验证码倒计时
          if (!this.timer) {
            this.show = false
            this.timer = setInterval(() => {
              if (this.count > 0 && this.count <= this.TIME_COUNT) {
                this.count--
              } else {
                this.show = true
                clearInterval(this.timer)
                this.timer = null
              }
            }, 1000)
          }

        }
      }
    },

    refreshCode () {
      this.identifyCode = ''
      this.makeCode(this.identifyCodes, 4)
    },
    makeCode (o, l) {
      for (let i = 0; i < l; i++) {
        this.identifyCode += this.identifyCodes[this.randomNum(0, this.identifyCodes.length)]
      }
    },
    randomNum (min, max) {
      return Math.floor(Math.random() * (max - min) + min)
    },

    submitForm(){
      let flag = true;
      if (this.formRegister.code.toLowerCase() !== this.identifyCode.toLowerCase()) {
        this.$message.error('请填写正确验证码');
        this.refreshCode();
        flag=false;
      }
      else if(!this.formRegister.emailCode){
        this.$message.error('请填写邮箱验证码');
        this.refreshCode();
        flag=false;
      }
      else if(!this.formRegister.email){
        this.$message.error('已填写邮箱请勿删除或修改邮箱,恶意操作将在120分钟内禁止注册!');
        this.refreshCode();
        flag=false;
      }

      if(flag){
        //这边后面做一个提交,提交对于消息
      }
    },
    goLogin(){
      this.$router.push("/login")
    }
  },
}
</script>
<style scoped>
.login-background {
  width: 80%;
  margin: 0 auto;
  height: 500px; /**宽高100%是为了图片铺满屏幕 */
  z-index: 0;
  position: absolute;
}

</style>

总结

这个的话就是咱们的前端的登录了,那么后面咱们开始填写逻辑。之后咱们聊聊Auto2.0 现在的话,咱们这个要申请,咱们还没搞,所以没办法。加上时间关系,咱们现在就先这样。