实现逻辑如下
1、对于每次请求数据,从cookie中取token并赋值到请求头上(headers.Authorization)
2、对后端返回的数据,如果返回401,则通过存储在cookie中的刷新令牌(refreshTokenKey)来刷新token值,(一般而言token有效期7天或者一天,刷新令牌比token存放的久)
3、如果刷新令牌存在,则通过刷新令牌,重新获取用户信息,包括,token,userInfo(用户信息),refreshToken(刷新令牌)
4、如果刷新令牌不存在,则重新登录
(刷新页面,Vuex状态变成初始值)
发送请求
service.interceptors.request.use(
config => {
// 获取token
const accessToken = PcCookie.get(Key.accessTokenKey)
if (accessToken) {
// 如果有token,则通过请求头添加
config.headers.Authorization = `Bearer ${accessToken}`
// 也可以使用下面这种
// config.headers['Authorization'] = `Bearer ${accessToken}`
// 后台接收的key:Authorization value值: Bearer ${accessToken}
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
接收后台返回数据
service.interceptors.response.use(
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
//写正常返回逻辑
},
error => {
console.log('err' + error) // for debug
// 判断状态
if (error.response && error.response.status !== 401) {
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
// 如果是401未认证,则通过刷新令牌获取状态信息
let isLock = true//防止重复提交
if (isLock && PcCookie.get(Key.refreshTokenKey)) {
isLock = false
// 跳转到统一认证终端。实现令牌刷新token的效果
window.location.href = `${process.env.VUE_APP_AUTH_CENTER_URL}/refesh?/redirectURL=${window.location.href}`
} else {
// 如果刷新令牌没有,则进入登录页面
window.location.href = `${process.env.VUE_APP_AUTH_CENTER_URL}/login?/redirectURL=${window.location.href}`
}
}
)
以上已经实现请求时候带上token值
Vuex菜单,按钮权限状态管理
vuex 权限菜单集合,根据 vuex 中可访问的菜单,渲染侧边栏组件
创建menu.js
在store下面的module文件夹下面创建menu.js文件如下
// 请求用户权限接口API
import { getUserMenuList } from "@/api/user"
import { PcCookie, Key } from '@/utils/cookie'
const state = {
init: false,//是否已经加载用户权限
menuList: [],//用户拥有的菜单权限
buttonList: [],//用户拥有的按钮权限
}
const mutations = { //改变状态值
SET_SYSTEM_MENU: (state, data) => {
state.init = true//已经加载用户权限
state.menuList = data.menuTreeList//保存菜单权限
state.buttonList = data.buttonList//保存按钮权限
}
}
// 定义行为
const actions = {
GetUserMenu({ commit }) {
// reslove正常返回结果,reject异常返回结果
return new Promise((reslove, reject) => {
//获取用户ID
const userId = PcCookie.get(Key.userInfoKey) ? JSON.parse(PcCookie.get(Key.userInfoKey)).uid : null
// 发送请求获取权限信息
if (userId) {
getUserMenuList(userId).then(response => {
// 将获取到的菜单按钮数据信息,存放到Vuex进行状态管理
commit('SET_SYSTEM_MENU', response.data)
reslove()
}).catch(error => {
// 返回异常对象
reject()
})
}
})
}
}
export default {
namespaced: true,//引用需要的模块名称 /menu/GetUserMenu
state,
mutations,
actions
}
在getters.js中引入state中定义的几个状态,外面使用方式 {{$store.getters.buttonList}}
const getters = {
........
// 添加菜单相关状态
init: state => state.menu.init,
menuList: state => state.menu.menuList,
buttonList: state => state.menu.buttonList
}
export default getters
在index,js中引入menu.js给vuex状态管理
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
// tagsView
import tagsView from './modules/tagsView'
//
import menu from './modules/menu'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
settings,
user,
tagsView,
menu
},
getters
})
export default store
在permission中对,存在token值且没有拿取到菜单,按钮的信息,就调用 store.dispatch('menu/GetUserMenu')方法进行拿取,只要不刷新页面,vuex就会存在菜单按钮信息,通过store.getters.init 判断,就不用每次请求路由都调用(刷新页面置空Vuex数据)
const hasGetUserInfo = PcCookie.get(Key.accessTokenKey)
if (hasGetUserInfo) {
// 如果有用户信息,则通过用户ID来查询当前用户的菜单和按钮权限
if (store.getters.init === false) {
// 还没有查询用户信息,开始查询用户信息
//因为我们采用namespaced: true,所以需要
store.dispatch('menu/GetUserMenu').then(() => {
//继续访问目标路由,且不会留下history记录
next({ ...to, replace: true })
}).catch(error => {
Message({
message: "获取用户信息失败",
type: "error"
})
})
} else {
// 跳转到目标路由
next()
}
通过Vuex中的管理的数据,动态渲染左边的菜单组件
<sidebar-item v-for="menu in menuList" :key="menu.id" :item="menu" />
import { mapGetters } from 'vuex'
export default {
components: { SidebarItem, Logo },
computed: {
...mapGetters([
'sidebar',
'menuList'//获取menuList中的状态值,menuList
]),
<div>
<!-- 没有子菜单,只有一级菜单 -->
<template v-if="!item.children || item.children.length === 0">
<app-link :to="item.url">
<el-menu-item
:index="item.url"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<item :icon="item.icon" :title="item.name" />
</el-menu-item>
</app-link>
</template>
<!-- 有子菜单 -->
<el-submenu v-else ref="subMenu" :index="item.id" popper-append-to-body>
<template slot="title">
<item :icon="item.icon" :title="item.name" />
</template>
<sidebar-item
v-for="child in item.children"
:key="child.id"
:is-nest="true"
:item="child"
class="nest-menu"
/>
<!-- :base-path="resolvePath(child.path)" -->
</el-submenu>
</div>