一、本章内容
本章介绍系统登录界面、登录流程、登录接口等相关内容的开发,实现包括账号密码登录、短信验证登录等不同的登录方式,使用svg-capter生成图形验证码,使用expressjwt实现登录token的生成及验证。
1. 详细课程地址: https://edu.csdn.net/course/detail/38183
2. 源码下载地址: 点击下载
二、界面预览
三、开发视频
基于VUE3+Layui从头搭建通用后台管理系统合集-登录界面框架搭建
四、登录流程说明
五、登录接口实现
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文章来源:https://www.toymoban.com/news/detail-516524.html
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失效。文章来源地址https://www.toymoban.com/news/detail-516524.html
- 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;
}
}
- 登出处理:
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;
到了这里,关于基于VUE3+Layui从头搭建通用后台管理系统(前端篇)二:登录界面及对应功能实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!