import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; // import 'package:flutter_easyloading/flutter_easyloading.dart'; // 已替换为自定义组件 import 'package:flutter_loggy_dio/flutter_loggy_dio.dart'; import 'package:kaer_with_panels/app/common/app_config.dart'; import 'package:kaer_with_panels/app/common/app_run_data.dart'; import 'package:kaer_with_panels/app/network/base_response.dart'; import 'package:kaer_with_panels/app/localization/kr_language_utils.dart'; import 'package:kaer_with_panels/app/utils/kr_common_util.dart'; import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart'; import 'package:kaer_with_panels/app/services/kr_site_config_service.dart'; import 'package:kaer_with_panels/app/utils/kr_http_adapter_util.dart'; import 'package:kaer_with_panels/singbox/model/singbox_status.dart'; // import 'package:crypto/crypto.dart'; // import 'package:encrypt/encrypt.dart'; import 'package:loggy/loggy.dart'; import '../utils/kr_aes_util.dart'; import '../utils/kr_log_util.dart'; // import 'package:video/app/utils/common_util.dart'; // import 'package:video/app/utils/log_util.dart'; /// 定义请求方法的枚举 enum HttpMethod { GET, POST, DELETE, PUT } /// 封装请求 class HttpUtil { final Dio _dio = Dio(); static final HttpUtil _instance = HttpUtil._internal(); HttpUtil._internal() { initDio(); } factory HttpUtil() => _instance; static HttpUtil getInstance() { return _instance; } /// 对dio进行配置 void initDio() { KRLogUtil.kr_i('🚀 HttpUtil.initDio() 开始初始化', tag: 'HttpUtil'); // 不使用 Loggy,改用自定义简洁拦截器 _dio.interceptors.add(_KRSimpleHttpInterceptor(_dio)); _dio.options.baseUrl = AppConfig.getInstance().baseUrl; // 设置连接超时时间 _dio.options.connectTimeout = const Duration(seconds: 60); _dio.options.receiveTimeout = const Duration(seconds: 60); _dio.options.sendTimeout = const Duration(seconds: 60); // 设置请求头 _dio.options.headers = { 'Accept': 'application/json', 'Content-Type': 'application/json; charset=UTF-8', // 移除固定的UserAgent,使用动态的 }; // 设置响应类型 _dio.options.responseType = ResponseType.json; // 设置验证状态 _dio.options.validateStatus = (status) { return status != null && status >= 200 && status < 500; }; // 🔧 配置HttpClientAdapter 优先走本地 sing-box mixed 端口, // 若代理不可用则回推到直连 KRLogUtil.kr_i('🔧 配置 HttpClientAdapter...', tag: 'HttpUtil'); _dio.httpClientAdapter = KRHttpAdapterUtil.createAdapter( useSingBoxProxy: true, timeout: const Duration(seconds: 10), ); KRLogUtil.kr_i('✅ HttpUtil.initDio() 初始化完成', tag: 'HttpUtil'); } /// 更新baseUrl void updateBaseUrl() { String newBaseUrl = AppConfig.getInstance().baseUrl; if (_dio.options.baseUrl != newBaseUrl) { KRLogUtil.kr_i('🔄 更新baseUrl: ${_dio.options.baseUrl} -> $newBaseUrl', tag: 'HttpUtil'); _dio.options.baseUrl = newBaseUrl; } } /// 初始化请求头 :signature签名字符串 Map _initHeader( String signature, String? userId, String? token) { Map map = {}; if (KRAppRunData().kr_isLogin.value == true) { map["Authorization"] = KRAppRunData().kr_token; } // 添加语言请求头 map["lang"] = KRLanguageUtils.getCurrentLanguageCode(); map['Login-Type'] = 'device'; // 添加动态UserAgent头 map["User-Agent"] = _kr_getUserAgent(); return map; } /// 获取当前系统的 user_agent String _kr_getUserAgent() { if (Platform.isAndroid) { return 'android'; } else if (Platform.isIOS) { return 'ios'; } else if (Platform.isMacOS) { return 'mac'; } else if (Platform.isWindows) { return 'windows'; } else if (Platform.isLinux) { return 'linux'; } else if (Platform.isFuchsia) { return 'harmony'; } else { return 'unknown'; } } /// request请求:T为转换的实体类, path:请求地址,query:请求参数, method: 请求方法, isShowLoading(可选): 是否显示加载中的状态,默认true显示, false为不显示, silentInvite: 是否为静默邀请 Future> request(String path, Map params, {HttpMethod method = HttpMethod.POST, bool isShowLoading = true, bool isSilentInvite = false}) async { try { // 每次请求前更新baseUrl,确保使用最新的域名 updateBaseUrl(); if (isShowLoading) { KRCommonUtil.kr_showLoading(); } var map = {}; final plainText = jsonEncode(params); map = KRAesUtil.encryptData(plainText, AppConfig.kr_encryptionKey); // 初始化请求头 final headers = _initHeader('signature', 'userId', 'token'); if (isSilentInvite) { headers['X-Client-Mode'] = 'invite_silent'; } final options = Options( contentType: "application/json", headers: headers, extra: {'silentInvite': isSilentInvite}, ); Response> responseTemp; if (method == HttpMethod.GET) { responseTemp = await _dio.get>( path, queryParameters: map, options: options, ); } else if (method == HttpMethod.DELETE) { responseTemp = await _dio.delete>( path, data: map, options: options, ); } else if (method == HttpMethod.PUT) { responseTemp = await _dio.put>( path, data: map, options: options, ); } else { responseTemp = await _dio.post>( path, data: map, options: options, ); } if (isShowLoading) { KRCommonUtil.kr_hideLoading(); } return BaseResponse.fromJson(responseTemp.data!); } on DioException catch (err) { if (isShowLoading) { KRCommonUtil.kr_hideLoading(); } int code = -90000; String msg = ""; msg = err.message ?? err.type.toString(); switch (err.type) { case DioExceptionType.connectionTimeout: code = -90001; break; case DioExceptionType.sendTimeout: code = -90002; break; case DioExceptionType.receiveTimeout: code = -90003; break; case DioExceptionType.badResponse: code = err.response?.statusCode ?? -90004; break; case DioExceptionType.cancel: break; case DioExceptionType.connectionError: code = -90006; break; case DioExceptionType.badCertificate: code = -90007; break; default: if (err.error != null) { if (err.error.toString().contains("Connection reset by peer")) { code = -90008; } } } print('err.type ${err.type}'); if (err.type == DioExceptionType.unknown) { final _pathOnly = (err.requestOptions.path.isNotEmpty) ? err.requestOptions.path : err.requestOptions.uri.path; msg = '${msg.isNotEmpty ? msg : 'unknown'} ($_pathOnly)'; final _ua = (err.requestOptions.extra['__unknown_attempts'] as int?) ?? 0; final bool isSilent = err.requestOptions.extra['silentInvite'] ?? false; if (_ua >= 2 && !isSilent) { KRCommonUtil.kr_showToast('请求失败($_pathOnly)', timeout: 3500); } } return BaseResponse.fromJson( {'code': code, 'msg': msg, 'data': {}}); } catch (e) { if (isShowLoading) { KRCommonUtil.kr_hideLoading(); } return BaseResponse.fromJson({ 'code': -90000, 'msg': '${e.toString()} (${path})', 'data': {} }); } } } /// 自定义简洁 HTTP 拦截器(无边框符号) class _KRSimpleHttpInterceptor extends Interceptor { /// 常量:手动控制是否打印拦截器日志与解密结果 static const bool KR_HTTP_PRINT = false; final Dio _dio; _KRSimpleHttpInterceptor(this._dio); static String? _lastPath; static int _lastTsMs = 0; @override /// 请求拦截器 /// /// 功能:在调试模式下优先打印请求的解密明文;若解密失败则打印原始数据并标识失败。 /// 参数: /// - options: 请求选项(包含 data 与 queryParameters) /// - handler: 拦截器处理器 /// 返回:void void onRequest(RequestOptions options, RequestInterceptorHandler handler) { if (KR_HTTP_PRINT) { print('>>> Request │ ${options.method} │ ${options.uri}'); } if (KR_HTTP_PRINT) { Map? m; if (options.data is Map) { m = options.data as Map; } else if (options.queryParameters.isNotEmpty) { m = options.queryParameters; } if (m != null) { final decrypted = _tryDecryptFromMap(m); _printDecryptedOrFallback( phase: 'Request', raw: m, decrypted: decrypted); } } handler.next(options); } @override /// 响应拦截器 /// /// 功能:在调试模式下优先打印响应的解密明文;若解密失败则打印原始数据并标识失败。 /// 参数: /// - response: 响应对象(优先识别 Map 格式) /// - handler: 拦截器处理器 /// 返回:void void onResponse(Response response, ResponseInterceptorHandler handler) { if (KR_HTTP_PRINT) { print( '<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}'); } if (KR_HTTP_PRINT && response.data is Map) { final dataMap = response.data as Map; String? decrypted = _tryDecryptFromMap(dataMap); if (decrypted == null && dataMap['data'] is Map) { decrypted = _tryDecryptFromMap(dataMap['data'] as Map); } _printDecryptedOrFallback( phase: 'Response', raw: dataMap, decrypted: decrypted); } handler.next(response); } @override /// 错误拦截器 /// /// 功能:打印错误信息并保持原有的 Unknown 错误重试与失败提示去重逻辑。 /// 参数: /// - err: Dio 错误对象 /// - handler: 拦截器处理器 /// 返回:void void onError(DioException err, ErrorInterceptorHandler handler) { if (KR_HTTP_PRINT) { print( '<<< Error │ ${err.requestOptions.method} │ ${err.requestOptions.uri}'); } if (KR_HTTP_PRINT) { print('Error Type: ${err.type}'); } if (err.message != null) { if (KR_HTTP_PRINT) { print('Error Message: ${err.message}'); } } if (err.response?.data != null) { if (KR_HTTP_PRINT) { print('Response Data: ${err.response?.data}'); } } if (err.type == DioExceptionType.unknown) { final path = (err.requestOptions.path.isNotEmpty) ? err.requestOptions.path : err.requestOptions.uri.path; int unknownAttempts = (err.requestOptions.extra['__unknown_attempts'] as int?) ?? 0; if (unknownAttempts < 2) { err.requestOptions.extra['__unknown_attempts'] = unknownAttempts + 1; final delayMs = unknownAttempts == 0 ? 300 : 700; Future.delayed(Duration(milliseconds: delayMs)).then((_) async { final start = DateTime.now(); while (!KRSingBoxImp.instance.kr_isProxyReady && DateTime.now().difference(start) < const Duration(seconds: 1)) { await Future.delayed(const Duration(milliseconds: 100)); } try { final Response r = await _dio.fetch(err.requestOptions); handler.resolve(r); } catch (e) { handler.next(e is DioException ? e : DioException(requestOptions: err.requestOptions, error: e)); } }); return; } else { final now = DateTime.now().millisecondsSinceEpoch; final bool isSilent = err.requestOptions.extra['silentInvite'] ?? false; if (!(_lastPath == path && (now - _lastTsMs) < 2000) && !isSilent) { _lastPath = path; _lastTsMs = now; KRCommonUtil.kr_showToast('请求失败($path)', timeout: 3500); } KRLogUtil.kr_e('请求失败($path)', tag: 'HttpUtil', error: err, stackTrace: err.stackTrace); } } handler.next(err); } /// 尝试从 Map 中解密,若存在 data/time 字段则返回明文,否则返回 null String? _tryDecryptFromMap(Map m) { final data = m['data']; final time = m['time']; if (data is String && time is String) { try { return KRAesUtil.decryptData(data, time, AppConfig.kr_encryptionKey); } catch (_) { return null; } } return null; } /// 将字符串尽量按 JSON 缩进格式化,失败时返回原文 String _prettyPrintJson(String s) { try { final jsonData = jsonDecode(s); return JsonEncoder.withIndent(' ').convert(jsonData); } catch (_) { return s; } } /// 统一输出逻辑:成功仅打印解密明文;失败打印失败标识与原文 void _printDecryptedOrFallback({ required String phase, required Object? raw, required String? decrypted, }) { if (!KR_HTTP_PRINT) return; if (decrypted != null) { print('🔓 $phase 明文:'); print(_prettyPrintJson(decrypted)); } else { print('⚠️ $phase 解密失败'); print('原文:'); if (raw is Map) { try { print(JsonEncoder.withIndent(' ').convert(raw)); } catch (_) { print(raw.toString()); } } else { print(raw?.toString() ?? 'null'); } } } }