request.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import type { UseFetchOptions } from '#app'
  2. import type { BasicResponse } from '~/models/common'
  3. import type { RequestQueueItem, IInterceptor, IConfig, IOption } from './interface'
  4. import { useLoginStore } from '~/stores/user/login'
  5. import { getToken, getRefreshToken } from '../auth'
  6. class Request {
  7. public baseURL: string
  8. public interceptor: IInterceptor
  9. private isRefreshing = false // 是否正在刷新token,开启请求队列
  10. private requestQueue: RequestQueueItem[] // 待请求队列
  11. constructor({ baseURL, interceptor }: IConfig) {
  12. this.baseURL = baseURL
  13. this.interceptor = interceptor as IInterceptor
  14. this.isRefreshing = false
  15. this.requestQueue = []
  16. }
  17. request<T = BasicResponse>({ url, method, params, data, options }: IOption<T>): Promise<T> {
  18. const newOptions: UseFetchOptions<T> = {
  19. baseURL: this.baseURL,
  20. method,
  21. query: params,
  22. body: data,
  23. ...options,
  24. onRequest: this.interceptor?.onRequest,
  25. onRequestError: this.interceptor?.onRequestError,
  26. onResponse: this.interceptor?.onResponse,
  27. onResponseError: this.interceptor?.onResponseError,
  28. }
  29. return new Promise((resolve, reject) => {
  30. this.requestPipeline(url, newOptions, resolve, reject)
  31. })
  32. }
  33. // 请求管道处理具体细节
  34. requestPipeline<T = BasicResponse>(
  35. url: string,
  36. options: UseFetchOptions<T>,
  37. resolve: (data: T) => void,
  38. reject: (data: unknown) => void
  39. ): void {
  40. const token = getToken()
  41. const newOptions = {
  42. ...options,
  43. headers: {
  44. Authorization: token ? `Bearer ${token}` : '',
  45. ...options?.headers,
  46. },
  47. }
  48. $fetch<T>(url, newOptions as any)
  49. .then((res) => {
  50. resolve(res as T)
  51. })
  52. .catch((error) => {
  53. if (error.status === 401) {
  54. if (!this.isRefreshing) {
  55. this.isRefreshing = true
  56. this.refreshToken()
  57. }
  58. this.addRequestQueueForRefreshToken<T>(url, options, resolve, reject)
  59. } else {
  60. reject(error)
  61. }
  62. })
  63. }
  64. // 刷新token
  65. refreshToken() {
  66. const loginStore = useLoginStore()
  67. this.postRefreshTokenFunc()
  68. .then((res) => {
  69. if (res.data) {
  70. const data = res.data
  71. loginStore.updateToken(data)
  72. this.requestQueueStartAfterRefreshToken()
  73. }
  74. })
  75. .catch(() => {
  76. loginStore.logout()
  77. navigateTo({ path: '/' })
  78. // ElMessage.error('登录已失效,需要重新登录')
  79. // navigateTo({ path: '/login' })
  80. })
  81. .finally(() => {
  82. this.isRefreshing = false
  83. })
  84. }
  85. postRefreshTokenFunc(): Promise<BasicResponse> {
  86. const data = {
  87. clientId: getCanvasFingerprint(),
  88. }
  89. const token = getRefreshToken()
  90. return $fetch(this.baseURL + '/auth/refresh', {
  91. method: 'post',
  92. body: data,
  93. headers: {
  94. Authorization: `Bearer ${token}`,
  95. 'Content-Type': 'application/json',
  96. },
  97. })
  98. }
  99. // 添加请求到等待队列
  100. addRequestQueueForRefreshToken<T = BasicResponse>(
  101. url: string,
  102. options: UseFetchOptions<T>,
  103. resolve: (data: T) => void,
  104. reject: (data: unknown) => void
  105. ): void {
  106. this.requestQueue.push({
  107. url,
  108. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  109. // @ts-ignore
  110. options,
  111. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  112. // @ts-ignore
  113. resolve,
  114. reject,
  115. })
  116. }
  117. // 刷新token成功等待队列开始请求
  118. requestQueueStartAfterRefreshToken(): void {
  119. let requestQueueItem = this.requestQueue.pop()
  120. while (requestQueueItem) {
  121. const { options, url, resolve, reject } = requestQueueItem
  122. this.requestPipeline(url, options, resolve, reject)
  123. requestQueueItem = this.requestQueue.pop()
  124. }
  125. }
  126. }
  127. export default Request