Skip to content
Ray Hao edited this page Jun 6, 2024 · 4 revisions

功能清单

  • 支持单一令牌和多令牌缓存
  • 支持 JWT(JSON Web Token) 格式配置

API

配置项

interface TokenOptions {
  /**
   * Window 对象
   * @default window
   */
  window?: Window
  /**
   * 是否持久化数据到 `storage` 中
   * @default true
   */
  persistent?: boolean
  /**
   * 存储中心,支持实现 `Storage` 接口的对象
   * @default window.localStorage
   */
  storage?: StorageLike
  /**
   * 持久化到存储中心的键
   * @default '__VUE_APP_SDK__TOKEN__'
   */
  storageKey?: string
  /**
   * 访问令牌格式
   * - `normal`: 普通格式,`resolve()` 存储即所得
   * - `jwt`: JWT(JSON Web Token) 格式,`resolve()` 返回 `toJWT()` 格式
   *
   * @default 'normal'
   */
  format?: MaybeRefOrGetter<'normal' | 'jwt'>
  /**
   * 默认的 JWT(JSON Web Token) 前缀
   * @default 'Bearer'
   */
  jwtPrefix?: string
}

实例

interface Token {
  /**
   * 插件 ID
   */
  id: PluginID<Token>

  /**
   * 配置项
   */
  options: TokenOptions

  /**
   * 指定令牌是否存在
   * @param type 令牌类型
   * @param key 令牌键
   * @example
   * ```ts
   * // 应用仅存在单令牌
   * token.has('accessToken')
   *
   * // 应用存在多令牌
   * token.has('accessToken', 'second')
   * ```
   */
  has: (type: 'accessToken' | 'refreshToken', key?: string) => boolean

  /**
   * 获取指定令牌
   * @param type 令牌类型
   * @param key 令牌键
   * @example
   * ```ts
   * // 应用仅存在单令牌
   * token.get('accessToken')
   *
   * // 应用存在多令牌
   * token.get('accessToken', 'second')
   * ```
   */
  get: (type: 'accessToken' | 'refreshToken', key?: string) => string | undefined

  /**
   * 设置令牌状态
   * @param type 令牌类型
   * @param value 令牌
   * @param key 令牌键
   * @param profile 令牌配置
   *
   * @example
   * ```ts
   * // 应用仅存在单令牌
   * token.set('accessToken', 'token value', { format: 'jwt' })
   * token.set({ accessToken: 'token value' }, { format: 'jwt' })
   *
   * // 应用存在多令牌
   * token.set('accessToken', 'token value', 'second', { format: 'jwt', jwtPrefix: 'Token' })
   * token.set({ accessToken: 'token value' }, 'second', { format: 'jwt', jwtPrefix: 'Token' })
   * ```
   */
  set: {
    (type: 'accessToken' | 'refreshToken', value?: string, key?: string, profile?: TokenProfile): void
    (type: 'accessToken' | 'refreshToken', value?: string, profile?: TokenProfile): void
    (state: Partial<TokenState<false>>, key?: string, profile?: TokenProfile): void
    (state: Partial<TokenState<false>>, profile?: TokenProfile): void
  }

  /**
   * 清理令牌状态
   * @param type 令牌类型,设为空时清理全部令牌
   * @param key 令牌键,设为空时清理指定类型全部令牌
   * @example
   * ```ts
   * // 应用仅存在单令牌
   * token.clear('accessToken')
   *
   * // 应用存在多令牌
   * token.clear('accessToken', 'second')
   *
   * // 删除指定类型所有令牌
   * token.clear('accessToken', '')
   *
   * // 删除全部令牌
   * token.clear()
   * ```
   */
  clear: (type?: 'accessToken' | 'refreshToken', key?: string) => void

  /**
   * 转为 JWT(JSON Web Token) 格式令牌
   * @param prefix JWT(JSON Web Token) 前缀
   * @param key 令牌键
   * @returns JWT(JSON Web Token) 格式令牌
   * @example
   * ```ts
   * // 使用默认 `jwtPrefix`
   * token.toJWT() // 'Bearer abcdefg'
   *
   * // 使用自定义 `jwtPrefix`
   * token.toJWT('Token') // 'Token abcdefg''
   * ```
   */
  toJWT: (prefix?: string, key?: string) => string

