You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Mobx MST项目Refresh Token逻辑优化及依赖注入实现问题

现有代码核心问题
  • 重复实例化AuthenticationApi,无意义开销
  • 刷新token无并发锁,多请求同时触发401时会多次调用刷新接口,易导致refresh token提前失效
  • 401处理逻辑耦合在logout方法内,其他业务接口无法复用,不符合你预期的全局请求重放流程
  • logout方法内递归调用self.rootStore.authenticationStore.logout(),存在死循环风险
  • 命名错误:isAuthenticationed应为isAuthenticated
一、基于API中间件实现全局Token刷新逻辑

Ignited脚手架默认使用apisauce作为请求封装,可直接通过响应拦截器(也就是你说的中间件)实现全局401处理、刷新token、请求重放逻辑,完全不用在每个业务action内单独判断401状态。

// environment.ts 初始化请求实例的位置
import { ApisauceInstance } from 'apisauce'
import { RootStore } from '../models/root-store'

// 刷新状态锁
let isRefreshing = false
// 待重放的请求队列
let pendingRequests: Array<(newToken: string) => void> = []

export const setupAuthInterceptor = (api: ApisauceInstance, rootStore: RootStore) => {
  api.addResponseTransform(async (response) => {
    const originalRequest = response.config
    // 仅处理非刷新请求的401状态
    if (response.status === 401 && !originalRequest._retry) {
      // 正在刷新token时,把当前请求塞入队列等待
      if (isRefreshing) {
        return new Promise(resolve => {
          pendingRequests.push((newToken) => {
            originalRequest.headers.Authorization = `Bearer ${newToken}`
            resolve(api.any(originalRequest))
          })
        })
      }

      originalRequest._retry = true
      isRefreshing = true

      try {
        const refreshSuccess = await rootStore.authenticationStore.refreshToken()
        if (refreshSuccess) {
          // 从store取刷新后的新token
          const newToken = rootStore.authenticationStore.accessToken
          // 重放队列内所有缓存的请求
          pendingRequests.forEach(cb => cb(newToken))
          pendingRequests = []
          // 重放当前触发401的请求
          originalRequest.headers.Authorization = `Bearer ${newToken}`
          return api.any(originalRequest)
        } else {
          // 刷新失败直接走登出逻辑
          rootStore.authenticationStore.logout()
          return Promise.reject('Refresh token invalid')
        }
      } catch (err) {
        pendingRequests = []
        rootStore.authenticationStore.logout()
        return Promise.reject(err)
      } finally {
        isRefreshing = false
      }
    }
  })
}

对应优化后的AuthenticationStore:

export const AuthenticationStoreModel = types
  .model("AuthenticationStore")
  .props({
    isAuthenticated: types.optional(types.boolean, false),
    accessToken: types.maybe(types.string),
    refreshToken: types.maybe(types.string),
  })
  .extend(withEnvironment)
  .extend(withRootStore)
  .actions((self) => ({
    setAuthenticated(value: boolean) {
      self.isAuthenticated = value
    },
    setToken(accessToken: string, refreshToken: string) {
      self.accessToken = accessToken
      self.refreshToken = refreshToken
    },
    clearToken() {
      self.accessToken = undefined
      self.refreshToken = undefined
    },

    refreshToken: flow(function* () {
      // 直接用注入的authenticationApi实例,不用重复new
      const result: RefreshTokenResult = yield self.environment.authenticationApi.refreshToken()
      if (result.kind === "ok") {
        // 刷新成功更新store内的token
        self.setToken(result.data.accessToken, result.data.refreshToken)
        return true
      } else {
        __DEV__ && console.tron.log(result.kind)
        return false
      }
    }),

    logout: flow(function* () {
      const result: LogoutResult = yield self.environment.authenticationApi.logout()
      // 401逻辑已经被拦截器处理,这里不用重复判断
      if (result.kind === "ok" || result.kind === "unauthorized") {
        self.setAuthenticated(false)
        self.clearToken()
      } else {
        __DEV__ && console.tron.log(result.kind)
      }
    }),
  }))
二、AuthenticationApi依赖注入实现

Ignited自带的withEnvironment扩展天然支持依赖注入,不需要在store内手动实例化API类:

  1. 先改造AuthenticationApi构造函数,接收全局请求实例作为参数:
// authentication-api.ts
export class AuthenticationApi {
  private api: ApisauceInstance
  // 外部传入初始化好的请求实例,方便后续mock测试
  constructor(api: ApisauceInstance) {
    this.api = api
  }

  async refreshToken() {
    return this.api.post('/auth/refresh', { refreshToken: this.api.headers['Refresh-Token'] })
  }

  async logout() {
    return this.api.post('/auth/logout')
  }
}
  1. 在environment初始化时实例化AuthenticationApi,挂载到environment对象上:
// environment.ts
import { createApi } from './api'
import { AuthenticationApi } from './authentication-api'

export function createEnvironment(rootStore: RootStore) {
  const api = createApi()
  // 实例化API类
  const authenticationApi = new AuthenticationApi(api)
  // 挂载鉴权拦截器
  setupAuthInterceptor(api, rootStore)

  return {
    api,
    authenticationApi // 注入到全局environment
  }
}

后续所有store通过withEnvironment扩展后,都可以直接通过self.environment.authenticationApi调用相关接口,不需要重复实例化,单元测试时也可以直接传入mock的API实例完成测试。

内容的提问来源于stack exchange,提问作者revcerseOjectjs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 15:39:00