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 '../services/singbox_imp/kr_sing_box_imp.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",]; 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.omntech.com", // "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(); // 🔧 配置HttpClientAdapter使用sing-box的mixed代理 dio.httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { final client = HttpClient(); client.findProxy = (url) { final proxyConfig = KRSingBoxImp.instance.kr_buildProxyRule(); KRLogUtil.kr_i( '🔍 KRDomain 请求使用代理: $proxyConfig, url: $url', tag: 'KRDomain', ); return proxyConfig; }; return client; }, ); 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 { // 其他错误(如404、403等)认为域名可用 KRLogUtil.kr_i('✅ 快速检测成功,域名 $domain 可用(有响应但状态码异常): ${e.message}', tag: 'KRDomain'); return true; } } 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 { // 其他错误(如404、403等)认为域名可用 KRLogUtil.kr_i('✅ 域名 $domain 可用(有响应但状态码异常): ${e.message}', tag: 'KRDomain'); return true; } } 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.maodag.top"; // 快速验证兜底域名 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, ); } /// 从本地加载域名 static Future kr_loadBaseDomain() async { // 加载域名列表 String? savedDomains = await _storage.kr_readData(key: kr_domainsKey); if (savedDomains != null) { kr_baseDomains = savedDomains.split(','); } // 加载当前域名 String? savedDomain = await _storage.kr_readData(key: kr_domainKey); if (savedDomain != null && kr_baseDomains.contains(savedDomain)) { kr_currentDomain = savedDomain; } else { kr_currentDomain = kr_baseDomains[0]; await kr_saveCurrentDomain(); } } /// 清理过期的域名缓存 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 { /// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /// 加密密钥配置 /// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /// 统一加密密钥(用于所有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 = ""; /// 网站ID String kr_website_id = ""; /// 是否为白天模式 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() { // 初始化时加载保存的域名 KRDomain.kr_loadBaseDomain(); } factory AppConfig() => _instance; static AppConfig getInstance() { return _instance; } KRUpdateApplication? kr_update_application; Future initConfig({ Future Function()? onSuccess, }) async { if (_isInitializing) { KRLogUtil.kr_w('配置初始化已在进行中,跳过重复调用', tag: 'AppConfig'); return; } _isInitializing = true; try { // 🔧 修复6:启动时优先加载上次成功的域名 await KRDomain.kr_loadBaseDomain(); KRLogUtil.kr_i('📍 当前使用域名: ${KRDomain.kr_currentDomain}', tag: 'AppConfig'); // 所有模式都走正常的配置请求流程 KRLogUtil.kr_i('🚀 开始配置初始化', tag: 'AppConfig'); await _startAutoRetry(onSuccess); } finally { _isInitializing = false; } } Future _startAutoRetry(Future Function()? onSuccess) async { _retryTimer?.cancel(); int currentRetryCount = 0; // 🔧 P0修复:添加总体尝试次数限制,防止无限递归 int totalAttempts = 0; const int maxTotalAttempts = 5; // 最多5次尝试(包括域名切换后的重试) Future executeConfigRequest() async { try { // 🔧 P0修复:检查总体尝试次数 totalAttempts++; if (totalAttempts > maxTotalAttempts) { KRLogUtil.kr_e('❌ 超过最大总尝试次数($maxTotalAttempts),停止重试', tag: 'AppConfig'); // 使用默认配置继续启动 if (onSuccess != null) { await onSuccess(); } return; } // 检查是否超过最大重试次数 if (currentRetryCount >= kr_maxRetryCount) { KRLogUtil.kr_w('达到最大重试次数,尝试使用备用域名', tag: 'AppConfig'); // 最后一次尝试使用备用域名 String? newDomain = await KRDomain.kr_fastDomainSwitch(); if (newDomain != null) { KRDomain.kr_currentDomain = newDomain; await KRDomain.kr_saveCurrentDomain(); KRLogUtil.kr_i('✅ 最终切换到备用域名: $newDomain', tag: 'AppConfig'); // 重置当前重试计数,但保留总尝试次数 currentRetryCount = 0; // 继续重试配置请求 await executeConfigRequest(); } else { KRLogUtil.kr_e('❌ 备用域名切换失败,使用默认配置继续', tag: 'AppConfig'); // 使用默认配置继续启动 if (onSuccess != null) { await onSuccess(); } } return; } final result = await _kr_userApi.kr_config(); result.fold( (error) async { KRLogUtil.kr_e('配置初始化失败: $error', tag: 'AppConfig'); currentRetryCount++; // 计算重试延迟时间 final retryDelay = (kr_retryInterval * pow(kr_backoffFactor, currentRetryCount)).toInt(); // 尝试切换域名 await KRDomain.kr_switchToNextDomain(); // 等待后重试,至少延迟100ms避免立即重试 final actualDelay = max(retryDelay, 100); await Future.delayed(Duration(milliseconds: actualDelay)); await executeConfigRequest(); }, (config) async { _retryTimer?.cancel(); currentRetryCount = 0; kr_official_email = config.kr_official_email; kr_official_website = config.kr_official_website; kr_official_telegram = config.kr_official_telegram; kr_official_telephone = config.kr_official_telephone; kr_invitation_link = config.kr_invitation_link; kr_website_id = config.kr_website_id; if (config.kr_domains.isNotEmpty) { KRDomain.kr_handleDomains(config.kr_domains); } /// 判断当前是白天 kr_is_daytime = await config.kr_update_application.kr_is_daytime() ; KRUpdateUtil().kr_initUpdateInfo(config.kr_update_application); if (onSuccess != null) { onSuccess(); } }, ); } catch (e) { KRLogUtil.kr_e('配置初始化异常: $e', tag: 'AppConfig'); currentRetryCount++; // 检查是否超过最大重试次数 if (currentRetryCount >= kr_maxRetryCount) { KRLogUtil.kr_w('达到最大重试次数,尝试使用备用域名', tag: 'AppConfig'); // 最后一次尝试使用备用域名 String? newDomain = await KRDomain.kr_fastDomainSwitch(); if (newDomain != null) { KRDomain.kr_currentDomain = newDomain; await KRDomain.kr_saveCurrentDomain(); KRLogUtil.kr_i('✅ 最终切换到备用域名: $newDomain', tag: 'AppConfig'); // 继续重试配置请求 await executeConfigRequest(); } return; } // 计算重试延迟时间 final retryDelay = (kr_retryInterval * pow(kr_backoffFactor, currentRetryCount)).toInt(); // 尝试切换域名 await KRDomain.kr_switchToNextDomain(); // 等待后重试,至少延迟100ms避免立即重试 final actualDelay = max(retryDelay, 100); await Future.delayed(Duration(milliseconds: actualDelay)); await executeConfigRequest(); } } // 开始第一次请求 await executeConfigRequest(); } /// 停止自动重连 void kr_stopAutoRetry() { _retryTimer?.cancel(); } }