import 'dart:io'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:get/get.dart' as getx; import '../../../config/translations/strings_enum.dart'; import '../../../utils/log/logger.dart'; import '../../components/country_restricted_overlay.dart'; import '../../data/models/api_result.dart'; enum DomainType { api, // 普通API域名 log, // 日志上传域名 file, // 文件上传域名 } /// HTTP请求方法 enum RequestMethod { get, post, put, delete, patch } /// 重试状态类 class _RetryState { int currentRetry = 0; bool isRetrying = false; void reset() { currentRetry = 0; isRetrying = false; } } /// API封装类 abstract class BaseApi { final CancelToken _cancelToken = CancelToken(); late final Dio dio; // 不同类型域名的最大重试次数 static const Map _maxRetries = { DomainType.api: 0, // API域名最多重试3次 DomainType.log: 0, // 日志上传最多重试1次 DomainType.file: 0, // 文件上传最多重试1次 }; // 当前重试状态 final Map _retryStates = { DomainType.api: _RetryState(), DomainType.log: _RetryState(), DomainType.file: _RetryState(), }; String _baseUrl = ''; Map _hostIpMap = {}; // 需要绑定的 host -> ip var _proxy = ''; // 代理配置 /// 设置API基础请求URL void setbaseUrl(String value, {bool? useProxy}) { try { // 参数验证 if (value.isEmpty) { return; } // 解析URL和IP final urlConfig = _parseUrlConfig(value); if (urlConfig == null) { return; } final url = urlConfig['url']!; final ip = urlConfig['ip']!; // 验证URL格式 if (!_isValidUrl(url)) { return; } if (ip.isNotEmpty) { // 验证IP格式 if (!_isValidIp(ip)) { return; } // 解析URL获取host final uri = Uri.parse(url); if (dio.options.baseUrl == url && _hostIpMap[uri.host] == ip) { return; } _hostIpMap = {uri.host: ip}; _baseUrl = value; } else { _hostIpMap = {}; _baseUrl = url; } if (dio.options.baseUrl == url) { return; } dio.options.baseUrl = url; // 更新HttpClient _updateHttpClient(); } catch (e) { log('BaseApi', 'setBaseUrl: $e'); } } /// 获取API基础请求URL String get baseUrl => _baseUrl; /// 设置HTTP代理 /// /// [proxy]为代理规则,如:PROXY 127.0.0.1:11029 void setProxy(String proxy) { if (proxy.isNotEmpty) { _proxy = proxy; _updateHttpClient(); } } void _updateHttpClient() { dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { final client = HttpClient(); // client.findProxy = (uri) => proxy; client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; // 设置其他属性 client.autoUncompress = true; // 设置用户代理 client.userAgent = 'Nomo/1.0'; // 设置连接工厂 client.connectionFactory = (Uri uri, String? host, int? port) async { final resolvedHost = host ?? _hostIpMap[uri.host] ?? uri.host; final actualPort = port ?? uri.port; if (uri.scheme == 'https') { // 使用SecureSocket.connect建立真正的SSL连接 return SecureSocket.startConnect( resolvedHost, actualPort, onBadCertificate: (X509Certificate cert) { return true; // 接受所有证书 }, ); } else { // 对于HTTP,返回普通Socket连接 return Socket.startConnect(resolvedHost, actualPort); } }; client.findProxy = (uri) { if (_proxy.isNotEmpty) { return _proxy; } return 'DIRECT'; // 不使用代理 }; return client; }, ); } /// 解析URL配置字符串 /// 格式: "https://www.google.com:443|ip=54.236.5.237" Map? _parseUrlConfig(String value) { try { final parts = value.split('|ip='); if (parts.length != 2) { return {'url': value, 'ip': ""}; } final url = parts[0].trim(); final ip = parts[1].trim(); if (url.isEmpty || ip.isEmpty) { return null; } return {'url': url, 'ip': ip}; } catch (e) { return null; } } /// 验证URL格式 bool _isValidUrl(String url) { try { final uri = Uri.parse(url); return uri.hasScheme && uri.hasAuthority; } catch (e) { return false; } } /// 验证IP地址格式 bool _isValidIp(String ip) { try { final parts = ip.split('.'); if (parts.length != 4) return false; for (final part in parts) { final num = int.tryParse(part); if (num == null || num < 0 || num > 255) { return false; } } return true; } catch (e) { return false; } } /// 重置HTTP代理 void resetProxy() { _proxy = ''; _updateHttpClient(); } /// 获取默认Header Map? getDefaultHeader() { return null; } /// 获取默认Query Map? getDefaultQuery() { return null; } /// 加密数据 dynamic encrypt(dynamic input) { return input; } /// 解密数据 dynamic decrypt(dynamic input) { return input; } /// 反序列化API返回结果 ApiResult getApiResult(Map map) { return ApiResult( success: map['success'] ?? false, data: map['data'], errorCode: map['errorCode'], errorMessage: map['errorMessage'], ); } // /// 更新基础URL // void updateBaseUrl(String baseUrl) { // dio.options.baseUrl = baseUrl; // log('BaseApi', 'Base URL updated to: $baseUrl'); // } /// 判断是否应该重试错误 bool _shouldRetryError(DioException error, DomainType domainType) { final retryState = _retryStates[domainType]!; // 检查重试次数 if (retryState.currentRetry >= (_maxRetries[domainType] ?? 3)) { return false; } // 网络错误、超时错误、服务器错误(5xx)都应该重试 return checkDioException(error); } bool checkDioException(DioException error) { return error.type == DioExceptionType.connectionTimeout || error.type == DioExceptionType.sendTimeout || error.type == DioExceptionType.receiveTimeout || error.type == DioExceptionType.connectionError || (error.response != null && error.response!.statusCode != null && error.response!.statusCode! >= 500 && error.response!.statusCode! < 600) || (error.response != null && error.response!.statusCode != null && error.response!.statusCode! == 404); } /// 原始请求方法 Future rawRequest( RequestMethod method, String path, { dynamic data, Map? header, Map? query, Options? options, CancelToken? cancelToken, DomainType domainType = DomainType.api, // 默认使用API域名 }) async { final retryState = _retryStates[domainType]!; log('BaseApi', 'Request: $method ${dio.options.baseUrl}$path'); try { // 重置重试状态 if (!retryState.isRetrying) { retryState.currentRetry = 0; } // 合并默认Header和传入的Header final headers = {...?getDefaultHeader(), ...?header}; // 合并默认Query和传入的Query final queries = {...?getDefaultQuery(), ...?query}; // 设置请求选项 final requestOptions = options ?? Options(); requestOptions.headers = headers; // 根据请求方法发送请求 Response response; switch (method) { case RequestMethod.get: response = await dio.get( path, queryParameters: queries, options: requestOptions, cancelToken: cancelToken, ); break; case RequestMethod.post: response = await dio.post( path, data: data, queryParameters: queries, options: requestOptions, cancelToken: cancelToken, ); break; case RequestMethod.put: response = await dio.put( path, data: data, queryParameters: queries, options: requestOptions, cancelToken: cancelToken, ); break; case RequestMethod.delete: response = await dio.delete( path, data: data, queryParameters: queries, options: requestOptions, cancelToken: cancelToken, ); break; case RequestMethod.patch: response = await dio.patch( path, data: data, queryParameters: queries, options: requestOptions, cancelToken: cancelToken, ); break; } // 重置重试状态 retryState.reset(); // 先处理特殊状态码,不进行解密 if (response.statusCode == 204) { getx.Get.offAll( () => const CountryRestrictedOverlay(), transition: getx.Transition.fadeIn, ); return getApiResult({ 'success': false, 'data': null, 'errorCode': "${response.statusCode}", 'errorMessage': Strings.regionRestricted.tr, }); } // 正常状态码才进行解密 if (response.statusCode == 200) { return getApiResult(decrypt(response.data)); } // 其他状态码返回原始数据 return getApiResult(response.data); } on DioException catch (e) { // 检查是否应该重试 if (_shouldRetryError(e, domainType)) { retryState.isRetrying = true; retryState.currentRetry++; log( 'BaseApi', 'Request failed (${domainType.name}). Retrying (${retryState.currentRetry}/${_maxRetries[domainType]})...', ); // 重试请求 return rawRequest( method, path, data: data, header: header, query: query, options: options, cancelToken: cancelToken, domainType: domainType, ); } // 重置重试状态 retryState.reset(); rethrow; } catch (e) { // 重置重试状态 retryState.reset(); rethrow; } } /// GET请求 Future get( String path, { Map? query, Map? header, Options? options, CancelToken? cancelToken, DomainType domainType = DomainType.api, }) async { return rawRequest( RequestMethod.get, path, query: query, header: header, options: options, cancelToken: cancelToken, domainType: domainType, ); } /// POST请求 Future post( String path, { dynamic data, Map? query, Map? header, Options? options, CancelToken? cancelToken, DomainType domainType = DomainType.api, }) async { // 加密数据 final encryptedData = encrypt(data); return rawRequest( RequestMethod.post, path, data: encryptedData, query: query, header: header, options: options, cancelToken: cancelToken, domainType: domainType, ); } /// PUT请求 Future put( String path, { dynamic data, Map? query, Map? header, Options? options, CancelToken? cancelToken, DomainType domainType = DomainType.api, }) async { // 加密数据 final encryptedData = encrypt(data); return rawRequest( RequestMethod.put, path, data: encryptedData, query: query, header: header, options: options, cancelToken: cancelToken, domainType: domainType, ); } /// DELETE请求 Future delete( String path, { dynamic data, Map? query, Map? header, Options? options, CancelToken? cancelToken, DomainType domainType = DomainType.api, }) async { // 加密数据 final encryptedData = encrypt(data); return rawRequest( RequestMethod.delete, path, data: encryptedData, query: query, header: header, options: options, cancelToken: cancelToken, domainType: domainType, ); } /// PATCH请求 Future patch( String path, { dynamic data, Map? query, Map? header, Options? options, CancelToken? cancelToken, DomainType domainType = DomainType.api, }) async { // 加密数据 final encryptedData = encrypt(data); return rawRequest( RequestMethod.patch, path, data: encryptedData, query: query, header: header, options: options, cancelToken: cancelToken, domainType: domainType, ); } /// 取消所有未返回的请求 void cancelRequests() { _cancelToken.cancel("cancelled"); } }