requets.ts 5.5 KB

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