如何更加完善的封装axios请求:防抖拦截、无感刷新token、各环境baseUrl配置等
前言
先看下常见的封装请求及使用方法:
1.新建request.js,内容为:
import axios from 'axios'
const service = axios.create({
baseURL: '',
timeout: 3000,
headers: {}
})
service.interceptors.request.use(
config => {
// 这里是添加token、判断登录权限等等一些处理逻辑
return config
},
error => {
Promise.reject(error)
}
)
service.interceptors.response.use(
res => {
// 这里是一些处理逻辑
return res.data
},
error => {
return Promise.reject(error)
}
)
export default service
2.新建一些api文件如 /api/user.js,内容为:
import request from '@/utils/request'
// 用户登录
export function userLogin(data) {
return request({
url: '/api/user/login',
method: 'POST', // post请求
data
})
}
// 获取列表信息
export function userList(id) {
return request({
url: '/api/user/list?id='+id,
method: 'GET' // get请求
})
}
// 文件下载类
export function fileMyDownload(data) {
return request({
url: '/api/file/my/file/download/' + data,
method: 'GET',
responseType: 'blob'
})
}
3.页面引入使用
import {userLogin,userList,fileMyDownload} from '/api/user.js'
//页面中的请求方法
userLogin(data).then()
userList(data).then()
fileMyDownload(data).then()
小结
上面这写法api下全是export function,接口多了然后就是几百成千行类似的代码或者是被拆分成几十个js文件。页面使用中也需根据实际import或移除相应的接口方法名称。对于这问题可继续优化完善。
优化
优化api
首先将整合所有的export function方法,将多个export function封装为getRequest。优化为:
import request from '@/utils/request'
const api={
userLogin:'/user/login',
userList:'/user/list',
getUserList:'get|/user/list', // 不同请求类型可在url前添加,使用|分隔
getUserListById:'/user/list/$id' // 动态参数id时,可添加$标识符
download:'/user/my/file/download'
}
export const getRequest = (apiKey,data, options= {}) => {
let url = api[apiKey] || apiKey
if (Object.keys(options.apiKey || {}).length) {
for (const key in options.apiKey) {
url = url.replace(key, options.apiKey[key])
}
}
let method = 'POST' // 默认请求方式
// 如果url带有|分隔符,提取|前面的作为请求method并过滤掉|前面的
if (url.indexOf('|') !== -1) {
method = url.split('|')[0]
url = url.replace(/.*\|/, '')
}
return Object.assign(
{
url: '/api'+ url, // 添加前缀
method: method,
data
},
options
)
}
页面使用
import { getRequest } from '/api/getRequest'
export default {
mounted() {
const data = {}
// 1.正常请求,以post方式请求/api/user/login
getRequest('userLogin', data).then()
// 2.以get方式请求/api/user/list。同时可在request.js中设置将data转为params,即追加到url后面
// 2.1 url接口前添加get|
getRequest('getUserList', data).then()
// 2.2使用options参数
getRequest('userList', data, { method: 'get' }).then()
// 2.3不使用api定义的,直接使用接口url
getRequest('get|/user/list', data).then()
// 或
getRequest('/user/list', data, { method: 'get' }).then()
//3.url带参数的,使用options参数的apikey,即可正常请求/api/user/list/1
getRequest('getUserListById', data, { apiKey: { $id: '1' } }).then()
//4.数据流下载类,添加responseType,同时还可以定义不同的headers
getRequest('download', data, { responseType: 'blob', headers: {} }).then()
}
}
经过上面优化封装,可解决所有不同的请求,可大量减少类似的重复代码,更方便阅读。同步的request.js可优化为:
除了常见的添加token请求头外,还增加了:
1. 在get请求时将data参数转为params方式,直接拼接到url后面;
3. 请求防抖拦截,同一个接口和参数在一定时间内不重复发请求
4. 无感刷新token
5.不同环境baseUrl配置
import * as md5 from 'md5'
//5 通过运行的命令获取各不同环境的请求api等,无需在根目录添加如.env.development等文件
const mode = import.meta.env.MODE
const baseUrl: any = {
development: '', // 默认命令dev
production: '', // build
preRelease: '' // 自定义其他环境如 "build:pre": "vite build --mode preRelease"
}
const service = axios.create({
baseURL: baseUrl[mode],
timeout: 3000, // request timeout
headers: {}
})
const axiosList = {}
/***************************无感刷新换token相关****************/
let refreshTokenAjax: boolean = false
// 存储请求的数组
const subscribesArr = []
// 请求push到数组中
function subscribesArrRefresh(cb) {
subscribesArr.push(cb)
}
// 用新token发起请求
function reloadSubscribesArr(newToken) {
subscribesArr.map(cb => cb(newToken))
}
// 使用refreshToken请求获取新的token
function getNewToken(refreshToken) {
//根据接口需求,处理刷新所需参数
const params: any = { refreshToken: refreshToken }
axios
.post('/mock/system/user/token.json', params)
.then((result: any) => {
// 刷新成功,这里需要将新的token保存
// .... 一些保存逻辑
reloadSubscribesArr(data.token)
refreshTokenAjax = false
})
.catch(() => {
//... 换取token失败的处理逻辑,一般为退出到登录页
refreshTokenAjax = false
})
}
/***************************无感刷新换token相关结束****************/
service.interceptors.request.use(
config => {
//1. get请求时,将参数放到url后面
if (config.method.toUpperCase() === 'GET') {
config.params = config.data
}
//2. 让每个请求携带自定义token 请根据实际情况自行修改
const token = '' // 一般从store或storage里获取
if (token) {
config.headers['Authorization'] = token
}
//3. 全局防抖拦截,请根据实际情况自行修改
const urlKey = md5(config.url + JSON.stringify(config.data || {}))
const lastTime = axiosList[urlKey]
if (lastTime) {
const diffTime = new Date().getTime() - lastTime
if (diffTime < 1500) {
//间隔时间,小于此时间视为重复提交
return Promise.reject({ code: 1, msg: '数据正在处理,请勿重复提交' })
}
}
axiosList[urlKey] = new Date().getTime() // 保持或更新请求记录
// 添加记录,然后自动清除
setTimeout(() => {
delete axiosList[urlKey]
}, 5000)
// 全局防抖拦截结束
// 4.无感刷新token开始
const refreshToken = '' // 一般从store或storage里获取刷新token所需的refreshToken
if (!token && refreshToken) { //token过期逻辑判断
if (!refreshTokenAjax) {
getNewToken(refreshToken)
}
refreshTokenAjax = true
return new Promise(resolve => {
subscribesArrRefresh(newToken => {
config.headers['Authorization'] = newToken
resolve(config)
})
})
}
// 无感刷新token结束
return config
},
error => {
Promise.reject(error)
}
)
总结
至此封装一个getRequest方法即满足所有接口请求,新增接口url时直接修改api={}即可,在页面引入使用时也无需单独引入,统一引入同一方式即可。减少了大量的代码