rpc-client.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import Taro from '@tarojs/taro'
  2. import { hc } from 'hono/client'
  3. import ResponsePolyfill from './response-polyfill'
  4. // 刷新token的函数
  5. let isRefreshing = false
  6. let refreshSubscribers: ((token: string) => void)[] = []
  7. // 执行token刷新
  8. const refreshToken = async (): Promise<string | null> => {
  9. if (isRefreshing) {
  10. // 如果已经在刷新,等待结果
  11. return new Promise((resolve) => {
  12. refreshSubscribers.push((token) => {
  13. resolve(token)
  14. })
  15. })
  16. }
  17. isRefreshing = true
  18. try {
  19. const refreshToken = Taro.getStorageSync('enterprise_refresh_token')
  20. if (!refreshToken) {
  21. throw new Error('未找到刷新token')
  22. }
  23. // 调用刷新token接口
  24. const response = await Taro.request({
  25. url: `${process.env.TARO_APP_API_BASE_URL || 'http://localhost:3000'}/api/v1/yongren/auth/refresh-token`,
  26. method: 'POST',
  27. header: {
  28. 'Content-Type': 'application/json',
  29. 'Authorization': `Bearer ${refreshToken}`
  30. }
  31. })
  32. if (response.statusCode === 200) {
  33. const { token, refresh_token: newRefreshToken } = response.data
  34. Taro.setStorageSync('enterprise_token', token)
  35. if (newRefreshToken) {
  36. Taro.setStorageSync('enterprise_refresh_token', newRefreshToken)
  37. }
  38. // 通知所有等待的请求
  39. refreshSubscribers.forEach(callback => callback(token))
  40. refreshSubscribers = []
  41. return token
  42. } else {
  43. throw new Error('刷新token失败')
  44. }
  45. } catch (error) {
  46. console.error('刷新token失败:', error)
  47. // 清除token,跳转到登录页
  48. Taro.removeStorageSync('enterprise_token')
  49. Taro.removeStorageSync('enterprise_refresh_token')
  50. Taro.removeStorageSync('enterpriseUserInfo')
  51. // 跳转到登录页
  52. Taro.showToast({
  53. title: '登录已过期,请重新登录',
  54. icon: 'none'
  55. })
  56. setTimeout(() => {
  57. Taro.redirectTo({
  58. url: '/pages/login/index'
  59. })
  60. }, 1500)
  61. return null
  62. } finally {
  63. isRefreshing = false
  64. }
  65. }
  66. // API配置
  67. const API_BASE_URL = process.env.TARO_APP_API_BASE_URL || 'http://localhost:3000'
  68. // 完整的API地址
  69. // const BASE_URL = `${API_BASE_URL}/api/${API_VERSION}`
  70. // 创建自定义fetch函数,适配Taro.request,支持token自动刷新
  71. const taroFetch: any = async (input, init) => {
  72. const url = typeof input === 'string' ? input : input.url
  73. const method = init.method || 'GET'
  74. const requestHeaders: Record<string, string> = init.headers;
  75. const keyOfContentType = Object.keys(requestHeaders).find(item => item.toLowerCase() === 'content-type')
  76. if (!keyOfContentType) {
  77. requestHeaders['content-type'] = 'application/json'
  78. }
  79. // 构建Taro请求选项
  80. const options: Taro.request.Option = {
  81. url,
  82. method: method as any,
  83. data: init.body,
  84. header: requestHeaders
  85. }
  86. // 添加token - 优先使用企业token,兼容mini_token
  87. let token = Taro.getStorageSync('enterprise_token')
  88. if (!token) {
  89. token = Taro.getStorageSync('mini_token')
  90. }
  91. if (token) {
  92. options.header = {
  93. ...options.header,
  94. 'Authorization': `Bearer ${token}`
  95. }
  96. }
  97. // 发送请求
  98. const sendRequest = async (): Promise<any> => {
  99. try {
  100. console.log('API请求:', options.url)
  101. const response = await Taro.request(options)
  102. const responseHeaders = response.header;
  103. // 处理204 No Content响应,不设置body
  104. const body = response.statusCode === 204
  105. ? null
  106. : responseHeaders['content-type']!.includes('application/json')
  107. ? JSON.stringify(response.data)
  108. : response.data;
  109. return new ResponsePolyfill(
  110. body,
  111. {
  112. status: response.statusCode,
  113. statusText: response.errMsg || 'OK',
  114. headers: responseHeaders
  115. }
  116. )
  117. } catch (error) {
  118. console.error('API Error:', error)
  119. throw error
  120. }
  121. }
  122. try {
  123. let response = await sendRequest()
  124. // 检查是否为401错误,尝试刷新token
  125. if (response.status === 401 && token) {
  126. console.log('检测到401错误,尝试刷新token...')
  127. const newToken = await refreshToken()
  128. if (newToken) {
  129. // 更新请求header中的token
  130. options.header = {
  131. ...options.header,
  132. 'Authorization': `Bearer ${newToken}`
  133. }
  134. // 重试原始请求
  135. response = await sendRequest()
  136. } else {
  137. // 刷新失败,返回原始401响应
  138. return response
  139. }
  140. }
  141. return response
  142. } catch (error) {
  143. console.error('API请求失败:', error)
  144. Taro.showToast({
  145. title: error.message || '网络错误',
  146. icon: 'none'
  147. })
  148. throw error
  149. }
  150. }
  151. // 创建Hono RPC客户端
  152. export const rpcClient = <T extends any>() => {
  153. // @ts-ignore
  154. return hc<T>(`${API_BASE_URL}`, {
  155. fetch: taroFetch
  156. })
  157. }