赞
踩
通常用vue脚手架搭建的项目包含路由层和视图层,向后端发送请求获取数据的方法会写在视图层(即.vue文件中),但这造成了数据与页面的高耦合,后期维护不便的问题。因此我们将axios进行二次封装,写在request.js中,使每个请求都能直接使用这个封装后的方法,并创建api文件夹,将不同的请求方法按业务进行分模块管理。
首先我们来看一下二次封装需要实现哪些功能。
最基础的,先创建axios实例,配置请求的baseURL和请求超时的时间。根据接口文档配置请求头中的Content-Type。
request.js
import axios from 'axios'
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: 'http://localhost:8080',
// 超时
timeout: 10000
})
然后,要分别配置请求拦截器和响应拦截器,请求拦截器配置的内容会在发送请求前执行,响应拦截器会在请求后执行,这两个拦截器分别接受两个参数,即成功的拦截和失败的拦截。
在请求拦截器中,首先要给每个请求的请求头携带token,传给后端让后端进行身份验证。
service.interceptors.request.use(config => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token
}
//注意一定要return config
return config
},error => {
console.log(error)
Promise.reject(error)
})
在用户提交表单等操作时,若用户点击提交了两次,那么就会向后端发两个请求,因此我们要在拦截器里拦截重复请求。一个常用的方法是利用axios的cancelToken,具体如下:
1、定义pending数组,收集请求信息。
2.用axios生成cancel函数和cancelToken。
3.每次axios请求之前判断pending中是否有该请求信息,如果有,则利用axios生成的cancel函数和
cancelToken取消之前请求,再把本次请求信息添加pending。如果没有,则直接添加。
4. 接口返回后,移除pending数组中该请求信息。
但是,这个方法不能解决上述问题,因为取消借口时,有可能是服务器已经响应但还没返回,没法保证在服务器响应前取消。因此,我们将请求的地址、数据和时间存在sessionStorage中,并判断与上次存的内容是否一致且时间间隔小于设定值。
cache.js
const sessionCache = { setJSON (key, jsonValue) { if (jsonValue != null) { this.set(key, JSON.stringify(jsonValue)) } }, getJSON (key) { const value = this.get(key) if (value != null) { return JSON.parse(value) } } } export default { /** * 会话级缓存 */ session: sessionCache, }
import cache from '@/plugins/cache' if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { const requestObj = { url: config.url, data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, time: new Date().getTime() } const sessionObj = cache.session.getJSON('sessionObj') if (sessionObj === undefined || sessionObj === null || sessionObj === '') { cache.session.setJSON('sessionObj', requestObj) } else { const s_url = sessionObj.url; // 请求地址 const s_data = sessionObj.data; // 请求数据 const s_time = sessionObj.time; // 请求时间 const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { const message = '数据正在处理,请勿重复提交'; console.warn(`[${s_url}]: ` + message) return Promise.reject(new Error(message)) } else { cache.session.setJSON('sessionObj', requestObj) } } }
响应拦截器中如果响应成功返回,那就可以拿到响应状态码与返回的数据,根据状态码要做一个判断token是否过期的处理。如果token过期了,就弹窗显示是否重新登录,重新登录则跳转回登陆页面。
service.interceptors.response.use(res => { // 未设置状态码则默认成功状态 const code = res.data.code || 200; // 获取错误信息 const msg = errorCode[code] || res.data.msg || errorCode['default'] // 二进制数据则直接返回 if(res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer'){ return res.data } //token过期 if (code === 401) { if (!isRelogin.show) { isRelogin.show = true; ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' } ).then(() => { isRelogin.show = false; store.dispatch('LogOut').then(() => { location.href = '/index'; }) }).catch(() => { isRelogin.show = false; }); } return Promise.reject('无效的会话,或者会话已过期,请重新登录。') } else if (code === 500) { ElMessage({ message: msg, type: 'error' }) return Promise.reject(new Error(msg)) } else if (code !== 200) { ElNotification.error({ title: msg }) return Promise.reject('error') } else { return Promise.resolve(res.data) } }, error => { console.log('err' + error) let { message } = error; if (message == "Network Error") { message = "后端接口连接异常"; } else if (message.includes("timeout")) { message = "系统接口请求超时"; } else if (message.includes("Request failed with status code")) { message = "系统接口" + message.substr(message.length - 3) + "异常"; } ElMessage({ message: message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } )
在api文件夹下新建login.js,用上述封装的request.js实现登陆登出方法
// 登录方法 export function login(username, password, code, uuid) { const data = { username, password, code, uuid } return request({ url: '/login', headers: { isToken: false }, method: 'post', data: data }) }
// 退出方法
export function logout() {
return request({
url: '/logout',
method: 'post'
})
}
然后,我们在store文件夹下新建user.js,用vuex来统一管理token
import { login, logout } from '@/api/login' import { getToken, setToken, removeToken } from '@/utils/auth' const user = { state: { token: getToken() }, mutations: { SET_TOKEN: (state, token) => { state.token = token } }, actions: { // 登录 Login({ commit }, userInfo) { const username = userInfo.username.trim() const password = userInfo.password const code = userInfo.code const uuid = userInfo.uuid return new Promise((resolve, reject) => { login(username, password, code, uuid).then(res => { setToken(res.token) commit('SET_TOKEN', res.token) resolve() }).catch(error => { reject(error) }) }) }, // 退出系统 LogOut({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { commit('SET_TOKEN', '') commit('SET_ROLES', []) commit('SET_PERMISSIONS', []) removeToken() resolve() }).catch(error => { reject(error) }) }) } } } export default user
@/utils/auth如下,即将token存在cookie中
import Cookies from 'js-cookie' const TokenKey = 'Admin-Token' export function getToken() { return Cookies.get(TokenKey) } export function setToken(token) { return Cookies.set(TokenKey, token) } export function removeToken() { return Cookies.remove(TokenKey) }
这样我们就可以在登陆页面login.vue中调用action的登录方法,并在成功的回调中跳转页面到主页。登出同理。
首先在项目文件夹下定义permission.js,通过路由守卫判断要跳转的页面。如果要跳转到主页,则要获取用户的权限,然后根据权限生成动态路由表。
permission.js
router.beforeEach((to, from, next) => { NProgress.start() if (getToken()) { to.meta.title && store.dispatch('settings/setTitle', to.meta.title) /* has token*/ if (to.path === '/login') { next({ path: '/' }) NProgress.done() } else { if (store.getters.roles.length === 0) { isRelogin.show = true // 判断当前用户是否已拉取完user_info信息 store.dispatch('GetInfo').then(() => { isRelogin.show = false store.dispatch('GenerateRoutes').then(accessRoutes => { // 根据roles权限生成可访问的路由表 accessRoutes.forEach(route => { if (!isHttp(route.path)) { router.addRoute(route) // 动态添加可访问路由表 } }) next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 }) }).catch(err => { store.dispatch('LogOut').then(() => { ElMessage.error(err) next({ path: '/' }) }) }) } else { next() } } } else { // 没有token if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 next() } else { next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 NProgress.done() } } })
生成动态路由的方法主要在store/modules/permission.js中的GenerateRoutes方法。主要逻辑就是向后端请求路由,后端根据token会直接返回该用户权限对应的路由。我们要做的是首先对路由进行过滤,通过懒加载的形式根据路由导入模块。由于存在多级子路由的情况,就要递归调用这个过滤函数。处理完后返回处理后的路由,再一一通过router.addRoute动态添加到可访问路由表中。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。