hi-client/lib/app/common/app_config.dart

1247 lines
46 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 '../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 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:dio/dio.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 = "ws";
}
/// 域名配置
class KRDomain {
static const String kr_domainKey = "kr_base_domain";
static const String kr_domainsKey = "kr_domains_list";
static List<String> kr_baseDomains = ["apicn.bearvpn.top","apibear.nsdsox.com"];
static String kr_currentDomain = "apicn.bearvpn.top";
// static List<String> kr_baseDomains = ["api.kkmen.cc"];
// static String kr_currentDomain = "api.kkmen.cc";
// 备用域名获取地址列表
static List<String> 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<String> 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; // 最大重试次数
static const int kr_domainTimeout = 3; // 域名检测超时时间(秒)
static const int kr_totalTimeout = 6; // 总体超时时间(秒)
static Set<String> _triedDomains = {}; // 已尝试过的域名集合
static Map<String, int> _domainResponseTimes = {}; // 域名响应时间记录
static Map<String, DateTime> _domainLastCheck = {}; // 域名最后检测时间
static const int _domainCacheDuration = 300; // 域名缓存时间(秒)
static final Dio _dio = Dio(); // Dio 实例
/// API 域名
static String get kr_api => kr_currentDomain;
/// WebSocket 域名
static String get kr_ws => "$kr_currentDomain/v1/app";
/// 从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<bool> 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<bool> kr_checkDomainAvailability(String domain) async {
// 清理过期缓存
_kr_clearExpiredCache();
// 检查缓存
final lastCheck = _domainLastCheck[domain];
if (lastCheck != null) {
final timeSinceLastCheck = DateTime.now().difference(lastCheck).inSeconds;
if (timeSinceLastCheck < _domainCacheDuration) {
// 使用缓存的响应时间判断域名是否可用
final responseTime = _domainResponseTimes[domain];
if (responseTime != null && responseTime < 5000) { // 5秒内响应认为可用
KRLogUtil.kr_i('📋 使用缓存结果,域名 $domain 可用(缓存时间: ${timeSinceLastCheck}s', 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<String?> 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<String?> kr_fastDomainSwitch() async {
if (kr_baseDomains.isEmpty) return null;
KRLogUtil.kr_i('🚀 开始快速域名切换,检测 ${kr_baseDomains.length} 个主域名: $kr_baseDomains', tag: 'KRDomain');
final startTime = DateTime.now();
// 先检查缓存,如果有可用的域名直接返回
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];
if (responseTime != null && responseTime < 2000) { // 降低缓存阈值
KRLogUtil.kr_i('🎯 使用缓存结果快速切换,域名: $domain', tag: 'KRDomain');
return domain;
}
}
}
}
// 创建并发任务列表
List<Future<MapEntry<String, bool>>> 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<MapEntry<String, bool>> 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<String, bool> 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<String, bool> 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;
}
// 最后兜底方案
KRLogUtil.kr_w('⚠️ 所有域名都失败,使用兜底域名', tag: 'KRDomain');
return "api.omntech.com";
} 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<void> 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<String?> kr_fastBackupDomainSwitch() async {
KRLogUtil.kr_i('🔄 开始快速备用域名切换,备用地址: $kr_backupDomainUrls', tag: 'KRDomain');
final startTime = DateTime.now();
// 并发获取所有备用地址的域名
List<Future<List<String>>> 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<String> 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 <String>[];
}).toList();
try {
KRLogUtil.kr_i('⏱️ 等待备用地址响应,超时时间: ${kr_totalTimeout - 1}', tag: 'KRDomain');
List<List<String>> backupResults = await Future.wait(
backupTasks,
).timeout(Duration(seconds: kr_totalTimeout - 1)); // 留1秒给域名测试
// 合并所有备用域名并去重
Set<String> uniqueBackupDomains = {};
for (List<String> domains in backupResults) {
uniqueBackupDomains.addAll(domains);
}
List<String> 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<Future<MapEntry<String, bool>>> testTasks = allBackupDomains.map((domain) async {
bool isAvailable = await kr_checkDomainAvailability(domain);
return MapEntry(domain, isAvailable);
}).toList();
List<MapEntry<String, bool>> 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<String, bool> 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<String, bool> 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<List<String>> kr_getBackupDomains() async {
List<String> 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<String> 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<String> kr_parseBackupDomains(String jsonData) {
List<String> domains = [];
try {
KRLogUtil.kr_i('🔍 开始解析备用域名数据: $jsonData', tag: 'KRDomain');
// 尝试解析为JSON数组
if (jsonData.startsWith('[') && jsonData.endsWith(']')) {
List<dynamic> 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<String, dynamic> 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<String> 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<String> 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<bool> kr_tryBackupDomains() async {
KRLogUtil.kr_i('开始尝试备用域名', tag: 'KRDomain');
// 获取备用域名列表
List<String> 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<void> kr_handleDomains(List<String> domains) async {
// 提取所有域名
List<String> 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<bool> 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');
// 检查是否有预检测成功的域名可以直接使用
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<void> 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<void> kr_testBackupDomains() async {
KRLogUtil.kr_i('开始手动测试备用域名功能', tag: 'KRDomain');
// 清空当前域名列表,模拟所有主域名失效
List<String> 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<bool> 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<String?> kr_tryLocalBackupDomains() async {
KRLogUtil.kr_i('🔄 开始尝试本地备用域名: $kr_localBackupDomains', tag: 'KRDomain');
final startTime = DateTime.now();
// 并发检测所有本地备用域名
List<Future<MapEntry<String, bool>>> 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<MapEntry<String, bool>> 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<String, bool> 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<String, bool> 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<String> 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<String> domains = kr_parseBackupDomains(testData[i]);
KRLogUtil.kr_i('📊 解析结果 $i: $domains', tag: 'KRDomain');
}
}
/// 保存域名列表到本地
static Future<void> kr_saveDomains(List<String> domains) async {
await _storage.kr_saveData(
key: kr_domainsKey,
value: domains.join(','),
);
}
/// 保存当前域名到本地
static Future<void> kr_saveCurrentDomain() async {
await _storage.kr_saveData(
key: kr_domainKey,
value: kr_currentDomain,
);
}
/// 从本地加载域名
static Future<void> 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 = <String>[];
for (MapEntry<String, DateTime> 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}";
}
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 = "";
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/// 用户信息固定值(临时)
/// ⚠️ 注意:新版本后端已废弃 /v1/app/user/info 接口
/// 等待新接口实现后,这些值应该从新接口动态获取
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/// 用户余额(单位:分)
/// 临时固定值0表示0.00元
static const int kr_userBalance = 0;
/// 用户邀请码
/// 临时固定值:空字符串,待新接口实现
static const String kr_userReferCode = "";
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/// 是否为白天模式
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<void> initConfig({
Future<void> Function()? onSuccess,
}) async {
if (_isInitializing) {
KRLogUtil.kr_w('配置初始化已在进行中,跳过重复调用', tag: 'AppConfig');
return;
}
_isInitializing = true;
try {
// Debug 模式下直接使用固定地址,跳过所有配置请求和域名切换逻辑
if (kDebugMode) {
KRLogUtil.kr_i('🐛 Debug 模式,使用固定 API 地址,跳过配置请求', tag: 'AppConfig');
if (onSuccess != null) {
await onSuccess();
}
return;
}
await _startAutoRetry(onSuccess);
} finally {
_isInitializing = false;
}
}
Future<void> _startAutoRetry(Future<void> Function()? onSuccess) async {
_retryTimer?.cancel();
int currentRetryCount = 0;
Future<void> executeConfigRequest() async {
try {
// 检查是否超过最大重试次数
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 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();
}
}