1.封装Vuex的登录Action并处理token
在vuex中封装登录的action,并处理token
在Vuex中对token进行管理
组件直接和接口打交道,这并没有什么问题,但是对于用户token这一高频使用的**钥匙**,我们需要让vuex来介入,将用户的token状态共享,更方便的读取
实现store/modules/user.js基本配置
// 状态
const state = {}
// 修改状态
const mutations = {}
// 执行异步
const actions = {}
export default {
namespaced: true,
state,
mutations,
actions
}
设置token的共享状态
const state = {
token: {}
}
我们需要知道,**
钥匙**不能每次都通过登录获取,我们可以将token放置到本地的缓存中
在**utils/auth.js中,基础模板已经为我们提供了获取token,设置token,删除token**的方法,可以直接使用
只需要将存储的key放置成特定值即可
import Cookies from 'js-cookie'
const TokenKey = 'hrsaas-ihrm-token' // 设定一个独一无二的key
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
初始化token状态 - store/modules/user.js
import { getToken, setToken, removeToken } from '@/utils/auth'
// 状态
// 初始化的时候从缓存中读取状态 并赋值到初始化的状态上
// Vuex的持久化 如何实现 ? Vuex和前端缓存相结合
const state = {
token: getToken() // 设置token初始状态 token持久化 => 放到缓存中
}
提供修改token的mutations
// 修改状态
const mutations = {
// 设置token
setToken(state, token) {
state.token = token // 设置token 只是修改state的数据 123 =》 1234
// vuex变化 => 缓存数据
setToken(token) // vuex和 缓存数据的同步
},
// 删除缓存
removeToken(state) {
state.token = null // 删除vuex的token
removeToken() // 先清除 vuex 再清除缓存 vuex和 缓存数据的同步
}
}
封装登录的Action
封装登录的action
登录action要做的事情,
调用登录接口,成功后设置token到vuex,失败则返回失败
// 执行异步
const actions = {
// 定义login action 也需要参数 调用action时 传递过来的参数
async login(context, data) {
const result = await login(data) // 实际上就是一个promise result就是执行的结果
// axios默认给数据加了一层data
if (result.data.success) {
// 表示登录接口调用成功 也就是意味着你的用户名和密码是正确的
// 现在有用户token
// actions 修改state 必须通过mutations
context.commit('setToken', result.data.data)
}
}
}
主页的token拦截处理
完成登录的过程,并且存储了token,但是此时主页并没有因为token的有无而被控制访问权限
**
src/permission.js**是专门处理路由权限的,所以我们在这里处理
// 权限拦截 导航守卫 路由守卫 router
import router from '@/router' // 引入路由实例
import store from '@/store' // 引入vuex store实例
import NProgress from 'nprogress' // 引入一份进度条插件
import 'nprogress/nprogress.css' // 引入进度条样式
const whiteList = ['/login', '/404'] // 定义白名单 所有不受权限控制的页面
// 路由的前置守卫
router.beforeEach(function(to, from, next) {
NProgress.start() // 开启进度条
// 首先判断有无token
if (store.getters.token) {
// 如果有token 继续判断是不是去登录页
if (to.path === '/login') {
// 表示去的是登录页
next('/') // 跳到主页
} else {
next() // 直接放行
}
} else {
// 如果没有token
if (whiteList.indexOf(to.path) > -1) {
// 如果找到了 表示在在名单里面
next()
} else {
next('/login') // 跳到登录页
}
}
NProgress.done() // 手动强制关闭一次 为了解决 手动切换地址时 进度条的不关闭的问题
})
// 后置守卫
router.afterEach(function() {
NProgress.done() // 关闭进度条
})
在导航守卫的位置,我们添加了NProgress的插件,可以完成进入时的进度条效果
获取用户资料接口和token注入
获取用户资料接口
在**
src/api/user.js**中封装获取用户资料的方法/** * 获取用户的基本资料 * * **/ export function getUserInfo() { return request({ url: '/sys/profile', method: 'post' }) }我们忽略了一个问题!我们的headers参数并没有在这里传入
headers中的Authorization相当于我们开门(调用接口)时**
钥匙(token),我们在打开任何带安全权限的门的时候都需要钥匙(token)**每次在接口中携带**
钥匙(token)**很麻烦,所以我们可以在axios拦截器中统一注入token统一注入token
src/utils/request.jsservice.interceptors.request.use(config => { // 在这个位置需要统一的去注入token if (store.getters.token) { // 如果token存在 注入token config.headers['Authorization'] = `Bearer ${store.getters.token}` } return config // 必须返回配置 }, error => { return Promise.reject(error) })封装获取用户资料的action并共享用户状态
用户状态会在后续的开发中,频繁用到,所以我们将用户状态同样的封装到action中
封装获取用户资料action
actionsrc/store/modules/user.jsimport { login, getUserInfo } from '@/api/user' // 获取用户资料action async getUserInfo (context) { const result = await getUserInfo() // 获取返回值 context.commit('setUserInfo', result) // 将整个的个人信息设置到用户的vuex数据中 return result // 这里为什么要返回 为后面埋下伏笔 }初始化state
stateconst state = { token: getToken(), // 设置token初始状态 token持久化 => 放到缓存中 userInfo: {} // 定义一个空的对象 不是null 因为后边我要开发userInfo的属性给别人用 userInfo.name }userInfo为什么我们不设置为null,而是设置为 {}
因为我们会在**
getters**中引用userinfo的变量,如果设置为null,则会引起异常和报错设置和删除用户资料
mutations// 设置用户信息 setUserInfo(state, userInfo) { state.userInfo = { ...userInfo } // 用 浅拷贝的方式去赋值对象 因为这样数据更新之后,才会触发组件的更新 }, // 删除用户信息 reomveUserInfo(state) { state.userInfo = {} }将所有的资料设置到了userInfo这个对象中,如果想要取其中一个值,我们还可以在getters中建立相应的映射
因为我们要做映射,如果初始值为null,一旦引用了getters,就会报错
建立用户名的映射
src/store/getters.jsconst getters = { sidebar: state => state.app.sidebar, device: state => state.app.device, token: state => state.user.token, name: state => state.user.userInfo.username // 建立用户名称的映射 } export default getters到现在为止,我们将用户资料的action => mutation => state => getters 都设置好了, 那么我们应该在什么位置来调用这个action呢 ?
权限拦截处调用获取资料action
权限拦截器调用action
完成了用户资料的整个流程,那么这个action在哪里调用呢?
用户资料有个硬性要求,**
必须有token**才可以获取,那么我们就可以在确定有token的位置去获取用户资料调用action
src/permission.jsif(!store.state.user.userInfo.userId) { await store.dispatch('user/getUserInfo') }如果我们觉得获取用户id的方式写了太多层级,可以在vuex中的getters中设置一个映射
src/store/getters.jsuserId: state => state.user.userInfo.userId // 建立用户id的映射代码就变成了
if (!store.getters.userId) { // 如果没有id这个值 才会调用 vuex的获取资料的action await store.dispatch('user/getUserInfo') // 为什么要写await 因为我们想获取完资料再去放行 }最后一步,只需要将头部菜单中的名称换成真实的用户名即可
实现登出功能
登出仅仅是跳到登录页吗?
不,当然不是,我们要处理如下
同样的,登出功能,我们在vuex中的用户模块中实现对应的action
登出action
src/store/modules/user.js// 登出的action logout(context) { // 删除token context.commit('removeToken') // 不仅仅删除了vuex中的 还删除了缓存中的 // 删除用户资料 context.commit('removeUserInfo') // 删除用户信息 }头部菜单调用action
src/layout/components/Navbar.vueasync logout() { await this.$store.dispatch('user/logout') // 这里不论写不写 await 登出方法都是同步的 this.$router.push(`/login`) // 跳到登录 }**
注意**我们这里也可以采用vuex中的模块化引入辅助函数import { mapGetters, createNamespacedHelpers } from 'vuex' const { mapActions } = createNamespacedHelpers('user') // 这是的mapAction直接对应模块下的action辅助函数 methods: { ...mapActions(['lgout']), }以上代码,实际上直接对user模块下的action进行了引用,
Token失效的主动介入
主动介入token处理的业务逻辑
开门的钥匙不是一直有效的,如果一直有效,会有安全风险,所以我们尝试在客户端进行一下token的时间检查
**
src/utils/auth.jsconst timeKey = 'hrsaas-timestamp-key' // 设置一个独一无二的key // 获取时间戳 export function getTimeStamp() { return Cookies.get(timeKey) } // 设置时间戳 export function setTimeStamp() { Cookies.set(timeKey, Date.now()) }
src/utils/request.jsimport axios from 'axios' import store from '@/store' import router from '@/router' import { Message } from 'element-ui' import { getTimeStamp } from '@/utils/auth' const TimeOut = 3600 // 定义超时时间 const service = axios.create({ // 当执行 npm run dev => .evn.development => /api => 跨域代理 baseURL: process.env.VUE_APP_BASE_API, // npm run dev => /api npm run build => /prod-api timeout: 5000 // 设置超时时间 }) // 请求拦截器 service.interceptors.request.use(config => { // config 是请求的配置信息 // 注入token if (store.getters.token) { // 只有在有token的情况下 才有必要去检查时间戳是否超时 if (IsCheckTimeOut()) { // 如果它为true表示 过期了 // token没用了 因为超时了 store.dispatch('user/logout') // 登出操作 // 跳转到登录页 router.push('/login') return Promise.reject(new Error('token超时了')) } config.headers['Authorization'] = `Bearer ${store.getters.token}` } return config // 必须要返回的 }, error => { return Promise.reject(error) }) // 响应拦截器 service.interceptors.response.use(response => { // axios默认加了一层data const { success, message, data } = response.data // 要根据success的成功与否决定下面的操作 if (success) { return data } else { // 业务已经错误了 还能进then ? 不能 ! 应该进catch Message.error(message) // 提示错误消息 return Promise.reject(new Error(message)) } }, error => { Message.error(error.message) // 提示错误信息 return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入 catch }) // 是否超时 // 超时逻辑 (当前时间 - 缓存中的时间) 是否大于 时间差 function IsCheckTimeOut() { var currentTime = Date.now() // 当前时间戳 var timeStamp = getTimeStamp() // 缓存时间戳 return (currentTime - timeStamp) / 1000 > TimeOut } export default service我们在调用登录接口的时候 一定是没有token的,所以token检查不会影响登录接口的调用
同理,在登录的时候,如果登录成功,我们应该设置时间戳
// 定义login action 也需要参数 调用action时 传递过来的参数 // async 标记的函数其实就是一个异步函数 -> 本质是还是 一个promise async login(context, data) { // 经过响应拦截器的处理之后 这里的result实际上就是 token const result = await login(data) // 实际上就是一个promise result就是执行的结果 // axios默认给数据加了一层data // 表示登录接口调用成功 也就是意味着你的用户名和密码是正确的 // 现在有用户token // actions 修改state 必须通过mutations context.commit('setToken', result) // 写入时间戳 setTimeStamp() // 将当前的最新时间写入缓存 }有主动处理就有被动处理,也就是后端告诉我们超时了,我们被迫做出反应,如果后端接口没有做处理,主动介入就是一种简单的方式
Token失效的被动处理
除了token的主动介入之外,我们还可以对token进行被动的处理
token超时的错误码是**
10002**代码实现
src/utils/request.jserror => { // error 信息 里面 response的对象 if (error.response && error.response.data && error.response.data.code === 10002) { // 当等于10002的时候 表示 后端告诉我token超时了 store.dispatch('user/logout') // 登出action 删除token router.push('/login') } else { Message.error(error.message) // 提示错误信息 } return Promise.reject(error) }无论是主动介入还是被动处理,这些操作都是为了更好地处理token,减少错误异常的可能性