需求
在一个项目中需要调用另一个项目的接口,但另一个项目中的接口在调用时需要先登录获取到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 {
// 刷新 token
await 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 {
// 调用登录接口获取新 token
const 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);
// 清除无效 token
tokenInfo = { 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
}
})
}