request.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import axios from 'axios'
  2. import { ElMessage } from 'element-plus'
  3. import router from '../router'
  4. const apiBaseURL =
  5. import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:5000'
  6. const request = axios.create({
  7. baseURL: apiBaseURL,
  8. timeout: 600000,
  9. headers: {
  10. 'ngrok-skip-browser-warning': 'true',
  11. },
  12. })
  13. // 是否正在刷新 token
  14. let isRefreshing = false
  15. // 请求队列
  16. let requestQueue = []
  17. // 请求拦截器
  18. request.interceptors.request.use(
  19. config => {
  20. // 从 localStorage 获取 token
  21. const token = localStorage.getItem('access_token')
  22. if (token) {
  23. config.headers.Authorization = `Bearer ${token}`
  24. }
  25. return config
  26. },
  27. error => {
  28. return Promise.reject(error)
  29. }
  30. )
  31. // 响应拦截器
  32. request.interceptors.response.use(
  33. response => {
  34. return response.data
  35. },
  36. async error => {
  37. // 如果响应中包含数据,则返回数据
  38. if (error.response?.data) {
  39. // 处理 token 过期情况 (401 错误)
  40. if (error.response.status === 401) {
  41. const originalRequest = error.config
  42. const refreshToken = localStorage.getItem('refresh_token')
  43. // 如果没有 refresh_token,直接跳转到登录页
  44. if (!refreshToken) {
  45. ElMessage.error({
  46. message: '登录已过期,请重新登录',
  47. duration: 5000
  48. })
  49. localStorage.clear()
  50. router.push('/login')
  51. return Promise.reject(error)
  52. }
  53. // 如果已经在刷新 token,将请求加入队列
  54. if (isRefreshing) {
  55. return new Promise(resolve => {
  56. requestQueue.push(() => {
  57. originalRequest.headers.Authorization = `Bearer ${localStorage.getItem('access_token')}`
  58. resolve(request(originalRequest))
  59. })
  60. })
  61. }
  62. isRefreshing = true
  63. try {
  64. // 创建一个新的 axios 实例来刷新 token,避免进入拦截器循环
  65. const refreshResponse = await axios.post(`${apiBaseURL}/user/refresh`, {}, {
  66. headers: {
  67. 'Authorization': `Bearer ${refreshToken}`
  68. }
  69. })
  70. // 更新 token
  71. const newToken = refreshResponse.data.access_token
  72. localStorage.setItem('access_token', newToken)
  73. // 重新发送队列中的请求
  74. requestQueue.forEach(callback => callback())
  75. requestQueue = []
  76. // 重新发送原始请求
  77. originalRequest.headers.Authorization = `Bearer ${newToken}`
  78. return request(originalRequest)
  79. } catch (refreshError) {
  80. // 刷新 token 失败,说明 refresh_token 过期,清除 token 并跳转到登录页
  81. console.error('刷新 token 失败', refreshError)
  82. ElMessage.error('登录已过期,请重新登录')
  83. ElMessage.error({
  84. message: '登录已过期,请重新登录',
  85. duration: 5000
  86. })
  87. localStorage.clear()
  88. router.push('/login')
  89. return Promise.reject(refreshError)
  90. } finally {
  91. isRefreshing = false
  92. }
  93. }
  94. // 显示失败信息
  95. ElMessage.warning({
  96. message: error.response.data.operation?.failure_message || error.response.data.failure_message || '请求失败',
  97. duration: 4000
  98. })
  99. // 返回响应数据,让业务代码可以继续处理
  100. return error.response.data
  101. }
  102. // 如果没有响应数据,则拒绝 Promise
  103. ElMessage.error({
  104. message: '网络错误,请稍后重试',
  105. duration: 5000
  106. })
  107. return Promise.reject(error)
  108. }
  109. )
  110. export default request