如何编写热插拔的token续传插件(proveAxios)

224 阅读2分钟

书接上文

proveAxios插件调度原理

前置知识(axios,proveAxios的基本使用),先看看最终效果是如何

import { initializationAxios, InitializeContainer, instanceAlias, interceptorsResponseSuccess, Module } from '@zealforchange/proveaxios'
import { UserAuthorization, useUserAuthorizationHelper } from '@zealforchange/proveaxios/userAuthorization'
import { AxiosResponse } from 'axios'

// useUserAuthorizationHelper 用于 配置 token续传插件(UserAuthorization) 配置
useUserAuthorizationHelper({
  authorizationRequired: true, // 是否验证token已过期
  statusExpirationCode: 401, // 如果同意验证token过期,判断返回的code是否等于401,为true则立即触发 refreshToken 函数
  refreshToken: async () => {
// 业务逻辑
    const result = await helper({ url: '/refreshToken' })
    // ... 省略无关代码
  },
// 当 refreshToken 调用结束,会重试此前由token过期导致请求失败的请求
})

@Module([UserAuthorization]) // 重点为 UserAuthorization 插件再此配置token续传插件
@initializationAxios({
  baseURL: '<http://localhost:3000/user>',
})
class UserAuth {
  @interceptorsResponseSuccess()
  static response(res: AxiosResponse) {
    return Promise.resolve(res.data)
  }
}

const g = new InitializeContainer().collect([UserAuth]) //初始化UserAuth生成实例化axios
const helper = g.get(instanceAlias.firstInstance) // 取出由UserAuth实例化的axios

it('verify', async () => {
  // 这是一个token已过期的请求
  // 调用链路:第一次调用此请求会进入 refreshToken 回调,刷新token后会重新调用此请求
  // resultList 不会是token过期的响应,而是有正常token getList请求的响应
  const resultList = await helper({ url: '/getList' }) 
  expect(resultList).toStrictEqual({ code: 200, data: [1, 2] })
})

如果你没读过书接上文 这一篇文章,你很有可能会认为现有代码非常的冗余,其实不然

接上来我会从0到1的实现 UserAuthorization 插件应该如何实现

首先我想应该先明确,这个插件都需要哪些配置项?

interface userAuthorizationConf<S = unknown> {
  authorizationRequired?: boolean // 是否需要授权token
  statusExpirationCode?: number // 状态过期码
  refreshToken?: ((response: AxiosResponse<S>) => void | Promise<void>) | null // 刷新token handler
}

接下来需要实现proveAxios特定的插件配置

proveAxios插件调度原理

// userAuthorizationConf 默认配置
const h: userAuthorizationConf = {
  statusExpirationCode: 401, 
  refreshToken: null,
  authorizationRequired: false,
}
// 插件优先级,用处是填入 Module 装饰器时将插件所有装饰器的执行顺序进行排序
// TOP 最高优先级
@dynamicModule({ priority: priority.TOP })
export class UserAuthorization {
  // 响应成功拦截器
  @interceptorsResponseSuccess()
  static async res(resp: AxiosResponse) {
    // 执行 refreshToken 函数 刷新token
    await h.refreshToken?.(resp)
    // 刷新token后重试之前因token过期而请求失败的请求
    const latestAuthorizationResult = await axios(resp.config)
    // 将token刷新后请求成功的结果返回
    return Promise.resolve(latestAuthorizationResult)
  }
  
  // 通过该函数返回值判断是否需要执行该插件中的响应成功拦截器
  @dynamicModuleSuccessInstall(decisionInstaller.installResSuc)
  static installSuc(res: AxiosResponse<{ code: number }>) { 
    // 是否需要判断授权token
    if (!h.authorizationRequired) return false
    // 当前响应返回的code是否等于 配置中的 statusExpirationCode 字段
    // 如果等于则会执行该插件中的响应成功拦截器
    if (res.data.code === h.statusExpirationCode) {
      return true
    }
    // 不执行响应拦截器
    return false
  }
}

// 修改UserAuthorization配置
export function useUserAuthorizationHelper<S>(conf: userAuthorizationConf) {
  h.statusExpirationCode = conf.statusExpirationCode
  h.refreshToken = conf.refreshToken || null
  h.authorizationRequired = conf.authorizationRequired
}

自己动手试试这些测试用例吧!

git clone https://github.com.cnpmjs.org/erqiu-sj/proveAxios.git