登录
这种处理可以应用在多种场景,这里拿刷新token举例。
场景:
用户正在使用后台管理系统,已经登录了,当用户打开某个页面时,恰好token过期。
那么此后访问的接口,都会返回【登录已过期】的标记,前台需要静默的自动去刷新token,token刷新成功后,直接再次调用接口。
但有时情况比较特殊,比如某个页面一打开,需要同时调用5个接口甚至更多,由于这些接口几乎都是同时调用的,那这些接口都会收到【登录已过期】的标记,那每个接口都会去调用刷新token的接口。
这就导致,查看控制台的network(网络),会发现5个接口都失败了,同时瞬间出现了5个刷新token的接口,后面就知道了,token一瞬间被刷新了5次,很不优雅。
解决:
所以,这里添加了一个缓存控制器,原理就是,使用额外一个字段和一个数组。
封装refreshToken方法:
// 刷新token方法
// 为了处理同时多个接口要刷新token的控制器
const refreshTokenCache = {
// 是否正在刷新token
isRefreshToken: false as boolean,
// 当正在进行的token刷新时,后来的请求都存放在这个数组中,刷新token后再执行,传入token是否刷新成功
cbList: [] as ((isSuccess: boolean) => void)[],
}
// 供外部调用的刷新token的方法
async function refreshToken(): Promise<boolean> {
return new Promise<boolean>(async resolve => {
// 直接把promise的resolve推入缓存数组中,等token刷新完成后,会依次执行
refreshTokenCache.cbList.push(resolve)
// 发现正在进行刷新token,直接返回
if (refreshTokenCache.isRefreshToken) return
// 设定为正在进行刷新token
refreshTokenCache.isRefreshToken = true
// 刷新token是否成功
let isSuccess = false
try {
// ...调用刷新token的接口
isSuccess = true
} catch (error: any) {
isSuccess = false
}
// 处理所有等待刷新token完后的方法
while (refreshTokenCache.cbList.length) {
// 这里每一个cb方法,都是一个resolve,去执行就行
let cb = refreshTokenCache.cbList.shift()
// 把token刷新的结果传入
cb?.(isSuccess)
}
// 设定为没有在刷新token
refreshTokenCache.isRefreshToken = false
// 刷新token失败,进行错误处理
if (!isSuccess) {
// 一般可以在这里弹一个登录过期请重新登录的提示
// 然后清除token、用户数据等,然后跳转到登录页
}
})
}
使用refreshToken的示例:
// 封装的接口请求后,处理响应状态、参数的拦截方法
async requestHandler<T = any>(axiosResponse: AxiosResponse) {
// 比如和后台约定,当状态值是424时,是登录过期
if (axiosResponse?.status === 424) {
// token过期,自动去刷新token
const success = await refreshToken()
// 注意,这里如果刷新token成功,会重新发起请求,所以这里需要递归调用
if (success) return this.request(axiosResponse?.config)
// 刷新token失败,直接返回空数据
// 这里不进行错误提示,因为如果有多个接口在同时刷新token,会造成多个错误提示
// 所以错误提示在 refreshToken 中处理,那里有管理器,可以保证只报错一次
return null
} else if (axiosResponse?.status !== 200) {
// 其他错误
}
// ...其他处理
// 接口正常,返回解析后的接口响应接口
return axiosResponse.data
}