毕设之-Hlang后端架构-邮箱登录注册实现(SpringBoot)-

文章目录

  • 前言
  • 前端
    • 页面设计
    • 基本逻辑
      • axios封装
      • 请求类
      • api封装
      • 全局用户信息
      • 状态存储
    • 注册逻辑
      • 前端验证
      • 发送验证码
      • 注册
    • 登录逻辑
    • 前端完整代码
  • 后端
    • 工具类
      • 邮箱工具类
      • Redis工具类
      • IP地址工具类
    • 限流切面
    • 注册实现
    • 登录实现
      • 生成token
      • 验证token
  • 总结

前言

没想到又鸽了一天,okey,调整心情,我们接着出发。那么今天要实现的就是我们第一个基本业务的实现,那就是登录注册功能。这个功能做好了就意味着前后端基本打通,接下来的工作就是划水就好了。当然对于接下来的计划是,接下来可能会来个Python研发系列的文章。差不多干活了,就要有干活的样子。

okey,废话不多说,我们来看看我们要实现的效果

首先我们来看到前端:
首先未登录的情况下显示这个:
在这里插入图片描述
然后的话进入登录注册页面
在这里插入图片描述

之后登录之后,我们会回到主页
然后就会看到头像:
在这里插入图片描述
这个头像看起来有点眼熟哈。

前端

毫无疑问,我们首先还是先来看到我们的前端,这次的话,我会尽可能写详细一点儿。
当然这里的话,我们使用的是vue3

页面设计

首先我们来看到我们前端的设计。
我们整个html部分长这样:

<template>
    <div class="main">
        <div ref="container" class="container" id="container">
            <div class="form-container sign-up-container">
                <form action="#">
                    <h3>创建账户</h3>
                    <div class="social-container">
                        <a href="#" class="social"><i class="fab fa-facebook-f"></i></a>
                        <a href="#" class="social"><i class="fab fa-google-plus-g"></i></a>
                        <a href="#" class="social"><i class="fab fa-linkedin-in"></i></a>
                    </div>
                    <span>注册账户(暂不支持第三方)</span>
                    <input v-model="registQ.username" type="text" placeholder="账号(长度为8-12位)" />
                    <input v-model="registQ.nickname" type="text" placeholder="昵称(长度为6-12位)" />
                    <input v-model="registQ.password" type="password" placeholder="密码(长度为6-12位)" />
                    <input v-model="registQ.email" type="email" placeholder="邮箱(必须填写)" />
                    <input v-model="registQ.emailCode" type="text" placeholder="验证码(十分钟有效)" />
                    <div class="registerButton">
                        <el-button @click="getRegister">注册</el-button>
                        <el-button @click="getCode">{{ showSendButton }}</el-button>
                    </div>
                </form>
            </div>
            <div class="form-container sign-in-container">
                <form action="#">
                    <h3>登录</h3>
                    <div class="social-container">
                        <a href="#" class="social"><i class="fab fa-facebook-f"></i></a>
                        <a href="#" class="social"><i class="fab fa-google-plus-g"></i></a>
                        <a href="#" class="social"><i class="fab fa-linkedin-in"></i></a>
                    </div>
                    <span>使用账户登录(暂不支持第三方)</span>
                    <input v-model="loginQ.userName" type="text" placeholder="账号" />
                    <input v-model="loginQ.passWord" type="password" placeholder="密码" />
                    <a class="forget" href="#">忘记密码? >></a>
                    <el-button @click="LoginGo">登录</el-button>
                </form>
            </div>
            <div class="overlay-container">
                <div class="overlay">
                    <div class="overlay-panel overlay-left">
                        <h1>欢迎登录!</h1>
                        <p>登录后,30天自动登录</p>
                        <button @click="signInSwitch" class="ghost" id="signIn"> 登录</button>
                    </div>
                    <div class="overlay-panel overlay-right">
                        <h1>Hello, Friend!</h1>
                        <p>创建账户,加入我们</p>
                        <button @click="signUpSwitch" class="ghost" id="signUp">注册</button>
                    </div>
                </div>
            </div>
        </div>
        <Vcode :show="isShow" @success="success" @close="close" @fail="fail"></Vcode>
    </div>
</template>

然后我们整个页面对应的css代码是这样的:

.registerButton {
    display: flex;
    gap: 5rem;
}

.forget:hover {
    color: #04a5ea;
}

.main {
    width: 90%;
    margin: 0 auto;
    height: 33.125rem;
}

* {
    box-sizing: border-box;
}

body {
    background: #f6f5f7;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    font-family: 'Montserrat', sans-serif;
    height: 100vh;
    margin: -1.25rem 0 3.125rem;
}

h1 {
    font-weight: bold;
    margin: 0;
}

h2 {
    text-align: center;
}

p {
    font-size: 0.875rem;
    font-weight: 100;
    line-height: 1.25rem;
    letter-spacing: 0.0313rem;
    margin: 1.25rem 0 1.875rem;
}

span {
    font-size: 0.75rem;
}

a {
    color: #333;
    font-size: 0.875rem;
    text-decoration: none;
    margin: 0.9375rem 0;
}

button {
    border-radius: 1.25rem;
    border: 0.0625rem solid #2b80ff;
    background-color: #2b8eff;
    color: #FFFFFF;
    font-size: 0.75rem;
    font-weight: bold;
    padding: 0.75rem 1.5rem;
    letter-spacing: 0.0625rem;
    text-transform: uppercase;
    transition: transform 80ms ease-in;
}

button:active {
    transform: scale(0.95);
}

button:focus {
    outline: none;
}

button.ghost {
    background-color: transparent;
    border-color: #FFFFFF;
}

form {
    background-color: #FFFFFF;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    padding: 0 3.125rem;
    height: 100%;
    text-align: center;
}

input {
    background-color: #eee;
    border: none;
    padding: 0.75rem 0.9375rem;
    margin: 0.2rem 0;
    width: 100%;
}

.container {
    background-color: #fff;
    border-radius: 0.625rem;
    box-shadow: 0 0.875rem 1.75rem rgba(0, 0, 0, 0.25),
        0 0.625rem 0.625rem rgba(0, 0, 0, 0.22);
    position: relative;
    overflow: hidden;
    width: 48rem;
    max-width: 100%;
    min-height: 30rem;
    margin: 3.125rem auto;
}

.form-container {
    position: absolute;
    top: 0;
    height: 100%;
    transition: all 0.6s ease-in-out;
}

