一个项目中调用两个不同后台,前端如何优雅实现无感刷新Token调用接口
文章目录
- 需求
- 分析
- 解决
- 1. 配置多个代理地址
- 2. 在之前的拦截器外再写一套针对该问题的拦截器
需求
在一个项目中需要调用另一个项目的接口,但另一个项目中的接口在调用时需要先登录获取到token,才能调用该接口,应该如何做呢?
分析
自动处理流程:
- 每次请求前检查 token 是否存在或过期
- token 无效时自动调用登录接口获取新 token
- 401 错误时自动刷新 token 并重试请求
- 并发请求会等待 token 刷新完成后再执行
解决
- 对新加的代理地址单独做一套请求
- 在请求时判断是否有登录信息,如果没有或 token 过期,则先调用登录接口存储 token 信息
- 在接下来的接口中就可以正常调用了
1. 配置多个代理地址
proxy: {'/prod-api': {target: "http://地址1", //请求的地址changeOrigin: false,rewrite: (path) => path.replace(/^\/prod-api/, '/prod-api'),},'/dev-api': {target: 'http://地址2',changeOrigin: true,rewrite: (path) => path.replace(/^\/dev-api/, '/dev-api'),},
}
2. 在之前的拦截器外再写一套针对该问题的拦截器
新建request.js
import axios from 'axios';// 创建 axios 实例
const service = axios.create({timeout: 10000,
});// 存储 token 信息
let tokenInfo = {token: localStorage.getItem('video_token') || null,expiresAt: localStorage.getItem('video_token_expires_at')? new Date(localStorage.getItem('video_token_expires_at')): null
};// 请求拦截器 - 在每次请求前检查 token
service.interceptors.request.use(async (config) => {// 检查是否需要 token(根据实际项目调整)if (needToken(config.url)) {// 检查 token 是否存在或已过期if (!tokenInfo.token || isTokenExpired()) {await refreshToken();}// 设置 token 到请求头config.headers['access-token'] = `${tokenInfo.token}`}return config;},(error) => {console.error('请求拦截器错误:', error);return Promise.reject(error);}
);// 响应拦截器 - 处理 401 错误
service.interceptors.response.use((response) => {return response;},async (error) => {const originalRequest = error.config;// 处理 401 错误(token 过期)if (error.response?.status === 401 && !originalRequest._retry) {originalRequest._retry = true;try {// 刷新 tokenawait refreshToken();// 重试原请求originalRequest.headers['access-token'] = `${tokenInfo.token}`return service(originalRequest);} catch (refreshError) {console.error('刷新 token 失败:', refreshError);// 跳转到登录页或执行其他操作return Promise.reject(refreshError);}}return Promise.reject(error);}
);// 判断请求是否需要 token
function needToken (url) {// 排除登录接口return !url.includes('/login');
}// 检查 token 是否过期
function isTokenExpired () {return !tokenInfo.expiresAt || new Date() >= tokenInfo.expiresAt;
}// 刷新 token
async function refreshToken () {// 如果正在刷新 token,等待刷新完成if (window.isRefreshingToken) {return new Promise((resolve) => {const waitInterval = setInterval(() => {if (!window.isRefreshingToken) {clearInterval(waitInterval);resolve(tokenInfo.token);}}, 100);});}window.isRefreshingToken = true;try {// 调用登录接口获取新 tokenconst response = await axios.get('/dev-api/api/user/login?username=admin&password=21232f297a57a5a743894a0e4a801fc3');// 提取 token 和过期时间(根据实际接口返回调整)tokenInfo.token = response.data.accessToken;// 假设返回中包含 expiresIn(秒)const expiresIn = response.data.expiresIn || 3600;tokenInfo.expiresAt = new Date(Date.now() + expiresIn * 1000);// 存储到本地存储localStorage.setItem('video_token', tokenInfo.token);localStorage.setItem('video_token_expires_at', tokenInfo.expiresAt.toString());return tokenInfo.token;} catch (error) {console.error('获取 token 失败:', error);// 清除无效 tokentokenInfo = { token: null, expiresAt: null };localStorage.removeItem('video_token');localStorage.removeItem('video_token_expires_at');throw error;} finally {window.isRefreshingToken = false;}
}// 导出配置好的 axios 实例
export default service;
- 接口调用
import request from './request.js'
export function auxiliary([deviceId, channelDeviceId, command, switchId]) {return request({method: 'get',url: `/dev-api/api/front-end/auxiliary/${deviceId}/${channelDeviceId}`,params: {command: command,switchId: switchId}})
}