基于VUE3+Layui从头搭建通用后台管理系统(前端篇)二:登录界面及对应功能实现

一、本章内容

  本章介绍系统登录界面、登录流程、登录接口等相关内容的开发,实现包括账号密码登录、短信验证登录等不同的登录方式,使用svg-capter生成图形验证码,使用expressjwt实现登录token的生成及验证。

1. 详细课程地址: https://edu.csdn.net/course/detail/38183
2. 源码下载地址: 点击下载

二、界面预览

请添加图片描述
在这里插入图片描述
在这里插入图片描述

三、开发视频

基于VUE3+Layui从头搭建通用后台管理系统合集-登录界面框架搭建

四、登录流程说明

用户 后台 浏览器 请求获取验证码 返回验证码 读取本地localStore中存储的账号信息 返回往期存储的账号信息 输入用户名、密码及图形验证码调用登录接口 1.验证参数是否合法. 2.验证账号密码是否合法 3.合法后生成token 返回登录的用户信息及token标识 用户 后台 浏览器

五、登录接口实现

5.1 依赖库安装

  安装登录相关需要依赖的相关库文件,包括验证码生成、jwt登录验证token的生成及引用等。
1. svg库安装

npm install svg-captcha --save

2. jwt库安装

npm install jsonwebtoken --save
npm install express-jwt --save

3. 引入使用

//导入用于生成JWT字符串的包
const jwt = require('jsonwebtoken');
//导入用于将客户端发送过来的JWT字符串解析还原成JSON对象的包
const expressJWT = require('express-jwt');

