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/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:flutter/foundation.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.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 = IOHttpClientAdapter( createHttpClient: () { KRLogUtil.kr_i('📱 createHttpClient 回调被调用', tag: 'HttpUtil'); final client = HttpClient(); // ✅ 优化:智能代理回退逻辑 client.findProxy = (url) { try { // 检查 SingBox 是否正在运行 final singBoxStatus = KRSingBoxImp.instance.kr_status; final isProxyAvailable = singBoxStatus == SingboxStatus.started(); if (!isProxyAvailable) { // 代理未运行,直接使用直连 KRLogUtil.kr_i( '🔄 代理未运行,使用直连模式: $url', tag: 'HttpUtil', ); return 'DIRECT'; } // 代理正在运行,使用代理配置 final proxyConfig = KRSingBoxImp.instance.kr_buildProxyRule(); KRLogUtil.kr_i( '✅ 使用代理模式, url: $url, proxy: $proxyConfig', tag: 'HttpUtil', ); return proxyConfig; } catch (e) { // 发生异常时回退到直连 KRLogUtil.kr_w( '⚠️ 代理配置异常,回退到直连: $e', tag: 'HttpUtil', ); return 'DIRECT'; } }; // ✅ 优化:设置连接失败时的自动回退 client.connectionTimeout = const Duration(seconds: 10); client.badCertificateCallback = (cert, host, port) => true; return client; }, ); 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为不显示 Future> request(String path, Map params, {HttpMethod method = HttpMethod.POST, bool isShowLoading = true}) async { try { // 每次请求前更新baseUrl,确保使用最新的域名 updateBaseUrl(); if (isShowLoading) { KRCommonUtil.kr_showLoading(); } var map = {}; // 判断是否需要加密:根据站点配置的 enable_security 字段 final shouldEncrypt = KRSiteConfigService().isDeviceSecurityEnabled(); if (shouldEncrypt) { KRLogUtil.kr_i('🔐 需要加密请求数据', tag: 'HttpUtil'); final plainText = jsonEncode(params); map = KRAesUtil.encryptData(plainText, AppConfig.kr_encryptionKey); } else { map = params; } // 初始化请求头 final headers = _initHeader('signature', 'userId', 'token'); int? _kr_parseUserIdFromToken(String token) { try { // JWT格式: header.payload.signature final parts = token.split('.'); if (parts.length != 3) { KRLogUtil.kr_e('JWT token格式错误', tag: 'AppRunData'); return null; } // 解码payload部分(base64) String payload = parts[1]; // 手动添加必要的padding(base64要求长度是4的倍数) switch (payload.length % 4) { case 0: break; // 不需要padding case 2: payload += '=='; break; case 3: payload += '='; break; default: KRLogUtil.kr_e('JWT payload长度无效', tag: 'AppRunData'); return null; } final decodedBytes = base64.decode(payload); final decodedString = utf8.decode(decodedBytes); // 解析JSON final Map payloadMap = jsonDecode(decodedString); // 获取UserId if (payloadMap.containsKey('UserId')) { final userId = payloadMap['UserId']; KRLogUtil.kr_i('从JWT解析出userId: $userId', tag: 'AppRunData'); return userId is int ? userId : int.tryParse(userId.toString()); } return null; } catch (e) { KRLogUtil.kr_e('解析JWT token失败: $e', tag: 'AppRunData'); return null; } } final userId = _kr_parseUserIdFromToken(KRAppRunData().kr_token.toString()); // 调试:打印请求头 KRLogUtil.kr_i('🔍 请求头: $headers', tag: 'HttpUtil'); KRLogUtil.kr_i('🔍 请求userId: $userId', tag: 'HttpUtil'); KRLogUtil.kr_i('🔍 请求头map: $map', tag: 'HttpUtil'); Response> responseTemp; if (method == HttpMethod.GET) { responseTemp = await _dio.get>( path, queryParameters: map, options: Options( contentType: "application/json", headers: headers, // 添加请求头 ), ); } else if (method == HttpMethod.DELETE) { responseTemp = await _dio.delete>( path, data: map, options: Options( contentType: "application/json", headers: headers, // 添加请求头 ), ); } else if (method == HttpMethod.PUT) { responseTemp = await _dio.put>( path, data: map, options: Options( contentType: "application/json", headers: headers, // 添加请求头 ), ); } else { responseTemp = await _dio.post>( path, data: map, options: Options( contentType: "application/json", headers: headers, // 添加请求头 ), ); } 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; } } } return BaseResponse.fromJson({ 'code': code, 'msg': msg, 'data': {} }); } catch (e) { if (isShowLoading) { KRCommonUtil.kr_hideLoading(); } return BaseResponse.fromJson({ 'code': -90000, 'msg': e.toString(), 'data': {} }); } } } /// 拦截器(简洁格式,无边框) class MyInterceptor extends Interceptor { @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { if (kDebugMode) { print('>>> Request │ ${options.method} │ ${options.uri}'); } if (options.data != null) { if (kDebugMode) { print('Body: ${options.data}'); } } handler.next(options); } @override void onResponse(Response response, ResponseInterceptorHandler handler) { if (kDebugMode) { print('<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}'); } if (response.data != null) { if (kDebugMode) { print('Body: ${response.data}'); } } handler.next(response); } @override void onError(DioException err, ErrorInterceptorHandler handler) { if (kDebugMode) { print('<<< Error │ ${err.requestOptions.method} │ ${err.requestOptions.uri}'); } if (kDebugMode) { print('Error Type: ${err.type}'); } if (err.message != null) { if (kDebugMode) { print('Error Message: ${err.message}'); } } if (err.response?.data != null) { if (kDebugMode) { print('Response Data: ${err.response?.data}'); } } handler.next(err); } } /// 自定义简洁 HTTP 拦截器(无边框符号) class _KRSimpleHttpInterceptor extends Interceptor { @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { if (kDebugMode) { print('>>> Request │ ${options.method} │ ${options.uri}'); } if (options.data != null) { if (kDebugMode) { print('Body: ${options.data}'); } // 检查是否是加密数据(包含 data 和 time 字段) if (options.data is Map) { final data = options.data as Map; if (data.containsKey('data') && data.containsKey('time')) { try { if (kDebugMode) { print(''); } if (kDebugMode) { print('🔐 检测到加密请求,正在解密...'); } // 尝试解密并打印原始数据 final encryptedData = data['data'] as String; final nonce = data['time'] as String; final decrypted = KRAesUtil.decryptData( encryptedData, nonce, AppConfig.kr_encryptionKey, ); if (kDebugMode) { print('🔓 解密后的原始请求数据:'); } // 尝试格式化 JSON try { final jsonData = jsonDecode(decrypted); final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData); if (kDebugMode) { print(prettyJson); } } catch (_) { if (kDebugMode) { print(decrypted); } } } catch (e) { if (kDebugMode) { print('⚠️ 请求解密失败: $e'); } } } } } handler.next(options); } @override void onResponse(Response response, ResponseInterceptorHandler handler) { if (kDebugMode) { print('<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}'); } if (response.data != null) { if (kDebugMode) { print('Body: ${response.data}'); } // 检查响应是否是加密数据(包含 data 和 time 字段) if (response.data is Map) { final dataMap = response.data as Map; // 检查是否包含嵌套的 data 字段(加密数据格式) final nestedData = dataMap['data']; if (nestedData is Map && nestedData.containsKey('data') && nestedData.containsKey('time')) { try { if (kDebugMode) { print(''); } if (kDebugMode) { print('🔐 检测到加密响应,正在解密...'); } // 尝试解密并打印原始响应数据 final encryptedData = nestedData['data'] as String; final nonce = nestedData['time'] as String; final decrypted = KRAesUtil.decryptData( encryptedData, nonce, AppConfig.kr_encryptionKey, ); if (kDebugMode) { print('🔓 解密后的原始响应数据:'); } // 尝试格式化 JSON try { final jsonData = jsonDecode(decrypted); final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData); if (kDebugMode) { print(prettyJson); } } catch (_) { if (kDebugMode) { print(decrypted); } } } catch (e) { if (kDebugMode) { print('⚠️ 响应解密失败: $e'); } } } } } handler.next(response); } @override void onError(DioException err, ErrorInterceptorHandler handler) { if (kDebugMode) { print('<<< Error │ ${err.requestOptions.method} │ ${err.requestOptions.uri}'); } if (kDebugMode) { print('Error Type: ${err.type}'); } if (err.message != null) { if (kDebugMode) { print('Error Message: ${err.message}'); } } if (err.response?.data != null) { if (kDebugMode) { print('Response Data: ${err.response?.data}'); } } handler.next(err); } }