serverReq.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import { v4 as uuid_v4 } from 'uuid'
  2. import { FetchClient, type FetchResponse } from './fetchClient'
  3. import { useUserStore } from '@/stores/user'
  4. import { jumpToSignin } from '@/utils/casdoor/casdoor'
  5. import { refreshToken } from './auth'
  6. const baseUrl = import.meta.env.BASE_URL
  7. export interface Response<T = any> {
  8. code: number
  9. message: string
  10. data: T
  11. traceId: string
  12. timestamp: number
  13. }
  14. const http = new FetchClient(baseUrl + 'api', 60 * 1000) // 超时时间设置为60s
  15. let isRefreshing = false
  16. let pendingQueue: (() => void)[] = []
  17. async function handleTokenExpired(
  18. response: FetchResponse<Response<any>>,
  19. ): Promise<FetchResponse<Response<any>>> {
  20. if (!('retry' in response)) {
  21. return response
  22. }
  23. // 原始请求包装成函数
  24. const originalRequest = response.retry
  25. if (originalRequest === undefined) {
  26. return response
  27. }
  28. // 如果已经在刷新,挂起请求
  29. if (isRefreshing) {
  30. return new Promise<FetchResponse<Response<any>>>((resolve, reject) => {
  31. pendingQueue.push(() => originalRequest().then(resolve).catch(reject))
  32. })
  33. }
  34. isRefreshing = true
  35. try {
  36. // 调用刷新接口
  37. const res = await refreshToken()
  38. const userStore = useUserStore()
  39. userStore.setToken(res)
  40. // 刷新完成,重放队列请求
  41. await Promise.all(pendingQueue.map((fn) => fn()))
  42. pendingQueue = []
  43. // 重新执行当前请求
  44. return originalRequest()
  45. } catch (err) {
  46. handleRefreshFailed()
  47. throw err
  48. } finally {
  49. isRefreshing = false
  50. }
  51. }
  52. function handleRefreshFailed() {
  53. const userStore = useUserStore()
  54. userStore.setToken(undefined)
  55. pendingQueue = []
  56. isRefreshing = false
  57. console.log('jumpToSignin')
  58. // 跳转登录页
  59. jumpToSignin()
  60. }
  61. // 设置拦截器(可选)
  62. http.setRequestInterceptor((config) => {
  63. const userStore = useUserStore()
  64. // 自动加 token
  65. const token = userStore.token
  66. if (token) {
  67. config.headers = {
  68. ...config.headers,
  69. Authorization: `Bearer ${token}`,
  70. 'X-Trace-Id': uuid_v4(),
  71. }
  72. }
  73. return config
  74. })
  75. http.setResponseInterceptor(async (response: FetchResponse<Response<any>>): Promise<any> => {
  76. // 全局响应处理,比如统一格式校验
  77. if (!response.ok) {
  78. console.log({ response })
  79. switch (response.status) {
  80. case 401: // Unauthorized Token过期
  81. // 刷新Token
  82. response = await handleTokenExpired(response)
  83. if (!response.ok) {
  84. console.log({ response })
  85. throw new Error(response.data)
  86. }
  87. break
  88. case 403: // Forbidden 刷新Token失败
  89. // 重新登录
  90. handleRefreshFailed()
  91. break
  92. case 204: // No Content
  93. break
  94. default:
  95. console.log({ response })
  96. throw new Error(response.data)
  97. }
  98. return response.data
  99. }
  100. return response.data.data
  101. })
  102. export default http