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类:
- 先改造
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') } }
- 在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
相关产品推荐
相关产品推荐

