import 'dart:async'; import 'package:dio/dio.dart'; import '../config/api_config.dart'; import '../core/exceptions/app_exception.dart'; import '../core/utils/app_logger.dart'; /// API响应 class ApiResponse { final int code; final String message; final T? data; final String? error; final String? correlationId; ApiResponse({ required this.code, required this.message, this.data, this.error, this.correlationId, }); factory ApiResponse.fromJson(Map json) { return ApiResponse( code: json['code'] as int? ?? 0, message: json['msg'] as String? ?? '', data: json['data'] as T?, error: json['error'] as String?, correlationId: json['correlation_id'] as String?, ); } bool get isSuccess => code == 0; bool get isContinue => code == 203; } /// API服务 class ApiService { late Dio _dio; static ApiService? _instance; bool _isRefreshing = false; final List> _refreshSubscribers = []; bool _initialized = false; /// 获取 token 的回调 Future Function()? getTokenCallback; /// 刷新 token 的回调 Future Function()? refreshTokenCallback; /// 清除认证数据的回调 Future Function()? clearAuthCallback; ApiService._() { _dio = Dio( BaseOptions( baseUrl: ApiConfig.defaultBaseUrl, connectTimeout: const Duration(seconds: ApiConfig.connectTimeout), receiveTimeout: const Duration(seconds: ApiConfig.receiveTimeout), sendTimeout: const Duration(seconds: ApiConfig.sendTimeout), headers: {'Content-Type': 'application/json'}, ), ); _dio.interceptors.add(_requestInterceptor()); _dio.interceptors.add(_responseInterceptor()); _dio.interceptors.add(_errorInterceptor()); } /// 是否正在刷新token bool get isRefreshing => _isRefreshing; /// 暴露 Dio 实例(用于二进制下载等不走 _parseResponse 的场景) Dio get dio => _dio; /// 获取单例 static ApiService get instance { _instance ??= ApiService._(); return _instance!; } /// 设置认证回调 /// 由 AuthProvider 在初始化时调用 static void setAuthCallbacks({ required Future Function() getToken, required Future Function() refreshToken, required Future Function() clearAuth, }) { final service = instance; service.getTokenCallback = getToken; service.refreshTokenCallback = refreshToken; service.clearAuthCallback = clearAuth; } /// 初始化API服务(设置正确的baseUrl) Future init() async { if (_initialized) return; final baseUrl = await ApiConfig.baseUrl; _dio.options.baseUrl = baseUrl; _initialized = true; } /// 动态设置 API baseUrl Future setBaseUrl(String baseUrl) async { _dio.options.baseUrl = baseUrl; AppLogger.d('ApiService baseUrl 已更新为: $baseUrl'); } /// 请求拦截器 Interceptor _requestInterceptor() { return InterceptorsWrapper( onRequest: (options, handler) async { // 从回调获取 Token if (getTokenCallback != null) { final token = await getTokenCallback!(); if (token != null && token.isNotEmpty) { options.headers['Authorization'] = 'Bearer $token'; } } return handler.next(options); }, ); } /// 响应拦截器 Interceptor _responseInterceptor() { return InterceptorsWrapper( onResponse: (response, handler) { AppLogger.d( 'API Response: ${response.statusCode} - ${response.requestOptions.uri}', ); // 检查 JSON 响应中的 code 字段 if (response.data is Map) { final data = response.data as Map; final code = data['code'] as int?; AppLogger.d('_responseInterceptor -> JSON code: $code'); if (code == 401) { // HTTP 200 但 JSON code 是 401,需要处理未授权 final isNoAuth = response.requestOptions.extra['noAuth'] as bool? ?? false; AppLogger.d('_responseInterceptor -> isNoAuth: $isNoAuth'); if (!isNoAuth) { // 直接在响应拦截器中处理 401 AppLogger.d('_responseInterceptor -> 触发 401 处理'); // 异步处理,不阻塞响应 _handle401InResponse(response.requestOptions); } } } return handler.next(response); }, ); } /// 在响应拦截器中处理 401 错误 Future _handle401InResponse(RequestOptions requestOptions) async { final path = requestOptions.path; if (path.contains('/session/token/refresh')) { return; } if (_isRefreshing) { return; } _isRefreshing = true; try { AppLogger.d('_handle401InResponse -> 开始刷新 token'); if (refreshTokenCallback != null) { await refreshTokenCallback!(); } AppLogger.d('_handle401InResponse -> token 刷新完成'); } catch (e) { AppLogger.d('_handle401InResponse -> 刷新失败: $e'); if (clearAuthCallback != null) { await clearAuthCallback!(); } } finally { _isRefreshing = false; } } /// 错误拦截器 Interceptor _errorInterceptor() { return InterceptorsWrapper( onError: (error, handler) async { AppLogger.d("_errorInterceptor -> 获取files列表: response"); AppLogger.d('API Error: ${error.requestOptions.uri} - ${error.message}'); // 检查是否是 401 错误(HTTP 401 或 JSON code: 401) bool is401Error = error.response?.statusCode == 401; if (!is401Error && error.response?.data is Map) { final data = error.response!.data as Map; is401Error = data['code'] == 401; } if (is401Error) { final isNoAuth = error.requestOptions.extra['noAuth'] as bool? ?? false; if (!isNoAuth) { // 不是noAuth请求,需要刷新token final response = await _handle401Error(error, handler); if (response != null) { return handler.resolve(response); } } } // 处理错误 if (error.response == null) { // 网络错误 throw NetworkException( '网络连接失败,请检查网络设置', code: error.response?.statusCode, ); } final statusCode = error.response?.statusCode; final responseData = error.response?.data; AppLogger.d('Error Response Data: $responseData'); if (responseData is Map) { final response = ApiResponse.fromJson(responseData); throw ServerException(response.message, code: response.code); } throw ServerException( responseData?.toString() ?? '请求失败', code: statusCode, ); }, ); } /// 处理401错误,尝试刷新token Future _handle401Error( DioException error, ErrorInterceptorHandler handler, ) async { // 检查是否需要跳过token检查(如刷新token请求本身) final path = error.requestOptions.path; if (path.contains('/session/token/refresh')) { // 刷新token的请求也失败了,直接返回错误 return null; } // 如果正在刷新token,等待刷新完成后再重试 if (_isRefreshing) { final completer = Completer(); _refreshSubscribers.add(completer); // 等待刷新完成 await completer.future; // 刷新完成后,移除旧的 Authorization header,让拦截器重新添加新 token error.requestOptions.headers.remove('Authorization'); // 重试请求(拦截器会自动添加新 token) return await _dio.fetch(error.requestOptions); } // 开始刷新token _isRefreshing = true; try { // 调用回调刷新 token if (refreshTokenCallback != null) { await refreshTokenCallback!(); } _isRefreshing = false; // 通知所有等待的请求可以重试了 for (final subscriber in _refreshSubscribers) { if (!subscriber.isCompleted) { subscriber.complete(); } } _refreshSubscribers.clear(); // 重试当前请求:移除旧 header,让拦截器重新添加新 token error.requestOptions.headers.remove('Authorization'); return await _dio.fetch(error.requestOptions); } catch (e) { AppLogger.d('Refresh token failed: $e'); _isRefreshing = false; // 刷新失败,清除认证数据 if (clearAuthCallback != null) { await clearAuthCallback!(); } // 通知所有等待的请求 for (final subscriber in _refreshSubscribers) { if (!subscriber.isCompleted) { subscriber.completeError(e); } } _refreshSubscribers.clear(); // 返回null,让原始错误继续传播 return null; } } /// GET请求 , 如果是分享请求, 则不进入 _parseResponse Future get( String path, { Map? queryParameters, bool noAuth = false, bool isNoData = false, Map? headers, }) async { final response = await _dio.get( path, queryParameters: queryParameters, options: Options(extra: {'noAuth': noAuth}, headers: headers), ); // 如果是分享请求, 则不进入 _parseResponse if (isNoData) { return response.data as T; } return _parseResponse(response); } /// POST请求 Future post( String path, { dynamic data, Map? queryParameters, bool noAuth = false, Map? headers, bool isNoData = false, }) async { AppLogger.d('API POST Request: $path'); AppLogger.d('Request Data: $data'); final response = await _dio.post( path, data: data, queryParameters: queryParameters, options: Options(extra: {'noAuth': noAuth}, headers: headers), ); AppLogger.d('Response Data: ${response.data}'); var isActivEmail = 0; if (response.statusCode == 200) { Map? tmp = response.data as Map?; isActivEmail = tmp?['code'] as int; } if (isNoData || isActivEmail == 203) { return response.data as T; } return _parseResponse(response); } /// POST请求(带上传进度) Future postWithProgress( String path, { dynamic data, Map? queryParameters, bool noAuth = false, Map? headers, ProgressCallback? onSendProgress, }) async { AppLogger.d('API POST Request with progress: $path'); final response = await _dio.post( path, data: data, queryParameters: queryParameters, options: Options(extra: {'noAuth': noAuth}, headers: headers), onSendProgress: onSendProgress, ); AppLogger.d('Response Data: ${response.data}'); return _parseResponse(response); } /// PUT请求 Future put( String path, { dynamic data, bool noAuth = false, bool isNoData = false, }) async { final response = await _dio.put( path, data: data, options: Options(extra: {'noAuth': noAuth}), ); // 当请求的接口为创建分享时, 逻辑上不适合走到 _parseResponse -> ApiResponse.fromJson 直接返回结果即可 if (isNoData) { return response.data as T; } return _parseResponse(response); } /// PATCH请求 Future patch( String path, { dynamic data, Map? queryParameters, bool noAuth = false, }) async { final response = await _dio.patch( path, data: data, queryParameters: queryParameters, options: Options(extra: {'noAuth': noAuth}), ); return _parseResponse(response); } /// DELETE请求 Future delete( String path, { dynamic data, Map? queryParameters, bool noAuth = false, }) async { final response = await _dio.delete( path, data: data, queryParameters: queryParameters, options: Options(extra: {'noAuth': noAuth}), ); return _parseResponse(response); } /// 解析响应 T _parseResponse(Response response) { final data = response.data; if (data is Map) { final apiResponse = ApiResponse.fromJson(data); if (!apiResponse.isSuccess && !apiResponse.isContinue) { throw ServerException(apiResponse.message, code: apiResponse.code); } return apiResponse.data as T; } return data as T; } }