| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600 |
- 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/api_statistics.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<DomainType, int> _maxRetries = {
- DomainType.api: 0, // API域名最多重试3次
- DomainType.log: 0, // 日志上传最多重试1次
- DomainType.file: 0, // 文件上传最多重试1次
- };
- // 当前重试状态
- final Map<DomainType, _RetryState> _retryStates = {
- DomainType.api: _RetryState(),
- DomainType.log: _RetryState(),
- DomainType.file: _RetryState(),
- };
- String _baseUrl = '';
- Map<String, String> _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<String, String>? _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<String, dynamic>? getDefaultHeader() {
- return null;
- }
- /// 获取默认Query
- Map<String, dynamic>? getDefaultQuery() {
- return null;
- }
- /// 加密数据
- dynamic encrypt(dynamic input) {
- return input;
- }
- /// 解密数据
- dynamic decrypt(dynamic input) {
- return input;
- }
- /// 反序列化API返回结果
- ApiResult getApiResult(Map<String, dynamic> 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<ApiResult> rawRequest(
- RequestMethod method,
- String path, {
- dynamic data,
- Map<String, dynamic>? header,
- Map<String, dynamic>? query,
- Options? options,
- CancelToken? cancelToken,
- DomainType domainType = DomainType.api, // 默认使用API域名
- }) async {
- final retryState = _retryStates[domainType]!;
- log('BaseApi', 'Request: $method ${dio.options.baseUrl}$path');
- // 记录请求开始时间
- final requestStartTime = DateTime.now().millisecondsSinceEpoch;
- 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();
- // 计算响应耗时
- final responseTime =
- DateTime.now().millisecondsSinceEpoch - requestStartTime;
- // 先处理特殊状态码,不进行解密
- if (response.statusCode == 204) {
- ApiStatistics.instance.addFailure(
- domain: _baseUrl,
- path: path,
- apiRequestTime: requestStartTime,
- apiResponseTime: responseTime,
- errorCode: response.statusCode ?? 204,
- errorMessage: Strings.regionRestricted.tr,
- );
- 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) {
- final result = getApiResult(decrypt(response.data));
- // 记录统计
- if (result.success) {
- ApiStatistics.instance.addSuccess(
- domain: _baseUrl,
- path: path,
- apiRequestTime: requestStartTime,
- apiResponseTime: responseTime,
- errorCode: response.statusCode ?? 200,
- );
- } else {
- ApiStatistics.instance.addFailure(
- domain: _baseUrl,
- path: path,
- apiRequestTime: requestStartTime,
- apiResponseTime: responseTime,
- errorCode: int.tryParse(result.errorCode ?? '0') ?? 0,
- errorMessage: result.errorMessage ?? '',
- );
- }
- return result;
- }
- // 其他状态码返回原始数据
- final result = getApiResult(response.data);
- // 记录失败统计
- ApiStatistics.instance.addFailure(
- domain: _baseUrl,
- path: path,
- apiRequestTime: requestStartTime,
- apiResponseTime: responseTime,
- errorCode: response.statusCode ?? 0,
- errorMessage: result.errorMessage ?? 'Unknown error',
- );
- return result;
- } 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,
- );
- }
- // 计算响应耗时
- final responseTime =
- DateTime.now().millisecondsSinceEpoch - requestStartTime;
- // 记录失败统计
- ApiStatistics.instance.addFailure(
- domain: _baseUrl,
- path: path,
- apiRequestTime: requestStartTime,
- apiResponseTime: responseTime,
- errorCode: e.response?.statusCode ?? -1,
- errorMessage: e.message ?? e.type.name,
- );
- // 重置重试状态
- retryState.reset();
- rethrow;
- } catch (e) {
- // 重置重试状态
- retryState.reset();
- rethrow;
- }
- }
- /// GET请求
- Future<dynamic> get(
- String path, {
- Map<String, dynamic>? query,
- Map<String, dynamic>? 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<ApiResult> post(
- String path, {
- dynamic data,
- Map<String, dynamic>? query,
- Map<String, dynamic>? 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<dynamic> put(
- String path, {
- dynamic data,
- Map<String, dynamic>? query,
- Map<String, dynamic>? 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<dynamic> delete(
- String path, {
- dynamic data,
- Map<String, dynamic>? query,
- Map<String, dynamic>? 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<dynamic> patch(
- String path, {
- dynamic data,
- Map<String, dynamic>? query,
- Map<String, dynamic>? 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");
- }
- }
|