hi-client/lib/app/common/app_config.dart
Rust 7a223d614b
Some checks failed
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (iOS/tvOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 构建 iOS (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
安卓15部分机型出现界面不兼容并且UI库有BUG,MD
(cherry picked from commit 010405edda74bdb0251dcff2e32482edae2c9976)
2025-11-02 02:50:55 -08:00

1506 lines
57 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 '../services/singbox_imp/kr_sing_box_imp.dart';
import '../utils/kr_init_log_collector.dart'; // 🔧 新增:导入日志收集器
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/foundation.dart';
/// 协议配置
class KRProtocol {
static const String kr_https = "https";
static const String kr_http = "http";
static const String kr_ws = "wss";
}
/// 域名配置
class KRDomain {
static const String kr_domainKey = "kr_base_domain";
static const String kr_domainsKey = "kr_domains_list";
// static List<String> kr_baseDomains = ["apicn.bearvpn.top","apibear.nsdsox.com"];
// static String kr_currentDomain = "apicn.bearvpn.top";
static List<String> kr_baseDomains = ["api.hifast.biz",];
static String kr_currentDomain = "api.hifast.biz";
// 备用域名获取地址列表
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; // 最大重试次数
// 🔧 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<String> _triedDomains = {}; // 已尝试过的域名集合
static Map<String, int> _domainResponseTimes = {}; // 域名响应时间记录
static Map<String, DateTime> _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<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) {
// 🔧 修复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<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();
// 🔧 修复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<String?> _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<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;
}
// 🔧 修复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<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');
// 🔧 修复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<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,
);
}
/// 从本地加载域名方案1加载后验证可用性
static Future<void> kr_loadBaseDomain() async {
KRLogUtil.kr_i('📂 开始从 Hive 加载域名配置', tag: 'KRDomain');
final startTime = DateTime.now();
try {
// 🔧 Android 15 关键修复添加总体超时保护15秒
await Future.any([
_executeLoadAndVerify(),
Future.delayed(const Duration(seconds: 15), () {
throw TimeoutException('域名加载和验证总超时15秒');
}),
]);
} on TimeoutException catch (e) {
final duration = DateTime.now().difference(startTime).inSeconds;
KRLogUtil.kr_e('⏱️ 域名加载验证超时($duration 秒): $e', tag: 'KRDomain');
// 超时时使用默认域名
if (kr_baseDomains.isNotEmpty) {
kr_currentDomain = kr_baseDomains[0];
KRLogUtil.kr_w('⚠️ 超时后使用默认域名: $kr_currentDomain', tag: 'KRDomain');
}
} catch (e) {
final duration = DateTime.now().difference(startTime).inSeconds;
KRLogUtil.kr_e('❌ 域名加载异常($duration 秒): $e', tag: 'KRDomain');
// 异常时使用默认域名
if (kr_baseDomains.isNotEmpty) {
kr_currentDomain = kr_baseDomains[0];
}
}
final totalDuration = DateTime.now().difference(startTime).inMilliseconds;
KRLogUtil.kr_i('✅ 域名加载完成,耗时: ${totalDuration}ms当前域名: $kr_currentDomain', tag: 'KRDomain');
}
/// 执行域名加载和验证
static Future<void> _executeLoadAndVerify() async {
// 加载域名列表
String? savedDomains = await _storage.kr_readData(key: kr_domainsKey);
if (savedDomains != null && savedDomains.isNotEmpty) {
kr_baseDomains = savedDomains.split(',').where((d) => d.isNotEmpty).toList();
KRLogUtil.kr_i('📋 从 Hive 加载的域名列表: $kr_baseDomains', tag: 'KRDomain');
} else {
KRLogUtil.kr_w('⚠️ Hive 中没有保存的域名列表,使用默认配置', tag: 'KRDomain');
}
// 加载当前域名
String? savedDomain = await _storage.kr_readData(key: kr_domainKey);
KRLogUtil.kr_i('📌 从 Hive 加载的当前域名: $savedDomain', tag: 'KRDomain');
if (savedDomain != null && savedDomain.isNotEmpty && kr_baseDomains.contains(savedDomain)) {
// 🔧 Android 15 关键修复:验证 Hive 缓存的域名是否仍然可用
KRLogUtil.kr_i('🔍 验证 Hive 缓存域名的可用性: $savedDomain', tag: 'KRDomain');
try {
// 快速验证3 秒超时)
bool isAvailable = await kr_fastCheckDomainAvailability(savedDomain).timeout(
const Duration(seconds: 3),
onTimeout: () {
KRLogUtil.kr_w('⏱️ 域名验证超时(3秒),视为不可用', tag: 'KRDomain');
return false;
},
);
if (isAvailable) {
kr_currentDomain = savedDomain;
KRLogUtil.kr_i('✅ Hive 缓存域名验证通过,继续使用: $kr_currentDomain', tag: 'KRDomain');
return;
} else {
KRLogUtil.kr_w('❌ Hive 缓存域名不可用: $savedDomain,需要切换', tag: 'KRDomain');
}
} catch (e) {
KRLogUtil.kr_e('❌ 域名验证异常: $e,需要切换', tag: 'KRDomain');
}
} else {
if (savedDomain == null || savedDomain.isEmpty) {
KRLogUtil.kr_w('⚠️ Hive 中没有保存的当前域名', tag: 'KRDomain');
} else {
KRLogUtil.kr_w('⚠️ Hive 缓存的域名 $savedDomain 不在域名列表中', tag: 'KRDomain');
}
}
// 🔧 Android 15 增强Hive 域名验证失败,自动选择可用域名
KRLogUtil.kr_i('🔄 Hive 域名不可用,开始选择可用域名...', tag: 'KRDomain');
// 尝试从域名列表中找到可用的域名(每个最多 3 秒)
for (String domain in kr_baseDomains) {
try {
KRLogUtil.kr_i('🧪 测试域名: $domain', tag: 'KRDomain');
bool isAvailable = await kr_fastCheckDomainAvailability(domain).timeout(
const Duration(seconds: 3),
onTimeout: () {
KRLogUtil.kr_w('⏱️ 域名 $domain 验证超时(3秒)', tag: 'KRDomain');
return false;
},
);
if (isAvailable) {
kr_currentDomain = domain;
await kr_saveCurrentDomain();
KRLogUtil.kr_i('✅ 找到可用域名: $kr_currentDomain,已保存到 Hive', tag: 'KRDomain');
return;
}
} catch (e) {
KRLogUtil.kr_w('⚠️ 域名 $domain 验证异常: $e', tag: 'KRDomain');
}
}
// 如果所有主域名都不可用快速尝试备用域名5秒总超时
KRLogUtil.kr_w('⚠️ 所有主域名都不可用,尝试备用域名...', tag: 'KRDomain');
try {
String? backupDomain = await kr_tryLocalBackupDomains().timeout(
const Duration(seconds: 5),
onTimeout: () {
KRLogUtil.kr_w('⏱️ 备用域名获取超时', tag: 'KRDomain');
return null;
},
);
if (backupDomain != null) {
kr_currentDomain = backupDomain;
await kr_saveCurrentDomain();
KRLogUtil.kr_i('✅ 使用备用域名: $kr_currentDomain', tag: 'KRDomain');
return;
}
} catch (e) {
KRLogUtil.kr_w('⚠️ 备用域名获取异常: $e', tag: 'KRDomain');
}
// 最后的兜底:使用列表中的第一个域名(即使不可用)
if (kr_baseDomains.isNotEmpty) {
kr_currentDomain = kr_baseDomains[0];
await kr_saveCurrentDomain();
KRLogUtil.kr_w('⚠️ 无法验证任何域名,使用默认域名: $kr_currentDomain', tag: 'KRDomain');
} else {
KRLogUtil.kr_e('❌ 没有可用的域名配置!', tag: 'KRDomain');
}
}
/// 清理过期的域名缓存
static void _kr_clearExpiredCache() {
final now = DateTime.now();
final expiredDomains = <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 {
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/// 初始化日志收集开关
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/// 🔧 全局开关:是否启用初始化日志收集
/// true将所有初始化日志写入文件方便问题诊断
/// false关闭日志收集减少 I/O 操作
///
/// 建议:
/// - 测试版本、Beta 版本:设置为 true
/// - 正式生产版本:根据需要设置为 false或在遇到问题时临时开启
static const bool enableInitLogCollection = false;
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/// 加密密钥配置
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/// 统一加密密钥用于所有API接口和设备登录
/// 密钥来源OmnOem 项目 ppanel.json 配置
static const String kr_encryptionKey = 'c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx';
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/// 请求域名地址
/// 基础url
///
// static String baseUrl = "http://103.112.98.72:8088";
/// 请求域名地址
String get baseUrl {
// if (kDebugMode) {
// return "http://192.168.0.113:8082";
// }
return "${KRProtocol.kr_https}://${KRDomain.kr_api}";
}
/// ⚠️ 已遗弃WebSocket 连接不再使用
@Deprecated('wsBaseUrl has been deprecated')
String get wsBaseUrl {
// if (kDebugMode) {
// return "ws://192.168.0.113";
// }
return "${KRProtocol.kr_ws}://${KRDomain.kr_ws}";
}
static final AppConfig _instance = AppConfig._internal();
/// 官方邮箱
String kr_official_email = "";
/// 官方网站
String kr_official_website = "";
/// 官方电报群
String kr_official_telegram = "";
/// 官方电话
String kr_official_telephone = "";
/// 邀请链接
String kr_invitation_link = "";
/// 网站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;
// 🔧 新增:日志收集器实例
final _initLog = KRInitLogCollector();
Future<void> initConfig({
Future<void> Function()? onSuccess,
}) async {
_initLog.logSeparator();
_initLog.log('🌐 开始应用配置初始化(域名加载)', tag: 'Domain');
if (_isInitializing) {
_initLog.logWarning('配置初始化已在进行中,跳过重复调用', tag: 'Domain');
KRLogUtil.kr_w('配置初始化已在进行中,跳过重复调用', tag: 'AppConfig');
return;
}
_isInitializing = true;
try {
// 🔧 修复6启动时优先加载上次成功的域名
_initLog.log('开始加载基础域名配置', tag: 'Domain');
await KRDomain.kr_loadBaseDomain();
_initLog.logSuccess('当前使用域名: ${KRDomain.kr_currentDomain}', tag: 'Domain');
KRLogUtil.kr_i('📍 当前使用域名: ${KRDomain.kr_currentDomain}', tag: 'AppConfig');
// 所有模式都走正常的配置请求流程
_initLog.log('开始配置请求流程(包含重试机制)', tag: 'Domain');
KRLogUtil.kr_i('🚀 开始配置初始化', tag: 'AppConfig');
await _startAutoRetry(onSuccess);
} finally {
_isInitializing = false;
}
}
Future<void> _startAutoRetry(Future<void> Function()? onSuccess) async {
_retryTimer?.cancel();
int currentRetryCount = 0;
// 🔧 P0修复添加总体尝试次数限制防止无限递归
int totalAttempts = 0;
const int maxTotalAttempts = 5; // 最多5次尝试包括域名切换后的重试
Future<void> 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;
}
_initLog.log('发起配置请求 API (尝试 $totalAttempts/$maxTotalAttempts)', tag: 'Domain');
final result = await _kr_userApi.kr_config();
result.fold(
(error) async {
_initLog.logError('配置请求失败 (重试 $currentRetryCount/$kr_maxRetryCount)', tag: 'Domain', error: error);
KRLogUtil.kr_e('配置初始化失败: $error', tag: 'AppConfig');
currentRetryCount++;
// 计算重试延迟时间
final retryDelay = (kr_retryInterval * pow(kr_backoffFactor, currentRetryCount)).toInt();
final actualDelay = max(retryDelay, 100);
_initLog.log('将在 ${actualDelay}ms 后重试', tag: 'Domain');
// 尝试切换域名
_initLog.log('尝试切换到下一个备用域名', tag: 'Domain');
await KRDomain.kr_switchToNextDomain();
_initLog.log('当前域名: ${KRDomain.kr_currentDomain}', tag: 'Domain');
// 等待后重试
await Future.delayed(Duration(milliseconds: actualDelay));
await executeConfigRequest();
},
(config) async {
_initLog.logSuccess('配置请求成功!', tag: 'Domain');
_initLog.log('网站ID: ${config.kr_website_id}', tag: 'Domain');
_initLog.log('官网: ${config.kr_official_website}', tag: 'Domain');
_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();
}
}