//使用 app.use() 注册将JWT字符串解析还原成JSON对象的中间件
//.unless() 方法通过正则表达式 指定哪些接口不需要通过权限
//正则中 '\'用来转义 '^'表示指定以什么开头的字符串
app.use(expressJWT.expressjwt({
  secret: config.secretKey,
  requestProperty: 'token',
  algorithms: ['HS256'],
  getToken(req) {
    let token = req.headers.token;
    if (token) {
      try {//自动刷新token
        let decodedToken = jwt.decode(token, { complete: true });
        if (decodedToken && decodedToken.payload && decodedToken.payload.exp) {
          console.log("初始化时间:" + new Date(decodedToken.payload.iat * 1000).toLocaleString())
          console.log("当前时间:" + new Date().toLocaleString())
          console.log("过期时间:" + new Date(decodedToken.payload.exp * 1000).toLocaleString())
          if (decodedToken.payload.exp * 1000 > new Date().getTime()) {
            if (new Date().getTime() - req.session.last < config.expiresTime * 1000) {
              //白名单中清除此token
              delete tokenData[token];
              token = jwt.sign({ account: decodedToken.payload.account }, config.secretKey, { expiresIn: config.expiresTime });
              req.headers.newToken = token;
              //将有效的token添加到白名单中
              tokenData[token] = 1;
              console.log("自动刷新token")
            }
          }
        }
      }
      catch (err) {
      }
    }
    return token;
  },
  onExpired(req, err) {
    //白名单中清除此token
    delete tokenData[req.headers.token];
    throw err;
  }
}).unless({ path: [/^\/login\//, /^\/resetpwd\//, /^\/register\//] }));

5.2 图形验证码接口

//验证码生成
const svgCaptcha = require('svg-captcha');
/**
 * 生成登录验证码
 */
router.get('/captcha', function (req, res, next) {
    let width = req.query.width;
    let height = req.query.height;
    if (!width) {
        width = 100;
    }
    if (!height) {
        height = 50;
    }
    let captcha = svgCaptcha.create({
        size: 4,  //验证码长度
        width: width, //svg宽度
        height: height, //svg高度
        noise: 5, //干扰线条数
        fontSize: 35, //字体大小
        ignoreChars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxz',   //验证码字符中排除
        color: false // 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有           
    });
    req.session.captcha = captcha.text;
    req.session.captchaDate = new Date().getTime();
    res.type('svg');
    res.status(200).send(captcha.data);
});

5.3 账号密码登录接口

/**
 * 账号登录
 */
router.post('/password', function (req, res, next) {
    let account = req.body.account;
    let password = req.body.password;
    let captchaText = req.body.captcha;
    if (!account || !password || !captchaText) {
        res.send({
            code: 400,
            msg: '参数为空',
            data: null
        });
        return;
    }
    if (!req.session.captcha
        || req.session.captcha != captchaText
        || !req.session.captchaDate
        || (new Date().getTime() - req.session.captchaDate) > config.captchaTimeout) {
        res.send({
            code: 400,
            msg: '验证码错误',
            data: null
        });
        return;
    }
    // 登录逻辑,检测账号以及密码是否正确,登录成功后生成jwt的token信息,并返回到前端
    let userInfo = uesrData.find((item) => {
        return item.account == account && item.password == password;
    })
    if (!userInfo) {
        res.send({
            code: 400,
            msg: '账号密码错误',
            data: null
        });
        return;
    }
    //登录成功
    //在登录成功之后 调用 jwt.sign() 方法生成JWT字符串 并通过 token 属性发送给客户端
    //参数1: 用户的信息对象
    //参数2: 加密的密钥
    //参数3: 配置对象 可以配置当前 token 的有效期
    const tokenStr = jwt.sign({ account: userInfo.account }, config.secretKey, { expiresIn: config.expiresTime });
    //将有效的token添加到白名单中
    tokenData[tokenStr] = 1;
    res.setHeader('token', tokenStr)
    res.send({
        code: 200,
        msg: '',
        data: userInfo
    });
});

5.4 获取短信验证码接口


//1.获取短信验证码
router.post('/getSmsCode', function (req, res, next) {
    let account = req.body.account;
    if (!account) {
        res.send({
            code: 400,
            msg: '参数为空',
            data: null
        })
        return;
    }
    let user = userData.find((item) => {
        return item.account == account || item.phone == account;
    })
    if (!user) {
        res.send({
            code: 400,
            msg: '账号未注册',
            data: null
        })
        return;
    }
    //校验发送是否太频繁
    let lastCodes = smsCodeData.filter((item) => {
        return item.account == user.account;
    })
    if (lastCodes && lastCodes.length > 0 && (new Date().getTime() - lastCodes[lastCodes.length - 1].date) < config.smsLimtTime) {
        res.send({
            code: 400,
            msg: '发送太频繁,请稍后再试……',
            data: null
        })
        return;
    }
    //生成验证码并发送短信
    let code = parseInt(Math.random() * 1000000);
    console.log(code);
    //TODO 调用短信发送接口,发送短信
    smsCodeData.push({
        code: code,
        account: user.account,
        date: new Date().getTime(),
        type: '1'//类型:1是登录验证码,2找回密码验证
    })
    res.send({
        code: 200,
        msg: '验证码已发送,5分钟之内有效。',
        data: null
    })
});

5.5 短信验证登录接口

//2.短信验证码登录
router.post('/smsCode', function (req, res, next) {
    let account = req.body.account;
    let smscode = req.body.smscode;
    let captchaText = req.body.captcha;
    if (!account || !smscode || !captchaText) {
        res.send({
            code: 400,
            msg: '参数为空',
            data: null
        });
        return;
    }
    if (!req.session.captcha
        || req.session.captcha != captchaText
        || !req.session.captchaDate
        || (new Date().getTime() - req.session.captchaDate) > config.captchaTimeount) {
        res.send({
            code: 400,
            msg: '验证码错误',
            data: null
        });
        return;
    }
    // 登录逻辑,检测账号以及密码是否正确,登录成功后生成jwt的token信息,并返回到前端
    let userInfo = uesrData.find((item) => {
        return item.account == account || item.phone == account;
    })
    if (!userInfo) {
        res.send({
            code: 400,
            msg: '账号不存在',
            data: null
        });
        return;
    }
    //短信验证码校验
    let lastCodes = smsCodeData.filter((item) => {
        return item.account == userInfo.account && item.code == smscode && item.type == '1';
    })
    if (!lastCodes || lastCodes.length <= 0 || (new Date().getTime() - lastCodes[lastCodes.length - 1].date) > config.smsCodeTimeout) {
        res.send({
            code: 400,
            msg: '验证码已过期',
            data: null
        })
        return;
    }
    //登录成功
    //在登录成功之后 调用 jwt.sign() 方法生成JWT字符串 并通过 token 属性发送给客户端
    //参数1: 用户的信息对象
    //参数2: 加密的密钥
    //参数3: 配置对象 可以配置当前 token 的有效期
    const tokenStr = jwt.sign({ account: userInfo.account }, config.secretKey, { expiresIn: config.expiresTime });
    //将有效的token添加到白名单中
    tokenData[tokenStr] = 1;
    res.setHeader('token', tokenStr)
    res.send({
        code: 200,
        msg: '',
        data: userInfo
    });
});

5.6 后端配置文件

module.exports = {
    /**
     * token加密秘钥
     */
    secretKey: 'junjunjun23141321321',
    /**
     * token的过期时间
     */
    expiresTime: 10,
    /**
     * 短信验证码发送限制时间
     */
    smsLimtTime: 60 * 1000,
    /**
     * 验证码过期时间
     */
    captchaTimeout: 5 * 60 * 1000,
    /**
     * 短信验证码的过期时间
     */
    smsCodeTimeout: 5 * 60 * 1000,
    /**
     * 发送邮件的邮箱stmp授权码
     */
    emailserce: "*************"
}

六、登录功能实现

6.1 登录主页框架

1. html代码:

<template>
    <lay-container>
        <div class="login-header">
            <div class="header-logo">
                <img src="@/assets/logo2.png" />
            </div>
            <div class="header-btns">
                <lay-button type="danger" @click="() => router.push('/')">官方网站</lay-button>
                <lay-button type="danger" @click="() => router.push('/register')">注册账号</lay-button>
            </div>
        </div>
        <div class="login-panel">
            <div class="content-panel">
                <div class="login-logo">
                    <img src="@/assets/logo1.png" />
                </div>
                <div class="split-line"></div>
                <div class="login-content">
                    <div class="tab-items">
                        <div @click="(e) => currentSelectTab = 0" class="tab-item "
                            :class="(currentSelectTab === 0) ? 'tab-item-selected' : ''">账号密码登录</div>
                        <div @click="(e) => currentSelectTab = 1" class="tab-item"
                            :class="(currentSelectTab === 1) ? 'tab-item-selected' : ''">短信验证登录</div>
                    </div>
                    <div>
                        <UsernameLoginVue v-if="currentSelectTab === 0"></UsernameLoginVue>
                        <PhoneCodeLogin v-else></PhoneCodeLogin>
                    </div>
                </div>
            </div>
        </div>
        <div class="login-footer">版权所有:@copyright 军军君</div>
    </lay-container>
</template>

2. js代码:

<script setup>
import { ref } from "vue"

import { useRouter } from 'vue-router';
//路由信息
const router = useRouter();

import PhoneCodeLogin from "./components/PhoneCodeLogin.vue";
import UsernameLoginVue from "./components/UsernameLogin.vue";

//当前选中的tab
const currentSelectTab = ref(0);

</script>

3. css代码:

<style scoped>
.layui-container {
    padding: 0;
}

.login-panel {
    width: 100vw;
    height: 100vh;
    background: linear-gradient(159deg, #1f6fc7, #2196f3, #1177c9);
    display: flex;
    justify-items: center;
    align-items: center;
}

.content-panel {
    background: #fff;
    border-radius: 10px;
    margin: 0 auto;
    width: 50vw;
    height: 50vh;
    box-shadow: 0 0 13px 2px #7c7c7c9e;
    display: flex;
}

.login-logo {
    width: 40%;
    text-align: center;
    display: flex;
    justify-items: center;
    align-items: center;
}

.login-logo img {
    margin: 0 auto;
    width: 200px;
}

.split-line {
    width: 1px;
    height: 100%;
    background: linear-gradient(182deg, #02835300, #03a9f4, #02835300);
}

.login-content {
    flex: 1;
    padding: 20px;
    padding-top: 40px;
}

.tab-items {
    width: 300px;
    height: 35px;
    line-height: 35px;
    display: flex;
    text-align: center;
    margin: 0 auto;
    font-size: 1rem;
}

.tab-items .tab-item {
    flex: 1;
    cursor: pointer;
}

.tab-items .tab-item-selected {
    color: #e91e63;
}

.login-header {
    position: fixed;
    height: 60px;
    padding: 10px;
    display: flex;
    width: calc(100% - 20px);
}

.login-header .header-logo {
    flex: 1;
}

.login-header .header-logo img {
    height: 60px;
}

.login-footer {
    position: fixed;
    height: 40px;
    width: 100%;
    bottom: 0px;
    line-height: 40px;
    text-align: center;
    font-size: .75rem;
    color: #fff;
}

.header-btns:deep(.layui-btn-danger) {
    background: #00000052;
    border: none;
}
</style>

6.2 账号登录界面

1. html代码:

<!-- 用户名密码登录 -->
<template>
    <div class="username-login">
        <lay-form :model="loginData" ref="usernameForm" :rules="rules" required>
            <lay-form-item label="账号:" prop="account" size="lg">
                <lay-input v-model="loginData.account" placeholder="请输入账号" suffix-icon="layui-icon-username"></lay-input>
            </lay-form-item>
            <lay-form-item label="密码:" prop="password" size="lg">
                <lay-input v-model="loginData.password" type="password" placeholder="请输入密码"
                    suffix-icon="layui-icon-password"></lay-input>
            </lay-form-item>
            <lay-form-item label="验证码:" prop="captcha">
                <div class="flex">
                    <div class="flex-item">
                        <lay-input v-model="loginData.captcha" placeholder="请输入验证码"></lay-input>
                    </div>
                    <div class="captcha">
                        <img :src="captchaSrc" @click="refreshCaptcha" />
                    </div>
                </div>
            </lay-form-item>
            <lay-form-item label="">
                <div class="flex">
                    <div class="flex-item">
                        <lay-checkbox name="like" skin="primary" v-model="rememberMe" value="1" label="记住账号"></lay-checkbox>
                    </div>
                    <div class="flex-item resetpwd">
                        <router-link to="/resetpwd" style="color:#FF5722">找回密码</router-link>
                        <router-link to="/register" style="margin-left:10px;color:#16baaa">注册账号</router-link>
                    </div>
                </div>
            </lay-form-item>
            <lay-form-item style="text-align: center">
                <lay-button class="submit-btn" @click="submit" type="normal">提交</lay-button>
            </lay-form-item>
        </lay-form>
    </div>
</template>

2. js代码:

<script setup>
import { reactive, ref, onMounted } from 'vue';
import api from '@/utils/api';
import { layer } from '@layui/layer-vue'
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user'

// 数据状态管理
const userStore = useUserStore()
//路由信息
const router = useRouter();

//登录表单绑定的数据
const loginData = reactive({
    account: '',
    password: '',
    captcha: ''
})
//表单示例
const usernameForm = ref();
//表单检验规则
const rules = ref({
    account: {
        type: 'string'
    },
    password: {
        type: 'string'
    },
    captcha: {
        type: 'string'
    },
})
//记住密码
const rememberMe = ref("1");
/**
 * 
 */
const captchaSrc = ref("/api/login/captcha?width=120&height=36");
/**
 * 刷新验证码
 */
const refreshCaptcha = () => {
    captchaSrc.value = "/api/login/captcha?width=120&height=36&t_=" + new Date().getTime()
}
/**
 * 提交表单,完成登录
 */
const submit = () => {
    let loadding = layer.load(0)
    usernameForm.value.validate((isValidate, model, errors) => {
        if (!isValidate) {
            layer.close(loadding);
            layer.msg(errors[0].message, { icon: 2, time: 1000 })
            return;
        }
        // 调用接口完成登录,登录成功后如果是选中了记住账号,则自动保存账号密码到本地
        api.login(loginData.account, loginData.password, loginData.captcha).then((res) => {
            refreshCaptcha();
            layer.close(loadding)
            if (!res || res.status != 200 || !res.data || !res.data.data || res.data.code != 200) {
                layer.msg(res.data.msg ? res.data.msg : '登录失败', { icon: 2, time: 1000 });
                return;
            }
            let userInfo = res.data.data;
            let token = res.headers['token'];
            if (!userInfo || !token) {
                layer.msg('登录失败', { icon: 2, time: 1000 });
                return;
            }
            //检测是否记录账号,如果需要记录,则记录到本地
            localStorage.setItem("rememberMe", rememberMe.value ? '1' : '0');
            if (rememberMe.value) {
                localStorage.setItem("account", loginData.account);
                localStorage.setItem("password", loginData.password);
            }
            console.log("登录成功", userInfo, token);
            // 存储登录状态
            userStore.login(userInfo, token);
            //并转到首页
            router.push({
                path: '/'
            })
        }).catch((error) => {
            refreshCaptcha();
            layer.close(loadding)
            layer.msg(error.message, { icon: 2, time: 1000 });
        })
    });
}
onMounted(() => {
    //读取本地存储的账号信息
    let rememberMe_ = localStorage.getItem("rememberMe");
    rememberMe.value = (rememberMe_ && rememberMe_ == '1') ? '1' : 0;
    if (rememberMe.value) {//记住账号密码了,则读取账号密码
        let account_ = localStorage.getItem("account");
        let password_ = localStorage.getItem("password");
        loginData.account = account_;
        loginData.password = password_;
    }
})
</script>

3. css代码:

<style scoped>
.username-login {
    width: 90%;
    margin: 0 auto;
    padding-top: 40px;
}

.username-login:deep(.layui-form-label),
.username-login:deep(input) {
    font-size: .875rem
}

.submit-btn {
    width: 78%;
    margin-left: 108px;
    --button-border-radius: 35px
}

.captcha {
    width: 120px;
}

.captcha img {
    width: 100%;
    cursor: pointer;
}

.resetpwd {
    text-align: right;
    font-size: .875rem;
    line-height: 36px;
}
</style>

6.3 短信验证登录

1. html代码:

<!-- 短信验证码登录 -->
<template>
    <div class="phonecode-login">
        <lay-form :model="loginData" ref="phonecodeForm" :rules="rules" required>
            <lay-form-item label="账号:" prop="account" size="lg">
                <lay-input v-model="loginData.account" placeholder="请输入账号" suffix-icon="layui-icon-username"></lay-input>
            </lay-form-item>
            <lay-form-item label="短信验证码:" prop="smscode" size="lg">
                <div class="flex">
                    <div class="flex-item">
                        <lay-input v-model="loginData.smscode" type="smscode" placeholder="请输入短信验证码"
                            suffix-icon="layui-icon-password"></lay-input>
                    </div>
                    <div class="smscode">
                        <lay-button class="smscode-btn" @click="getSmsCode" type="normal" :disabled="smsCodeTime > 0">{{
                            smsCodeTime > 0 ?
                            smsCodeTime + '秒后再试' : '获取验证码'
                        }}</lay-button>
                    </div>
                </div>
            </lay-form-item>
            <lay-form-item label="图形验证码:" prop="captcha">
                <div class="flex">
                    <div class="flex-item">
                        <lay-input v-model="loginData.captcha" placeholder="请输入图形验证码"></lay-input>
                    </div>
                    <div class="captcha">
                        <img :src="captchaSrc" @click="refreshCaptcha" />
                    </div>
                </div>
            </lay-form-item>
            <lay-form-item label="">
                <div class="flex">
                    <div class="flex-item">
                        <lay-checkbox name="like" skin="primary" v-model="rememberMe" value="1" label="记住账号"></lay-checkbox>
                    </div>
                    <div class="flex-item resetpwd">
                        <router-link to="/resetpwd" style="color:#FF5722">找回密码</router-link>
                        <router-link to="/register" style="margin-left:10px;color:#16baaa">注册账号</router-link>
                    </div>
                </div>
            </lay-form-item>
            <lay-form-item style="text-align: center">
                <lay-button class="submit-btn" @click="submit" type="normal">提交</lay-button>
            </lay-form-item>
        </lay-form>
    </div>
</template>

2. js代码:

<script setup>
import { reactive, ref, onMounted, onUnmounted } from 'vue';
import api from '@/utils/api';
import { layer } from '@layui/layer-vue'
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user'

// 数据状态管理
const userStore = useUserStore()
//路由信息
const router = useRouter();

//登录表单绑定的数据
const loginData = reactive({
    account: '',
    smscode: '',
    captcha: ''
})
//表单示例
const phonecodeForm = ref();
//表单检验规则
const rules = ref({
    account: {
        type: 'string'
    },
    smscode: {
        type: 'string'
    },
    captcha: {
        type: 'string'
    },
})
//获取验证码的到期时间
const smsCodeTime = ref(0);
/**
 * 验证码获取倒计时
 */
let smsCodeTimer = null;
//记住密码
const rememberMe = ref("1");
/**
 * 
 */
const captchaSrc = ref("/api/login/captcha?width=120&height=36");
/**
 * 刷新验证码
 */
const refreshCaptcha = () => {
    captchaSrc.value = "/api/login/captcha?width=120&height=36&t_=" + new Date().getTime()
}
/**
 * 获取短信验证码
 */
const getSmsCode = () => {
    let account = loginData.account;
    if (!account) {
        layer.msg("账号不能为空");
        return;
    }
    if (smsCodeTime.value > 0) {
        layer.msg(smsCodeTime.value + "秒之后再试");
        return;
    }
    let loadding = layer.load(0)
    api.getSmsCode(account).then((res) => {
        layer.close(loadding);
        if (!res || res.status != 200 || !res.data || res.data.code != 200) {
            layer.msg((res.data.msg ? res.data.msg : '验证码获取异常'), { icon: 2, time: 1000 });
            return;
        }
        //启动验证码获取倒计时
        smsCodeTime.value = 60;
        smsCodeTimer = setInterval(() => {
            smsCodeTime.value--;
            if (smsCodeTime.value <= 0) {
                clearInterval(smsCodeTimer);
                smsCodeTime.value = 0;
                smsCodeTimer = null;
            }
        }, 1000);

        layer.notifiy({
            title: "提示",
            content: res.data.msg,
            icon: 1
        })
    }).catch((error) => {
        layer.close(loadding)
        layer.msg(error.message, { icon: 2, time: 1000 });
    });
}
/**
 * 提交表单,完成登录
 */
const submit = () => {
    let loadding = layer.load(0)
    phonecodeForm.value.validate((isValidate, model, errors) => {
        if (!isValidate) {
            layer.close(loadding);
            layer.msg(errors[0].message, { icon: 2, time: 1000 })
            return;
        }
        // 调用接口完成登录,登录成功后如果是选中了记住账号,则自动保存账号密码到本地
        api.loginBySmsCode(loginData.account, loginData.smscode, loginData.captcha).then((res) => {
            refreshCaptcha();
            layer.close(loadding)
            if (!res || res.status != 200 || !res.data || !res.data.data || res.data.code != 200) {
                layer.msg(res.data.msg ? res.data.msg : '登录失败', { icon: 2, time: 1000 });
                return;
            }
            let userInfo = res.data.data;
            let token = res.headers['token'];
            if (!userInfo || !token) {
                layer.msg('登录失败', { icon: 2, time: 1000 });
                return;
            }
            //检测是否记录账号,如果需要记录,则记录到本地
            localStorage.setItem("rememberMe", rememberMe.value ? '1' : '0');
            if (rememberMe.value) {
                localStorage.setItem("account", loginData.account);
            }
            console.log("登录成功", userInfo, token);
            // 存储登录状态
            userStore.login(userInfo, token);
            //并转到首页
            router.push({
                path: '/'
            })
        }).catch((error) => {
            refreshCaptcha();
            layer.close(loadding)
            layer.msg(error.message, { icon: 2, time: 1000 });
        })
    });
}
onMounted(() => {
    //读取本地存储的账号信息
    let rememberMe_ = localStorage.getItem("rememberMe");
    rememberMe.value = (rememberMe_ && rememberMe_ == '1') ? '1' : 0;
    if (rememberMe.value) {//记住账号密码了,则读取账号密码
        let account_ = localStorage.getItem("account");
        loginData.account = account_;
    }
})
onUnmounted(() => {
    //停止定时器
    smsCodeTimer && clearInterval(smsCodeTimer);
})
</script>

3. css代码:

<style scoped>
.phonecode-login {
    width: 90%;
    margin: 0 auto;
    padding-top: 40px;
}

.phonecode-login:deep(.layui-form-label),
.phonecode-login:deep(input) {
    font-size: .875rem
}

.submit-btn {
    width: 78%;
    margin-left: 108px;
    --button-border-radius: 35px
}

.captcha {
    width: 120px;
}

.captcha img {
    width: 100%;
    cursor: pointer;
}

.smscode {
    width: 120px;
}

.smscode-btn {
    margin-left: 5px;
    width: 115px;
}

.resetpwd {
    text-align: right;
    font-size: .875rem;
    line-height: 36px;
}
</style>

6.4 登录状态检测

router/index.js

import { createRouter, createWebHashHistory } from 'vue-router';

import { useUserStore } from '@/stores/user'
// 系统路由信息
const routes = [
    {
        path: '/',
        component: () => import('../views/home/Home.vue'),
        // 只有经过身份验证的用户才能创建帖子
        meta: {
            requiresAuth: true
        }
    },
    {
        path: '/login',
        component: () => import('../views/login/Login.vue'),
        meta: {
            requiresAuth: false
        }
    },
    {
        path: '/resetpwd',
        component: () => import('../views/resetpwd/Resetpwd.vue'),
        meta: {
            requiresAuth: false
        }
    },
    {
        path: '/register',
        component: () => import('../views/register/Register.vue'),
        meta: {
            requiresAuth: false
        }
    },
]

//创建路由信息
const router = createRouter({
    // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
    history: createWebHashHistory(),
    routes, // `routes: routes` 的缩写
})

//添加路由守卫
router.beforeEach(async (to, from) => {
    // 数据状态管理
    const userStore = useUserStore()
    if (to.meta.requiresAuth && !userStore.token) {
        // 将用户重定向到登录页面
        return { path: '/login' }
    }
})
export default router;

6.5 请求接口实现

import axios from "axios";
//引入状态管理,获取登录后的token
import { useUserStore } from '@/stores/user'
import router from "@/routers";
/**
 * 创建api实例
 */
const api = axios.create({
    baseURL: "/api",
    timeout: 1000,
    headers: {}
});
// 添加请求拦截器
api.interceptors.request.use(function (config) {
    // 数据状态管理
    const userStore = useUserStore()
    if (userStore && userStore.token) {
        //附带上token信息
        config.headers.token = userStore.token;
        // config.headers.authorization = "Bearer " + userStore.token;
        // config.headers.auth = "Bearer " + userStore.token;
    }
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
api.interceptors.response.use(function (response) {
    // 数据状态管理
    const userStore = useUserStore()
    //刷新客户端的token信息
    if (response.headers.token && userStore.token != response.headers.token) {
        userStore.token = response.headers.token;
    }
    return response;
}, function (error) {
    switch (error.response.status) {
        case 401://提示先登录,自动转到登录界面
            // 数据状态管理
            const userStore = useUserStore()
            userStore.logout();
            router.push("/login")
            break;
        default:
            break;
    }
    return Promise.reject(error);
});
/**
 * 登录接口
 * @param {*} account 账号
 * @param {*} password 密码
 * @param {*} captcha 验证码
 */
const login = (account, password, captcha) => {
    return api.post('/login/password', {
        account: account,
        password: password,
        captcha: captcha
    })
}
/**
 * 获取短信验证码
 * @param {*} account 
 */
const getSmsCode = (account) => {
    return api.post('/login/getSmsCode', {
        account
    })
}
/**
 * 短信验证码登录
 * @param {*} account 
 * @param {*} smscode 
 * @param {*} captcha 
 * @returns 
 */
const loginBySmsCode = (account, smscode, captcha) => {
    return api.post('/login/smsCode', {
        account: account,
        smscode: smscode,
        captcha: captcha
    })
}
/**
 * 获取当前用户信息
 * @returns 
 */
const getUserInfo = () => {
    return api.get('/user/info')
}
/**
 * 获取短信验证码
 * @param {*} account 
 * @param {*} captcha 
 */
const getResetPhoneCode = (account, captcha) => {
    return api.get('/resetpwd/smscode?account=' + account + "&captcha=" + captcha)
}
/**
 * 校验找回密码短信验证码是否合法
 * @param {*} account 
 * @param {*} smsode 
 */
const validateResetpwdPhoneCode = (account, smscode) => {
    return api.post('/resetpwd/validate/smscode', {
        account, smscode
    })
}
/**
 * 通过短信验证码重置密码
 * @param {*} account 
 * @param {*} smsode 
 * @param {*} password 
 */
const resetpwdByPhoneCode = (account, smscode, password) => {
    return api.post('/resetpwd/reset/smscode', {
        account, smscode, password
    })
}
/**
 * 获取邮箱验证码
 * @param {*} account 
 * @param {*} captcha 
 * @returns 
 */
const getResetEmailCode = (account, captcha) => {
    return api.get('/resetpwd/emailcode?account=' + account + "&captcha=" + captcha)
}
/**
 * 验证邮箱验证码是否合法
 * @param {*} account 
 * @param {*} emailcode 
 * @returns 
 */
const validateResetpwdEmailCode = (account, emailcode) => {
    return api.post('/resetpwd/validate/emailcode', {
        account, emailcode
    })
}
/**
 * 通过邮箱验证码重置密码
 * @param {*} account 
 * @param {*} emailcode 
 * @param {*} password 
 * @returns 
 */
const resetpwdByEmailCode = (account, emailcode, password) => {
    return api.post('/resetpwd/reset/emailcode', {
        account, emailcode, password
    })
}
/**
 * 获取注册验证码
 * @param {*} phone 
 * @param {*} captcha 
 */
const getRegisterPhoneCode = (phone, captcha) => {
    return api.get('/register/smscode?phone=' + phone + "&captcha=" + captcha)
}
/**
 * 校验注册验证码是否合法
 * @param {*} phone 
 * @param {*} smscode 
 */
const validateRegisterPhoneCode = (phone, smscode) => {
    return api.post('/register/validate/smscode', {
        phone, smscode
    })
}
/**
 * 邮箱注册接口
 * @param {*} phone 
 * @param {*} smscode 
 * @param {*} account 
 * @param {*} name 
 * @param {*} sex 
 * @param {*} email 
 * @param {*} password 
 */
const registerByPhone = (phone, smscode, account, name, sex, email, password) => {
    return api.post('/register/phone', {
        phone, smscode, account, name, sex, email, password
    })
}
/**
 * 检测电话是否可用
 * @param {*} phone 
 */
const checkPhone = (phone) => {
    return api.get('/register/checkphone?phone=' + phone)
}
/**
 * 检测账号是否可用
 * @param {*} account 
 */
const checkAccount = (account) => {
    return api.get('/register/checkaccount?account=' + account)
}
/**
 * 检测邮箱是否可用
 * @param {*} email 
 */
const checkEmail = (email) => {
    return api.get('/register/checkemail?email=' + email)
}
export default {
    api,
    getUserInfo,
    login,
    getSmsCode, loginBySmsCode,
    getResetPhoneCode, validateResetpwdPhoneCode, resetpwdByPhoneCode, getResetEmailCode, validateResetpwdEmailCode, resetpwdByEmailCode,
    getRegisterPhoneCode, validateRegisterPhoneCode, registerByPhone, checkPhone, checkAccount, checkEmail,
}

七、登出功能实现

  登出功能使用白名单的方式实现,即将有效的token记录到白名单中,如果token登出、失效、过期时,将token从白名单中移除,登录检测token时,查询白名单,查看是否在白名单中,不是则token失效。

  1. token检测:
//导入用于生成JWT字符串的包
const jwt = require('jsonwebtoken');
const config = require("../config");
const tokenData = require('../data/tokenData');
module.exports = {
    /**
     * 检测当前是否已登录
     */
    validataLogin(req, res) {
        if (!req.token || !req.token.account) {
            res.status(401).send({
                code: 401,
                msg: '请先登录',
                data: null
            });
            return false;
        }

        //检测当前token是否在白名单中
        if (!(req.headers.token in tokenData)) {
            res.status(401).send({
                code: 401,
                msg: 'token已失效,请先登录',
                data: null
            });
            return false;
        }

        if (req.headers.newToken) {
            res.setHeader('token', req.headers.newToken);
        }
        //过期的处理
        // let expiresTime = req.token.exp;
        // let initTime = req.token.iat;
        // if (req.session.last) {
        //     initTime = req.session.last;
        // }
        //当前时间离过期时间还有5秒钟,最后一次访问时间
        // if ((expiresTime * 1000 - new Date().getTime()) < 5000) {//如果块要过期了,则自动刷新token
        //     let tokenStr = jwt.sign({ account: req.token.acount }, config.secretKey, { expiresIn: config.expiresTime });
        //     res.setHeader('token', tokenStr)
        // }
        return true;
    }
}
  1. 登出处理:
const express = require('express');
const router = express.Router();
/**
 * token白名单
 */
const tokenData = require('../data/tokenData');

/**
 *使用白名单的方式处理登录状态,
 */
router.get('/', function (req, res, next) {
    let token = req.headers.token;
    //从白名单中清除此token信息
    delete tokenData[token];
    res.send({
        code: 200,
        msg: '',
        data: null
    })
});
module.exports = router;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Vue 3 和 Spring Boot 是一对强大的技术组合,可用于搭建现代化的后台管理系统。下面是一个基本的指南,介绍如何从零开始搭建一个 Vue 3 + Spring Boot 后台管理系统。 首先,我们需要准备开发环境。在电脑上安装好 Node.js 和 Java JDK,并配置好环境变量。然后,安装 Vue CLI 和 Spring Boot CLI,这些工具将帮助我们创建和管理项目。 第步是创建一个新的 Spring Boot 项目。使用 Spring Boot CLI,我们可以快速创建一个空的 Spring Boot 项目,并进行基本的配置。可以使用命令行,输入如下命令来创建项目: ``` $ spring init --name=my-project --groupId=com.example --artifactId=my-project --dependencies=web my-project ``` 这将创建一个名为 my-project 的 Spring Boot 项目。 接下来,我们可以创建一个 Vue 3 项目。使用 Vue CLI,我们可以选择一个预定义的模板,例如 Vue Router 和 Vuex,以及一些常用的插件。可以使用命令行,输入如下命令来创建项目: ``` $ vue create my-project ``` 这将创建一个名为 my-project 的 Vue 3 项目。 现在,我们已经有了一个空的 Spring Boot 项目和一个空的 Vue 3 项目。接下来,我们需要将这两者连接起来。 在 Vue 3 项目中,可以使用 axios 或者 fetch 来发送请求到后端。在 Spring Boot 项目中,可以使用 Spring Data JPA 来管理数据库,Spring Boot Security 来进行身份验证和授权。 我们可以编写 RESTful API 接口,用于在前端和后端之间传输数据。同时,也需要编写前端组件和页面,以及后端的 Controller 和 Service 层代码,来实现各种功能。 最后,我们可以使用打包和部署工具,将项目打包为可部署的文件,并将其部署到服务器上。例如,可以使用 Maven 将 Spring Boot 项目打包为 Java 可执行文件(JAR 文件),并使用 Nginx 或 Apache 将 Vue 3 项目部署为静态文件。 总之,使用 Vue 3 和 Spring Boot 可以快速搭建一个功能丰富的后台管理系统。只需按照上述步骤创建项目、编写代码、连接前后端,最后打包部署即可。当然,在实际开发过程中还需要考虑安全性、性能优化和代码质量等方面的问题,这些都需要进一步的学习和实践。 ### 回答2: Vue3是一个流行的JavaScript框架,用于构建用户界面。Spring Boot是一个基于Java的框架,用于构建快速且易于配置的应用程序。下面是在Vue3和Spring Boot中从零搭建后台管理系统的步骤: 1. 搭建Spring Boot后端: - 在IDE中创建一个新的Spring Boot项目。 - 添加所需的依赖项,如Spring Security、Spring Data JPA和MySQL数据库驱动程序。 - 创建实体类和存储库接口,用于管理系统数据的持久化。 - 创建控制器类,用于处理来自前端的请求,并调用适当的服务方法。 - 配置数据库连接和安全性设置。 - 运行应用程序,确保后端正常工作。 2. 搭建Vue3前端: - 在命令行中使用Vue CLI创建一个新的Vue3项目。 - 在Vue3项目中安装所需的依赖项,如Vue Router和Axios。 - 创建路由配置文件,定义前端路由和对应的组件。 - 创建后台API服务文件,使用Axios发送HTTP请求到后端。 - 创建所需的组件,如登录、注册、用户管理和权限管理。 - 配置应用程序的主要入口点,并将路由和组件添加到Vue实例中。 - 运行应用程序,确保前端正常工作。 3. 连接前端和后端: - 在Vue3中使用Axios调用后端API。 - 在Spring Boot中创建适当的控制器和服务方法,以接收和处理来自前端的请求。 - 在Vue3中处理返回的数据,并根据需要进行展示和处理。 4. 完善功能和界面设计: - 根据系统需求和设计规范,完善后台管理系统功能和界面。 - 添加用户认证和授权功能,确保只有授权用户才能访问特定页面和功能。 - 使用Vue3的组件化和响应式特性,实现良好的用户体验。 - 进行测试和调试,确保系统稳定性和安全性。 以上是使用Vue3和Spring Boot搭建后台管理系统的一般步骤,具体的实施过程可能因项目需求和个人偏好而有所不同。在开始搭建项目之前,建议先了解Vue3和Spring Boot的基本知识,并参考官方文档和教程,以帮助顺利完成项目。 ### 回答3: 搭建一个基于Vue3和Spring Boot的后台管理系统,需要经过以下步骤: 1. 确保你已经安装了Node.js和Java开发环境。可以从官网上下载并按照指引进行安装。 2. 创建Vue3项目。使用命令行工具或者Vue CLI来创建一个新的Vue3项目。运行命令`vue create project-name`,然后根据指引选择需要的配置项,比如包管理工具、路由、状态管理等。等待项目创建完成。 3. 构建前端界面。在Vue3项目中,根据需求使用Vue组件来搭建前端界面。可以使用Vue3提供的Composition API来编写组件逻辑,通过Vue Router来管理路由,使用Vuex来管理状态。 4. 编写API接口。使用Spring Boot来构建后端服务。创建一个Spring Boot项目,添加所需的依赖,如Spring Web、Spring Data JPA等。编写API接口的Controller类,定义各个接口的URL映射和请求处理方法。 5. 连接数据库。使用Spring Data JPA或其他适当的技术在后台系统中连接数据库。配置数据库连接信息,创建实体类和仓库接口,实现对数据库的增删改查操作。 6. 实现前后端交互。在Vue3项目中,使用axios或其他合适的HTTP库发送HTTP请求到后端接口,获取数据并进行展示。前端页面通过调用API接口来实现数据的增删改查操作。 7. 运行和部署。在开发过程中可以使用命令行运行Vue3前端项目和Spring Boot后端项目,通过不同的端口来访问前后端。在开发完成后,可以使用打包工具如Webpack将前端项目打包成静态文件,然后将打包结果部署到服务器,运行Spring Boot项目。 通过以上步骤,你就可以搭建一个基于Vue3和Spring Boot的后台管理系统。这个系统可以实现前后端分离,通过API接口进行数据交互,具备良好的可扩展性和可维护性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

军军君01

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

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

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

打赏作者

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

抵扣说明:

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

余额充值