在axios请求中使用TypeScript实现双token的逻辑,当token过期时中断请求,并将中断的请求都存到数组中,使用refreshToken获取token,获取token成功后,将token和refreshToken存储在本地,重新发送之前中断的请求。在下面代码中加入双token的逻辑。 import type { CustomAbortController, UseRequestWrapperConfig, UseRequestWrapperExecConfig, } from './types.ts' import useGlobalLoadingStore from '@/store/useGlobalLoadingStore.ts' import { onTokenExpired } from '@/utils/business.ts' import { AxiosError, CanceledError } from 'axios' import { onMounted, onUnmounted, ref } from 'vue' import { ResponseError } from './_base.ts' function useRequestWrapper<T>(wrapperConfig: UseRequestWrapperConfig<T>) { const globalLoadingStore = useGlobalLoadingStore() const { requestFn, onError, onSuccess } = wrapperConfig /** * is interrupt prev request */ let interruptPrevRequest = false /** * is use global loading */ let globalLoading = false /** * is show overlay when global loading */ let globalLoadingOverlay = false /** * loading */ const isLoading = ref(false) function openLoading() { isLoading.value = true globalLoading && globalLoadingStore.openGlobalLoading(globalLoadingOverlay) } function closeLoading() { isLoading.value = false globalLoading && globalLoadingStore.closeGlobalLoading(globalLoadingOverlay) } /** * is error happen */ const isError = ref(false) const data = ref<T>() function setData(next?: T) { data.value = next } /** * abort request controller */ const abortController: CustomAbortController = { _value: new AbortController(), get value() { if (this._value && !this._value.signal.aborted) { return this._value } else { this._value = new AbortController() return this._value } }, abort() { abortController.value.abort() }, } /** * request execute * @param execConfig request execute config */ async function exec(execConfig?: UseRequestWrapperExecConfig) { if ( (wrappe
时间: 2025-06-13 18:26:32 浏览: 8
<think>嗯,用户想在现有的Axios请求封装中添加双token机制,也就是访问令牌和刷新令牌。当访问令牌过期时,自动用刷新令牌获取新的访问令牌,然后重试失败的请求。这听起来挺常见的需求,但具体怎么实现呢?
首先,我需要回顾一下Axios的拦截器机制。请求拦截器和响应拦截器是关键。请求拦截器可以在发送请求前检查access_token是否过期,如果没过期就添加到请求头里。响应拦截器则需要处理401未授权的情况,这时候可能要用refresh_token去刷新access_token。
用户给的代码片段里有useRequestWrapper函数,看起来是封装了一个自定义的请求方法。现有的代码可能已经处理了部分逻辑,但需要补充双token的部分。根据引用[1]和[2],access_token和refresh_token的结构以及响应拦截器的处理方式已经有了参考。比如,当响应状态码是401时,应该触发刷新token的逻辑,然后重新发送之前的请求。
接下来,需要考虑如何检测token过期。通常有两种方式:一种是在发送请求前检查过期时间,另一种是在接收到401响应后处理。第一种方法更主动,可以在请求发出前就判断,避免不必要的请求。但需要知道access_token的过期时间expires_in,这个信息通常在登录时返回,如引用[2]中提到的数据结构。
然后是中断请求的问题。当检测到access_token过期时,可能需要暂停当前的请求,等待新的token获取后再重试。这里可能需要一个队列来保存挂起的请求,同时避免重复刷新。比如,当多个请求同时返回401时,应该只触发一次刷新token的操作,其他请求等待这次刷新完成后再用新的token重试。
刷新token的过程需要异步处理,并且要保证同一时间只有一个刷新请求。可以使用一个标志位isRefreshing来标记是否正在刷新中,如果是,则后续的请求需要等待,直到刷新完成。同时,当刷新成功后,需要更新本地存储中的access_token和refresh_token(如果有新的refresh_token的话,但根据常见实现,refresh_token可能有较长的有效期,不一定每次刷新都会更新)。
在代码实现上,请求拦截器中需要检查access_token是否过期。比如,计算当前时间与token保存时间的差,如果超过expires_in,则视为过期。此时,可能需要先刷新token,再继续发送请求。但如果在拦截器中直接刷新,可能会遇到异步问题,需要返回一个Promise,并等待刷新完成后再继续。
响应拦截器的错误处理部分,当响应状态是401时,需要将请求放入队列,并触发刷新token。刷新成功后,用新的token重新发送所有队列中的请求。这里要注意,重新发送请求时可能需要修改原来的请求配置,比如更新Authorization头。
另外,需要考虑并发请求的情况。如果多个请求同时触发401,应该只进行一次token刷新,其他请求等待。这可以通过一个队列和刷新状态标志来实现。当刷新完成后,逐一处理队列中的请求,并清空队列。
在TypeScript中,还需要处理类型定义,比如拦截器的参数类型,以及队列中的请求类型。可能还需要处理错误重试的次数,避免无限循环。
现在,把这些思路整理成步骤:
1. 在请求拦截器中检查access_token是否过期。如果过期,先刷新token,再继续请求。
2. 使用标志位和队列管理刷新过程中的并发请求。
3. 在响应拦截器中捕获401错误,将请求加入队列,并触发刷新流程。
4. 刷新成功后,更新请求头中的access_token,并重试队列中的请求。
5. 处理刷新失败的情况,比如跳转到登录页。
具体到代码,可能需要修改现有的useRequestWrapper函数,添加拦截器逻辑。例如,在创建axios实例时,添加请求和响应拦截器。请求拦截器中处理token添加和过期检查,响应拦截器处理401错误。使用一个队列来保存挂起的请求,并管理刷新状态。
在请求拦截器部分,可能需要计算token的过期时间。比如,存储token的时间戳,然后当前时间减去存储时间,如果超过expires_in,则需要刷新。或者在每次请求时,检查剩余有效时间是否足够,比如剩余少于一定时间(比如5分钟)就主动刷新。
此外,刷新token的请求本身需要使用refresh_token,并且要防止刷新请求本身被拦截器处理,可能需要设置一个标记,或者使用单独的axios实例,避免循环拦截。
最后,测试各种情况,比如token过期时的多个并发请求,刷新成功后的重试,以及刷新失败后的处理,确保逻辑正确。</think>### 分步实现方案
#### 一、基础配置(在现有代码基础上添加)
```typescript
// 存储未完成的请求队列和刷新状态
let isRefreshing = false;
let requests: ((token: string) => void)[] = [];
```
#### 二、请求拦截器实现
```typescript
axiosInstance.interceptors.request.use(
async (config) => {
// 获取token和过期时间(根据引用[2]的数据结构)
const accessToken = localStorage.getItem('access_token');
const expiresIn = Number(localStorage.getItem('expires_in')) * 1000;
const tokenTime = Number(localStorage.getItem('token_time'));
// 主动过期检查:距离过期还剩5分钟时触发刷新
if (Date.now() - tokenTime > expiresIn - 300000 && !config.url?.includes('/refresh')) {
if (!isRefreshing) {
await refreshTokenHandler();
}
return new Promise((resolve) => {
requests.push((newToken: string) => {
config.headers.Authorization = `Bearer ${newToken}`;
resolve(config);
});
});
}
config.headers.Authorization = `Bearer ${accessToken}`;
return config;
},
(error) => Promise.reject(error)
);
```
#### 三、响应拦截器实现
```typescript
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// 处理401错误(根据引用[1]的拦截逻辑)
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
if (!isRefreshing) {
isRefreshing = true;
try {
const newToken = await refreshTokenHandler();
localStorage.setItem('access_token', newToken);
requests.forEach(cb => cb(newToken));
requests = [];
return axiosInstance(originalRequest);
} catch (refreshError) {
localStorage.clear();
window.location.href = '/login';
} finally {
isRefreshing = false;
}
}
return new Promise((resolve) => {
requests.push((newToken: string) => {
originalRequest.headers.Authorization = `Bearer ${newToken}`;
resolve(axiosInstance(originalRequest));
});
});
}
return Promise.reject(error);
}
);
```
#### 四、刷新Token核心方法
```typescript
const refreshTokenHandler = async () => {
const refreshToken = localStorage.getItem('refresh_token');
const { data } = await axios.post('/auth/refresh', { refresh_token: refreshToken });
// 更新存储时间戳(根据引用[2]的过期时间处理)
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('expires_in', data.expires_in);
localStorage.setItem('token_time', Date.now().toString());
return data.access_token;
};
```
#### 五、关键实现要点说明
1. **双重过期检查**:
- 主动检查:请求前计算剩余有效期,提前5分钟触发刷新[^2]
- 被动检查:通过401状态码触发刷新[^1]
2. **请求队列机制**:
- 使用Promise链保存待重试请求
- 通过闭包保持请求配置的原始上下文
3. **并发控制**:
- `isRefreshing`标志确保单次刷新
- 所有并发请求共享同一个刷新过程
4. **安全处理**:
- 刷新失败时清空本地存储并跳转登录页
- 排除刷新请求自身的拦截处理(`!config.url?.includes('/refresh')`)
阅读全文
相关推荐


















