一个项目中调用两个不同后台,前端如何优雅实现无感刷新Token调用接口

发布于:2025-06-26 ⋅ 阅读:(21) ⋅ 点赞:(0)

需求

在一个项目中需要调用另一个项目的接口,但另一个项目中的接口在调用时需要先登录获取到token,才能调用该接口,应该如何做呢?

分析

自动处理流程:

  1. 每次请求前检查 token 是否存在或过期
  2. token 无效时自动调用登录接口获取新 token
  3. 401 错误时自动刷新 token 并重试请求
  4. 并发请求会等待 token 刷新完成后再执行

解决

  1. 对新加的代理地址单独做一套请求
  2. 在请求时判断是否有登录信息,如果没有或 token 过期,则先调用登录接口存储 token 信息
  3. 在接下来的接口中就可以正常调用了

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;
  1. 接口调用
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
		}
	})
}

网站公告

今日签到

点亮在社区的每一天
去签到