1.在common.ts定义刷新token的接口
javascript
export function getAuthToken(data?: Object) {
return request({
url: '/admin/oauth2/retoken',
method: 'post',
data,
});
}
2.创建token.ts文件
javascript
import Cookies from 'js-cookie';
import { getAuthToken } from '/@/api/common'
import { ElMessage } from 'element-plus';
let timer;
export interface TokenData {
accessToken: string;
refreshToken: string;
expiresIn?: number;
}
class TokenManager {
private accessToken: string | null = null;
private refreshToken: string | null = null;
private refreshPromise: Promise<TokenData> | null = null;
constructor() {
this.loadTokens();
}
// 从 Cookies 加载 tokens
private loadTokens() {
this.accessToken = Cookies.get('access_token');
this.refreshToken = Cookies.get('refresh_token');
}
// 设置 tokens
setTokens(tokens: TokenData): void {
this.accessToken = tokens.accessToken;
this.refreshToken = tokens.refreshToken;
Cookies.set('access_token', tokens.accessToken);
Cookies.set('refresh_token', tokens.refreshToken);
if (tokens.expiresIn) {
const expireTime = Date.now() / 1000 + tokens.expiresIn;
Cookies.set('tokenAgeing', expireTime.toString());
}
}
// 获取 access token
getAccessToken(): string | null {
return Cookies.get('access_token');
}
// 获取 refresh token
getRefreshToken(): string | null {
return Cookies.get('refresh_token');
}
// 清除 Promise
clearPromise(): void {
this.refreshPromise = null;
}
// 检查 token 是否即将过期
isTokenExpiring(): boolean {
const expireTime = Cookies.get('tokenAgeing');
if (!expireTime) return false;
const currentTime = Date.now();
const timeUntilExpire = parseInt(expireTime) * 1000 - currentTime;
// 提前 5 分钟刷新
return timeUntilExpire < 5 * 60 * 1000;
}
// 刷新 token
async refreshTokens(): Promise<TokenData> {
// 如果已经在刷新,返回同一个 promise
if (this.refreshPromise) {
return this.refreshPromise;
}
const refreshToken = this.getRefreshToken();
if (!refreshToken) {
ElMessage.error('refreshToken不存在!');
timer = setTimeout(()=>{
console.log('跳转到登录页')
}, 2000)
}
this.refreshPromise = new Promise(async (resolve, reject) => {
try {
getAuthToken({ refreshToken: refreshToken }).then((res) => {
console.log(res)
if(res.code === 200 && res.data !== null){
this.setTokens(res.data?.list[0]);
resolve(res.data?.list[0]);
}else{
ElMessage.error('token刷新失败!');
timer = setTimeout(()=>{
console.log('跳转到登录页')
}, 2000)
}
});
} catch (error) {
console.log(error)
reject(error);
} finally {
this.clearPromise()
}
});
return this.refreshPromise;
}
}
export const tokenManager = new TokenManager();
3.在request.ts中进行接口拦截处理
javascript
import axios from 'axios';
import { tokenManager } from '/@/utils/token';
import { ElMessage } from 'element-plus';
let timer;
// 使用 accessToken(短期)和 refreshToken(长期)
// 请求拦截器
axios.interceptors.request.use(config => {
// 统一增加Authorization请求头
const token = tokenManager.getAccessToken();
config.headers.Authorization = `Bearer ${token}`;
// 如果 token 即将过期,提前刷新
if (tokenManager.isTokenExpiring() && !config.url?.includes('/admin/oauth2/retoken')) {
try {
await tokenManager.refreshTokens();
const newToken = tokenManager.getAccessToken();
if (newToken) {
config.headers.Authorization = `Bearer ${newToken}`;
}
} catch (error) {
// 刷新失败
tokenManager.clearTokens();
ElMessage.error('token刷新失败!');
timer = setTimeout(()=>{
console.log('跳转到登录页')
}, 2000)
}
}
return config;
});
// 创建请求唯一标识符
const createRequestId = (config): string => {
return `${config.method}-${config.url}-${JSON.stringify(config.data)}`;
};
// 在全局状态中管理重试记录
const retryRecords = new Map<string, number>();
// 响应拦截器处理函数
const handleResponse = (response) => {
// 请求成功
const requestId = createRequestId(response.config);
retryRecords.delete(requestId);//清除重试记录
tokenManager.clearPromise() //清除promise
return response.data;
};
// 响应拦截器
axios.interceptors.response.use(
response => handleResponse(response ),
async error => {
const originalRequest = error.config;
const requestId = createRequestId(originalRequest);
// 检查重试次数
const retryCount = retryRecords.get(requestId) || 0;
// 如果响应状态码为401,且请求的URL不是刷新token的接口
if (error.response?.status === 401 && !originalRequest.url?.includes('/admin/oauth2/retoken') && retryCount < 1) {
retryRecords.set(requestId, retryCount + 1);
try {
// 刷新 token
await tokenManager.refreshTokens();
const newToken = tokenManager.getAccessToken();
if (newToken) {
// 更新 Authorization 头
config.headers.Authorization = `Bearer ${newToken}`
// 重试原始请求
return axios(originalRequest);
}
} catch (refreshError) {
retryRecords.delete(requestId);
// 刷新失败
tokenManager.clearTokens();
ElMessage.error('token刷新失败!');
timer = setTimeout(()=>{
console.log('跳转到登录页')
}, 2000)
return Promise.reject(refreshError);
}
}
retryRecords.delete(requestId);
return Promise.reject(error);
}
);