  /**
   * 根据设定令牌格式获取访问令牌
   * @param key 令牌键
   * @example
   * ```ts
   * // 普通格式
   * const token = createToken()
   * token.set('accessToken', 'abcdefg')
   * token.resolve() // 'abcdefg'
   *
   * // JWT 格式
   * const token = createToken({ format: 'jwt' })
   * token.set('accessToken', 'abcdefg')
   * token.resolve() // 'Bearer abcdefg'
   *
   * // 单独设置
   * const token = createToken()
   * token.set('accessToken', 'abcdefg', { format: 'jwt', jwtPrefix: 'Token' })
   * token.resolve() // 'Token abcdefg'
   * ```
   */
  resolve: (key?: string) => string | undefined

  /**
   * 获取令牌配置
   * @param key 令牌键
   */
  getTokenProfile: (key?: string) => TokenProfile

  /**
   * 插件安装
   */
  install: (sdk: AppSDKInternalInstance) => void
}

初始化

// sdk.ts
import { createAppSDK, createToken } from 'vue-app-sdk'

export const sdk = createAppSDK()

sdk.use(
  createToken({
    // `accessToken` 格式
    // - `normal`: 普通格式,`resolve()` 存储即所得
    // - `jwt`: JWT(JSON Web Token) 格式,`resolve()` 返回 `toJWT()` 格式
    format: 'jwt',

    // JWT(JSON Web Token) 前缀
    jwtPrefix: 'Bearer'
  }),
)

使用方式

在调用 sdk.cleanup() 时自动清理令牌缓存。

单令牌管理

import { TOKEN_ID, useAppSDK } from 'vue-app-sdk'
import Axios from 'axios'

const token = useAppSDK().getPlugin(TOKEN_ID)!
const axios = Axios.create()

axios.interceptors.request.use((config) => {
  // 如果有访问令牌则携带
  if (token.has('accessToken')) {
    // 设置最终的访问令牌
    config.headers.Authorization = token.resolve()
  }

  return config
})

login()

async function login() {
  // ...

  const response = await axios<{ accessToken: string, refreshToken?: string }>('/login')
  if (response.status === 200) {
    // 设置令牌信息,刷新令牌非必须
    token.set({
      accessToken: response.data.accessToken,
      refreshToken: response.data.refreshToken
    })
  }
  else {
    // 清除所有令牌信息,可传入指定令牌类型
    token.clear()
  }
}

多令牌管理

  1. 定义令牌键,调用方法时可获得类型提示
// types/vue-app-sdk.d.ts

declare module 'vue-app-sdk' {
  interface TokenRecord {
    // 三方开放接口访问令牌,值类型随意设置
    open: void
  }
}

export {}
  1. 调用方法时传入指定令牌键
// ...

fetchUserInfo().then((data) => {
  // 设置三方开放接口访问令牌和令牌配置
  token.set('accessToken', data.openToken, 'open', { format: 'normal' })

  // 控制台输出三方开放接口访问令牌
  console.log(token.get('accessToken', 'open'))
})

function fetchUserInfo() {
  return Promise.resolve({ id: 1, name: 'admin', openToken: 'abcd' })
}

适配不同接口使用指定令牌

本示例以 axios 为例,其他请求工具可自行尝试。

  1. 定义 axios 全局配置项,获取类型提示
// types/axios.d.ts
import type { TokenKey } from 'vue-app-sdk'

declare module 'axios' {
  interface AxiosRequestConfig {
    /**
     * 令牌键,多令牌时可设置指定访问令牌键
     */
    tokenKey?: TokenKey
  }
}

export {}
  1. 拦截器携带指定令牌键的访问令牌
// ...

axios.interceptors.request.use((config) => {
  // 如果有指定访问令牌则携带
  if (token.has('accessToken', config.tokenKey)) {
    // 设置最终的访问令牌
    config.headers.Authorization = token.resolve(config.tokenKey)
  }

  return config
})

// 请求三方开放接口数据
function fetchData() {
  // 设置令牌键,在接口请求时携带指定令牌键的访问令牌
  return axios.get('/open/list', { tokenKey: 'open' })
}
Clone this wiki locally