hi-client/lib/app/network/http_util.dart
2025-10-27 22:15:25 +08:00

399 lines
13 KiB
Dart
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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: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.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) {
final proxyConfig = KRSingBoxImp.instance.kr_buildProxyRule();
KRLogUtil.kr_i(
'🔍 findProxy 被调用, url: $url, proxy: $proxyConfig',
tag: 'HttpUtil',
);
return proxyConfig;
};
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<String, dynamic> _initHeader(
String signature, String? userId, String? token) {
Map<String, dynamic> map = <String, dynamic>{};
if (KRAppRunData().kr_isLogin.value == true) {
map["Authorization"] = KRAppRunData().kr_token;
}
// 添加语言请求头
map["lang"] = KRLanguageUtils.getCurrentLanguageCode();
// 添加动态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<BaseResponse<T>> request<T>(String path, Map<String, dynamic> params,
{HttpMethod method = HttpMethod.POST, bool isShowLoading = true}) async {
try {
// 每次请求前更新baseUrl确保使用最新的域名
updateBaseUrl();
if (isShowLoading) {
KRCommonUtil.kr_showLoading();
}
var map = <String, dynamic>{};
// 判断是否需要加密:根据站点配置的 enable_security 字段
final shouldEncrypt = KRSiteConfigService().isDeviceSecurityEnabled();
if (shouldEncrypt && path.contains("app")) {
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');
// 调试:打印请求头
KRLogUtil.kr_i('🔍 请求头: $headers', tag: 'HttpUtil');
Response<Map<String, dynamic>> responseTemp;
if (method == HttpMethod.GET) {
responseTemp = await _dio.get<Map<String, dynamic>>(
path,
queryParameters: map,
options: Options(
contentType: "application/json",
headers: headers, // 添加请求头
),
);
} else if (method == HttpMethod.DELETE) {
responseTemp = await _dio.delete<Map<String, dynamic>>(
path,
data: map,
options: Options(
contentType: "application/json",
headers: headers, // 添加请求头
),
);
} else if (method == HttpMethod.PUT) {
responseTemp = await _dio.put<Map<String, dynamic>>(
path,
data: map,
options: Options(
contentType: "application/json",
headers: headers, // 添加请求头
),
);
} else {
responseTemp = await _dio.post<Map<String, dynamic>>(
path,
data: map,
options: Options(
contentType: "application/json",
headers: headers, // 添加请求头
),
);
}
if (isShowLoading) {
KRCommonUtil.kr_hideLoading();
}
return BaseResponse<T>.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<T>.fromJson({
'code': code,
'msg': msg,
'data': <String, dynamic>{}
});
} catch (e) {
if (isShowLoading) {
KRCommonUtil.kr_hideLoading();
}
return BaseResponse<T>.fromJson({
'code': -90000,
'msg': e.toString(),
'data': <String, dynamic>{}
});
}
}
}
/// 拦截器(简洁格式,无边框)
class MyInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print('>>> Request │ ${options.method}${options.uri}');
if (options.data != null) {
print('Body: ${options.data}');
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print('<<< Response │ ${response.requestOptions.method}${response.statusCode} ${response.statusMessage}${response.requestOptions.uri}');
if (response.data != null) {
print('Body: ${response.data}');
}
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
print('<<< Error │ ${err.requestOptions.method}${err.requestOptions.uri}');
print('Error Type: ${err.type}');
if (err.message != null) {
print('Error Message: ${err.message}');
}
if (err.response?.data != null) {
print('Response Data: ${err.response?.data}');
}
handler.next(err);
}
}
/// 自定义简洁 HTTP 拦截器(无边框符号)
class _KRSimpleHttpInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print('>>> Request │ ${options.method}${options.uri}');
if (options.data != null) {
print('Body: ${options.data}');
// 检查是否是加密数据(包含 data 和 time 字段)
if (options.data is Map<String, dynamic>) {
final data = options.data as Map<String, dynamic>;
if (data.containsKey('data') && data.containsKey('time')) {
try {
print('');
print('🔐 检测到加密请求,正在解密...');
// 尝试解密并打印原始数据
final encryptedData = data['data'] as String;
final nonce = data['time'] as String;
final decrypted = KRAesUtil.decryptData(
encryptedData,
nonce,
AppConfig.kr_encryptionKey,
);
print('🔓 解密后的原始请求数据:');
// 尝试格式化 JSON
try {
final jsonData = jsonDecode(decrypted);
final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData);
print(prettyJson);
} catch (_) {
print(decrypted);
}
} catch (e) {
print('⚠️ 请求解密失败: $e');
}
}
}
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print('<<< Response │ ${response.requestOptions.method}${response.statusCode} ${response.statusMessage}${response.requestOptions.uri}');
if (response.data != null) {
print('Body: ${response.data}');
// 检查响应是否是加密数据(包含 data 和 time 字段)
if (response.data is Map<String, dynamic>) {
final dataMap = response.data as Map<String, dynamic>;
// 检查是否包含嵌套的 data 字段(加密数据格式)
final nestedData = dataMap['data'];
if (nestedData is Map<String, dynamic> &&
nestedData.containsKey('data') &&
nestedData.containsKey('time')) {
try {
print('');
print('🔐 检测到加密响应,正在解密...');
// 尝试解密并打印原始响应数据
final encryptedData = nestedData['data'] as String;
final nonce = nestedData['time'] as String;
final decrypted = KRAesUtil.decryptData(
encryptedData,
nonce,
AppConfig.kr_encryptionKey,
);
print('🔓 解密后的原始响应数据:');
// 尝试格式化 JSON
try {
final jsonData = jsonDecode(decrypted);
final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData);
print(prettyJson);
} catch (_) {
print(decrypted);
}
} catch (e) {
print('⚠️ 响应解密失败: $e');
}
}
}
}
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
print('<<< Error │ ${err.requestOptions.method}${err.requestOptions.uri}');
print('Error Type: ${err.type}');
if (err.message != null) {
print('Error Message: ${err.message}');
}
if (err.response?.data != null) {
print('Response Data: ${err.response?.data}');
}
handler.next(err);
}
}