世外天堂

世外天堂

代码、番剧与灵感共振的赛博据点

首页token

前端实现token无感刷新

📅 2026年6月29日✍️ 1196 字⏱ 4 分钟阅读 👁 -- 次阅读

关键思路

accesstoken=短token; refreshtoken=长token; 白名单接口不携带token,不参与刷新; 401时拦截请求,长token去换短token

白名单

// 不需要携带 AccessToken 的接口
const whiteList = [
    "/login",
    "/refresh_token"
]

function isWhiteList(url?: string) {
    if (!url) return false
    return whiteList.some(item => url.includes(item))
}

关键变量

let isRefreshing = false

let requests: Array<{
    resolve: (token: string) => void
    reject: (err: any) => void
}> = []

请求拦截器

service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const reqConfig = config as RequestConfig
    if (reqConfig.showLoading ?? requestConfig.showLoading) {
      showLoading()
    }
    // 白名单接口不携带AccessToken
    if (!isWhiteList(config.url as string)) {
      const token = getToken()
      if (token) {
        config.headers.set("Authorization",`Bearer ${token}`)
      }
    }
    return config
  },
  error => Promise.reject(error)
)

响应拦截器

service.interceptors.response.use(
    (response: AxiosResponse<ApiResponse>) => {
        const config = response.config as RequestConfig
        if (config.showLoading ?? requestConfig.showLoading) {
            hideLoading()
        }
        const { code, msg } = response.data
        // 后端业务失败(不是401)
        if (code !== 200) {
            if (config.showErrorMessage ?? requestConfig.showErrorMessage) {
                ElMessage.error(msg || '请求失败')
            }
            return Promise.reject(response.data)
        }
        // 成功提示
        if (config.showSuccessMessage ?? requestConfig.showSuccessMessage) {
            ElMessage.success(msg || '操作成功')
        }
        return response
    },
    async (error: any) => {
        const config = (error?.config || {}) as RequestConfig
        if (config.showLoading ?? requestConfig.showLoading) {
            hideLoading()
        }
        const status = error?.response?.status
        // 非401
        if (status !== 401) {
            handleHttpError(status, config.showErrorMessage ?? requestConfig.showErrorMessage)
            return Promise.reject(error)
        }
        /**
         * refresh接口自己401
         * 或 login接口401
         * 不允许再次刷新
         */
        if (isWhiteList(config.url as string)) {
            handleLogout()
            return Promise.reject(error)
        }
        /**
         * 第一个401
         */
        if (!isRefreshing) {
            isRefreshing = true
            try {
                const { accessToken, refreshToken: newRefreshToken } = await refreshToken()
                setToken(accessToken)
                setRefreshToken(newRefreshToken)
                /**
                 * 唤醒所有等待请求
                 */
                requests.forEach((item) => {
                    item.resolve(accessToken)
                })
                requests = []
                /**
                 * 当前请求重新发送
                 */
                config.headers = config.headers || {}
                config.headers.Authorization = `Bearer ${accessToken}`
                return service.request(config)
            } catch (e) {
                /**
                 * 通知所有等待请求失败
                 */
                requests.forEach((item) => {
                    item.reject(e)
                })
                requests = []
                handleLogout()
                return Promise.reject(e)
            } finally {
                isRefreshing = false
            }
        }

        /**
         * 已经有人在刷新Token
         * 当前请求进入等待队列
         */
        return new Promise((resolve, reject) => {
            requests.push({
                resolve: (token: string) => {
                    config.headers = config.headers || {}
                    config.headers.Authorization = `Bearer ${token}`
                    resolve(service.request(config))
                },
                reject,
            })
        })
    },
)

封装函数

const ACCESS_TOKEN = "token"
const REFRESH_TOKEN = "refreshToken"

function getToken() {
    return localStorage.getItem(ACCESS_TOKEN)
}

function getRefreshToken() {
    return localStorage.getItem(REFRESH_TOKEN)
}

function setToken(token: string) {
    localStorage.setItem(ACCESS_TOKEN, token)
}

function setRefreshToken(token: string) {
    localStorage.setItem(REFRESH_TOKEN, token)
}
/**
 * 刷新Token
 */
async function refreshToken() {
    const refreshToken = getRefreshToken()

    return service.post("/refresh_token", {
        refreshToken
    })
}

logout

function handleLogout() {
    localStorage.removeItem(ACCESS_TOKEN)
    localStorage.removeItem(REFRESH_TOKEN)
    router.replace({
        name: "login"
    })
}

HTTP错误

这里不用处理401。

function handleHttpError(
  status: number,
  showError: boolean
) {

  const map: Record<number, string> = {
    400: "请求错误",
    403: "拒绝访问",
    404: "接口不存在",
    500: "服务器错误"
  }

  if (showError) {
    ElMessage.error(
      map[status] || "网络错误"
    )
  }

}

世外天堂

代码、番剧与灵感共振的赛博据点

© 2026 小翼·访客 --·浏览 --