.sign-in-container {
    left: 0;
    width: 50%;
    z-index: 2;
}

.container.right-panel-active .sign-in-container {
    transform: translateX(100%);
}

.sign-up-container {
    left: 0;
    width: 50%;
    opacity: 0;
    z-index: 1;
}

.container.right-panel-active .sign-up-container {
    transform: translateX(100%);
    opacity: 1;
    z-index: 5;
    animation: show 0.6s;
}

@keyframes show {

    0%,
    49.99% {
        opacity: 0;
        z-index: 1;
    }

    50%,
    100% {
        opacity: 1;
        z-index: 5;
    }
}

.overlay-container {
    position: absolute;
    top: 0;
    left: 50%;
    width: 50%;
    height: 100%;
    overflow: hidden;
    transition: transform 0.6s ease-in-out;
    z-index: 100;
}

.container.right-panel-active .overlay-container {
    transform: translateX(-100%);
}

.overlay {
    background: #04a5ea;
    background: -webkit-linear-gradient(to right, #2b95ff, #4184ff);
    background: linear-gradient(to right, #2bd5ff, #41adff);
    background-repeat: no-repeat;
    background-size: cover;
    background-position: 0 0;
    color: #FFFFFF;
    position: relative;
    left: -100%;
    height: 100%;
    width: 200%;
    transform: translateX(0);
    transition: transform 0.6s ease-in-out;
}

.container.right-panel-active .overlay {
    transform: translateX(50%);
}

.overlay-panel {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    padding: 0 2.5rem;
    text-align: center;
    top: 0;
    height: 100%;
    width: 50%;
    transform: translateX(0);
    transition: transform 0.6s ease-in-out;
}

.overlay-left {
    transform: translateX(-20%);
}

.container.right-panel-active .overlay-left {
    transform: translateX(0);
}

.overlay-right {
    right: 0;
    transform: translateX(0);
}

.container.right-panel-active .overlay-right {
    transform: translateX(20%);
}

.social-container {
    margin: 0 0;
}

.social-container a {
    border: 0.0625rem solid #DDDDDD;
    border-radius: 50%;
    display: inline-flex;
    justify-content: center;
    align-items: center;
    margin: 0 0.3125rem;
    height: 2.5rem;
    width: 2.5rem;
}

那么同样的,为了实现一些动画效果,这里也是用了一点点的js.不过原理很简单,其实就是先写好了css的样式,动画效果,然后js,切换对应的class,就可以了。
那么在这里用到的就是这两个对象:

//切换选项卡
const container = ref();
const signUpSwitch = () => {
    container.value.classList.add("right-panel-active");
}
const signInSwitch = () => {
    container.value.classList.remove("right-panel-active");
}

这样一来,我们的整个页面都设计好了。

基本逻辑

聊完了整个页面上的设计,我们再来看到我们前端的基本逻辑。

axios封装

首先,我们这边第一件事情就是对axios进行封装,当然这里的话,只是进行了简单封装。
在这里插入图片描述

import axios from 'axios'
import StateStorage from '../utils/store'
import { defaultUserLogin,UserLoginR} from "./rentity"
export function request(config) {
    // 1.创建axios的实例
    const instance = axios.create({
        // 设置基础的url配置项,这样接口处的url前面就不用写url:'http://127.0.0.1:8000/api/home',直接写成 url:'/api/home', 就可以了
        baseURL: 'http://127.0.0.1:88/api/hlang-server',
        //设置请求超时时间
        timeout: 5000
    })

    // 2.axios的拦截器,用不到的可以忽略这节
    // 2.1.请求拦截的作用
    instance.interceptors.request.use(config => {
        const userLoginR = StateStorage.getByLocalStateDefault("userLoginR") as UserLoginR
        config.headers['loginType'] = 'PcType'
        config.headers['userid'] = userLoginR.userid
        config.headers['loginToke'] = userLoginR.token
        return config
    }, err => {
        console.log('请求拦截err: '+err);
    })

    // 2.2.响应拦截
    instance.interceptors.response.use(res => {
        return res.data
    }, err => {
        console.log('响应拦截err: '+err);
    })
    // 3.发送真正的网络请求
    return instance(config)
}



同时,由于我们这块使用的是ts,所以的话,我们这块还定义了几个基本的请求类。

请求类

在这里插入图片描述
主要是方便,前端和后端进行交互。

api封装

那么同样的对api进行简单封装。
在这里插入图片描述
之后的话,方便我们进行使用。

全局用户信息

之后的话,在定义一个全局的响应式的一个用户状态信息。

import { ref } from 'vue'
//用户登录之后的返回信息类
export interface UserLoginR {
    token: String;
    userid: String;
    nickname: String;
    upic: String;
    login: boolean

}
//初始默认值,这是不能存在的值在系统当中,响应式的
export const defaultUserLogin= ref<UserLoginR>( {
    token: "-1",
    userid: "-1",
    nickname: "-1",
    upic: "-1",
    login: false
  });

状态存储

之后的话,就要聊到状态存储了。这里的话,就不用pinia,好吧主要是一直有个小bug不好解决。受不了,直接自己写一个工具类就得了。

/**
 * 实现存储功能,这里不用pinia,插件有毒
 */
class StateStorage {
    public static getBySession(key: string): object | undefined {
      const json = sessionStorage.getItem(key);
      if (json) {
        return JSON.parse(json);
      }
      return undefined;
    }
  
    /**
     * @param key 
     * @param value 
     */
    public static setBySession(key: string, value: object) {
      const json = JSON.stringify(value);
      sessionStorage.setItem(key, json);
    }


    /**
     * @param key 
     * @returns 
     */
    public static getByLocalState(key: string): object | undefined {
      const json = localStorage.getItem(key);
      if (json) {
        return JSON.parse(json);
      }
      return undefined;
    }
  
    /**
     * 
     * @param key 
     * @param value 
     * @param time 单位是毫秒,不给默认是永久存储
     */
    public static setByLocalState(key: string, value: object, time?: number) {
      const json = JSON.stringify(value);
      localStorage.setItem(key, json);
      if (time) {
        const expireTime = new Date().getTime() + time;
        localStorage.setItem(`${key}_expiresIn`, expireTime.toString());
      }
    }
  
    /**
     * 默认存储30天
     * @param key 
     * @param value 
     * @param days 
     */
    public static setByLocalStateDefault(
      key: string,
      value: object,
      days: number = 30
    ) {
      const json = JSON.stringify(value);
      localStorage.setItem(key, json);
      const expireTime = new Date().getTime() + days * 24 * 60 * 60 * 1000;
      localStorage.setItem(`${key}_expiresIn`, expireTime.toString());
    }

    /**
     * 获取默认存储30天的key
     * @param key 
     * @returns 
     */
    public static getByLocalStateDefault(key: string): object | undefined {
      const json = localStorage.getItem(key);
      const expiresIn = localStorage.getItem(`${key}_expiresIn`);
      if (json && expiresIn) {
        const currentTime = new Date().getTime();
        const expireTime = parseInt(expiresIn, 10);
        if (currentTime <= expireTime) {
          return JSON.parse(json);
        } else {
          localStorage.removeItem(key);
          localStorage.removeItem(`${key}_expiresIn`);
        }
      }
      return undefined;
    }
  }
  

export default StateStorage 

注册逻辑

okey, 这里聊完了,我们来看到我们基本的注册逻辑。
其实很简单,前端的逻辑:

  1. 点击发送验证码,先在前端进行验证,例如前端的图形验证码
  2. 前端验证通过后,后端发一个邮箱验证码,然后这边输入验证码
  3. 携带账号,密码,验证码等信息完成注册

前端验证

我们先来看到前端的验证,这个前端的验证的话,直接使用第三方组件库。
在这里插入图片描述

npm install vue3-puzzle-vcode --save

基本用法的话,在这里的代码都有体现哈。

发送验证码

那么前端验证通过之后,我们发送验证码

//导数计时
const countdown = (seconds) => {
    const intervalId = setInterval(() => {
        seconds--;
        //更新读秒
        showSendButton.value = seconds + "s"
        if (seconds < 0) {
            showSendButton.value = "获取验证码"
            isSendEmail.value = false
            clearInterval(intervalId);
        }
    }, 1000);
}



// 获取验证码,这里先负责前端验证
const getCode = () => {

    if (isSendEmail.value) {
        ElMessage({
            message: '请稍后再发送验证码',
            type: 'warning',
        })
        return;
    }
    if (
        StringUtil.checkLength(registQ.value.password, 6, 12) &&
        StringUtil.checkLength(registQ.value.username, 8, 12) &&
        StringUtil.isEmail(registQ.value.email) &&
        StringUtil.checkLength(registQ.value.nickname, 6, 12)
    ) {
        isShow.value = true
    } else {
        ElMessage({
            message: '请按照要求填写账号,密码,邮箱,昵称',
            type: 'warning',
        })
    }

}
const close = () => {
    isShow.value = false
}

//前端验证通过,向后台请求验证码
const success = (msg) => {
    isShow.value = false
    //开始发送验证码
    isSendEmail.value = true
    //发送验证码
    emailQ.value.password = registQ.value.password;
    emailQ.value.username = registQ.value.username;
    emailQ.value.email = registQ.value.email;
    EmailCode(emailQ.value).then(
        (res: any) => {
            //服务器默认返回成功的状态码是0
            if (res.code != 0) {
                ElMessage({
                    message: res.msg,
                    type: 'error',
                })
            } else {
                ElMessage({
                    message: "验证码发送成功",
                    type: 'success',
                })
            }
        }
    ).catch(
        err => {
            console.log(err)
        }
    );
    //倒计时
    countdown(60)

}

这块的话,有个倒计时60秒

注册

然后注册就完了。


//正式注册
const getRegister = () => {

    //还没有发送验证码
    if (!isSendEmail.value) {
        ElMessage({
            message: '请先获取验证码',
            type: 'warning',
        })
        return;
    }

    if (
        StringUtil.checkLength(registQ.value.password, 6, 12) &&
        StringUtil.checkLength(registQ.value.username, 8, 12) &&
        StringUtil.isEmail(registQ.value.email) &&
        StringUtil.checkLength(registQ.value.nickname, 6, 12) &&
        StringUtil.checkLength(registQ.value.emailCode, 6, 10)
    ) {
        //开始请求完成注册
        Register(registQ.value).then(
            (res: any) => {
                if (res.code != 0) {
                    ElMessage({
                        message: res.msg,
                        type: 'error',
                    })
                } else {
                    //切换到登录卡片
                    signInSwitch();
                }
            }
        ).catch(
            err => {
                console.log(err)
            }
        );
    } else {
        ElMessage({
            message: '请按照要求填写账号,密码,邮箱,昵称,验证码',
            type: 'warning',
        })
    }


}

登录逻辑

之后的话就是登录的逻辑,这个的话,拿着账号密码就好了。

//开始正式进行登录
const LoginGo = () => {
    if (
        StringUtil.checkLength(loginQ.value.passWord, 6, 12) &&
        StringUtil.checkLength(loginQ.value.userName, 8, 12)
    ) {
        Login(loginQ.value).then(
            (res: any) => {
                if (res.code != 0) {
                    ElMessage({
                        message: res.msg,
                        type: 'error',
                    })
                } else {
                    //保存状态
                    defaultUserLogin.value = res.userLoginR
                    StateStorage.setByLocalStateDefault("userLoginR",defaultUserLogin.value);
                    //进入首页
                    router.push(
                        {
                            path: '/'
                        }
                    );
                }
            }
        ).catch(
            err => {
                console.log(err)
            }
        );

    } else {
        ElMessage({
            message: '输入的账号,密码不合规',
            type: 'warning',
        })
    }
}

前端完整代码

okey,那么这里再贴出前端代码参考一下,

<template>
    <div class="main">
        <div ref="container" class="container" id="container">
            <div class="form-container sign-up-container">
                <form action="#">
                    <h3>创建账户</h3>
                    <div class="social-container">
                        <a href="#" class="social"><i class="fab fa-facebook-f"></i></a>
                        <a href="#" class="social"><i class="fab fa-google-plus-g"></i></a>
                        <a href="#" class="social"><i class="fab fa-linkedin-in"></i></a>
                    </div>
                    <span>注册账户(暂不支持第三方)</span>
                    <input v-model="registQ.username" type="text" placeholder="账号(长度为8-12位)" />
                    <input v-model="registQ.nickname" type="text" placeholder="昵称(长度为6-12位)" />
                    <input v-model="registQ.password" type="password" placeholder="密码(长度为6-12位)" />
                    <input v-model="registQ.email" type="email" placeholder="邮箱(必须填写)" />
                    <input v-model="registQ.emailCode" type="text" placeholder="验证码(十分钟有效)" />
                    <div class="registerButton">
                        <el-button @click="getRegister">注册</el-button>
                        <el-button @click="getCode">{{ showSendButton }}</el-button>
                    </div>
                </form>
            </div>
            <div class="form-container sign-in-container">
                <form action="#">
                    <h3>登录</h3>
                    <div class="social-container">
                        <a href="#" class="social"><i class="fab fa-facebook-f"></i></a>
                        <a href="#" class="social"><i class="fab fa-google-plus-g"></i></a>
                        <a href="#" class="social"><i class="fab fa-linkedin-in"></i></a>
                    </div>
                    <span>使用账户登录(暂不支持第三方)</span>
                    <input v-model="loginQ.userName" type="text" placeholder="账号" />
                    <input v-model="loginQ.passWord" type="password" placeholder="密码" />
                    <a class="forget" href="#">忘记密码? >></a>
                    <el-button @click="LoginGo">登录</el-button>
                </form>
            </div>
            <div class="overlay-container">
                <div class="overlay">
                    <div class="overlay-panel overlay-left">
                        <h1>欢迎登录!</h1>
                        <p>登录后,30天自动登录</p>
                        <button @click="signInSwitch" class="ghost" id="signIn"> 登录</button>
                    </div>
                    <div class="overlay-panel overlay-right">
                        <h1>Hello, Friend!</h1>
                        <p>创建账户,加入我们</p>
                        <button @click="signUpSwitch" class="ghost" id="signUp">注册</button>
                    </div>
                </div>
            </div>
        </div>
        <Vcode :show="isShow" @success="success" @close="close" @fail="fail"></Vcode>
    </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import Vcode from 'vue3-puzzle-vcode'
import { useRouter } from 'vue-router'
import { RegisterQ, EmailQ, LoginQ } from '../api/qentity'
import { ElMessage } from 'element-plus'
import StringUtil from "../utils/StringUtil.js"
import { Register, EmailCode, Login } from "../api/api"
import { defaultUserLogin,UserLoginR} from "../api/rentity"
import StateStorage from '../utils/store'

const router = useRouter()
//是否显示验证码组件
const isShow = ref(false)
//是否正在发送验证码
const isSendEmail = ref(false)
const showSendButton = ref<String>("获取验证码")
//注册请求类
const registQ = ref<RegisterQ>({

    email: "",
    emailCode: "",
    password: "",
    username: "",
    nickname: "",

})
//获取邮箱验证码的请求类
const emailQ = ref<EmailQ>({
    password: "",
    username: "",
    email: ""
})

//登录请求类
const loginQ = ref<LoginQ>({
    passWord: "",
    userName: "",
    type: "PcType"
})

//导数计时
const countdown = (seconds) => {
    const intervalId = setInterval(() => {
        seconds--;
        //更新读秒
        showSendButton.value = seconds + "s"
        if (seconds < 0) {
            showSendButton.value = "获取验证码"
            isSendEmail.value = false
            clearInterval(intervalId);
        }
    }, 1000);
}



// 获取验证码,这里先负责前端验证
const getCode = () => {

    if (isSendEmail.value) {
        ElMessage({
            message: '请稍后再发送验证码',
            type: 'warning',
        })
        return;
    }
    if (
        StringUtil.checkLength(registQ.value.password, 6, 12) &&
        StringUtil.checkLength(registQ.value.username, 8, 12) &&
        StringUtil.isEmail(registQ.value.email) &&
        StringUtil.checkLength(registQ.value.nickname, 6, 12)
    ) {
        isShow.value = true
    } else {
        ElMessage({
            message: '请按照要求填写账号,密码,邮箱,昵称',
            type: 'warning',
        })
    }

}
const close = () => {
    isShow.value = false
}

//前端验证通过,向后台请求验证码
const success = (msg) => {
    isShow.value = false
    //开始发送验证码
    isSendEmail.value = true
    //发送验证码
    emailQ.value.password = registQ.value.password;
    emailQ.value.username = registQ.value.username;
    emailQ.value.email = registQ.value.email;
    EmailCode(emailQ.value).then(
        (res: any) => {
            //服务器默认返回成功的状态码是0
            if (res.code != 0) {
                ElMessage({
                    message: res.msg,
                    type: 'error',
                })
            } else {
                ElMessage({
                    message: "验证码发送成功",
                    type: 'success',
                })
            }
        }
    ).catch(
        err => {
            console.log(err)
        }
    );
    //倒计时
    countdown(60)

}

//正式注册
const getRegister = () => {

    //还没有发送验证码
    if (!isSendEmail.value) {
        ElMessage({
            message: '请先获取验证码',
            type: 'warning',
        })
        return;
    }
    if (
        StringUtil.checkLength(registQ.value.password, 6, 12) &&
        StringUtil.checkLength(registQ.value.username, 8, 12) &&
        StringUtil.isEmail(registQ.value.email) &&
        StringUtil.checkLength(registQ.value.nickname, 6, 12) &&
        StringUtil.checkLength(registQ.value.emailCode, 6, 10)
    ) {
        //开始请求完成注册
        Register(registQ.value).then(
            (res: any) => {
                if (res.code != 0) {
                    ElMessage({
                        message: res.msg,
                        type: 'error',
                    })
                } else {
                    //切换到登录卡片
                    signInSwitch();
                }
            }
        ).catch(
            err => {
                console.log(err)
            }
        );
    } else {
        ElMessage({
            message: '请按照要求填写账号,密码,邮箱,昵称,验证码',
            type: 'warning',
        })
    }
}

const fail = () => {
    console.log('验证失败')
}

//切换选项卡
const container = ref();
const signUpSwitch = () => {
    container.value.classList.add("right-panel-active");
}
const signInSwitch = () => {
    container.value.classList.remove("right-panel-active");
}

//开始正式进行登录
const LoginGo = () => {
    if (
        StringUtil.checkLength(loginQ.value.passWord, 6, 12) &&
        StringUtil.checkLength(loginQ.value.userName, 8, 12)
    ) {
        Login(loginQ.value).then(
            (res: any) => {
                if (res.code != 0) {
                    ElMessage({
                        message: res.msg,
                        type: 'error',
                    })
                } else {
                    //保存状态
                    defaultUserLogin.value = res.userLoginR
                    StateStorage.setByLocalStateDefault("userLoginR",defaultUserLogin.value);
                    //进入首页
                    router.push(
                        {
                            path: '/'
                        }
                    );
                }
            }
        ).catch(
            err => {
                console.log(err)
            }
        );

    } else {
        ElMessage({
            message: '输入的账号,密码不合规',
            type: 'warning',
        })
    }
}

</script>

<style scoped>
.registerButton {
    display: flex;
    gap: 5rem;
}

.forget:hover {
    color: #04a5ea;
}

.main {
    width: 90%;
    margin: 0 auto;
    height: 33.125rem;
}

* {
    box-sizing: border-box;
}

body {
    background: #f6f5f7;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    font-family: 'Montserrat', sans-serif;
    height: 100vh;
    margin: -1.25rem 0 3.125rem;
}

h1 {
    font-weight: bold;
    margin: 0;
}

h2 {
    text-align: center;
}

p {
    font-size: 0.875rem;
    font-weight: 100;
    line-height: 1.25rem;
    letter-spacing: 0.0313rem;
    margin: 1.25rem 0 1.875rem;
}

span {
    font-size: 0.75rem;
}

a {
    color: #333;
    font-size: 0.875rem;
    text-decoration: none;
    margin: 0.9375rem 0;
}

button {
    border-radius: 1.25rem;
    border: 0.0625rem solid #2b80ff;
    background-color: #2b8eff;
    color: #FFFFFF;
    font-size: 0.75rem;
    font-weight: bold;
    padding: 0.75rem 1.5rem;
    letter-spacing: 0.0625rem;
    text-transform: uppercase;
    transition: transform 80ms ease-in;
}

button:active {
    transform: scale(0.95);
}

button:focus {
    outline: none;
}

button.ghost {
    background-color: transparent;
    border-color: #FFFFFF;
}

form {
    background-color: #FFFFFF;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    padding: 0 3.125rem;
    height: 100%;
    text-align: center;
}

input {
    background-color: #eee;
    border: none;
    padding: 0.75rem 0.9375rem;
    margin: 0.2rem 0;
    width: 100%;
}

.container {
    background-color: #fff;
    border-radius: 0.625rem;
    box-shadow: 0 0.875rem 1.75rem rgba(0, 0, 0, 0.25),
        0 0.625rem 0.625rem rgba(0, 0, 0, 0.22);
    position: relative;
    overflow: hidden;
    width: 48rem;
    max-width: 100%;
    min-height: 30rem;
    margin: 3.125rem auto;
}

.form-container {
    position: absolute;
    top: 0;
    height: 100%;
    transition: all 0.6s ease-in-out;
}

.sign-in-container {
    left: 0;
    width: 50%;
    z-index: 2;
}

.container.right-panel-active .sign-in-container {
    transform: translateX(100%);
}

.sign-up-container {
    left: 0;
    width: 50%;
    opacity: 0;
    z-index: 1;
}

.container.right-panel-active .sign-up-container {
    transform: translateX(100%);
    opacity: 1;
    z-index: 5;
    animation: show 0.6s;
}

@keyframes show {

    0%,
    49.99% {
        opacity: 0;
        z-index: 1;
    }

    50%,
    100% {
        opacity: 1;
        z-index: 5;
    }
}

.overlay-container {
    position: absolute;
    top: 0;
    left: 50%;
    width: 50%;
    height: 100%;
    overflow: hidden;
    transition: transform 0.6s ease-in-out;
    z-index: 100;
}

.container.right-panel-active .overlay-container {
    transform: translateX(-100%);
}

.overlay {
    background: #04a5ea;
    background: -webkit-linear-gradient(to right, #2b95ff, #4184ff);
    background: linear-gradient(to right, #2bd5ff, #41adff);
    background-repeat: no-repeat;
    background-size: cover;
    background-position: 0 0;
    color: #FFFFFF;
    position: relative;
    left: -100%;
    height: 100%;
    width: 200%;
    transform: translateX(0);
    transition: transform 0.6s ease-in-out;
}

.container.right-panel-active .overlay {
    transform: translateX(50%);
}

.overlay-panel {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    padding: 0 2.5rem;
    text-align: center;
    top: 0;
    height: 100%;
    width: 50%;
    transform: translateX(0);
    transition: transform 0.6s ease-in-out;
}

.overlay-left {
    transform: translateX(-20%);
}

.container.right-panel-active .overlay-left {
    transform: translateX(0);
}

.overlay-right {
    right: 0;
    transform: translateX(0);
}

.container.right-panel-active .overlay-right {
    transform: translateX(20%);
}

.social-container {
    margin: 0 0;
}

.social-container a {
    border: 0.0625rem solid #DDDDDD;
    border-radius: 50%;
    display: inline-flex;
    justify-content: center;
    align-items: center;
    margin: 0 0.3125rem;
    height: 2.5rem;
    width: 2.5rem;
}


</style>

后端

okey, 终于到了俺们后端的模块了。
那么这里的话,我先来说一下,我们接下来会使用到的一些工具类。

工具类

邮箱工具类

@Service
public class MaliServiceImpl implements MailService {
    /**
     * 邮箱服务类
     */
    @Autowired
    private JavaMailSenderImpl javaMailSender;

    @Value("${spring.mail.username}")
    private String sendMailer;

    /**
     * 检测邮件信息类
     * @param to
     * @param subject
     * @param text
     */
    private void checkMail(String to,String subject,String text){
        if(StringUtils.isEmpty(to)){
            throw new RuntimeException("邮件收信人不能为空");
        }
        if(StringUtils.isEmpty(subject)){
            throw new RuntimeException("邮件主题不能为空");
        }
        if(StringUtils.isEmpty(text)){
            throw new RuntimeException("邮件内容不能为空");
        }
    }

    /**
     * 发送纯文本邮件
     * @param to
     * @param subject
     * @param text
     */
    @Override
    public void sendTextMailMessage(String to,String subject,String text){

        try {
            //true 代表支持复杂的类型
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
            //邮件发信人
            mimeMessageHelper.setFrom(sendMailer);
            //邮件收信人  1或多个
            mimeMessageHelper.setTo(to.split(","));
            //邮件主题
            mimeMessageHelper.setSubject(subject);
            //邮件内容
            mimeMessageHelper.setText(text);
            //邮件发送时间
            mimeMessageHelper.setSentDate(new Date());

            //发送邮件
            javaMailSender.send(mimeMessageHelper.getMimeMessage());
            System.out.println("发送邮件成功:"+sendMailer+"->"+to);

        } catch (MessagingException e) {
            e.printStackTrace();
            System.out.println("发送邮件失败:"+e.getMessage());
        }
    }


    /**
     * 发送html邮件
     * @param to
     * @param subject
     * @param content
     */
    @Override
    public void sendHtmlMailMessage(String to,String subject,String content){

        content="<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                "<meta charset=\"utf-8\">\n" +
                "<title>邮件</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "\t<h3>这是一封HTML邮件!</h3>\n" +
                "</body>\n" +
                "</html>";
        try {
            //true 代表支持复杂的类型
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
            //邮件发信人
            mimeMessageHelper.setFrom(sendMailer);
            //邮件收信人  1或多个
            mimeMessageHelper.setTo(to.split(","));
            //邮件主题
            mimeMessageHelper.setSubject(subject);
            //邮件内容   true 代表支持html
            mimeMessageHelper.setText(content,true);
            //邮件发送时间
            mimeMessageHelper.setSentDate(new Date());

            //发送邮件
            javaMailSender.send(mimeMessageHelper.getMimeMessage());
            System.out.println("发送邮件成功:"+sendMailer+"->"+to);

        } catch (MessagingException e) {
            e.printStackTrace();
            System.out.println("发送邮件失败:"+e.getMessage());
        }
    }

    /**
     * 发送带附件的邮件
     * @param to      邮件收信人
     * @param subject 邮件主题
     * @param content 邮件内容
     * @param filePath 附件路径
     */
    @Override
    public void sendAttachmentMailMessage(String to,String subject,String content,String filePath){
        try {
            //true 代表支持复杂的类型
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
            //邮件发信人
            mimeMessageHelper.setFrom(sendMailer);
            //邮件收信人  1或多个
            mimeMessageHelper.setTo(to.split(","));
            //邮件主题
            mimeMessageHelper.setSubject(subject);
            //邮件内容   true 代表支持html
            mimeMessageHelper.setText(content,true);
            //邮件发送时间
            mimeMessageHelper.setSentDate(new Date());
            //添加邮件附件
            FileSystemResource file = new FileSystemResource(new File(filePath));
            String fileName = file.getFilename();
            mimeMessageHelper.addAttachment(fileName, file);

            //发送邮件
            javaMailSender.send(mimeMessageHelper.getMimeMessage());
            System.out.println("发送邮件成功:"+sendMailer+"->"+to);

        } catch (MessagingException e) {
            e.printStackTrace();
            System.out.println("发送邮件失败:"+e.getMessage());
        }
    }

    /**
     * 发送邮箱验证码
     * @param to
     * @param code
     */
    @Override
    public void sendCodeMailMessage(String to, String code) {
        String subject = "Hlang社区邮箱验证码";
        String text = "验证码10分钟内有效:"+code;
        sendTextMailMessage(to,subject,text);
    }
}

Redis工具类

public class RedisUtils {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    /**
     *  指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     */
    public void expire(String key, long time) {
        if (time > 0) {
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
        } else {
            throw new RuntimeException("超时时间小于0");
        }
    }


    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @param tiemtype 时间类型
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key,TimeUnit tiemtype) {

        return redisTemplate.getExpire(key, tiemtype);
    }

    /**
     *  判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    /**
     *  删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

// ============================String=============================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value  值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
        return true;
    }


    /**
     *  普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        if (time > 0) {
            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
        } else {
            this.set(key, value);
        }
        return true;
    }

    /**
     *  普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time time 时间类型自定义设定
     * @return true成功 false 失败
     */

    public boolean set(String key, Object value, long time,TimeUnit tiemtype) {
        if (time > 0) {
            redisTemplate.opsForValue().set(key, value, time, tiemtype);
        } else {
            this.set(key, value);
        }
        return true;
    }




    /**
     *  递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     *  递减
     * @param delta 要减少几(大于0)
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

// ================================Map=================================

    /**
     *  HashGet
     * @param key 键
     * @param item  项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }


    /**
     *  获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
        return true;
    }
    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        redisTemplate.opsForHash().putAll(key, map);
        if (time > 0) {
            expire(key, time);
        }
        return true;
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        redisTemplate.opsForHash().put(key, item, value);
        return true;
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        redisTemplate.opsForHash().put(key, item, value);
        if (time > 0) {
            expire(key, time);
        }
        return true;
    }
    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }
    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }
    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }
    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
    // ============================set=============================
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    public Set<Object> sGet(String key) {
        return redisTemplate.opsForSet().members(key);
    }
    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }
    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }
    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        final Long count = redisTemplate.opsForSet().add(key, values);
        if (time > 0)
            expire(key, time);
        return count;
    }
    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }
    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        final Long count = redisTemplate.opsForSet().remove(key, values);
        return count;
    }
    // ===============================list=================================
    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束  0 到 -1代表所有值
     */
    public List<Object> lGet(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }
    /**
     * 获取list缓存的长度
     * @param key 键
     */
    public long lGetListSize(String key) {
        return redisTemplate.opsForList().size(key);
    }
    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     */
    public Object lGetIndex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     */
    public boolean lSet(String key, Object value) {
        redisTemplate.opsForList().rightPush(key, value);
        return true;
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     */
    public boolean lSet(String key, Object value, long time) {
        redisTemplate.opsForList().rightPush(key, value);
        if (time > 0) {
            expire(key, time);
        }
        return true;
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     */
    public boolean lSetList(String key, List<Object> value) {
        redisTemplate.opsForList().rightPushAll(key, value);
        return true;
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     */
    public boolean lSetList(String key, List<Object> value, long time) {
        redisTemplate.opsForList().rightPushAll(key, value);
        if (time > 0) {
            expire(key, time);
        }
        return true;
    }
    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        redisTemplate.opsForList().set(key, index, value);
        return true;
    }

}

IP地址工具类


public class IPAddrUtils {

    public static HttpServletRequest GetHttpServletRequest(){
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        assert servletRequestAttributes != null;
        return servletRequestAttributes.getRequest();
    }

    public static String GetIPAddr() {
        HttpServletRequest request = GetHttpServletRequest();
        return GetIPAddr(request);
    }

    public static String GetIPAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (ipAddress.equals("127.0.0.1")) {
                    // 根据网卡取本机配置的IP
                    try {
                        ipAddress = InetAddress.getLocalHost().getHostAddress();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                }
            }
            // 通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null) {
                if (ipAddress.contains(",")) {
                    return ipAddress.split(",")[0];
                } else {
                    return ipAddress;
                }
            } else {
                return "";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }
}

基本上我们的这个工具类就这些。这里之所以给出的话,主要是因为我们这个玩意是通用的。

限流切面

那么在我们开始之前,我们先来实现一下我们一些限流的切面。比如我们那个邮箱验证码接口,你前端60s后访问,有啥用,后端还是可以访问的话,可不行。
所以我们定义一个注解

/**
 * 默认限制间隔时间是1秒,限制访问
 * */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestrictRequest {
    int frequency() default 1;
}

然后写个切面的类

/**
 * 实现接口限流,这里是针对IP进行限流
 * */
@Aspect
@Component
public class RestrictRequestAspect {

    private RedisUtils redisUtils;

    public RestrictRequestAspect(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }
    @Autowired
    public void setRedisUtils(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    @Pointcut("@annotation(com.huterox.hlangserver.anno.RestrictRequest)")
    public void verification() {}

    @Around("verification()")
    public R verification(ProceedingJoinPoint joinPoint) throws Throwable {

        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        String ipAddr = IPAddrUtils.GetIPAddr();

        //获取注解的值(暂停值)
        Signature signature = joinPoint.getSignature();
        MethodSignature msg=(MethodSignature) signature;
        Object target = joinPoint.getTarget();
        Method method = target.getClass().getMethod(msg.getName(), msg.getParameterTypes());
        RestrictRequest annotation = method.getAnnotation(RestrictRequest.class);
        int frequency = annotation.frequency();

        //进行校验审核
        String key = RedisTransKey.getServerRestrict(methodName + ":" + ipAddr + ":");
        if(redisUtils.get(key)!=null){
            return R.error(BizCodeEnum.OVER_REQUESTS.getCode(),BizCodeEnum.OVER_REQUESTS.getMsg());
        }
        //设置限流标志
        redisUtils.set(key,"1",frequency, TimeUnit.SECONDS);
        return (R) joinPoint.proceed();
    }
}

同样的,这次我们权限验证都不要第三方框架来做,自己斜切面美滋滋。

注册实现

刚刚在前端我们说了这个基本注册实现,所以我们这边也就是开放两个接口。

@Service
public class RegisterServiceImpl implements RegisterService {

    private RedisUtils redisUtils;

    private HgUserService hgUserService;

    private HgInfoService hgInfoService;

    private MailService mailService;

    @Value("${spring.mail.limit}")
    Integer limit;
    @Value("${spring.mail.limitTime}")
    Integer limitTime;

    public RegisterServiceImpl(RedisUtils redisUtils, HgUserService hgUserService, HgInfoService hgInfoService, MailService mailService) {
        this.redisUtils = redisUtils;
        this.hgUserService = hgUserService;
        this.hgInfoService = hgInfoService;
        this.mailService = mailService;
    }

    @Autowired
    public void setRedisUtils(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }
    @Autowired
    public void setHgUserService(HgUserService hgUserService) {
        this.hgUserService = hgUserService;
    }

    @Autowired
    public void setHgInfoService(HgInfoService hgInfoService) {
        this.hgInfoService = hgInfoService;
    }

    @Autowired
    public void setMailService(MailService mailService) {
        this.mailService = mailService;
    }

    @Override
    public R register(RegisterQ registerQ) {
        //先验证验证码
        Object o = redisUtils.get(RedisTransKey.getServerEmail(registerQ.getUsername()));
        EmailCode emailCode = JSON.parseObject(o.toString(), EmailCode.class);
        if (emailCode.getUsername().equals(registerQ.getUsername())
                &&emailCode.getPassword().equals(registerQ.getPassword())
                &&emailCode.getCode().equals(registerQ.getEmailCode())
                &&emailCode.getEmail().equals(registerQ.getEmail())
        )
        {
            //存储基本的用户信息
            HgInfoEntity hgInfoEntity = new HgInfoEntity();
            hgInfoEntity.setNickname(registerQ.getNickname());
            hgInfoService.save(hgInfoEntity);
            //验证通过创建用户
            HgUserEntity hgUserEntity = new HgUserEntity();
            hgUserEntity.setPassword(SecurityUtils.encodePassword(registerQ.getPassword()));
            hgUserEntity.setUsername(registerQ.getUsername());
            hgUserEntity.setEmail(registerQ.getEmail());
            //生成userid
            hgUserEntity.setUserid(UUIDUtils.get20UUID());
            hgUserEntity.setState(UserSateEnum.NORMAL.getState());
            hgUserEntity.setInfoid(hgInfoEntity.getInfoid());
            hgUserEntity.setUpdateTime(new Date());
            hgUserService.save(hgUserEntity);
            //删除验证码
            redisUtils.del(RedisTransKey.getServerEmail(registerQ.getUsername()));
            return R.ok();
        }
        return R.error(BizCodeEnum.BAD_PUTDATA.getCode(),BizCodeEnum.BAD_PUTDATA.getMsg());
    }

    @Override
    public R emilCode(EmailQ emailQ) {
        //生成验证码
        String code = CodeUtils.creatCode(6);
        EmailCode emailCode = new EmailCode();
        emailCode.setCode(code);
        emailCode.setPassword(emailQ.getPassword());
        emailCode.setUsername(emailQ.getUsername());
        emailCode.setEmail(emailQ.getEmail());

        //设置10分钟的验证码过期时间
        redisUtils.set(RedisTransKey.setServerEmail(emailQ.getUsername()),
            emailCode,limitTime, TimeUnit.MINUTES
        );
        //发送验证码
        mailService.sendCodeMailMessage(emailQ.getEmail(), emailCode.getCode());

        return R.ok();
    }
}

登录实现

之后来看到我们的登录实现,那么这里的话,我们两个部分

  1. 生成token
  2. 校验token

当然这里的话,我们用到还是单token,本来我是打算用用双token方案,然后看到无感刷新这几个字之后,一个脱裤子放屁的感觉突然涌上心头。这玩意除了骗骗小白爬虫一点儿用都没有
别人拿到你的token之后,虽然说,你访问接口,或者说验证的时候,用到不是这个token(长期),这个长期token是用来申请临时token的,然后由临时token进行验证。但问题是,我能拿到你的长期token,然后再拿到临时token,通过这个临时token去访问不就完了嘛,你还给我来个无感刷新,好家伙,请问除了让我js debug一下这个麻烦一点还有啥。所以还不如说,你在验证token的时候再验证IP,如果IP不一样直接token作废,如果IP还一样,我有理由怀疑就是你小子本人在爬取网站

生成token

这里的话,我们还有一个token生成类


public class JwtTokenUtil {

    private static final String secret;
    private static final Long expiration;
    private static Map<String, Object> header;
    static {
        secret="Hlang-Huterox";
        expiration = 31*24*60*60*1000L;
        header=new HashMap<>();
        header.put("typ", "jwt");


    }


    /**
     * 生成token令牌
     * @return 令token牌
     */
    public static String generateToken(User user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("username", user.getUsername());
        claims.put("userid",user.getUserid());
        claims.put("created", new Date());
        return generateToken(claims);
    }

    /**
     * @param token 令牌
     * @return 用户名
     */
    public static String GetUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = (String) claims.get("username");
        } catch (Exception e) {
            username = null;
        }
        return username;
    }
    public static String GetUserIDFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = (String) claims.get("userid");
        } catch (Exception e) {
            username = null;
        }
        return username;
    }
    /**
     * 判断令牌是否过期
     * @param token 令牌
     * @return 是否过期
     */
    public static Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 刷新令牌
     *
     * @param token 原令牌
     * @return 新令牌
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put("created", new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 验证令牌
     * @return 是否有效
     */
    public static Boolean validateToken(String token, User user) {
        String username = GetUserNameFromToken(token);
        return (username.equals(user.getUsername()) && !isTokenExpired(token));
    }


    /**
     * 从claims生成令牌,如果看不懂就看谁调用它
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private static String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + expiration);
        return Jwts.builder()
                .setHeader(header)
                .setClaims(claims)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 从令牌中获取数据声明,如果看不懂就看谁调用它
     *
     * @param token 令牌
     * @return 数据声明
     */
    public static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

}

然后的话,我们拿到登录接口

public class LoginServiceImpl implements LoginService {

    private HgUserService hgUserService;

    private HgInfoService hgInfoService;

    private RedisUtils redisUtils;

    public LoginServiceImpl(HgUserService hgUserService, HgInfoService hgInfoService, RedisUtils redisUtils) {
        this.hgUserService = hgUserService;
        this.hgInfoService = hgInfoService;
        this.redisUtils = redisUtils;
    }

    @Autowired
    public void setHgUserService(HgUserService hgUserService) {
        this.hgUserService = hgUserService;
    }

    @Autowired
    public void setRedisUtils(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    @Autowired
    public void setHgInfoService(HgInfoService hgInfoService) {
        this.hgInfoService = hgInfoService;
    }

    @Override
    public R login(LoginQ loginQ) {

        String userName = loginQ.getUserName();
        String passWord = loginQ.getPassWord();
        HgUserEntity userEntity = hgUserService.getOne(
                new QueryWrapper<HgUserEntity>().eq("username", userName)
        );
        if(userEntity!=null){
            //签发token
            User user = new User();
            user.setUserid(userEntity.getUserid());
            user.setUsername(userName);
            user.setPassword(passWord);
            String token = JwtTokenUtil.generateToken(user);

            //这里我们直接把IP地址也作为验证的一个条件
            Token tokenSave = new Token();
            tokenSave.setToken(token);
            tokenSave.setIp(IPAddrUtils.GetIPAddr());
            tokenSave.setLoginType(loginQ.getType());

            if(SecurityUtils.matchesPassword(passWord,userEntity.getPassword())){

                //将token存到redis当中
                if(loginQ.getType().equals(LoginType.PcType)){
                    redisUtils.set(RedisTransKey.setServerToken(userEntity.getUserid()+":"+LoginType.PcType),
                            tokenSave,30, TimeUnit.DAYS);
                }else if(loginQ.getType().equals(LoginType.MobileType)){
                    redisUtils.set(RedisTransKey.setServerToken(userEntity.getUserid()+":"+LoginType.MobileType),
                            tokenSave,30, TimeUnit.DAYS);
                }

                //组装返回消息
                HgInfoEntity infoEntity = hgInfoService.getById(userEntity.getInfoid());
                UserLoginR userLoginR = new UserLoginR();
                userLoginR.setUserid(userEntity.getUserid());
                userLoginR.setToken(token);
                userLoginR.setNickname(infoEntity.getNickname());
                userLoginR.setUpic(infoEntity.getUPic());
                userLoginR.setLogin(true);


                return R.ok(BizCodeEnum.SUCCESSFUL.getMsg()).put("userLoginR",userLoginR);
            }
        }
        return R.error(BizCodeEnum.NO_SUCHUSER.getCode(), BizCodeEnum.NO_SUCHUSER.getMsg());
    }
}

验证token

那么这里同样我们是搞了个注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedLogin {
}

然后做验证

/**
 * 负责专门校验,用户有没有登录用的
 * */
@Component
@Aspect
@Slf4j
public class VerificationAspect {


    private RedisUtils redisUtils;

    public VerificationAspect(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    @Autowired
    public void setRedisUtils(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    @Pointcut("@annotation(com.huterox.hlangserver.anno.NeedLogin)")
    public void verification() {}

    /**
     * 我们这里再直接抛出异常,反正有那个谁统一异常类
     */

    @Around("verification()")
    public Object verification(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        assert servletRequestAttributes != null;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String ipAddr = IPAddrUtils.GetIPAddr();

        //分登录的设备进行验证
        String loginType = request.getHeader("loginType");
        String userid = request.getHeader("userid");
        String tokenUser = request.getHeader("loginToken");
        String tokenKey = RedisTransKey.getServerToken(userid + ":" + loginType);
        if(tokenUser==null || userid==null || loginType==null){
            throw new BadLoginParamsException();
        }

        if(redisUtils.hasKey(tokenKey)){
            if(loginType.equals(LoginType.PcType)){
                Object o = redisUtils.get(tokenKey);
                Token loginToken = JSON.parseObject(o.toString(), Token.class);
                if(!(loginToken.getToken().equals(tokenUser)&&loginToken.getIp().equals(ipAddr))){
                    throw new BadLoginQException();
                }
            }else if (loginType.equals(LoginType.MobileType)){
                Object o = redisUtils.get(tokenKey);
                Token loginToken = JSON.parseObject(o.toString(), Token.class);
                if(!(loginToken.getToken().equals(tokenUser)&&loginToken.getIp().equals(ipAddr))){
                    throw new BadLoginQException();
                }
            }
        }else {
            throw new NotLoginException();
        }
        return proceedingJoinPoint.proceed();
    }

}

总结

okey,接下来几天慢慢写基本业务即可,在业务方面我们还有一个netty的消息和在线聊天室要做。之后就是算法层面有个推荐算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Huterox

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值