为了方便扩展,和增加配置的灵活性,这里将通过封装一个类来实现axios的二次封装,要实现的功能包括:
- 为请求传入自定义的配置,控制单次请求的不同行为
- 在响应拦截器中对业务逻辑进行处理,根据业务约定的成功数据结构,返回业务数据
- 对响应错误进行处理,配置显示对话框或消息形式的错误提示
- 显示全局的loading
- 给get请求加入时间戳,避免缓存
- 取消重复的请求
- 给请求携带token
- token过期时,根据配置决定退出登录或挂起未完成的请求,先去请求更新token的接口,成功后执行挂起的请求
- 前端系统有可能接入多个后端系统的api,不同后端系统的授权方式不同,通过配置解决这个问题,无需封装多个axios
- 在前端简单的解决并发问题:遇到浏览器返回特定code码,间隔一段时间重新发起请求,重发约定次数后还是不成功,则返回错误
- 封装实例方法取消所有pendding中的请求,使得路由切换时可以取消未完成的请求。
PS:暂时想到这些问题,如有其他痛点,请给我留言,大家一起探讨
pnpm add axios qs lodash-es
目录
- 整体结构
- 创建axios实例
- 存放多个系统api获取token、更换token的方法
- 扩展axios类型
- 自定义的默认请求配置
- 工具函数
- 二次封装axios
- 请求拦截器的处理
- 响应处理
-
- 响应正确的处理
- 响应错误的处理
整体结构
service/
├── apis 存放api接口,方便统一管理
├── axios
│ ├── axios.d.ts 扩展axios类型
│ ├── config.ts 自定义的配置
│ ├── index.ts 二次封装axios
│ ├── requestInterceptors.ts 处理请求拦截
│ ├── responseInterceptors.ts 处理响应拦截
│ └── utils.ts 工具函数
├── index.ts 创建axios实例
├── tokenManager.ts 存放多个系统api获取token、更换token的方法
创建axios实例
index.ts
import HttpService from './axios'
const service = new HttpService({
baseURL: '/api', // 可根据环境变量配置
timeout: 3000,
headers: {
'Content-Type': 'application/json' }
})
export default service
存放多个系统api获取token、更换token的方法
tokenManager.ts
import {
useUserStore } from '@/stores/user'
interface System {
tokenKey: string // token 存储的 key
loginStatus: number // 指定后端返回的code,refreshToken为true时会更新token
refresh: boolean // token是否自动续期
getToken: () => string | null
refreshToken?: (config: any) => Promise<unknown>
logout: () => void
}
// 存储刷新 token 的请求状态
let isRefreshing = false
// 存储挂起的请求列表
let requests: ((token: string) => void)[] = []
export const SYSTEMS: {
[key: string]: System } = {
admin: {
tokenKey: 'token',
loginStatus: 401,
refresh: true,
getToken: () => {
const userStore = useUserStore()
return userStore.getToken
},
refreshToken: async (config: any) => {
try {
if (!isRefreshing) {
isRefreshing = true // 标记正在刷新 token
const userStore = useUserStore()
const newToken: string = await userStore.refreshToken() // 刷新token
if (newToken) {
requests.forEach((callback) => callback(newToken)) // 执行挂起的请求
requests = []
config.headers[SYSTEMS.admin.tokenKey] = newToken // 附加 token 到请求头
isRefreshing = false // 标记刷新 token 结束
return newToken
} else {
userStore.logout() // 退出登录
throw new Error('Token 刷新失败')
}
}
// 将本次请求加入挂起队列,并返回一个 Promise,在 refresh token 成功后执行
return new Promise((resolve) => {
requests.push(() => {
resolve(config)
})
})
} catch (error) {
console.error('刷新 token 失败:', error)
return Promise.reject(error)
}finally {
isRefreshing = false; // 标记刷新 token 结束
}
},
logout: () => {
const userStore = useUserStore()
userStore.logout()
}
},
user: {
tokenKey: 'x-token',
loginStatus: 401, // 指定后端返回的code,refreshToken为true时会更新token
refresh: false, // token是否自动续期
getToken: () => {
return localStorage.getItem('x-token')
},
logout: () => {
localStorage.removeItem('x-token')
}
}
}
// 更换token
export const refreshToken = async (config: any, systemCode: string) => {
if (SYSTEMS[systemCode] && typeof SYSTEMS[systemCode].refreshToken === 'function') {
return SYSTEMS[systemCode].refreshToken(config)
} else {
throw new Error(`系统 ${
systemCode} 对应的刷新token方法未定义`