requets.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import axios, {
  2. AxiosInstance,
  3. AxiosRequestConfig,
  4. AxiosResponse,
  5. AxiosError,
  6. InternalAxiosRequestConfig
  7. } from 'axios';
  8. import {API_CONFIG, BaseResponse, ErrorResponse, ExtendedRequestConfig, RequestConfig } from './conf';
  9. import * as SecureStore from 'expo-secure-store';
  10. declare module 'axios' {
  11. interface AxiosRequestConfig {
  12. customConfig?: RequestConfig;
  13. }
  14. }
  15. // 创建 Axios 实例
  16. class ApiClient {
  17. public instance: AxiosInstance;
  18. private isRefreshing = false;
  19. private failedQueue: Array<{ resolve: (value: any) => void; reject: (error: any) => void }> = [];
  20. constructor() {
  21. this.instance = axios.create({
  22. baseURL: API_CONFIG.BASE_URL,
  23. timeout: API_CONFIG.TIMEOUT,
  24. headers: {
  25. 'Content-Type': 'application/json',
  26. },
  27. });
  28. this.setupInterceptors();
  29. }
  30. // 设置拦截器
  31. private setupInterceptors(): void {
  32. // 请求拦截器
  33. this.instance.interceptors.request.use(
  34. async (config: InternalAxiosRequestConfig) => {
  35. const extendedConfig = config as InternalAxiosRequestConfig & { customConfig?: RequestConfig };
  36. const { needAuth = true } = extendedConfig.customConfig || {};
  37. // 添加认证令牌
  38. if (needAuth) {
  39. const token = await SecureStore.getItemAsync('accessToken')
  40. if (token) {
  41. config.headers.Authorization = `Bearer ${token}`;
  42. }
  43. }
  44. return config;
  45. },
  46. (error: AxiosError) => {
  47. console.error('❌ 请求错误:', error);
  48. return Promise.reject(error);
  49. }
  50. );
  51. // 响应拦截器
  52. this.instance.interceptors.response.use(
  53. (response: AxiosResponse) => {
  54. console.log(`✅ ${response.config.method?.toUpperCase()} ${response.config.url} 成功`);
  55. return response;
  56. },
  57. async (error: AxiosError) => {
  58. return this.handleError(error);
  59. }
  60. );
  61. }
  62. // 错误处理
  63. private async handleError(error: AxiosError): Promise<never> {
  64. const originalRequest = error.config as InternalAxiosRequestConfig & {
  65. _retry?: boolean;
  66. customConfig?: RequestConfig;
  67. };
  68. // 网络错误
  69. if (!error.response) {
  70. const networkError: ErrorResponse = {
  71. code: -1,
  72. message: API_CONFIG.ERROR_MESSAGES.NETWORK_ERROR,
  73. };
  74. return Promise.reject(networkError);
  75. }
  76. const { status, data } = error.response;
  77. const errorData = data as ErrorResponse;
  78. // Token 过期,尝试刷新
  79. if (status === 401 && !originalRequest._retry) {
  80. if (this.isRefreshing) {
  81. // 如果正在刷新,将请求加入队列
  82. return new Promise((resolve, reject) => {
  83. this.failedQueue.push({ resolve, reject });
  84. });
  85. }
  86. originalRequest._retry = true;
  87. this.isRefreshing = true;
  88. try {
  89. // // 刷新 Token 逻辑
  90. // await this.refreshToken();
  91. // // 重试原始请求
  92. // const retryResponse = await this.instance(originalRequest);
  93. // this.isRefreshing = false;
  94. // this.processQueue(null);
  95. // return retryResponse;
  96. } catch (refreshError) {
  97. this.isRefreshing = false;
  98. this.processQueue(refreshError);
  99. // 刷新失败,跳转到登录页
  100. // await authManager.clearTokens();
  101. // navigation.navigate('Login'); // 根据实际导航库调整
  102. return Promise.reject({
  103. code: 401,
  104. message: API_CONFIG.ERROR_MESSAGES.UNAUTHORIZED,
  105. });
  106. }
  107. }
  108. // 其他错误处理
  109. const customError: ErrorResponse = {
  110. code: status,
  111. message: errorData?.message || this.getErrorMessageByStatus(status),
  112. };
  113. // 根据配置决定是否显示错误提示
  114. const { showError = true } = originalRequest.customConfig || {};
  115. if (showError) {
  116. this.showErrorToast(customError.message);
  117. }
  118. return Promise.reject(customError);
  119. }
  120. // 处理请求队列
  121. private processQueue(error: any): void {
  122. this.failedQueue.forEach(promise => {
  123. if (error) {
  124. promise.reject(error);
  125. } else {
  126. promise.resolve(this.instance);
  127. }
  128. });
  129. this.failedQueue = [];
  130. }
  131. // 刷新 Token
  132. // private async refreshToken(): Promise<void> {
  133. // const refreshToken = await authManager.getRefreshToken();
  134. // if (!refreshToken) {
  135. // throw new Error('No refresh token');
  136. // }
  137. // // 调用刷新 Token 接口
  138. // const response = await axios.post(`${API_CONFIG.BASE_URL}/auth/refresh`, {
  139. // refreshToken,
  140. // });
  141. // const newTokens = response.data.data;
  142. // await authManager.setTokens(newTokens);
  143. // }
  144. // 根据状态码获取错误消息
  145. private getErrorMessageByStatus(status: number): string {
  146. const messages: { [key: number]: string } = {
  147. 400: '请求参数错误',
  148. 403: '没有权限访问',
  149. 404: '请求资源不存在',
  150. 500: API_CONFIG.ERROR_MESSAGES.SERVER_ERROR,
  151. 502: '网关错误',
  152. 503: '服务不可用',
  153. 504: '网关超时',
  154. };
  155. return messages[status] || `请求失败 (${status})`;
  156. }
  157. // 显示错误提示(可根据实际使用的 Toast 库调整)
  158. private showErrorToast(message: string): void {
  159. // 使用你喜欢的 Toast 库,例如 react-native-toast-message
  160. // Toast.show({ type: 'error', text1: message });
  161. console.error('Error:', message);
  162. }
  163. }
  164. export const apiClient = new ApiClient();