vue3项目二次封装axios及登陆鉴权的实现
二次封装axios
一、二次封装axios
通常用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动态添加到可访问路由表中。
更多推荐
所有评论(0)