import '../model/response/kr_config_data.dart'; import '../services/api_service/kr_api.user.dart'; import '../utils/kr_update_util.dart'; import '../utils/kr_secure_storage.dart'; import '../utils/kr_log_util.dart'; import '../utils/kr_http_adapter_util.dart'; import '../services/singbox_imp/kr_sing_box_imp.dart'; import '../utils/kr_init_log_collector.dart'; // 🔧 新增:导入日志收集器 import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:flutter/foundation.dart'; /// 协议配置 class KRProtocol { static const String kr_https = "https"; static const String kr_http = "http"; static const String kr_ws = "wss"; } /// 域名配置 class KRDomain { static const String kr_domainKey = "kr_base_domain"; static const String kr_domainsKey = "kr_domains_list"; // static List kr_baseDomains = ["apicn.bearvpn.top","apibear.nsdsox.com"]; // static String kr_currentDomain = "apicn.bearvpn.top"; static List kr_baseDomains = [ "api.hifast.biz", "api.airovpn.tel", ]; static String kr_currentDomain = "api.hifast.biz"; // 备用域名获取地址列表 static List kr_backupDomainUrls = [ // "https://bear-1347601445.cos.ap-guangzhou.myqcloud.com/bear.txt", // "https://getbr.oss-cn-shanghai.aliyuncs.com/bear.txt", // "https://gitee.com/karelink/getbr/raw/master/README.en.md", // "https://configfortrans.oss-cn-guangzhou.aliyuncs.com/bear/bear.txt", ]; // 本地备用域名列表(当服务器获取的域名都不可用时使用) static List kr_localBackupDomains = [ "api.airovpn.tel", // "api6.omntech.com", // "api7.omntech.com", // "apicn.bearvpn.top", // "apibear.nsdsox.com", ]; static final _storage = KRSecureStorage(); static Timer? _retryTimer; static const int kr_retryInterval = 2; // 基础重试间隔(秒) static const int kr_maxRetryCount = 2; // 最大重试次数 // 🔧 P1修复:减少域名检测超时时间 static const int kr_domainTimeout = 2; // 域名检测超时时间(秒)3→2 static const int kr_totalTimeout = 4; // 总体超时时间(秒)6→4 // 🔧 修复5:域名检测总超时时间(防止多层检测累加) static const int kr_maxDomainSwitchTimeout = 10; // 域名切换最大总超时(秒) static Set _triedDomains = {}; // 已尝试过的域名集合 static Map _domainResponseTimes = {}; // 域名响应时间记录 static Map _domainLastCheck = {}; // 域名最后检测时间 // 🔧 修复6:增加缓存时长 300秒(5分钟) → 600秒(10分钟) static const int _domainCacheDuration = 600; // 域名缓存时间(秒) // Dio 实例及初始化 static final Dio _dio = (() { final dio = Dio(BaseOptions( connectTimeout: const Duration(seconds: kr_domainTimeout), sendTimeout: const Duration(seconds: kr_domainTimeout), receiveTimeout: const Duration(seconds: kr_domainTimeout), )); // 🔧 使用统一的 Adapter 转换工具 dio.httpClientAdapter = KRHttpAdapterUtil.createAdapter( timeout: const Duration(seconds: kr_domainTimeout), ); return dio; })(); /// API 域名 static String get kr_api => kr_currentDomain; /// WebSocket 域名 static String get kr_ws => "$kr_currentDomain/v1/app"; /// 🔧 修复1:应用启动时清理静态状态 /// 清理域名检测的所有静态状态,避免旧数据影响新的启动流程 static void kr_resetDomainState() { KRLogUtil.kr_i('🧹 清理域名检测静态状态', tag: 'KRDomain'); // 清空已尝试的域名集合 _triedDomains.clear(); // 清空响应时间记录 _domainResponseTimes.clear(); // 清空最后检测时间(保留最近成功的域名缓存) // 只清理超过缓存时长的记录 final now = DateTime.now(); _domainLastCheck.removeWhere((domain, lastCheck) { final age = now.difference(lastCheck).inSeconds; return age > _domainCacheDuration; }); // 取消重试定时器 _retryTimer?.cancel(); _retryTimer = null; KRLogUtil.kr_i('✅ 域名状态已清理', tag: 'KRDomain'); } /// 从URL中提取域名 static String kr_extractDomain(String url) { try { KRLogUtil.kr_i('🔍 提取域名,原始数据: $url', tag: 'KRDomain'); if (url.isEmpty) { KRLogUtil.kr_w('⚠️ 输入为空', tag: 'KRDomain'); return ''; } // 移除协议前缀 String domain = url.replaceAll(RegExp(r'^https?://'), ''); // 移除路径部分 domain = domain.split('/')[0]; // 移除查询参数 domain = domain.split('?')[0]; // 移除锚点 domain = domain.split('#')[0]; // 清理空白字符 domain = domain.trim(); // 验证域名格式 if (domain.isEmpty) { KRLogUtil.kr_w('⚠️ 提取后域名为空', tag: 'KRDomain'); return ''; } // 检查是否包含有效的域名字符 // 支持域名格式:example.com, example.com:8080, 192.168.1.1, 192.168.1.1:8080 if (!RegExp(r'^[a-zA-Z0-9.-]+(:\d+)?$').hasMatch(domain)) { KRLogUtil.kr_w('⚠️ 域名格式无效: $domain', tag: 'KRDomain'); return ''; } KRLogUtil.kr_i('✅ 成功提取域名: $domain', tag: 'KRDomain'); return domain; } catch (e) { KRLogUtil.kr_e('❌ 提取域名异常: $e', tag: 'KRDomain'); return ''; } } /// 快速检查域名可用性(用于预检测) static Future kr_fastCheckDomainAvailability(String domain) async { try { KRLogUtil.kr_i('⚡ 快速检测域名: $domain', tag: 'KRDomain'); final startTime = DateTime.now(); final response = await _dio.get( '${KRProtocol.kr_https}://$domain', options: Options( sendTimeout: Duration(seconds: 2), // 2秒超时 receiveTimeout: Duration(seconds: 2), // 允许所有状态码,只要能够连接就认为域名可用 validateStatus: (status) => status != null && status >= 200 && status < 600, ), ); final endTime = DateTime.now(); // 记录响应时间 final responseTime = endTime.difference(startTime).inMilliseconds; _domainResponseTimes[domain] = responseTime; // 只要能够连接就认为域名可用(包括404、403等状态码) if (response.statusCode != null) { KRLogUtil.kr_i( '✅ 快速检测成功,域名 $domain 可用,状态码: ${response.statusCode},响应时间: ${responseTime}ms', tag: 'KRDomain'); return true; } else { KRLogUtil.kr_w('❌ 快速检测失败,域名 $domain 响应异常,状态码: ${response.statusCode}', tag: 'KRDomain'); return false; } } on DioException catch (e) { // 检查是否是连接超时或网络错误 if (e.type == DioExceptionType.connectionTimeout || e.type == DioExceptionType.receiveTimeout || e.type == DioExceptionType.sendTimeout || e.type == DioExceptionType.connectionError) { KRLogUtil.kr_w('❌ 快速检测失败,域名 $domain 连接超时或网络错误: ${e.message}', tag: 'KRDomain'); return false; } else { // 其他错误(如未知错误、取消等)认为域名不可用 KRLogUtil.kr_w( '❌ 快速检测失败,域名 $domain 其他异常 (Type: ${e.type}): ${e.message}', tag: 'KRDomain'); return false; } } catch (e) { KRLogUtil.kr_e('❌ 快速检测异常,域名 $domain 检查异常: $e', tag: 'KRDomain'); return false; } } /// 检查域名可用性 static Future kr_checkDomainAvailability(String domain) async { // 清理过期缓存 _kr_clearExpiredCache(); // 检查缓存 final lastCheck = _domainLastCheck[domain]; if (lastCheck != null) { final timeSinceLastCheck = DateTime.now().difference(lastCheck).inSeconds; if (timeSinceLastCheck < _domainCacheDuration) { // 🔧 修复2:放宽缓存阈值 5000ms → 10000ms // 使用缓存的响应时间判断域名是否可用 final responseTime = _domainResponseTimes[domain]; if (responseTime != null && responseTime < 10000) { // 10秒内响应认为可用 KRLogUtil.kr_i( '📋 使用缓存结果,域名 $domain 可用(缓存时间: ${timeSinceLastCheck}s,响应时间: ${responseTime}ms)', tag: 'KRDomain'); return true; } } } try { KRLogUtil.kr_i('🔍 开始检测域名: $domain', tag: 'KRDomain'); final startTime = DateTime.now(); final response = await _dio.get( '${KRProtocol.kr_https}://$domain', options: Options( sendTimeout: Duration(seconds: kr_domainTimeout), receiveTimeout: Duration(seconds: kr_domainTimeout), // 允许所有状态码,只要能够连接就认为域名可用 validateStatus: (status) => status != null && status >= 200 && status < 600, ), ); final endTime = DateTime.now(); // 记录响应时间和检测时间 final responseTime = endTime.difference(startTime).inMilliseconds; _domainResponseTimes[domain] = responseTime; _domainLastCheck[domain] = DateTime.now(); // 只要能够连接就认为域名可用(包括404、403等状态码) if (response.statusCode != null) { KRLogUtil.kr_i( '✅ 域名 $domain 可用,状态码: ${response.statusCode},响应时间: ${responseTime}ms', tag: 'KRDomain'); return true; } else { KRLogUtil.kr_w('❌ 域名 $domain 响应异常,状态码: ${response.statusCode}', tag: 'KRDomain'); return false; } } on DioException catch (e) { // 检查是否是连接超时或网络错误 if (e.type == DioExceptionType.connectionTimeout || e.type == DioExceptionType.receiveTimeout || e.type == DioExceptionType.sendTimeout || e.type == DioExceptionType.connectionError) { KRLogUtil.kr_w('❌ 域名 $domain 连接超时或网络错误: ${e.message}', tag: 'KRDomain'); return false; } else { // 其他错误(如未知错误、取消等)认为域名不可用 KRLogUtil.kr_w('❌ 域名 $domain 检查异常 (Type: ${e.type}): ${e.message}', tag: 'KRDomain'); return false; } } catch (e) { KRLogUtil.kr_e('❌ 域名 $domain 检查异常: $e', tag: 'KRDomain'); return false; } } /// 获取最快的可用域名 static Future kr_getFastestAvailableDomain() async { if (kr_baseDomains.isEmpty) return null; // 按响应时间排序域名 final sortedDomains = kr_baseDomains.toList() ..sort((a, b) => (_domainResponseTimes[a] ?? double.infinity) .compareTo(_domainResponseTimes[b] ?? double.infinity)); // 检查最快的域名是否可用 for (String domain in sortedDomains) { if (await kr_checkDomainAvailability(domain)) { return domain; } } return null; } /// 快速域名切换 - 并发检测所有域名 static Future kr_fastDomainSwitch() async { if (kr_baseDomains.isEmpty) return null; KRLogUtil.kr_i( '🚀 开始快速域名切换,检测 ${kr_baseDomains.length} 个主域名: $kr_baseDomains', tag: 'KRDomain'); final startTime = DateTime.now(); // 🔧 修复5:为整个域名切换流程添加总超时 try { return await _executeFastDomainSwitch(startTime).timeout( Duration(seconds: kr_maxDomainSwitchTimeout), onTimeout: () { KRLogUtil.kr_e('⏰ 域名切换总超时(${kr_maxDomainSwitchTimeout}秒)', tag: 'KRDomain'); return null; }, ); } catch (e) { KRLogUtil.kr_e('❌ 域名切换异常: $e', tag: 'KRDomain'); return null; } } /// 执行快速域名切换的核心逻辑 static Future _executeFastDomainSwitch(DateTime startTime) async { // 先检查缓存,如果有可用的域名直接返回 for (String domain in kr_baseDomains) { final lastCheck = _domainLastCheck[domain]; if (lastCheck != null) { final timeSinceLastCheck = DateTime.now().difference(lastCheck).inSeconds; if (timeSinceLastCheck < _domainCacheDuration) { final responseTime = _domainResponseTimes[domain]; // 🔧 修复2:放宽缓存阈值 2000ms → 5000ms if (responseTime != null && responseTime < 5000) { // 5秒内响应认为可用 KRLogUtil.kr_i( '🎯 使用缓存结果快速切换,域名: $domain (响应时间: ${responseTime}ms)', tag: 'KRDomain'); return domain; } } } } // 创建并发任务列表 List>> tasks = kr_baseDomains.map((domain) async { bool isAvailable = await kr_checkDomainAvailability(domain); return MapEntry(domain, isAvailable); }).toList(); // 等待所有任务完成,但设置总体超时 try { KRLogUtil.kr_i('⏱️ 等待并发检测结果,超时时间: ${kr_totalTimeout}秒', tag: 'KRDomain'); List> results = await Future.wait( tasks, ).timeout(Duration(seconds: kr_totalTimeout)); final endTime = DateTime.now(); final duration = endTime.difference(startTime).inMilliseconds; KRLogUtil.kr_i('📊 主域名检测完成,耗时: ${duration}ms', tag: 'KRDomain'); // 统计结果 int availableCount = 0; for (MapEntry result in results) { if (result.value) { availableCount++; KRLogUtil.kr_i('✅ 主域名可用: ${result.key}', tag: 'KRDomain'); } else { KRLogUtil.kr_w('❌ 主域名不可用: ${result.key}', tag: 'KRDomain'); } } KRLogUtil.kr_i('📈 主域名检测结果: $availableCount/${results.length} 可用', tag: 'KRDomain'); // 找到第一个可用的域名 for (MapEntry result in results) { if (result.value) { KRLogUtil.kr_i('🎯 快速切换选择域名: ${result.key}', tag: 'KRDomain'); return result.key; } } KRLogUtil.kr_w('⚠️ 所有主域名都不可用,开始尝试备用域名', tag: 'KRDomain'); // 如果主域名都不可用,快速尝试备用域名 String? backupDomain = await kr_fastBackupDomainSwitch(); if (backupDomain != null) { KRLogUtil.kr_i('✅ 备用域名切换成功: $backupDomain', tag: 'KRDomain'); return backupDomain; } // 如果备用域名也失败,尝试使用本地配置的备用域名 KRLogUtil.kr_w('⚠️ 备用域名也失败,尝试使用本地配置的备用域名', tag: 'KRDomain'); String? localBackupDomain = await kr_tryLocalBackupDomains(); if (localBackupDomain != null) { KRLogUtil.kr_i('✅ 本地备用域名切换成功: $localBackupDomain', tag: 'KRDomain'); return localBackupDomain; } // 🔧 修复4:验证兜底域名是否可用 KRLogUtil.kr_w('⚠️ 所有域名都失败,尝试兜底域名', tag: 'KRDomain'); const fallbackDomain = "api.airovpn.tel"; // 快速验证兜底域名 bool isFallbackAvailable = await kr_fastCheckDomainAvailability(fallbackDomain); if (isFallbackAvailable) { KRLogUtil.kr_i('✅ 兜底域名可用: $fallbackDomain', tag: 'KRDomain'); return fallbackDomain; } KRLogUtil.kr_e('❌ 兜底域名也不可用: $fallbackDomain', tag: 'KRDomain'); return null; // 所有域名都失败,返回 null } catch (e) { final endTime = DateTime.now(); final duration = endTime.difference(startTime).inMilliseconds; KRLogUtil.kr_e('⏰ 快速域名切换超时或异常 (${duration}ms): $e', tag: 'KRDomain'); // 超时或异常时,尝试使用本地配置的备用域名 KRLogUtil.kr_w('⚠️ 快速切换超时,尝试使用本地备用域名', tag: 'KRDomain'); String? localBackupDomain = await kr_tryLocalBackupDomains(); if (localBackupDomain != null) { KRLogUtil.kr_i('✅ 本地备用域名切换成功: $localBackupDomain', tag: 'KRDomain'); return localBackupDomain; } return null; } } /// 预检测域名可用性(在应用启动时调用) static Future kr_preCheckDomains() async { // Debug 模式下跳过域名预检测 // if (kDebugMode) { // KRLogUtil.kr_i('🐛 Debug 模式,跳过域名预检测', tag: 'KRDomain'); // return; // } KRLogUtil.kr_i('🚀 开始预检测域名可用性', tag: 'KRDomain'); // 异步预检测,不阻塞应用启动 Future.microtask(() async { try { // 如果当前域名已经在主域名列表中,先检查它是否可用 if (kr_baseDomains.contains(kr_currentDomain)) { bool isCurrentAvailable = await kr_fastCheckDomainAvailability(kr_currentDomain); if (isCurrentAvailable) { KRLogUtil.kr_i('✅ 当前域名可用,无需切换: $kr_currentDomain', tag: 'KRDomain'); return; // 当前域名可用,不需要切换 } } // 快速检测第一个域名 if (kr_baseDomains.isNotEmpty) { String firstDomain = kr_baseDomains.first; bool isAvailable = await kr_fastCheckDomainAvailability(firstDomain); if (isAvailable) { KRLogUtil.kr_i('✅ 预检测成功,主域名可用: $firstDomain', tag: 'KRDomain'); // 预设置可用域名,避免后续切换 kr_currentDomain = firstDomain; await kr_saveCurrentDomain(); } else { KRLogUtil.kr_i('⚠️ 预检测失败,主域名不可用: $firstDomain', tag: 'KRDomain'); // 如果主域名不可用,立即尝试备用域名,不等待 kr_fastDomainSwitch().then((newDomain) { if (newDomain != null) { KRLogUtil.kr_i('✅ 预检测备用域名成功: $newDomain', tag: 'KRDomain'); } }); } } } catch (e) { KRLogUtil.kr_w('⚠️ 预检测异常: $e', tag: 'KRDomain'); } }); } /// 快速备用域名切换 - 直接从备用地址获取域名,不请求/v1/app/auth/config static Future kr_fastBackupDomainSwitch() async { KRLogUtil.kr_i('🔄 开始快速备用域名切换,备用地址: $kr_backupDomainUrls', tag: 'KRDomain'); final startTime = DateTime.now(); // 并发获取所有备用地址的域名 List>> backupTasks = kr_backupDomainUrls.map((url) async { try { KRLogUtil.kr_i('📡 从备用地址获取域名: $url', tag: 'KRDomain'); final response = await _dio.get( url, options: Options( sendTimeout: Duration(seconds: kr_domainTimeout), receiveTimeout: Duration(seconds: kr_domainTimeout), ), ); if (response.statusCode == 200 && response.data != null) { String responseData = response.data.toString(); KRLogUtil.kr_i('📥 备用地址 $url 返回数据: $responseData', tag: 'KRDomain'); List domains = kr_parseBackupDomains(responseData); KRLogUtil.kr_i('🔍 解析到备用域名: $domains', tag: 'KRDomain'); return domains; } else { KRLogUtil.kr_w('❌ 备用地址 $url 响应异常,状态码: ${response.statusCode}', tag: 'KRDomain'); } } catch (e) { KRLogUtil.kr_w('❌ 备用地址 $url 获取失败: $e', tag: 'KRDomain'); } return []; }).toList(); try { KRLogUtil.kr_i('⏱️ 等待备用地址响应,超时时间: ${kr_totalTimeout - 1}秒', tag: 'KRDomain'); List> backupResults = await Future.wait( backupTasks, ).timeout(Duration(seconds: kr_totalTimeout - 1)); // 留1秒给域名测试 // 合并所有备用域名并去重 Set uniqueBackupDomains = {}; for (List domains in backupResults) { uniqueBackupDomains.addAll(domains); } List allBackupDomains = uniqueBackupDomains.toList(); KRLogUtil.kr_i('📋 合并并去重后的备用域名: $allBackupDomains', tag: 'KRDomain'); if (allBackupDomains.isEmpty) { KRLogUtil.kr_w('⚠️ 没有获取到备用域名', tag: 'KRDomain'); return null; } KRLogUtil.kr_i('🧪 开始测试 ${allBackupDomains.length} 个备用域名', tag: 'KRDomain'); // 并发测试所有备用域名 List>> testTasks = allBackupDomains.map((domain) async { bool isAvailable = await kr_checkDomainAvailability(domain); return MapEntry(domain, isAvailable); }).toList(); List> testResults = await Future.wait( testTasks, ).timeout(Duration(seconds: 2)); // 增加到2秒内完成测试 final endTime = DateTime.now(); final duration = endTime.difference(startTime).inMilliseconds; KRLogUtil.kr_i('📊 备用域名检测完成,总耗时: ${duration}ms', tag: 'KRDomain'); // 统计备用域名结果 int availableBackupCount = 0; for (MapEntry result in testResults) { if (result.value) { availableBackupCount++; KRLogUtil.kr_i('✅ 备用域名可用: ${result.key}', tag: 'KRDomain'); } else { KRLogUtil.kr_w('❌ 备用域名不可用: ${result.key}', tag: 'KRDomain'); } } KRLogUtil.kr_i( '📈 备用域名检测结果: $availableBackupCount/${testResults.length} 可用', tag: 'KRDomain'); // 找到第一个可用的备用域名 for (MapEntry result in testResults) { if (result.value) { KRLogUtil.kr_i('🎯 快速切换选择备用域名: ${result.key}', tag: 'KRDomain'); // 更新当前域名并保存 kr_currentDomain = result.key; await kr_saveCurrentDomain(); KRLogUtil.kr_i('💾 已保存新域名: $kr_currentDomain', tag: 'KRDomain'); // 将备用域名添加到主域名列表 if (!kr_baseDomains.contains(result.key)) { kr_baseDomains.add(result.key); await kr_saveDomains(kr_baseDomains); KRLogUtil.kr_i('📝 已将备用域名添加到主域名列表', tag: 'KRDomain'); } // 重要:直接返回可用域名,不再请求/v1/app/auth/config KRLogUtil.kr_i('✅ 备用域名切换成功,直接使用: ${result.key}', tag: 'KRDomain'); return result.key; } } KRLogUtil.kr_w('⚠️ 所有备用域名都不可用', tag: 'KRDomain'); return null; } catch (e) { final endTime = DateTime.now(); final duration = endTime.difference(startTime).inMilliseconds; KRLogUtil.kr_e('⏰ 快速备用域名切换异常 (${duration}ms): $e', tag: 'KRDomain'); return null; } } /// 从备用地址获取域名列表 static Future> kr_getBackupDomains() async { List backupDomains = []; for (String backupUrl in kr_backupDomainUrls) { try { KRLogUtil.kr_i('尝试从备用地址获取域名: $backupUrl', tag: 'KRDomain'); final response = await _dio.get( backupUrl, options: Options( sendTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 10), ), ); if (response.statusCode == 200 && response.data != null) { String responseData = response.data.toString(); KRLogUtil.kr_i('备用地址返回数据: $responseData', tag: 'KRDomain'); // 处理返回的JSON数据 List domains = kr_parseBackupDomains(responseData); backupDomains.addAll(domains); KRLogUtil.kr_i('解析到备用域名: $domains', tag: 'KRDomain'); } } catch (e) { KRLogUtil.kr_w('从备用地址 $backupUrl 获取域名失败: $e', tag: 'KRDomain'); } } return backupDomains; } /// 解析备用域名JSON数据 static List kr_parseBackupDomains(String jsonData) { List domains = []; try { KRLogUtil.kr_i('🔍 开始解析备用域名数据: $jsonData', tag: 'KRDomain'); // 尝试解析为JSON数组 if (jsonData.startsWith('[') && jsonData.endsWith(']')) { List jsonList = json.decode(jsonData); KRLogUtil.kr_i('📋 解析为JSON数组,长度: ${jsonList.length}', tag: 'KRDomain'); for (int i = 0; i < jsonList.length; i++) { dynamic item = jsonList[i]; KRLogUtil.kr_i('🔍 处理第 $i 项: $item (类型: ${item.runtimeType})', tag: 'KRDomain'); if (item is String) { // 字符串格式 String domain = kr_extractDomain(item); if (domain.isNotEmpty) { domains.add(domain); KRLogUtil.kr_i('✅ 从字符串提取域名: $domain', tag: 'KRDomain'); } } else if (item is Map) { // 对象格式,如 {https:, 158.247.232.203:8080} KRLogUtil.kr_i('🔍 处理对象格式: $item', tag: 'KRDomain'); // 遍历对象的键值对 item.forEach((key, value) { KRLogUtil.kr_i('🔍 键: $key, 值: $value', tag: 'KRDomain'); if (value is String && value.isNotEmpty) { // 如果值是字符串,直接作为域名 String domain = kr_extractDomain(value); if (domain.isNotEmpty) { domains.add(domain); KRLogUtil.kr_i('✅ 从对象值提取域名: $domain', tag: 'KRDomain'); } } else if (key is String && key.isNotEmpty) { // 如果键是字符串,也尝试提取域名 String domain = kr_extractDomain(key); if (domain.isNotEmpty) { domains.add(domain); KRLogUtil.kr_i('✅ 从对象键提取域名: $domain', tag: 'KRDomain'); } } }); } else { KRLogUtil.kr_w('⚠️ 未知的数据类型: ${item.runtimeType}', tag: 'KRDomain'); } } } else if (jsonData.startsWith('{') && jsonData.endsWith('}')) { // 处理类似 { "url1", "url2" } 的格式 KRLogUtil.kr_i('🔍 尝试解析为对象格式', tag: 'KRDomain'); try { // 尝试解析为JSON对象 Map jsonMap = json.decode(jsonData); KRLogUtil.kr_i('📋 解析为JSON对象,键数量: ${jsonMap.length}', tag: 'KRDomain'); jsonMap.forEach((key, value) { KRLogUtil.kr_i('🔍 键: $key, 值: $value', tag: 'KRDomain'); if (value is String && value.isNotEmpty) { String domain = kr_extractDomain(value); if (domain.isNotEmpty) { domains.add(domain); KRLogUtil.kr_i('✅ 从对象值提取域名: $domain', tag: 'KRDomain'); } } else if (key.isNotEmpty) { String domain = kr_extractDomain(key); if (domain.isNotEmpty) { domains.add(domain); KRLogUtil.kr_i('✅ 从对象键提取域名: $domain', tag: 'KRDomain'); } } }); } catch (e) { KRLogUtil.kr_w('⚠️ JSON对象解析失败,尝试字符串解析: $e', tag: 'KRDomain'); // 如果不是标准JSON,尝试字符串解析 String cleanData = jsonData .replaceAll('{', '') .replaceAll('}', '') .replaceAll('"', '') .replaceAll("'", ''); KRLogUtil.kr_i('🧹 清理后的数据: $cleanData', tag: 'KRDomain'); // 按逗号分割 List parts = cleanData.split(','); for (String part in parts) { String trimmed = part.trim(); if (trimmed.isNotEmpty) { String domain = kr_extractDomain(trimmed); if (domain.isNotEmpty) { domains.add(domain); KRLogUtil.kr_i('✅ 从字符串提取域名: $domain', tag: 'KRDomain'); } } } } } else { // 尝试解析为字符串数组格式 KRLogUtil.kr_i('🔍 尝试解析为字符串格式', tag: 'KRDomain'); // 移除可能的引号和方括号 String cleanData = jsonData .replaceAll('[', '') .replaceAll(']', '') .replaceAll('{', '') .replaceAll('}', '') .replaceAll('"', '') .replaceAll("'", ''); KRLogUtil.kr_i('🧹 清理后的数据: $cleanData', tag: 'KRDomain'); // 按逗号分割 List parts = cleanData.split(','); for (String part in parts) { String trimmed = part.trim(); if (trimmed.isNotEmpty) { String domain = kr_extractDomain(trimmed); if (domain.isNotEmpty) { domains.add(domain); KRLogUtil.kr_i('✅ 从字符串提取域名: $domain', tag: 'KRDomain'); } } } } KRLogUtil.kr_i('📊 解析完成,总共提取到 ${domains.length} 个域名: $domains', tag: 'KRDomain'); } catch (e) { KRLogUtil.kr_e('❌ 解析备用域名数据失败: $e', tag: 'KRDomain'); } return domains; } /// 测试并切换到备用域名 static Future kr_tryBackupDomains() async { KRLogUtil.kr_i('开始尝试备用域名', tag: 'KRDomain'); // 获取备用域名列表 List backupDomains = await kr_getBackupDomains(); if (backupDomains.isEmpty) { KRLogUtil.kr_w('没有获取到备用域名', tag: 'KRDomain'); return false; } KRLogUtil.kr_i('获取到备用域名: $backupDomains', tag: 'KRDomain'); // 测试备用域名的可用性 for (String domain in backupDomains) { if (await kr_checkDomainAvailability(domain)) { KRLogUtil.kr_i('找到可用的备用域名: $domain', tag: 'KRDomain'); // 更新当前域名 kr_currentDomain = domain; await kr_saveCurrentDomain(); // 将备用域名添加到主域名列表 if (!kr_baseDomains.contains(domain)) { kr_baseDomains.add(domain); await kr_saveDomains(kr_baseDomains); } return true; } } KRLogUtil.kr_w('所有备用域名都不可用', tag: 'KRDomain'); return false; } /// 处理域名列表 static Future kr_handleDomains(List domains) async { // 提取所有域名 List extractedDomains = domains.map((url) => kr_extractDomain(url)).toList(); // 如果提取的域名为空,使用默认域名 if (extractedDomains.isEmpty) { extractedDomains = ["kkmen.cc"]; } // 保存域名列表 await kr_saveDomains(extractedDomains); // 更新当前域名列表 kr_baseDomains = extractedDomains; // 如果当前域名不在新列表中,使用第一个域名 if (!kr_baseDomains.contains(kr_currentDomain)) { kr_currentDomain = kr_baseDomains[0]; await kr_saveCurrentDomain(); } } /// 切换到下一个域名 static Future kr_switchToNextDomain() async { if (kr_baseDomains.isEmpty) return false; KRLogUtil.kr_i('🔄 开始域名切换,当前域名: $kr_currentDomain', tag: 'KRDomain'); _triedDomains.add(kr_currentDomain); KRLogUtil.kr_i('📝 已尝试域名: $_triedDomains', tag: 'KRDomain'); // 🔧 修复3:优先使用最后一次成功的域名(缓存) // 如果缓存中有最近成功的域名,优先使用它 if (_triedDomains.isEmpty || _triedDomains.length == 1) { for (String domain in kr_baseDomains) { final lastCheck = _domainLastCheck[domain]; final responseTime = _domainResponseTimes[domain]; if (lastCheck != null && responseTime != null && responseTime < 5000) { final age = DateTime.now().difference(lastCheck).inSeconds; if (age < _domainCacheDuration && !_triedDomains.contains(domain)) { kr_currentDomain = domain; await kr_saveCurrentDomain(); KRLogUtil.kr_i( '✅ 使用缓存的成功域名: $kr_currentDomain (响应时间: ${responseTime}ms)', tag: 'KRDomain'); return true; } } } } // 检查是否有预检测成功的域名可以直接使用 if (kr_baseDomains.contains(kr_currentDomain) && !_triedDomains.contains(kr_currentDomain)) { KRLogUtil.kr_i('✅ 使用预检测成功的域名: $kr_currentDomain', tag: 'KRDomain'); return true; } // 如果已经尝试过所有主域名,使用快速切换 if (_triedDomains.length >= kr_baseDomains.length) { KRLogUtil.kr_i('⚠️ 所有主域名都尝试过,切换到快速模式', tag: 'KRDomain'); String? newDomain = await kr_fastDomainSwitch(); if (newDomain != null) { kr_currentDomain = newDomain; await kr_saveCurrentDomain(); _triedDomains.clear(); // 清空已尝试列表 KRLogUtil.kr_i('✅ 快速切换成功,新域名: $kr_currentDomain', tag: 'KRDomain'); return true; } KRLogUtil.kr_w('❌ 快速切换失败', tag: 'KRDomain'); return false; } // 尝试使用最快的可用域名 KRLogUtil.kr_i('🏃 尝试使用最快的可用域名', tag: 'KRDomain'); String? fastestDomain = await kr_getFastestAvailableDomain(); if (fastestDomain != null && !_triedDomains.contains(fastestDomain)) { kr_currentDomain = fastestDomain; await kr_saveCurrentDomain(); KRLogUtil.kr_i('✅ 切换到最快域名: $kr_currentDomain', tag: 'KRDomain'); return true; } // 如果最快的域名不可用,尝试其他域名 KRLogUtil.kr_i('🔍 逐个尝试其他域名', tag: 'KRDomain'); for (String domain in kr_baseDomains) { if (!_triedDomains.contains(domain)) { KRLogUtil.kr_i('🧪 测试域名: $domain', tag: 'KRDomain'); if (await kr_checkDomainAvailability(domain)) { kr_currentDomain = domain; await kr_saveCurrentDomain(); KRLogUtil.kr_i('✅ 切换到可用域名: $kr_currentDomain', tag: 'KRDomain'); return true; } } } KRLogUtil.kr_w('❌ 没有找到可用的域名', tag: 'KRDomain'); return false; } /// 开始重试请求 static void kr_startRetry(Future Function() requestFunction) { // 取消之前的重试定时器 _retryTimer?.cancel(); _triedDomains.clear(); // 创建新的重试定时器 _retryTimer = Timer.periodic(Duration(seconds: kr_retryInterval), (timer) async { // 切换到下一个域名 bool hasNextDomain = await kr_switchToNextDomain(); if (!hasNextDomain) { timer.cancel(); return; } // 执行请求 try { await requestFunction(); // 请求成功,取消重试 timer.cancel(); } catch (e) { KRLogUtil.kr_e('重试请求失败: $e', tag: 'KRDomain'); } }); } /// 停止重试 static void kr_stopRetry() { _retryTimer?.cancel(); _triedDomains.clear(); } /// 手动测试备用域名功能 static Future kr_testBackupDomains() async { KRLogUtil.kr_i('开始手动测试备用域名功能', tag: 'KRDomain'); // 清空当前域名列表,模拟所有主域名失效 List originalDomains = List.from(kr_baseDomains); kr_baseDomains.clear(); try { // 尝试备用域名 bool success = await kr_tryBackupDomains(); if (success) { KRLogUtil.kr_i('备用域名测试成功,当前域名: $kr_currentDomain', tag: 'KRDomain'); } else { KRLogUtil.kr_i('备用域名测试失败', tag: 'KRDomain'); } } finally { // 恢复原始域名列表 kr_baseDomains = originalDomains; } } /// 手动触发快速域名切换 static Future kr_triggerFastSwitch() async { KRLogUtil.kr_i('🎯 手动触发快速域名切换', tag: 'KRDomain'); KRLogUtil.kr_i('📋 当前域名: $kr_currentDomain', tag: 'KRDomain'); KRLogUtil.kr_i('📋 主域名列表: $kr_baseDomains', tag: 'KRDomain'); KRLogUtil.kr_i('📋 备用地址列表: $kr_backupDomainUrls', tag: 'KRDomain'); final startTime = DateTime.now(); String? newDomain = await kr_fastDomainSwitch(); final endTime = DateTime.now(); final duration = endTime.difference(startTime).inMilliseconds; KRLogUtil.kr_i('⏱️ 快速切换总耗时: ${duration}ms', tag: 'KRDomain'); if (newDomain != null) { kr_currentDomain = newDomain; await kr_saveCurrentDomain(); KRLogUtil.kr_i('🎉 快速切换成功!新域名: $newDomain', tag: 'KRDomain'); return true; } else { KRLogUtil.kr_w('💥 快速切换失败,没有找到可用域名', tag: 'KRDomain'); return false; } } /// 尝试本地备用域名 static Future kr_tryLocalBackupDomains() async { KRLogUtil.kr_i('🔄 开始尝试本地备用域名: $kr_localBackupDomains', tag: 'KRDomain'); final startTime = DateTime.now(); // 并发检测所有本地备用域名 List>> tasks = kr_localBackupDomains.map((domain) async { bool isAvailable = await kr_checkDomainAvailability(domain); return MapEntry(domain, isAvailable); }).toList(); try { KRLogUtil.kr_i('⏱️ 等待本地备用域名检测结果,超时时间: ${kr_totalTimeout}秒', tag: 'KRDomain'); List> results = await Future.wait( tasks, ).timeout(Duration(seconds: kr_totalTimeout)); final endTime = DateTime.now(); final duration = endTime.difference(startTime).inMilliseconds; KRLogUtil.kr_i('📊 本地备用域名检测完成,耗时: ${duration}ms', tag: 'KRDomain'); // 统计结果 int availableCount = 0; for (MapEntry result in results) { if (result.value) { availableCount++; KRLogUtil.kr_i('✅ 本地备用域名可用: ${result.key}', tag: 'KRDomain'); } else { KRLogUtil.kr_w('❌ 本地备用域名不可用: ${result.key}', tag: 'KRDomain'); } } KRLogUtil.kr_i('📈 本地备用域名检测结果: $availableCount/${results.length} 可用', tag: 'KRDomain'); // 找到第一个可用的本地备用域名 for (MapEntry result in results) { if (result.value) { KRLogUtil.kr_i('🎯 选择本地备用域名: ${result.key}', tag: 'KRDomain'); // 更新当前域名并保存 kr_currentDomain = result.key; await kr_saveCurrentDomain(); KRLogUtil.kr_i('💾 已保存本地备用域名: $kr_currentDomain', tag: 'KRDomain'); // 将本地备用域名添加到主域名列表 if (!kr_baseDomains.contains(result.key)) { kr_baseDomains.add(result.key); await kr_saveDomains(kr_baseDomains); KRLogUtil.kr_i('📝 已将本地备用域名添加到主域名列表', tag: 'KRDomain'); } return result.key; } } KRLogUtil.kr_w('⚠️ 所有本地备用域名都不可用', tag: 'KRDomain'); return null; } catch (e) { final endTime = DateTime.now(); final duration = endTime.difference(startTime).inMilliseconds; KRLogUtil.kr_e('⏰ 本地备用域名检测异常 (${duration}ms): $e', tag: 'KRDomain'); return null; } } /// 测试备用域名解析 static void kr_testBackupDomainParsing() { KRLogUtil.kr_i('🧪 开始测试备用域名解析', tag: 'KRDomain'); // 测试数据 List testData = [ '["https://apicn.bearvpn.top", "http://158.247.232.203:8080"]', '[{"https": "apicn.bearvpn.top"}, {"http": "158.247.232.203:8080"}]', '[{https:, 158.247.232.203:8080}, {https:, 158.247.232.203:8080}]', 'https://apicn.bearvpn.top,http://158.247.232.203:8080', 'apicn.bearvpn.top,158.247.232.203:8080', // 你遇到的实际数据格式 '{\n"https://apicn.bearvpn.top",\n"http://158.247.232.203:8080"\n}' ]; for (int i = 0; i < testData.length; i++) { KRLogUtil.kr_i('🧪 测试数据 $i: ${testData[i]}', tag: 'KRDomain'); List domains = kr_parseBackupDomains(testData[i]); KRLogUtil.kr_i('📊 解析结果 $i: $domains', tag: 'KRDomain'); } } /// 保存域名列表到本地 static Future kr_saveDomains(List domains) async { await _storage.kr_saveData( key: kr_domainsKey, value: domains.join(','), ); } /// 保存当前域名到本地 static Future kr_saveCurrentDomain() async { await _storage.kr_saveData( key: kr_domainKey, value: kr_currentDomain, ); } /// 从本地加载域名(方案1:加载后验证可用性) static Future kr_loadBaseDomain() async { KRLogUtil.kr_i('📂 开始从 Hive 加载域名配置', tag: 'KRDomain'); final startTime = DateTime.now(); try { // 🔧 Android 15 关键修复:添加总体超时保护(15秒) await Future.any([ _executeLoadAndVerify(), Future.delayed(const Duration(seconds: 15), () { throw TimeoutException('域名加载和验证总超时(15秒)'); }), ]); } on TimeoutException catch (e) { final duration = DateTime.now().difference(startTime).inSeconds; KRLogUtil.kr_e('⏱️ 域名加载验证超时($duration 秒): $e', tag: 'KRDomain'); // 超时时使用默认域名 if (kr_baseDomains.isNotEmpty) { kr_currentDomain = kr_baseDomains[0]; KRLogUtil.kr_w('⚠️ 超时后使用默认域名: $kr_currentDomain', tag: 'KRDomain'); } } catch (e) { final duration = DateTime.now().difference(startTime).inSeconds; KRLogUtil.kr_e('❌ 域名加载异常($duration 秒): $e', tag: 'KRDomain'); // 异常时使用默认域名 if (kr_baseDomains.isNotEmpty) { kr_currentDomain = kr_baseDomains[0]; } } final totalDuration = DateTime.now().difference(startTime).inMilliseconds; KRLogUtil.kr_i('✅ 域名加载完成,耗时: ${totalDuration}ms,当前域名: $kr_currentDomain', tag: 'KRDomain'); } /// 执行域名加载和验证 static Future _executeLoadAndVerify() async { // 加载域名列表 String? savedDomains = await _storage.kr_readData(key: kr_domainsKey); if (savedDomains != null && savedDomains.isNotEmpty) { kr_baseDomains = savedDomains.split(',').where((d) => d.isNotEmpty).toList(); KRLogUtil.kr_i('📋 从 Hive 加载的域名列表: $kr_baseDomains', tag: 'KRDomain'); } else { KRLogUtil.kr_w('⚠️ Hive 中没有保存的域名列表,使用默认配置', tag: 'KRDomain'); } // 加载当前域名 String? savedDomain = await _storage.kr_readData(key: kr_domainKey); KRLogUtil.kr_i('📌 从 Hive 加载的当前域名: $savedDomain', tag: 'KRDomain'); if (savedDomain != null && savedDomain.isNotEmpty && kr_baseDomains.contains(savedDomain)) { // 🔧 Android 15 关键修复:验证 Hive 缓存的域名是否仍然可用 KRLogUtil.kr_i('🔍 验证 Hive 缓存域名的可用性: $savedDomain', tag: 'KRDomain'); try { // 快速验证(3 秒超时) bool isAvailable = await kr_fastCheckDomainAvailability(savedDomain).timeout( const Duration(seconds: 3), onTimeout: () { KRLogUtil.kr_w('⏱️ 域名验证超时(3秒),视为不可用', tag: 'KRDomain'); return false; }, ); if (isAvailable) { kr_currentDomain = savedDomain; KRLogUtil.kr_i('✅ Hive 缓存域名验证通过,继续使用: $kr_currentDomain', tag: 'KRDomain'); return; } else { KRLogUtil.kr_w('❌ Hive 缓存域名不可用: $savedDomain,需要切换', tag: 'KRDomain'); } } catch (e) { KRLogUtil.kr_e('❌ 域名验证异常: $e,需要切换', tag: 'KRDomain'); } } else { if (savedDomain == null || savedDomain.isEmpty) { KRLogUtil.kr_w('⚠️ Hive 中没有保存的当前域名', tag: 'KRDomain'); } else { KRLogUtil.kr_w('⚠️ Hive 缓存的域名 $savedDomain 不在域名列表中', tag: 'KRDomain'); } } // 🔧 Android 15 增强:Hive 域名验证失败,自动选择可用域名 KRLogUtil.kr_i('🔄 Hive 域名不可用,开始选择可用域名...', tag: 'KRDomain'); // 尝试从域名列表中找到可用的域名(每个最多 3 秒) for (String domain in kr_baseDomains) { try { KRLogUtil.kr_i('🧪 测试域名: $domain', tag: 'KRDomain'); bool isAvailable = await kr_fastCheckDomainAvailability(domain).timeout( const Duration(seconds: 3), onTimeout: () { KRLogUtil.kr_w('⏱️ 域名 $domain 验证超时(3秒)', tag: 'KRDomain'); return false; }, ); if (isAvailable) { kr_currentDomain = domain; await kr_saveCurrentDomain(); KRLogUtil.kr_i('✅ 找到可用域名: $kr_currentDomain,已保存到 Hive', tag: 'KRDomain'); return; } } catch (e) { KRLogUtil.kr_w('⚠️ 域名 $domain 验证异常: $e', tag: 'KRDomain'); } } // 如果所有主域名都不可用,快速尝试备用域名(5秒总超时) KRLogUtil.kr_w('⚠️ 所有主域名都不可用,尝试备用域名...', tag: 'KRDomain'); try { String? backupDomain = await kr_tryLocalBackupDomains().timeout( const Duration(seconds: 5), onTimeout: () { KRLogUtil.kr_w('⏱️ 备用域名获取超时', tag: 'KRDomain'); return null; }, ); if (backupDomain != null) { kr_currentDomain = backupDomain; await kr_saveCurrentDomain(); KRLogUtil.kr_i('✅ 使用备用域名: $kr_currentDomain', tag: 'KRDomain'); return; } } catch (e) { KRLogUtil.kr_w('⚠️ 备用域名获取异常: $e', tag: 'KRDomain'); } // 最后的兜底:使用列表中的第一个域名(即使不可用) if (kr_baseDomains.isNotEmpty) { kr_currentDomain = kr_baseDomains[0]; await kr_saveCurrentDomain(); KRLogUtil.kr_w('⚠️ 无法验证任何域名,使用默认域名: $kr_currentDomain', tag: 'KRDomain'); } else { KRLogUtil.kr_e('❌ 没有可用的域名配置!', tag: 'KRDomain'); } } /// 清理过期的域名缓存 static void _kr_clearExpiredCache() { final now = DateTime.now(); final expiredDomains = []; for (MapEntry entry in _domainLastCheck.entries) { final timeSinceLastCheck = now.difference(entry.value).inSeconds; if (timeSinceLastCheck >= _domainCacheDuration) { expiredDomains.add(entry.key); } } for (String domain in expiredDomains) { _domainLastCheck.remove(domain); _domainResponseTimes.remove(domain); } if (expiredDomains.isNotEmpty) { KRLogUtil.kr_i('🧹 清理过期缓存域名: $expiredDomains', tag: 'KRDomain'); } } } class AppConfig { /// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /// 初始化日志收集开关 /// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /// 🔧 全局开关:是否启用初始化日志收集 /// true:将所有初始化日志写入文件,方便问题诊断 /// false:关闭日志收集,减少 I/O 操作 /// /// 建议: /// - 测试版本、Beta 版本:设置为 true /// - 正式生产版本:根据需要设置为 false(或在遇到问题时临时开启) static const bool enableInitLogCollection = false; /// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /// 加密密钥配置 /// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /// 统一加密密钥(用于所有API接口和设备登录) /// 密钥来源:OmnOem 项目 ppanel.json 配置 static const String kr_encryptionKey = 'c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx'; /// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /// 请求域名地址 /// 基础url /// // static String baseUrl = "http://103.112.98.72:8088"; /// 请求域名地址 String get baseUrl { // if (kDebugMode) { // return "http://192.168.0.113:8082"; // } return "${KRProtocol.kr_https}://${KRDomain.kr_api}"; } /// ⚠️ 已遗弃:WebSocket 连接不再使用 @Deprecated('wsBaseUrl has been deprecated') String get wsBaseUrl { // if (kDebugMode) { // return "ws://192.168.0.113"; // } return "${KRProtocol.kr_ws}://${KRDomain.kr_ws}"; } static final AppConfig _instance = AppConfig._internal(); /// 官方邮箱 String kr_official_email = ""; /// 官方网站 String kr_official_website = ""; /// 官方电报群 String kr_official_telegram = ""; /// 官方电话 String kr_official_telephone = ""; /// 邀请链接 String kr_invitation_link = ""; /// 设备限制数量 String device_limit = '0'; /// 是否为白天模式 bool kr_is_daytime = true; /// 重连定时器 Timer? _retryTimer; /// User API 实例 final KRUserApi _kr_userApi = KRUserApi(); /// 防重复调用标志 bool _isInitializing = false; static const double kr_backoffFactor = 1.0; // 指数退避因子 - 不增加延迟 static const int kr_retryInterval = 0; // 基础重试间隔(秒)- 立即重试 static const int kr_maxRetryCount = 2; // 最大重试次数 - 重试两次 AppConfig._internal() { } factory AppConfig() => _instance; static AppConfig getInstance() { return _instance; } KRUpdateApplication? kr_update_application; // 🔧 新增:日志收集器实例 final _initLog = KRInitLogCollector(); Future initConfig({ Future Function()? onSuccess, }) async { _initLog.logSeparator(); _initLog.log('🌐 开始应用配置初始化(跳过域名加载,前置已完成)', tag: 'Domain'); if (_isInitializing) { _initLog.logWarning('配置初始化已在进行中,跳过重复调用', tag: 'Domain'); KRLogUtil.kr_w('配置初始化已在进行中,跳过重复调用', tag: 'AppConfig'); return; } _isInitializing = true; try { // 跳过域名加载(已在 _kr_initSiteConfig / KRSiteConfigService.initializeDomains 中完成) // 仅触发后续成功回调 if (onSuccess != null) { await onSuccess(); } } finally { _isInitializing = false; } } /// 停止自动重连 void kr_stopAutoRetry() { _retryTimer?.cancel(); } }