1222 lines
45 KiB
Dart
Executable File
1222 lines
45 KiB
Dart
Executable File
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 {
|
||
/// 请求域名地址
|
||
/// 基础url
|
||
// static String baseUrl = "http://103.112.98.72:8088";
|
||
|
||
/// 请求域名地址
|
||
String get baseUrl {
|
||
if (kDebugMode) {
|
||
return "http://192.168.0.113";
|
||
}
|
||
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 = "";
|
||
|
||
|
||
/// 是否为白天模式
|
||
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();
|
||
}
|
||
}
|