feat: 1.调整项目初始化,不用等待site/config接口,仅在hi_user_info中调用;将site_config_service拆成loadDomain和获取配置,在splash中只做loadDomain
2. 优化接口拦截的解密日志 3. 调整获取订阅到kr_home,与源版一致 4. 修复闪连抖动问题,增加套餐有效期判断
This commit is contained in:
parent
1d2a89a80a
commit
ccf9a76de9
@ -5,7 +5,7 @@ import '../utils/kr_secure_storage.dart';
|
||||
import '../utils/kr_log_util.dart';
|
||||
import '../utils/kr_http_adapter_util.dart';
|
||||
import '../services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import '../utils/kr_init_log_collector.dart'; // 🔧 新增:导入日志收集器
|
||||
import '../utils/kr_init_log_collector.dart'; // 🔧 新增:导入日志收集器
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
@ -23,15 +23,16 @@ class KRProtocol {
|
||||
|
||||
/// 域名配置
|
||||
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", "api.airovpn.tel",];
|
||||
static List<String> kr_baseDomains = [
|
||||
"api.hifast.biz",
|
||||
"api.airovpn.tel",
|
||||
];
|
||||
static String kr_currentDomain = "api.hifast.biz";
|
||||
|
||||
// 备用域名获取地址列表
|
||||
@ -170,7 +171,8 @@ class KRDomain {
|
||||
sendTimeout: Duration(seconds: 2), // 2秒超时
|
||||
receiveTimeout: Duration(seconds: 2),
|
||||
// 允许所有状态码,只要能够连接就认为域名可用
|
||||
validateStatus: (status) => status != null && status >= 200 && status < 600,
|
||||
validateStatus: (status) =>
|
||||
status != null && status >= 200 && status < 600,
|
||||
),
|
||||
);
|
||||
final endTime = DateTime.now();
|
||||
@ -181,10 +183,13 @@ class KRDomain {
|
||||
|
||||
// 只要能够连接就认为域名可用(包括404、403等状态码)
|
||||
if (response.statusCode != null) {
|
||||
KRLogUtil.kr_i('✅ 快速检测成功,域名 $domain 可用,状态码: ${response.statusCode},响应时间: ${responseTime}ms', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i(
|
||||
'✅ 快速检测成功,域名 $domain 可用,状态码: ${response.statusCode},响应时间: ${responseTime}ms',
|
||||
tag: 'KRDomain');
|
||||
return true;
|
||||
} else {
|
||||
KRLogUtil.kr_w('❌ 快速检测失败,域名 $domain 响应异常,状态码: ${response.statusCode}', tag: 'KRDomain');
|
||||
KRLogUtil.kr_w('❌ 快速检测失败,域名 $domain 响应异常,状态码: ${response.statusCode}',
|
||||
tag: 'KRDomain');
|
||||
return false;
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
@ -193,11 +198,14 @@ class KRDomain {
|
||||
e.type == DioExceptionType.receiveTimeout ||
|
||||
e.type == DioExceptionType.sendTimeout ||
|
||||
e.type == DioExceptionType.connectionError) {
|
||||
KRLogUtil.kr_w('❌ 快速检测失败,域名 $domain 连接超时或网络错误: ${e.message}', tag: 'KRDomain');
|
||||
KRLogUtil.kr_w('❌ 快速检测失败,域名 $domain 连接超时或网络错误: ${e.message}',
|
||||
tag: 'KRDomain');
|
||||
return false;
|
||||
} else {
|
||||
// 其他错误(如未知错误、取消等)认为域名不可用
|
||||
KRLogUtil.kr_w('❌ 快速检测失败,域名 $domain 其他异常 (Type: ${e.type}): ${e.message}', tag: 'KRDomain');
|
||||
KRLogUtil.kr_w(
|
||||
'❌ 快速检测失败,域名 $domain 其他异常 (Type: ${e.type}): ${e.message}',
|
||||
tag: 'KRDomain');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
@ -219,8 +227,11 @@ class KRDomain {
|
||||
// 🔧 修复2:放宽缓存阈值 5000ms → 10000ms
|
||||
// 使用缓存的响应时间判断域名是否可用
|
||||
final responseTime = _domainResponseTimes[domain];
|
||||
if (responseTime != null && responseTime < 10000) { // 10秒内响应认为可用
|
||||
KRLogUtil.kr_i('📋 使用缓存结果,域名 $domain 可用(缓存时间: ${timeSinceLastCheck}s,响应时间: ${responseTime}ms)', tag: 'KRDomain');
|
||||
if (responseTime != null && responseTime < 10000) {
|
||||
// 10秒内响应认为可用
|
||||
KRLogUtil.kr_i(
|
||||
'📋 使用缓存结果,域名 $domain 可用(缓存时间: ${timeSinceLastCheck}s,响应时间: ${responseTime}ms)',
|
||||
tag: 'KRDomain');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -236,7 +247,8 @@ class KRDomain {
|
||||
sendTimeout: Duration(seconds: kr_domainTimeout),
|
||||
receiveTimeout: Duration(seconds: kr_domainTimeout),
|
||||
// 允许所有状态码,只要能够连接就认为域名可用
|
||||
validateStatus: (status) => status != null && status >= 200 && status < 600,
|
||||
validateStatus: (status) =>
|
||||
status != null && status >= 200 && status < 600,
|
||||
),
|
||||
);
|
||||
final endTime = DateTime.now();
|
||||
@ -248,10 +260,13 @@ class KRDomain {
|
||||
|
||||
// 只要能够连接就认为域名可用(包括404、403等状态码)
|
||||
if (response.statusCode != null) {
|
||||
KRLogUtil.kr_i('✅ 域名 $domain 可用,状态码: ${response.statusCode},响应时间: ${responseTime}ms', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i(
|
||||
'✅ 域名 $domain 可用,状态码: ${response.statusCode},响应时间: ${responseTime}ms',
|
||||
tag: 'KRDomain');
|
||||
return true;
|
||||
} else {
|
||||
KRLogUtil.kr_w('❌ 域名 $domain 响应异常,状态码: ${response.statusCode}', tag: 'KRDomain');
|
||||
KRLogUtil.kr_w('❌ 域名 $domain 响应异常,状态码: ${response.statusCode}',
|
||||
tag: 'KRDomain');
|
||||
return false;
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
@ -264,7 +279,8 @@ class KRDomain {
|
||||
return false;
|
||||
} else {
|
||||
// 其他错误(如未知错误、取消等)认为域名不可用
|
||||
KRLogUtil.kr_w('❌ 域名 $domain 检查异常 (Type: ${e.type}): ${e.message}', tag: 'KRDomain');
|
||||
KRLogUtil.kr_w('❌ 域名 $domain 检查异常 (Type: ${e.type}): ${e.message}',
|
||||
tag: 'KRDomain');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
@ -292,11 +308,13 @@ class KRDomain {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 快速域名切换 - 并发检测所有域名
|
||||
/// 快速域名切换 - 并发检测所有域名
|
||||
static Future<String?> kr_fastDomainSwitch() async {
|
||||
if (kr_baseDomains.isEmpty) return null;
|
||||
|
||||
KRLogUtil.kr_i('🚀 开始快速域名切换,检测 ${kr_baseDomains.length} 个主域名: $kr_baseDomains', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i(
|
||||
'🚀 开始快速域名切换,检测 ${kr_baseDomains.length} 个主域名: $kr_baseDomains',
|
||||
tag: 'KRDomain');
|
||||
final startTime = DateTime.now();
|
||||
|
||||
// 🔧 修复5:为整个域名切换流程添加总超时
|
||||
@ -304,7 +322,8 @@ class KRDomain {
|
||||
return await _executeFastDomainSwitch(startTime).timeout(
|
||||
Duration(seconds: kr_maxDomainSwitchTimeout),
|
||||
onTimeout: () {
|
||||
KRLogUtil.kr_e('⏰ 域名切换总超时(${kr_maxDomainSwitchTimeout}秒)', tag: 'KRDomain');
|
||||
KRLogUtil.kr_e('⏰ 域名切换总超时(${kr_maxDomainSwitchTimeout}秒)',
|
||||
tag: 'KRDomain');
|
||||
return null;
|
||||
},
|
||||
);
|
||||
@ -316,17 +335,20 @@ class KRDomain {
|
||||
|
||||
/// 执行快速域名切换的核心逻辑
|
||||
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;
|
||||
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');
|
||||
if (responseTime != null && responseTime < 5000) {
|
||||
// 5秒内响应认为可用
|
||||
KRLogUtil.kr_i(
|
||||
'🎯 使用缓存结果快速切换,域名: $domain (响应时间: ${responseTime}ms)',
|
||||
tag: 'KRDomain');
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
@ -334,7 +356,8 @@ class KRDomain {
|
||||
}
|
||||
|
||||
// 创建并发任务列表
|
||||
List<Future<MapEntry<String, bool>>> tasks = kr_baseDomains.map((domain) async {
|
||||
List<Future<MapEntry<String, bool>>> tasks =
|
||||
kr_baseDomains.map((domain) async {
|
||||
bool isAvailable = await kr_checkDomainAvailability(domain);
|
||||
return MapEntry(domain, isAvailable);
|
||||
}).toList();
|
||||
@ -361,7 +384,8 @@ class KRDomain {
|
||||
}
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('📈 主域名检测结果: $availableCount/${results.length} 可用', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i('📈 主域名检测结果: $availableCount/${results.length} 可用',
|
||||
tag: 'KRDomain');
|
||||
|
||||
// 找到第一个可用的域名
|
||||
for (MapEntry<String, bool> result in results) {
|
||||
@ -393,7 +417,8 @@ class KRDomain {
|
||||
const fallbackDomain = "api.airovpn.tel";
|
||||
|
||||
// 快速验证兜底域名
|
||||
bool isFallbackAvailable = await kr_fastCheckDomainAvailability(fallbackDomain);
|
||||
bool isFallbackAvailable =
|
||||
await kr_fastCheckDomainAvailability(fallbackDomain);
|
||||
if (isFallbackAvailable) {
|
||||
KRLogUtil.kr_i('✅ 兜底域名可用: $fallbackDomain', tag: 'KRDomain');
|
||||
return fallbackDomain;
|
||||
@ -401,7 +426,6 @@ class KRDomain {
|
||||
|
||||
KRLogUtil.kr_e('❌ 兜底域名也不可用: $fallbackDomain', tag: 'KRDomain');
|
||||
return null; // 所有域名都失败,返回 null
|
||||
|
||||
} catch (e) {
|
||||
final endTime = DateTime.now();
|
||||
final duration = endTime.difference(startTime).inMilliseconds;
|
||||
@ -434,7 +458,8 @@ class KRDomain {
|
||||
try {
|
||||
// 如果当前域名已经在主域名列表中,先检查它是否可用
|
||||
if (kr_baseDomains.contains(kr_currentDomain)) {
|
||||
bool isCurrentAvailable = await kr_fastCheckDomainAvailability(kr_currentDomain);
|
||||
bool isCurrentAvailable =
|
||||
await kr_fastCheckDomainAvailability(kr_currentDomain);
|
||||
if (isCurrentAvailable) {
|
||||
KRLogUtil.kr_i('✅ 当前域名可用,无需切换: $kr_currentDomain', tag: 'KRDomain');
|
||||
return; // 当前域名可用,不需要切换
|
||||
@ -473,7 +498,8 @@ class KRDomain {
|
||||
final startTime = DateTime.now();
|
||||
|
||||
// 并发获取所有备用地址的域名
|
||||
List<Future<List<String>>> backupTasks = kr_backupDomainUrls.map((url) async {
|
||||
List<Future<List<String>>> backupTasks =
|
||||
kr_backupDomainUrls.map((url) async {
|
||||
try {
|
||||
KRLogUtil.kr_i('📡 从备用地址获取域名: $url', tag: 'KRDomain');
|
||||
final response = await _dio.get(
|
||||
@ -491,7 +517,8 @@ class KRDomain {
|
||||
KRLogUtil.kr_i('🔍 解析到备用域名: $domains', tag: 'KRDomain');
|
||||
return domains;
|
||||
} else {
|
||||
KRLogUtil.kr_w('❌ 备用地址 $url 响应异常,状态码: ${response.statusCode}', tag: 'KRDomain');
|
||||
KRLogUtil.kr_w('❌ 备用地址 $url 响应异常,状态码: ${response.statusCode}',
|
||||
tag: 'KRDomain');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('❌ 备用地址 $url 获取失败: $e', tag: 'KRDomain');
|
||||
@ -500,7 +527,8 @@ class KRDomain {
|
||||
}).toList();
|
||||
|
||||
try {
|
||||
KRLogUtil.kr_i('⏱️ 等待备用地址响应,超时时间: ${kr_totalTimeout - 1}秒', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i('⏱️ 等待备用地址响应,超时时间: ${kr_totalTimeout - 1}秒',
|
||||
tag: 'KRDomain');
|
||||
List<List<String>> backupResults = await Future.wait(
|
||||
backupTasks,
|
||||
).timeout(Duration(seconds: kr_totalTimeout - 1)); // 留1秒给域名测试
|
||||
@ -519,10 +547,12 @@ class KRDomain {
|
||||
return null;
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('🧪 开始测试 ${allBackupDomains.length} 个备用域名', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i('🧪 开始测试 ${allBackupDomains.length} 个备用域名',
|
||||
tag: 'KRDomain');
|
||||
|
||||
// 并发测试所有备用域名
|
||||
List<Future<MapEntry<String, bool>>> testTasks = allBackupDomains.map((domain) async {
|
||||
List<Future<MapEntry<String, bool>>> testTasks =
|
||||
allBackupDomains.map((domain) async {
|
||||
bool isAvailable = await kr_checkDomainAvailability(domain);
|
||||
return MapEntry(domain, isAvailable);
|
||||
}).toList();
|
||||
@ -546,7 +576,9 @@ class KRDomain {
|
||||
}
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('📈 备用域名检测结果: $availableBackupCount/${testResults.length} 可用', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i(
|
||||
'📈 备用域名检测结果: $availableBackupCount/${testResults.length} 可用',
|
||||
tag: 'KRDomain');
|
||||
|
||||
// 找到第一个可用的备用域名
|
||||
for (MapEntry<String, bool> result in testResults) {
|
||||
@ -573,7 +605,6 @@ class KRDomain {
|
||||
|
||||
KRLogUtil.kr_w('⚠️ 所有备用域名都不可用', tag: 'KRDomain');
|
||||
return null;
|
||||
|
||||
} catch (e) {
|
||||
final endTime = DateTime.now();
|
||||
final duration = endTime.difference(startTime).inMilliseconds;
|
||||
@ -630,7 +661,8 @@ class KRDomain {
|
||||
|
||||
for (int i = 0; i < jsonList.length; i++) {
|
||||
dynamic item = jsonList[i];
|
||||
KRLogUtil.kr_i('🔍 处理第 $i 项: $item (类型: ${item.runtimeType})', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i('🔍 处理第 $i 项: $item (类型: ${item.runtimeType})',
|
||||
tag: 'KRDomain');
|
||||
|
||||
if (item is String) {
|
||||
// 字符串格式
|
||||
@ -674,7 +706,8 @@ class KRDomain {
|
||||
try {
|
||||
// 尝试解析为JSON对象
|
||||
Map<String, dynamic> jsonMap = json.decode(jsonData);
|
||||
KRLogUtil.kr_i('📋 解析为JSON对象,键数量: ${jsonMap.length}', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i('📋 解析为JSON对象,键数量: ${jsonMap.length}',
|
||||
tag: 'KRDomain');
|
||||
|
||||
jsonMap.forEach((key, value) {
|
||||
KRLogUtil.kr_i('🔍 键: $key, 值: $value', tag: 'KRDomain');
|
||||
@ -747,8 +780,8 @@ class KRDomain {
|
||||
}
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('📊 解析完成,总共提取到 ${domains.length} 个域名: $domains', tag: 'KRDomain');
|
||||
|
||||
KRLogUtil.kr_i('📊 解析完成,总共提取到 ${domains.length} 个域名: $domains',
|
||||
tag: 'KRDomain');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 解析备用域名数据失败: $e', tag: 'KRDomain');
|
||||
}
|
||||
@ -796,7 +829,8 @@ class KRDomain {
|
||||
/// 处理域名列表
|
||||
static Future<void> kr_handleDomains(List<String> domains) async {
|
||||
// 提取所有域名
|
||||
List<String> extractedDomains = domains.map((url) => kr_extractDomain(url)).toList();
|
||||
List<String> extractedDomains =
|
||||
domains.map((url) => kr_extractDomain(url)).toList();
|
||||
|
||||
// 如果提取的域名为空,使用默认域名
|
||||
if (extractedDomains.isEmpty) {
|
||||
@ -835,7 +869,9 @@ class KRDomain {
|
||||
if (age < _domainCacheDuration && !_triedDomains.contains(domain)) {
|
||||
kr_currentDomain = domain;
|
||||
await kr_saveCurrentDomain();
|
||||
KRLogUtil.kr_i('✅ 使用缓存的成功域名: $kr_currentDomain (响应时间: ${responseTime}ms)', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i(
|
||||
'✅ 使用缓存的成功域名: $kr_currentDomain (响应时间: ${responseTime}ms)',
|
||||
tag: 'KRDomain');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -899,7 +935,8 @@ class KRDomain {
|
||||
_triedDomains.clear();
|
||||
|
||||
// 创建新的重试定时器
|
||||
_retryTimer = Timer.periodic(Duration(seconds: kr_retryInterval), (timer) async {
|
||||
_retryTimer =
|
||||
Timer.periodic(Duration(seconds: kr_retryInterval), (timer) async {
|
||||
// 切换到下一个域名
|
||||
bool hasNextDomain = await kr_switchToNextDomain();
|
||||
if (!hasNextDomain) {
|
||||
@ -978,13 +1015,15 @@ class KRDomain {
|
||||
final startTime = DateTime.now();
|
||||
|
||||
// 并发检测所有本地备用域名
|
||||
List<Future<MapEntry<String, bool>>> tasks = kr_localBackupDomains.map((domain) async {
|
||||
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');
|
||||
KRLogUtil.kr_i('⏱️ 等待本地备用域名检测结果,超时时间: ${kr_totalTimeout}秒',
|
||||
tag: 'KRDomain');
|
||||
List<MapEntry<String, bool>> results = await Future.wait(
|
||||
tasks,
|
||||
).timeout(Duration(seconds: kr_totalTimeout));
|
||||
@ -1004,7 +1043,8 @@ class KRDomain {
|
||||
}
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('📈 本地备用域名检测结果: $availableCount/${results.length} 可用', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i('📈 本地备用域名检测结果: $availableCount/${results.length} 可用',
|
||||
tag: 'KRDomain');
|
||||
|
||||
// 找到第一个可用的本地备用域名
|
||||
for (MapEntry<String, bool> result in results) {
|
||||
@ -1029,7 +1069,6 @@ class KRDomain {
|
||||
|
||||
KRLogUtil.kr_w('⚠️ 所有本地备用域名都不可用', tag: 'KRDomain');
|
||||
return null;
|
||||
|
||||
} catch (e) {
|
||||
final endTime = DateTime.now();
|
||||
final duration = endTime.difference(startTime).inMilliseconds;
|
||||
@ -1107,7 +1146,8 @@ class KRDomain {
|
||||
}
|
||||
|
||||
final totalDuration = DateTime.now().difference(startTime).inMilliseconds;
|
||||
KRLogUtil.kr_i('✅ 域名加载完成,耗时: ${totalDuration}ms,当前域名: $kr_currentDomain', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i('✅ 域名加载完成,耗时: ${totalDuration}ms,当前域名: $kr_currentDomain',
|
||||
tag: 'KRDomain');
|
||||
}
|
||||
|
||||
/// 执行域名加载和验证
|
||||
@ -1115,7 +1155,8 @@ class KRDomain {
|
||||
// 加载域名列表
|
||||
String? savedDomains = await _storage.kr_readData(key: kr_domainsKey);
|
||||
if (savedDomains != null && savedDomains.isNotEmpty) {
|
||||
kr_baseDomains = savedDomains.split(',').where((d) => d.isNotEmpty).toList();
|
||||
kr_baseDomains =
|
||||
savedDomains.split(',').where((d) => d.isNotEmpty).toList();
|
||||
KRLogUtil.kr_i('📋 从 Hive 加载的域名列表: $kr_baseDomains', tag: 'KRDomain');
|
||||
} else {
|
||||
KRLogUtil.kr_w('⚠️ Hive 中没有保存的域名列表,使用默认配置', tag: 'KRDomain');
|
||||
@ -1125,13 +1166,16 @@ class 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)) {
|
||||
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(
|
||||
bool isAvailable =
|
||||
await kr_fastCheckDomainAvailability(savedDomain).timeout(
|
||||
const Duration(seconds: 3),
|
||||
onTimeout: () {
|
||||
KRLogUtil.kr_w('⏱️ 域名验证超时(3秒),视为不可用', tag: 'KRDomain');
|
||||
@ -1141,7 +1185,8 @@ class KRDomain {
|
||||
|
||||
if (isAvailable) {
|
||||
kr_currentDomain = savedDomain;
|
||||
KRLogUtil.kr_i('✅ Hive 缓存域名验证通过,继续使用: $kr_currentDomain', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i('✅ Hive 缓存域名验证通过,继续使用: $kr_currentDomain',
|
||||
tag: 'KRDomain');
|
||||
return;
|
||||
} else {
|
||||
KRLogUtil.kr_w('❌ Hive 缓存域名不可用: $savedDomain,需要切换', tag: 'KRDomain');
|
||||
@ -1175,7 +1220,8 @@ class KRDomain {
|
||||
if (isAvailable) {
|
||||
kr_currentDomain = domain;
|
||||
await kr_saveCurrentDomain();
|
||||
KRLogUtil.kr_i('✅ 找到可用域名: $kr_currentDomain,已保存到 Hive', tag: 'KRDomain');
|
||||
KRLogUtil.kr_i('✅ 找到可用域名: $kr_currentDomain,已保存到 Hive',
|
||||
tag: 'KRDomain');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
@ -1300,8 +1346,6 @@ class AppConfig {
|
||||
/// 邀请链接
|
||||
String kr_invitation_link = "";
|
||||
|
||||
/// 网站ID
|
||||
String kr_website_id = "";
|
||||
|
||||
/// 设备限制数量
|
||||
String device_limit = '0';
|
||||
@ -1323,8 +1367,6 @@ class AppConfig {
|
||||
static const int kr_maxRetryCount = 2; // 最大重试次数 - 重试两次
|
||||
|
||||
AppConfig._internal() {
|
||||
// 初始化时加载保存的域名
|
||||
KRDomain.kr_loadBaseDomain();
|
||||
}
|
||||
|
||||
factory AppConfig() => _instance;
|
||||
@ -1342,7 +1384,7 @@ class AppConfig {
|
||||
Future<void> Function()? onSuccess,
|
||||
}) async {
|
||||
_initLog.logSeparator();
|
||||
_initLog.log('🌐 开始应用配置初始化(域名加载)', tag: 'Domain');
|
||||
_initLog.log('🌐 开始应用配置初始化(跳过域名加载,前置已完成)', tag: 'Domain');
|
||||
|
||||
if (_isInitializing) {
|
||||
_initLog.logWarning('配置初始化已在进行中,跳过重复调用', tag: 'Domain');
|
||||
@ -1352,149 +1394,16 @@ class AppConfig {
|
||||
|
||||
_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);
|
||||
// 跳过域名加载(已在 _kr_initSiteConfig / KRSiteConfigService.initializeDomains 中完成)
|
||||
// 仅触发后续成功回调
|
||||
if (onSuccess != null) {
|
||||
await 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();
|
||||
|
||||
@ -52,9 +52,6 @@ class KRAppRunData {
|
||||
/// 佣金
|
||||
final RxInt kr_commission = 0.obs;
|
||||
|
||||
/// 登录类型
|
||||
KRLoginType? kr_loginType;
|
||||
|
||||
/// 设备ID
|
||||
String? deviceId;
|
||||
|
||||
@ -72,6 +69,14 @@ class KRAppRunData {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
DateTime _stepStartTime = DateTime.now();
|
||||
void _logStepTiming(String stepName) {
|
||||
final now = DateTime.now();
|
||||
final stepMs = now.difference(_stepStartTime).inMilliseconds;
|
||||
KRLogUtil.kr_i('[SPLASH_TIMING] ⏱️ $stepName 耗时: ${stepMs}ms',
|
||||
tag: 'AppRunData');
|
||||
_stepStartTime = now;
|
||||
}
|
||||
|
||||
/// 判断是否是设备登录(游客模式)
|
||||
bool isDeviceLogin() {
|
||||
@ -87,7 +92,8 @@ class KRAppRunData {
|
||||
// JWT格式检查: header.payload.signature (三段,每段用.分隔)
|
||||
final parts = token.split('.');
|
||||
if (parts.length != 3) {
|
||||
KRLogUtil.kr_w('❌ Token格式无效:分段数不对 (${parts.length} != 3)', tag: 'AppRunData');
|
||||
KRLogUtil.kr_w('❌ Token格式无效:分段数不对 (${parts.length} != 3)',
|
||||
tag: 'AppRunData');
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -185,8 +191,7 @@ class KRAppRunData {
|
||||
Future<void> kr_saveUserInfo(
|
||||
String token,
|
||||
String account,
|
||||
KRLoginType loginType,
|
||||
String? areaCode) async {
|
||||
) async {
|
||||
KRLogUtil.kr_i('开始保存用户信息', tag: 'AppRunData');
|
||||
|
||||
try {
|
||||
@ -197,15 +202,11 @@ class KRAppRunData {
|
||||
// 更新内存中的数据
|
||||
kr_token = token;
|
||||
kr_account.value = accountText;
|
||||
kr_loginType = loginType;
|
||||
kr_areaCode = areaCode;
|
||||
|
||||
|
||||
final Map<String, dynamic> userInfo = {
|
||||
'token': token,
|
||||
'account': accountText,
|
||||
'loginType': loginType.value,
|
||||
'areaCode': areaCode ?? "",
|
||||
};
|
||||
|
||||
KRLogUtil.kr_i('准备保存用户信息到存储', tag: 'AppRunData');
|
||||
@ -230,11 +231,11 @@ class KRAppRunData {
|
||||
kr_isLogin.value = true;
|
||||
KRLogUtil.kr_i('用户信息-kr_isLogin$kr_isLogin', tag: 'AppRunData');
|
||||
|
||||
// 🔧 非游客模式下,调用用户信息接口获取 refer_code 等信息
|
||||
KRLogUtil.kr_i('🔍 [AppRunData] 检查登录模式: account=$account', tag: 'AppRunData');
|
||||
|
||||
await _fetchUserInfo();
|
||||
KRLogUtil.kr_i('🔍 [AppRunData] 检查登录模式: account=$account',
|
||||
tag: 'AppRunData');
|
||||
|
||||
// 不等待userinfo接口返回
|
||||
_fetchUserInfo();
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('保存用户信息失败: $e', tag: 'AppRunData');
|
||||
// 如果出错,重置登录状态
|
||||
@ -246,88 +247,89 @@ class KRAppRunData {
|
||||
/// 退出登录(其实是设备重新登录)
|
||||
Future<void> kr_loginOut() async {
|
||||
HIDialog.show(
|
||||
message: '当前登录已过期,请重新登录',
|
||||
preventBackDismiss: true,
|
||||
confirmText: '确定',
|
||||
autoClose: false,
|
||||
onConfirm: () async{
|
||||
// 先将登录状态设置为 false,防止重连
|
||||
kr_isLogin.value = false;
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('开始重新进行设备登录', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'AppRunData');
|
||||
// === 停止 VPN 服务 ===
|
||||
try {
|
||||
// 检查 SingBox 服务状态并停止
|
||||
if (KRSingBoxImp.instance.kr_status.value is SingboxStarted) {
|
||||
await KRSingBoxImp.instance.kr_stop();
|
||||
KRLogUtil.kr_i('VPN 服务已停止', tag: 'Logout');
|
||||
message: '当前登录已过期,请重新登录',
|
||||
preventBackDismiss: true,
|
||||
confirmText: '确定',
|
||||
autoClose: false,
|
||||
onConfirm: () async {
|
||||
// 先将登录状态设置为 false,防止重连
|
||||
kr_isLogin.value = false;
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('开始重新进行设备登录', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'AppRunData');
|
||||
// === 停止 VPN 服务 ===
|
||||
try {
|
||||
// 检查 SingBox 服务状态并停止
|
||||
if (KRSingBoxImp.instance.kr_status.value is SingboxStarted) {
|
||||
await KRSingBoxImp.instance.kr_stop();
|
||||
KRLogUtil.kr_i('VPN 服务已停止', tag: 'Logout');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('停止 VPN 服务失败: $e', tag: 'Logout');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('停止 VPN 服务失败: $e', tag: 'Logout');
|
||||
}
|
||||
|
||||
// 断开 Socket 连接
|
||||
await _kr_disconnectSocket();
|
||||
// 断开 Socket 连接
|
||||
await _kr_disconnectSocket();
|
||||
|
||||
// 清理用户信息
|
||||
kr_token = null;
|
||||
kr_account.value = null;
|
||||
kr_userId.value = null;
|
||||
kr_loginType = null;
|
||||
kr_areaCode = null;
|
||||
// 清理用户信息
|
||||
kr_token = null;
|
||||
kr_account.value = null;
|
||||
kr_userId.value = null;
|
||||
kr_areaCode = null;
|
||||
|
||||
// 删除存储的用户信息
|
||||
await KRSecureStorage().kr_deleteData(key: _keyUserInfo);
|
||||
// 删除存储的用户信息
|
||||
await KRSecureStorage().kr_deleteData(key: _keyUserInfo);
|
||||
|
||||
|
||||
// 🔧 修复4: 清理订阅服务数据 - 防止未登录用户访问订阅
|
||||
try {
|
||||
final subscribeService = Get.find<dynamic>(tag: 'KRSubscribeService');
|
||||
if (subscribeService != null && subscribeService is KRSubscribeService) {
|
||||
KRLogUtil.kr_i('🧹 清理订阅服务数据...', tag: 'AppRunData');
|
||||
await subscribeService.kr_logout();
|
||||
KRLogUtil.kr_i('✅ 订阅服务数据已清理', tag: 'AppRunData');
|
||||
// 🔧 修复4: 清理订阅服务数据 - 防止未登录用户访问订阅
|
||||
try {
|
||||
final subscribeService =
|
||||
Get.find<dynamic>(tag: 'KRSubscribeService');
|
||||
if (subscribeService != null &&
|
||||
subscribeService is KRSubscribeService) {
|
||||
KRLogUtil.kr_i('🧹 清理订阅服务数据...', tag: 'AppRunData');
|
||||
await subscribeService.kr_logout();
|
||||
KRLogUtil.kr_i('✅ 订阅服务数据已清理', tag: 'AppRunData');
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略异常:如果订阅服务未初始化或不可用,直接继续
|
||||
KRLogUtil.kr_d('⚠️ 无法获取订阅服务,跳过清理: $e', tag: 'AppRunData');
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略异常:如果订阅服务未初始化或不可用,直接继续
|
||||
KRLogUtil.kr_d('⚠️ 无法获取订阅服务,跳过清理: $e', tag: 'AppRunData');
|
||||
}
|
||||
|
||||
// 5️⃣ 执行设备登录
|
||||
final success = await kr_checkAndPerformDeviceLogin();
|
||||
// 5️⃣ 执行设备登录
|
||||
final success = await kr_checkAndPerformDeviceLogin();
|
||||
|
||||
if (!success) {
|
||||
// 设备登录失败 → 提示用户重试
|
||||
HIDialog.show(
|
||||
message: '设备登录失败,请检查网络或重试',
|
||||
confirmText: '重试',
|
||||
preventBackDismiss: true,
|
||||
onConfirm: () async {
|
||||
await kr_loginOut(); // 递归重试
|
||||
},
|
||||
);
|
||||
return; // 阻止跳首页
|
||||
}
|
||||
if (!success) {
|
||||
// 设备登录失败 → 提示用户重试
|
||||
HIDialog.show(
|
||||
message: '设备登录失败,请检查网络或重试',
|
||||
confirmText: '重试',
|
||||
preventBackDismiss: true,
|
||||
onConfirm: () async {
|
||||
await kr_loginOut(); // 递归重试
|
||||
},
|
||||
);
|
||||
return; // 阻止跳首页
|
||||
}
|
||||
|
||||
// 等待一小段时间,确保登录状态已经更新
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
// 等待一小段时间,确保登录状态已经更新
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
|
||||
// 刷新订阅信息
|
||||
KRLogUtil.kr_i('🔄 开始刷新订阅信息...', tag: 'DeviceManagement');
|
||||
try {
|
||||
await KRSubscribeService().kr_refreshAll();
|
||||
KRLogUtil.kr_i('✅ 订阅信息刷新成功', tag: 'DeviceManagement');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('订阅信息刷新失败: $e', tag: 'DeviceManagement');
|
||||
}
|
||||
// 刷新订阅信息
|
||||
KRLogUtil.kr_i('🔄 开始刷新订阅信息...', tag: 'DeviceManagement');
|
||||
try {
|
||||
await KRSubscribeService().kr_refreshAll();
|
||||
KRLogUtil.kr_i('✅ 订阅信息刷新成功', tag: 'DeviceManagement');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('订阅信息刷新失败: $e', tag: 'DeviceManagement');
|
||||
}
|
||||
|
||||
Get.offAllNamed(Routes.KR_HOME);
|
||||
}
|
||||
);
|
||||
Get.offAllNamed(Routes.KR_HOME);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> kr_loginOut_loading() async{
|
||||
Future<void> kr_loginOut_loading() async {
|
||||
KRCommonUtil.kr_showLoading();
|
||||
// 先将登录状态设置为 false,防止重连
|
||||
kr_isLogin.value = false;
|
||||
@ -353,13 +355,11 @@ class KRAppRunData {
|
||||
kr_token = null;
|
||||
kr_account.value = null;
|
||||
kr_userId.value = null;
|
||||
kr_loginType = null;
|
||||
kr_areaCode = null;
|
||||
|
||||
// 删除存储的用户信息
|
||||
await KRSecureStorage().kr_deleteData(key: _keyUserInfo);
|
||||
|
||||
|
||||
// 🔧 修复4: 清理订阅服务数据 - 防止未登录用户访问订阅
|
||||
try {
|
||||
final subscribeService = Get.find<dynamic>(tag: 'KRSubscribeService');
|
||||
@ -413,35 +413,41 @@ class KRAppRunData {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('🔍 开始执行设备登录...');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
_logStepTiming('设备登录开始');
|
||||
// 初始化设备信息服务
|
||||
await KRDeviceInfoService().initialize();
|
||||
_logStepTiming('初始化设备信息完成');
|
||||
|
||||
KRLogUtil.kr_i('🔐 开始执行设备登录', tag: 'AppRunData');
|
||||
|
||||
// 执行设备登录
|
||||
_logStepTiming('开始设备登录请求');
|
||||
final authApi = KRAuthApi();
|
||||
final result = await authApi.kr_deviceLogin();
|
||||
_logStepTiming('设备登录请求完成');
|
||||
|
||||
return await result.fold(
|
||||
(error) {
|
||||
(error) {
|
||||
print('❌ 设备登录失败: ${error.msg}');
|
||||
KRLogUtil.kr_e('❌ 设备登录失败: ${error.msg}', tag: 'SplashController');
|
||||
_logStepTiming('设备登录完成');
|
||||
return false;
|
||||
},
|
||||
(token) async {
|
||||
(token) async {
|
||||
print('✅ 设备登录成功!Token: $token');
|
||||
KRLogUtil.kr_i('✅ 设备登录成功', tag: 'SplashController');
|
||||
|
||||
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
_logStepTiming('开始保存用户信息');
|
||||
await kr_saveUserInfo(
|
||||
token,
|
||||
'device_$deviceId', // 临时账号
|
||||
KRLoginType.kr_email,
|
||||
null, // 设备登录无需区号
|
||||
);
|
||||
_logStepTiming('保存用户信息完成');
|
||||
|
||||
kr_isLogin.value = true;
|
||||
print('✅ 已标记为登录状态');
|
||||
_logStepTiming('设备登录完成');
|
||||
return true;
|
||||
},
|
||||
);
|
||||
@ -458,7 +464,6 @@ class KRAppRunData {
|
||||
KRLogUtil.kr_i('开始初始化用户信息', tag: 'AppRunData');
|
||||
|
||||
try {
|
||||
|
||||
deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
final String? userInfoString =
|
||||
await KRSecureStorage().kr_readData(key: _keyUserInfo);
|
||||
@ -470,23 +475,21 @@ class KRAppRunData {
|
||||
final Map<String, dynamic> userInfo = jsonDecode(userInfoString);
|
||||
kr_token = userInfo['token'];
|
||||
kr_account.value = userInfo['account'];
|
||||
final loginTypeValue = userInfo['loginType'];
|
||||
kr_loginType = KRLoginType.values.firstWhere(
|
||||
(e) => e.value == loginTypeValue,
|
||||
orElse: () => KRLoginType.kr_telephone,
|
||||
);
|
||||
kr_areaCode = userInfo['areaCode'] ?? "";
|
||||
|
||||
// 从token中解析userId
|
||||
if (kr_token != null && kr_token!.isNotEmpty) {
|
||||
kr_userId.value = _kr_parseUserIdFromToken(kr_token!);
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('解析用户信息成功: token=${kr_token != null}, account=${kr_account.value}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i(
|
||||
'解析用户信息成功: token=${kr_token != null}, account=${kr_account.value}',
|
||||
tag: 'AppRunData');
|
||||
|
||||
// 🔧 修复2:验证token有效性和账号信息完整性
|
||||
// 防止恢复被污染的或过期的数据
|
||||
if (kr_token != null && kr_token!.isNotEmpty && _kr_isValidToken(kr_token!)) {
|
||||
if (kr_token != null &&
|
||||
kr_token!.isNotEmpty &&
|
||||
_kr_isValidToken(kr_token!)) {
|
||||
// token格式验证通过(JWT格式检查)
|
||||
if (kr_account.value != null && kr_account.value!.isNotEmpty) {
|
||||
// 账号信息完整
|
||||
@ -502,7 +505,9 @@ class KRAppRunData {
|
||||
// Token无效或格式错误,清理旧数据
|
||||
KRLogUtil.kr_w('⚠️ Token验证失败或格式错误,清理该条用户数据', tag: 'AppRunData');
|
||||
if (kr_token != null && kr_token!.isNotEmpty) {
|
||||
KRLogUtil.kr_w(' ❌ 可能的原因:Token已过期或被污染,格式: ${kr_token!.substring(0, min(30, kr_token!.length))}...', tag: 'AppRunData');
|
||||
KRLogUtil.kr_w(
|
||||
' ❌ 可能的原因:Token已过期或被污染,格式: ${kr_token!.substring(0, min(30, kr_token!.length))}...',
|
||||
tag: 'AppRunData');
|
||||
}
|
||||
await kr_loginOut();
|
||||
}
|
||||
@ -558,7 +563,8 @@ class KRAppRunData {
|
||||
|
||||
/// 处理连接状态变化
|
||||
void _kr_handleConnectionState(bool isConnected) {
|
||||
KRLogUtil.kr_i('WebSocket 连接状态: ${isConnected ? "已连接" : "已断开"}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('WebSocket 连接状态: ${isConnected ? "已连接" : "已断开"}',
|
||||
tag: 'AppRunData');
|
||||
}
|
||||
|
||||
/// 断开 Socket 连接
|
||||
@ -569,42 +575,57 @@ class KRAppRunData {
|
||||
/// 获取用户详细信息(登录后调用)
|
||||
Future<void> _fetchUserInfo() async {
|
||||
try {
|
||||
KRLogUtil.kr_i('📞 [AppRunData] 开始调用用户信息接口 /v1/public/user/info ...', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('🔐 [AppRunData] 当前 Token: ${kr_token ?? "null"}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('📞 [AppRunData] 开始调用用户信息接口 /v1/public/user/info ...',
|
||||
tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('🔐 [AppRunData] 当前 Token: ${kr_token ?? "null"}',
|
||||
tag: 'AppRunData');
|
||||
|
||||
final result = await KRUserApi.kr_getUserInfo();
|
||||
|
||||
result.fold(
|
||||
(error) {
|
||||
KRLogUtil.kr_e('❌ [AppRunData] 获取用户信息失败: ${error.msg} (code: ${error.code})', tag: 'AppRunData');
|
||||
(error) {
|
||||
KRLogUtil.kr_e(
|
||||
'❌ [AppRunData] 获取用户信息失败: ${error.msg} (code: ${error.code})',
|
||||
tag: 'AppRunData');
|
||||
},
|
||||
(userInfo) {
|
||||
final authType = userInfo.authMethods.isNotEmpty
|
||||
? userInfo.authMethods[0].authType
|
||||
: null;
|
||||
final authIdentifier = userInfo.authMethods.isNotEmpty
|
||||
? userInfo.authMethods[0].authIdentifier
|
||||
: null;
|
||||
(userInfo) {
|
||||
final authType = userInfo.authMethods.isNotEmpty
|
||||
? userInfo.authMethods[0].authType
|
||||
: null;
|
||||
final authIdentifier = userInfo.authMethods.isNotEmpty
|
||||
? userInfo.authMethods[0].authIdentifier
|
||||
: null;
|
||||
KRLogUtil.kr_i('✅ [AppRunData] 获取用户信息成功', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('📋 [AppRunData] refer_code: "${userInfo.referCode}"', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('💰 [AppRunData] balance: ${userInfo.balance}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('💵 [AppRunData] commission: ${userInfo.commission}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('📧 [AppRunData] 登录类型: ${authType}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('📧 [AppRunData] email: ${authIdentifier}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('🆔 [AppRunData] id: ${userInfo.id}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('📋 [AppRunData] refer_code: "${userInfo.referCode}"',
|
||||
tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('💰 [AppRunData] balance: ${userInfo.balance}',
|
||||
tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('💵 [AppRunData] commission: ${userInfo.commission}',
|
||||
tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('📧 [AppRunData] 登录类型: ${authType}',
|
||||
tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('📧 [AppRunData] email: ${authIdentifier}',
|
||||
tag: 'AppRunData');
|
||||
KRLogUtil.kr_i('🆔 [AppRunData] id: ${userInfo.id}',
|
||||
tag: 'AppRunData');
|
||||
|
||||
// 保存到全局状态
|
||||
kr_referCode.value = userInfo.referCode;
|
||||
kr_refererId.value = userInfo.refererId;
|
||||
kr_account.value = authType == 'device' ? '9000${userInfo.id}' : authIdentifier;
|
||||
kr_account.value =
|
||||
authType == 'device' ? '9000${userInfo.id}' : authIdentifier;
|
||||
kr_balance.value = userInfo.balance;
|
||||
kr_commission.value = userInfo.commission;
|
||||
|
||||
KRLogUtil.kr_i('💾 [AppRunData] 用户信息已保存到全局状态:', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i(' - kr_referCode: "${kr_referCode.value}"', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i(' - kr_refererId: ${kr_refererId.value}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i(' - kr_balance: ${kr_balance.value}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i(' - kr_commission: ${kr_commission.value}', tag: 'AppRunData');
|
||||
KRLogUtil.kr_i(' - kr_referCode: "${kr_referCode.value}"',
|
||||
tag: 'AppRunData');
|
||||
KRLogUtil.kr_i(' - kr_refererId: ${kr_refererId.value}',
|
||||
tag: 'AppRunData');
|
||||
KRLogUtil.kr_i(' - kr_balance: ${kr_balance.value}',
|
||||
tag: 'AppRunData');
|
||||
KRLogUtil.kr_i(' - kr_commission: ${kr_commission.value}',
|
||||
tag: 'AppRunData');
|
||||
},
|
||||
);
|
||||
} catch (e, stackTrace) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import '../../utils/kr_log_util.dart';
|
||||
|
||||
@ -41,7 +42,6 @@ class KRConfigData {
|
||||
/// 邀请链接
|
||||
final String kr_invitation_link;
|
||||
|
||||
final String kr_website_id;
|
||||
|
||||
KRConfigData({
|
||||
this.kr_config = '',
|
||||
@ -56,16 +56,21 @@ class KRConfigData {
|
||||
this.kr_official_telegram = '',
|
||||
this.kr_official_telephone = '',
|
||||
this.kr_invitation_link = '',
|
||||
this.kr_website_id = '',
|
||||
}) : this.kr_domains = kr_domains ?? [],
|
||||
this.kr_update_application =
|
||||
kr_update_application ?? KRUpdateApplication();
|
||||
|
||||
factory KRConfigData.fromJson(Map<String, dynamic> json) {
|
||||
KRLogUtil.kr_e('配置数据: $json', tag: 'KRConfigData');
|
||||
KRLogUtil.kr_i('配置数据: $json', tag: 'KRConfigData');
|
||||
String _krConfigString = '';
|
||||
try {
|
||||
_krConfigString = jsonEncode(json);
|
||||
} catch (_) {
|
||||
_krConfigString = '';
|
||||
}
|
||||
return KRConfigData(
|
||||
kr_config: _krConfigString,
|
||||
kr_invitation_link: json['invitation_link'] ?? '',
|
||||
kr_config: json['kr_config'] ?? '',
|
||||
kr_encryption_key: json['encryption_key'] ?? '',
|
||||
kr_encryption_method: json['encryption_method'] ?? '',
|
||||
kr_domains: List<String>.from(json['domains'] ?? []),
|
||||
@ -77,7 +82,6 @@ class KRConfigData {
|
||||
kr_official_website: json['official_website'] ?? '',
|
||||
kr_official_telegram: json['official_telegram'] ?? '',
|
||||
kr_official_telephone: json['official_telephone'] ?? '',
|
||||
kr_website_id: json['kr_website_id'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,17 +81,6 @@ class KRSiteInfo {
|
||||
String crispId = '0';
|
||||
String deviceLimit = '0';
|
||||
|
||||
// 尝试解析 custom_data 中的 kr_website_id
|
||||
try {
|
||||
final customDataStr = json['custom_data'] ?? '';
|
||||
if (customDataStr.isNotEmpty) {
|
||||
final customDataJson = jsonDecode(customDataStr) as Map<String, dynamic>;
|
||||
crispId = customDataJson['kr_website_id'] ?? '0';
|
||||
}
|
||||
} catch (e) {
|
||||
// 解析失败时使用默认值
|
||||
}
|
||||
|
||||
// 尝试解析 custom_data 中的 deviceLimit
|
||||
try {
|
||||
final customDataStr = json['custom_data'] ?? '';
|
||||
|
||||
@ -12,8 +12,10 @@ import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:convert';
|
||||
import 'package:kaer_with_panels/utils/snackbar_util.dart';
|
||||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_config.dart';
|
||||
|
||||
class HIUserInfoController extends GetxController {
|
||||
/// 订阅服务
|
||||
@ -32,6 +34,7 @@ class HIUserInfoController extends GetxController {
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_initDeviceId();
|
||||
_initDeviceLimit();
|
||||
loadDeviceList();
|
||||
}
|
||||
|
||||
@ -55,13 +58,13 @@ class HIUserInfoController extends GetxController {
|
||||
final result = await KRUserApi().kr_getUserDevices();
|
||||
|
||||
result.fold(
|
||||
(error) {
|
||||
(error) {
|
||||
KRLogUtil.kr_e('加载设备列表失败: ${error.msg}', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, error.msg);
|
||||
},
|
||||
(deviceList) {
|
||||
KRLogUtil.kr_i('获取到 ${deviceList.length} 个设备', tag: 'DeviceManagement');
|
||||
|
||||
(deviceList) {
|
||||
KRLogUtil.kr_i('获取到 ${deviceList.length} 个设备',
|
||||
tag: 'DeviceManagement');
|
||||
|
||||
// 转换设备数据格式
|
||||
devices.value = deviceList.map((device) {
|
||||
@ -89,7 +92,8 @@ class HIUserInfoController extends GetxController {
|
||||
} catch (e, stackTrace) {
|
||||
KRLogUtil.kr_e('加载设备列表异常: $e', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_e('堆栈跟踪: $stackTrace', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.loadDeviceListFailed);
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error,
|
||||
AppTranslations.kr_deviceManagement.loadDeviceListFailed);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
@ -99,24 +103,26 @@ class HIUserInfoController extends GetxController {
|
||||
Future<bool> deleteDevice(String id) async {
|
||||
try {
|
||||
final device = devices.firstWhere(
|
||||
(d) => d['id'] == id,
|
||||
(d) => d['id'] == id,
|
||||
orElse: () => {},
|
||||
);
|
||||
if (device.isEmpty) return false;
|
||||
|
||||
final isCurrent = device['is_current'] ?? false;
|
||||
|
||||
KRLogUtil.kr_i('开始解绑设备 - id: $id, isCurrent: $isCurrent', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_i('开始解绑设备 - id: $id, isCurrent: $isCurrent',
|
||||
tag: 'DeviceManagement');
|
||||
|
||||
final result = await KRUserApi().kr_unbindUserDevice(id);
|
||||
|
||||
bool success = false;
|
||||
result.fold(
|
||||
(error) {
|
||||
(error) {
|
||||
KRLogUtil.kr_e('删除设备失败: ${error.msg}', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.deleteFailed(error.msg));
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error,
|
||||
AppTranslations.kr_deviceManagement.deleteFailed(error.msg));
|
||||
},
|
||||
(_) async {
|
||||
(_) async {
|
||||
KRLogUtil.kr_i('设备删除成功', tag: 'DeviceManagement');
|
||||
success = true;
|
||||
|
||||
@ -127,7 +133,8 @@ class HIUserInfoController extends GetxController {
|
||||
await _reloginWithDevice();
|
||||
} else {
|
||||
devices.removeWhere((device) => device['id'] == id);
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.success, AppTranslations.kr_deviceManagement.deleteSuccess);
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.success,
|
||||
AppTranslations.kr_deviceManagement.deleteSuccess);
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -136,18 +143,20 @@ class HIUserInfoController extends GetxController {
|
||||
} catch (e, stackTrace) {
|
||||
KRLogUtil.kr_e('删除设备异常: $e', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_e('堆栈跟踪: $stackTrace', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.deleteFailed(e.toString()));
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error,
|
||||
AppTranslations.kr_deviceManagement.deleteFailed(e.toString()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 重新使用设备登录
|
||||
Future<void> _reloginWithDevice() async {
|
||||
try {
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_i('开始重新进行设备登录', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'DeviceManagement');
|
||||
|
||||
// 先清除当前的用户信息(但不调用 kr_loginOut,避免显示登录界面)
|
||||
final appRunData = KRAppRunData.getInstance();
|
||||
@ -166,26 +175,27 @@ class HIUserInfoController extends GetxController {
|
||||
final result = await authApi.kr_deviceLogin();
|
||||
|
||||
result.fold(
|
||||
(error) {
|
||||
(error) {
|
||||
// 设备登录失败
|
||||
KRLogUtil.kr_e('设备登录失败: ${error.msg}', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.reloginFailed(error.msg));
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error,
|
||||
AppTranslations.kr_deviceManagement.reloginFailed(error.msg));
|
||||
|
||||
// 执行完整退出登录,显示登录界面
|
||||
appRunData.kr_loginOut();
|
||||
},
|
||||
(token) async {
|
||||
(token) async {
|
||||
// 设备登录成功
|
||||
KRLogUtil.kr_i('✅ 设备登录成功!', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_i('🎫 Token: ${token.substring(0, min(20, token.length))}...', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_i(
|
||||
'🎫 Token: ${token.substring(0, min(20, token.length))}...',
|
||||
tag: 'DeviceManagement');
|
||||
|
||||
// 保存新的用户信息
|
||||
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
await appRunData.kr_saveUserInfo(
|
||||
token,
|
||||
'device_$deviceId',
|
||||
KRLoginType.kr_email,
|
||||
null,
|
||||
);
|
||||
|
||||
KRLogUtil.kr_i('✅ 设备重新登录成功,已更新用户信息', tag: 'DeviceManagement');
|
||||
@ -209,35 +219,53 @@ class HIUserInfoController extends GetxController {
|
||||
} catch (e, stackTrace) {
|
||||
KRLogUtil.kr_e('设备重新登录异常: $e', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_e('堆栈跟踪: $stackTrace', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.reloginFailedGeneric);
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error,
|
||||
AppTranslations.kr_deviceManagement.reloginFailedGeneric);
|
||||
|
||||
// 发生异常,执行完整退出登录
|
||||
await KRAppRunData.getInstance().kr_loginOut();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initDeviceLimit() async {
|
||||
await KRSiteConfigService().fetchSiteConfig();
|
||||
}
|
||||
|
||||
/// 获取设备类型和图标
|
||||
Map<String, dynamic> getDeviceTypeInfo(String userAgent) {
|
||||
String deviceType = AppTranslations.kr_deviceManagement.deviceTypeUnknown;
|
||||
String iconName = 'devices';
|
||||
|
||||
if (userAgent.contains('Android') || userAgent.toLowerCase().contains('android')) {
|
||||
deviceType = 'Android' ;// AppTranslations.kr_deviceManagement.deviceTypeAndroid;
|
||||
if (userAgent.contains('Android') ||
|
||||
userAgent.toLowerCase().contains('android')) {
|
||||
deviceType =
|
||||
'Android'; // AppTranslations.kr_deviceManagement.deviceTypeAndroid;
|
||||
iconName = 'phone_android';
|
||||
} else if (userAgent.contains('iOS') || userAgent.contains('iPhone') || userAgent.toLowerCase().contains('ios')) {
|
||||
deviceType = 'iPhone'; // AppTranslations.kr_deviceManagement.deviceTypeIos;
|
||||
} else if (userAgent.contains('iOS') ||
|
||||
userAgent.contains('iPhone') ||
|
||||
userAgent.toLowerCase().contains('ios')) {
|
||||
deviceType =
|
||||
'iPhone'; // AppTranslations.kr_deviceManagement.deviceTypeIos;
|
||||
iconName = 'phone_iphone';
|
||||
} else if (userAgent.contains('iPad')) {
|
||||
deviceType = 'iPad';// AppTranslations.kr_deviceManagement.deviceTypeIpad;
|
||||
deviceType =
|
||||
'iPad'; // AppTranslations.kr_deviceManagement.deviceTypeIpad;
|
||||
iconName = 'tablet';
|
||||
} else if (userAgent.contains('macOS') || userAgent.contains('Mac') || userAgent.toLowerCase().contains('mac')) {
|
||||
deviceType = 'Mac';// AppTranslations.kr_deviceManagement.deviceTypeMacos;
|
||||
} else if (userAgent.contains('macOS') ||
|
||||
userAgent.contains('Mac') ||
|
||||
userAgent.toLowerCase().contains('mac')) {
|
||||
deviceType =
|
||||
'Mac'; // AppTranslations.kr_deviceManagement.deviceTypeMacos;
|
||||
iconName = 'desktop_mac';
|
||||
} else if (userAgent.contains('Windows') || userAgent.toLowerCase().contains('windows')) {
|
||||
deviceType = 'Windows'; //AppTranslations.kr_deviceManagement.deviceTypeWindows;
|
||||
} else if (userAgent.contains('Windows') ||
|
||||
userAgent.toLowerCase().contains('windows')) {
|
||||
deviceType =
|
||||
'Windows'; //AppTranslations.kr_deviceManagement.deviceTypeWindows;
|
||||
iconName = 'computer';
|
||||
} else if (userAgent.contains('Linux') || userAgent.toLowerCase().contains('linux')) {
|
||||
deviceType = 'Linux';//AppTranslations.kr_deviceManagement.deviceTypeLinux;
|
||||
} else if (userAgent.contains('Linux') ||
|
||||
userAgent.toLowerCase().contains('linux')) {
|
||||
deviceType =
|
||||
'Linux'; //AppTranslations.kr_deviceManagement.deviceTypeLinux;
|
||||
iconName = 'computer';
|
||||
}
|
||||
|
||||
@ -247,8 +275,6 @@ class HIUserInfoController extends GetxController {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
|
||||
@ -55,7 +55,7 @@ class KRCrispController extends GetxController {
|
||||
|
||||
// 创建原生 SDK 配置
|
||||
_nativeConfig = native_crisp.CrispConfig(
|
||||
websiteID: AppConfig.getInstance().kr_website_id,
|
||||
websiteID: '47fcc1ac-9674-4ab1-9e3c-6b5666f59a38',
|
||||
user: native_crisp.User(
|
||||
email: null,
|
||||
nickName: identifier,
|
||||
|
||||
@ -33,9 +33,6 @@ import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_init_log_collector.dart'; // 🔧 新增:导入日志收集器
|
||||
import 'package:kaer_with_panels/app/utils/kr_latency_tester.dart'; // 🔧 新增:导入真实延迟测试工具
|
||||
import 'package:kaer_with_panels/app/utils/account_guard.dart';
|
||||
import 'package:kaer_with_panels/app/services/iap/iap_service.dart';
|
||||
|
||||
|
||||
class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
// 🔧 新增:日志收集器实例
|
||||
@ -44,7 +41,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
/// 订阅服务
|
||||
final KRSubscribeService kr_subscribeService = KRSubscribeService();
|
||||
|
||||
|
||||
/// 当前视图状态,登录状态
|
||||
final Rx<KRHomeViewsStatus> kr_currentViewStatus =
|
||||
KRHomeViewsStatus.kr_notLoggedIn.obs;
|
||||
@ -52,25 +48,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
/// 当前列表视图状态
|
||||
final kr_currentListStatus = KRHomeViewsListStatus.kr_loading.obs;
|
||||
|
||||
/// 底部面板高度常量
|
||||
static const double kr_baseHeight = 120.0; // 基础高度(连接选项)
|
||||
static const double kr_subscriptionCardHeight = 200.0; // 订阅卡片高度
|
||||
static const double kr_connectionInfoHeight = 126.0; // 连接信息卡片高度(修复后)
|
||||
static const double kr_trialCardHeight = 120.0; // 试用卡片高度
|
||||
static const double kr_lastDayCardHeight = 120.0; // 最后一天卡片高度
|
||||
static const double kr_nodeListHeight = 400.0; // 节点列表高度
|
||||
static const double kr_errorHeight = 100.0; // 错误状态高度
|
||||
static const double kr_loadingHeight = 100.0; // 加载状态高度
|
||||
|
||||
/// 间距常量
|
||||
static const double kr_marginTop = 12.0; // 顶部间距
|
||||
static const double kr_marginBottom = 12.0; // 底部间距
|
||||
static const double kr_marginHorizontal = 16.0; // 水平间距
|
||||
static const double kr_marginVertical = 12.0; // 垂直间距
|
||||
|
||||
/// 底部面板高度
|
||||
final kr_bottomPanelHeight = 200.0.obs;
|
||||
|
||||
/// 连接字符串
|
||||
final kr_connectText = AppTranslations.kr_home.disconnected.obs;
|
||||
|
||||
@ -223,6 +200,23 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
return;
|
||||
}
|
||||
|
||||
final current = kr_subscribeService.kr_currentSubscribe.value;
|
||||
if (!KRAppRunData.getInstance().kr_isLogin.value || current == null) {
|
||||
KRLogUtil.kr_w('未登录或无可用订阅,阻止自动连接', tag: 'QuickConnect');
|
||||
return;
|
||||
}
|
||||
bool isExpired = false;
|
||||
if (current.expireTime.isNotEmpty) {
|
||||
try {
|
||||
isExpired =
|
||||
DateTime.parse(current.expireTime).isBefore(DateTime.now());
|
||||
} catch (_) {}
|
||||
}
|
||||
if (isExpired) {
|
||||
KRLogUtil.kr_w('订阅不可用(过期或流量耗尽),阻止自动连接', tag: 'QuickConnect');
|
||||
return;
|
||||
}
|
||||
|
||||
await _kr_prepareCountrySelectionBeforeStart();
|
||||
final selectedAfter =
|
||||
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
|
||||
@ -260,45 +254,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查是否满足自动连接条件(不执行连接)
|
||||
bool canAutoConnect() {
|
||||
return isQuickConnectEnabled.value &&
|
||||
KRAppRunData.getInstance().kr_isLogin.value &&
|
||||
!kr_isConnected.value;
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
// 🔧 紧急诊断:直接写文件验证 onInit 是否被调用
|
||||
try {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final debugFile = File('${dir.path}/HOME_CONTROLLER_DEBUG.txt');
|
||||
await debugFile.writeAsString(
|
||||
'=' * 60 +
|
||||
'\n'
|
||||
'HomeController.onInit 被调用!\n'
|
||||
'时间: ${DateTime.now()}\n'
|
||||
'实例 HashCode: ${hashCode}\n'
|
||||
'线程: ${Platform.isAndroid ? "Android" : "Unknown"}\n'
|
||||
'=' *
|
||||
60 +
|
||||
'\n',
|
||||
mode: FileMode.append,
|
||||
);
|
||||
} catch (e) {
|
||||
// 忽略错误,确保不影响主流程
|
||||
}
|
||||
|
||||
// 🔧 新增:记录 HomeController 初始化开始
|
||||
_initLog.logPhaseStart('HomeController 初始化');
|
||||
_initLog.log('KRHomeController.onInit 被调用', tag: 'Home');
|
||||
|
||||
// 🔧 Android 15 紧急修复:立即设置默认高度,确保底部面板可见
|
||||
// kr_updateBottomPanelHeight();
|
||||
|
||||
/// 底部面板高度处理
|
||||
// _kr_initBottomPanelHeight();
|
||||
// 加载闪连状态
|
||||
_loadQuickConnectStatus();
|
||||
// 加载已选择的国家
|
||||
@ -320,7 +278,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 延迟同步连接状态,确保状态正确
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
kr_forceSyncConnectionStatus(true);
|
||||
kr_forceSyncConnectionStatus();
|
||||
});
|
||||
|
||||
// 🔧 Android 15 新增:5秒后再次强制更新高度,兜底保护
|
||||
@ -372,14 +330,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
/// 底部面板高度处理
|
||||
void _kr_initBottomPanelHeight() {
|
||||
ever(kr_currentListStatus, (status) {
|
||||
kr_updateBottomPanelHeight();
|
||||
KRLogUtil.kr_i(status.toString(), tag: "_kr_initBottomPanelHeight");
|
||||
});
|
||||
}
|
||||
|
||||
void _kr_initLoginStatus() {
|
||||
_initLog.log('开始初始化登录状态处理', tag: 'Home');
|
||||
KRLogUtil.kr_i('初始化登录状态', tag: 'HomeController');
|
||||
@ -392,10 +342,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
KRLogUtil.kr_w('⏱️ 订阅服务初始化超时(8秒),强制设置为无数据状态', tag: 'HomeController');
|
||||
// 🔧 Android 15 优化:超时设置为 none 而非 error,避免底部面板显示错误界面
|
||||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||||
// 强制更新底部面板高度,确保显示正常
|
||||
kr_updateBottomPanelHeight();
|
||||
_initLog.log('已强制切换到默认视图, 底部面板高度: ${kr_bottomPanelHeight.value}',
|
||||
tag: 'Subscribe');
|
||||
|
||||
KRLogUtil.kr_i('✅ 已强制切换到默认视图', tag: 'HomeController');
|
||||
}
|
||||
});
|
||||
@ -458,8 +405,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
|
||||
_initLog.logSuccess('用户已登录,准备加载订阅数据', tag: 'Home');
|
||||
KRLogUtil.kr_i('设置为已登录状态', tag: 'HomeController');
|
||||
|
||||
// 订阅服务已在 splash 页面初始化,此处无需重复初始化
|
||||
_kr_ensureSubscribeServiceInitialized();
|
||||
KRLogUtil.kr_i('订阅服务已在启动页初始化,跳过重复初始化', tag: 'HomeController');
|
||||
} else {
|
||||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_notLoggedIn;
|
||||
@ -517,6 +463,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
throw TimeoutException('订阅服务初始化超时');
|
||||
},
|
||||
).then((_) async {
|
||||
_checkQuickConnectAutoStart();
|
||||
final elapsed = DateTime.now().difference(startTime).inMilliseconds;
|
||||
_initLog.logSuccess('订阅服务初始化完成, 耗时: ${elapsed}ms', tag: 'Subscribe');
|
||||
_initLog.log('最终列表状态: ${kr_currentListStatus.value}',
|
||||
@ -594,7 +541,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
|
||||
KRLogUtil.kr_i('登录状态变化:设置为已登录', tag: 'HomeController');
|
||||
|
||||
|
||||
// 订阅服务已在 splash 页面初始化,此处无需重复初始化
|
||||
KRLogUtil.kr_i('订阅服务已在启动页初始化,跳过重复初始化', tag: 'HomeController');
|
||||
} else {
|
||||
@ -681,7 +627,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
} else {
|
||||
// kr_updateBottomPanelHeight();
|
||||
}
|
||||
_checkQuickConnectAutoStart();
|
||||
_kr_testLatencyWithoutVpn();
|
||||
break;
|
||||
case KRSubscribeServiceStatus.kr_none:
|
||||
@ -1872,18 +1817,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 200), () async {
|
||||
ensureAccountExists().then((ok) {
|
||||
print('ok');
|
||||
});
|
||||
});
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 500), () async {
|
||||
if (Get.isRegistered<KRIAPService>()) {
|
||||
KRIAPService.instance.setup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1958,100 +1891,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
});
|
||||
}
|
||||
|
||||
// 更新底部面板高度
|
||||
void kr_updateBottomPanelHeight() {
|
||||
// 🔧 Android 15 优化:加载状态时也允许更新高度,显示加载指示器
|
||||
if (kr_subscribeService.kr_currentStatus ==
|
||||
KRHomeViewsListStatus.kr_loading &&
|
||||
kr_currentListStatus.value != KRHomeViewsListStatus.kr_loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('更新底部面板高度', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('当前视图状态: ${kr_currentViewStatus.value}',
|
||||
tag: 'HomeController');
|
||||
|
||||
KRLogUtil.kr_i('当前列表状态: ${kr_currentListStatus.value}',
|
||||
tag: 'HomeController');
|
||||
KRLogUtil.kr_i('是否试用: ${kr_subscribeService.kr_isTrial.value}',
|
||||
tag: 'HomeController');
|
||||
KRLogUtil.kr_i(
|
||||
'是否最后一天: ${kr_subscribeService.kr_isLastDayOfSubscription.value}',
|
||||
tag: 'HomeController');
|
||||
|
||||
double targetHeight = 0.0;
|
||||
|
||||
if (kr_currentViewStatus.value == KRHomeViewsStatus.kr_notLoggedIn) {
|
||||
// 未登录状态下,高度由内容撑开
|
||||
targetHeight = kr_subscriptionCardHeight +
|
||||
kr_baseHeight +
|
||||
kr_marginTop +
|
||||
kr_marginBottom +
|
||||
kr_marginVertical * 2;
|
||||
KRLogUtil.kr_i('未登录状态,目标高度: $targetHeight', tag: 'HomeController');
|
||||
} else if (kr_currentListStatus.value ==
|
||||
KRHomeViewsListStatus.kr_serverList ||
|
||||
kr_currentListStatus.value ==
|
||||
KRHomeViewsListStatus.kr_countrySubscribeList ||
|
||||
kr_currentListStatus.value ==
|
||||
KRHomeViewsListStatus.kr_serverSubscribeList ||
|
||||
kr_currentListStatus.value == KRHomeViewsListStatus.kr_subscribeList) {
|
||||
targetHeight = kr_nodeListHeight + kr_marginVertical * 2;
|
||||
KRLogUtil.kr_i('节点列表状态,目标高度: $targetHeight', tag: 'HomeController');
|
||||
}
|
||||
// 🔧 Android 15 新增:处理加载和错误状态
|
||||
else if (kr_currentListStatus.value == KRHomeViewsListStatus.kr_loading ||
|
||||
kr_currentListStatus.value == KRHomeViewsListStatus.kr_error) {
|
||||
// 加载或错误状态下显示最小高度
|
||||
targetHeight = kr_loadingHeight + kr_marginTop + kr_marginBottom;
|
||||
KRLogUtil.kr_i('加载/错误状态,目标高度: $targetHeight', tag: 'HomeController');
|
||||
} else {
|
||||
// 已登录状态下的默认高度计算
|
||||
targetHeight = kr_baseHeight + kr_marginTop + kr_marginBottom;
|
||||
KRLogUtil.kr_i('基础高度: $targetHeight', tag: 'HomeController');
|
||||
|
||||
// 🔧 关键修复:增加防御性检查,确保订阅服务访问异常时高度计算仍正常
|
||||
try {
|
||||
if (kr_subscribeService.kr_currentSubscribe.value != null) {
|
||||
targetHeight += kr_connectionInfoHeight + kr_marginTop;
|
||||
KRLogUtil.kr_i('添加连接信息卡片高度: $targetHeight', tag: 'HomeController');
|
||||
} else {
|
||||
targetHeight += kr_subscriptionCardHeight + kr_marginTop;
|
||||
KRLogUtil.kr_i('添加订阅卡片高度: $targetHeight', tag: 'HomeController');
|
||||
}
|
||||
|
||||
// 如果有试用状态,添加试用卡片高度
|
||||
if (kr_subscribeService.kr_isTrial.value) {
|
||||
targetHeight += kr_trialCardHeight + kr_marginTop;
|
||||
KRLogUtil.kr_i('添加试用卡片高度: $targetHeight', tag: 'HomeController');
|
||||
}
|
||||
// 如果是最后一天,添加最后一天卡片高度
|
||||
else if (kr_subscribeService.kr_isLastDayOfSubscription.value) {
|
||||
targetHeight += kr_lastDayCardHeight + kr_marginTop;
|
||||
KRLogUtil.kr_i('添加最后一天卡片高度: $targetHeight', tag: 'HomeController');
|
||||
}
|
||||
} catch (e) {
|
||||
// 🔧 修复:订阅服务访问异常时,使用默认订阅卡片高度
|
||||
KRLogUtil.kr_e('访问订阅服务数据异常,使用默认高度: $e', tag: 'HomeController');
|
||||
targetHeight += kr_subscriptionCardHeight + kr_marginTop;
|
||||
KRLogUtil.kr_i('使用默认订阅卡片高度: $targetHeight', tag: 'HomeController');
|
||||
}
|
||||
}
|
||||
|
||||
// 🔧 Android 15 优化:确保最小高度,避免出现 0 高度
|
||||
if (targetHeight < 100) {
|
||||
KRLogUtil.kr_w('计算的高度过小($targetHeight),设置为最小高度', tag: 'HomeController');
|
||||
targetHeight = kr_loadingHeight;
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('最终目标高度: $targetHeight', tag: 'HomeController');
|
||||
kr_bottomPanelHeight.value = targetHeight;
|
||||
}
|
||||
|
||||
// 移动到选中节点
|
||||
void kr_moveToSelectedNode() {
|
||||
|
||||
}
|
||||
void kr_moveToSelectedNode() {}
|
||||
|
||||
// 简化移动地图方法
|
||||
void kr_moveToLocation() {
|
||||
@ -2436,7 +2277,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
/// 强制同步连接状态
|
||||
void kr_forceSyncConnectionStatus([bool? isQuickConnect]) {
|
||||
void kr_forceSyncConnectionStatus() {
|
||||
try {
|
||||
KRLogUtil.kr_i('🔄 强制同步连接状态...', tag: 'HomeController');
|
||||
|
||||
@ -2479,10 +2320,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 强制更新UI
|
||||
update();
|
||||
print('isQuickConnect$isQuickConnect');
|
||||
if (isQuickConnect == true) {
|
||||
_checkQuickConnectAutoStart();
|
||||
}
|
||||
KRLogUtil.kr_i('✅ 连接状态同步完成', tag: 'HomeController');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 强制同步连接状态失败: $e', tag: 'HomeController');
|
||||
|
||||
@ -11,7 +11,6 @@ import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||||
import 'package:kaer_with_panels/app/services/global_overlay_service.dart';
|
||||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
|
||||
import 'package:kaer_with_panels/app/utils/account_guard.dart';
|
||||
|
||||
DateTime? _hiConnectBtnNextAllowedAt;
|
||||
const Duration _hiConnectBtnDebounce = Duration(milliseconds: 800);
|
||||
@ -93,8 +92,6 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
final ok = await ensureAccountExists();
|
||||
if (!ok) return;
|
||||
final _now = DateTime.now();
|
||||
if (_hiConnectBtnNextAllowedAt != null &&
|
||||
_now.isBefore(_hiConnectBtnNextAllowedAt!)) {
|
||||
|
||||
@ -8,7 +8,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
import '../../../routes/app_pages.dart';
|
||||
import 'package:kaer_with_panels/app/services/global_overlay_service.dart';
|
||||
import 'package:kaer_with_panels/app/utils/account_guard.dart';
|
||||
|
||||
// ======================= 1. Circular Clipper (保持不变) =======================
|
||||
class CircularClipper extends CustomClipper<Path> {
|
||||
@ -175,8 +174,6 @@ class _HISubscriptionCornerButtonState extends State<HISubscriptionCornerButton>
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final ok = await ensureAccountExists();
|
||||
if (!ok) return;
|
||||
_startCircularTransition(context);
|
||||
},
|
||||
child: Container(
|
||||
|
||||
@ -17,7 +17,6 @@ import 'package:kaer_with_panels/app/services/global_overlay_service.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart';
|
||||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||
import 'package:kaer_with_panels/app/utils/account_guard.dart';
|
||||
|
||||
class KRHomeView extends StatefulWidget implements HasSwipeConfig {
|
||||
const KRHomeView({super.key});
|
||||
@ -26,16 +25,10 @@ class KRHomeView extends StatefulWidget implements HasSwipeConfig {
|
||||
enableLeft: true,
|
||||
enableRight: true,
|
||||
onLeft: () {
|
||||
ensureAccountExists().then((ok) {
|
||||
if (!ok) return;
|
||||
Get.toNamed(Routes.HI_MENU);
|
||||
});
|
||||
Get.toNamed(Routes.HI_MENU);
|
||||
},
|
||||
onRight: () {
|
||||
ensureAccountExists().then((ok) {
|
||||
if (!ok) return;
|
||||
GlobalOverlayService.instance.triggerSubscriptionAnimation();
|
||||
});
|
||||
GlobalOverlayService.instance.triggerSubscriptionAnimation();
|
||||
},
|
||||
);
|
||||
|
||||
@ -109,43 +102,37 @@ class _KRHomeViewState extends State<KRHomeView> {
|
||||
);
|
||||
|
||||
// 3. 开始条件判断
|
||||
if (currentSubscribe == null) {
|
||||
// --- 情况1: 没有任何订阅信息 ---
|
||||
content = Text(
|
||||
'尚未购买套餐',
|
||||
style: highlightStyle,
|
||||
);
|
||||
final listStatus =
|
||||
controller.kr_currentListStatus.value;
|
||||
if (listStatus == KRHomeViewsListStatus.kr_loading) {
|
||||
content = Text('加载订阅中', style: normalStyle);
|
||||
} else {
|
||||
// --- 情况2: 有订阅信息,需要判断是否过期 ---
|
||||
final now = DateTime.now();
|
||||
DateTime? expireDateTime;
|
||||
try {
|
||||
expireDateTime =
|
||||
DateTime.parse(currentSubscribe.expireTime);
|
||||
} catch (e) {
|
||||
// 日期格式解析失败
|
||||
}
|
||||
|
||||
if (expireDateTime != null &&
|
||||
expireDateTime.isBefore(now)) {
|
||||
// --- 情况2.1: 订阅已过期 ---
|
||||
final formattedExpireDate =
|
||||
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
|
||||
// 使用换行符 \n 合并为单个 Text 组件
|
||||
content = Text(
|
||||
'您的套餐已于 $formattedExpireDate 到期\n请前往购买新套餐',
|
||||
style: highlightStyle,
|
||||
);
|
||||
if (currentSubscribe == null) {
|
||||
content = Text('尚未购买套餐', style: highlightStyle);
|
||||
} else {
|
||||
// --- 情况2.2: 订阅有效 ---
|
||||
final formattedExpireTime = expireDateTime != null
|
||||
final now = DateTime.now();
|
||||
DateTime? expireDateTime;
|
||||
try {
|
||||
expireDateTime =
|
||||
DateTime.parse(currentSubscribe.expireTime);
|
||||
} catch (e) {}
|
||||
if (expireDateTime != null &&
|
||||
expireDateTime.isBefore(now)) {
|
||||
final formattedExpireDate =
|
||||
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
|
||||
content = Text(
|
||||
'您的套餐已于 $formattedExpireDate 到期\n请前往购买新套餐',
|
||||
style: highlightStyle);
|
||||
} else {
|
||||
final formattedExpireTime = expireDateTime !=
|
||||
null
|
||||
? '${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')} ${expireDateTime.hour.toString().padLeft(2, '0')}:${expireDateTime.minute.toString().padLeft(2, '0')}'
|
||||
: '未知';
|
||||
// 使用换行符 \n 合并为单个 Text 组件
|
||||
content = Text(
|
||||
'套餐到期时间:$formattedExpireTime\n${controller.kr_isConnected.value ? '当前线路:${controller.kr_getRealConnectedNodeCountry()}' : '未连接'}',
|
||||
style: normalStyle,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -88,7 +88,6 @@ class KRLoginController extends GetxController
|
||||
RxBool kr_canSendCode = true.obs;
|
||||
|
||||
/// 国家编码列表
|
||||
late List<KRAreaCodeItem> kr_areaCodeList = KRAreaCode.kr_getCodeList();
|
||||
var kr_cutSeleteCodeIndex = 0.obs;
|
||||
|
||||
/// 是否加密密码
|
||||
@ -556,8 +555,7 @@ class KRLoginController extends GetxController
|
||||
KRAppRunData.getInstance().kr_saveUserInfo(
|
||||
token,
|
||||
accountController.text,
|
||||
KRLoginType.kr_email,
|
||||
null);
|
||||
);
|
||||
kr_loginStatus.value = KRLoginProgressStatus.kr_check;
|
||||
|
||||
// 登录/注册成功后,发送消息触发订阅服务刷新
|
||||
|
||||
@ -65,14 +65,8 @@ class KRSplashController extends GetxController {
|
||||
_stepStartTime = _startTime;
|
||||
|
||||
// 使用 print 确保日志一定会输出
|
||||
print('[SPLASH_TIMING] ═══════════════════════════════════════');
|
||||
print('[SPLASH_TIMING] 🎬 KRSplashController onInit 被调用了!');
|
||||
print('[SPLASH_TIMING] ⏰ 启动开始时间: ${_startTime.toIso8601String()}');
|
||||
print('[SPLASH_TIMING] ═══════════════════════════════════════');
|
||||
|
||||
KRLogUtil.kr_i('[SPLASH_TIMING] ═══════════════════════════════════════', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('[SPLASH_TIMING] 🎬 启动页控制器 onInit', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('[SPLASH_TIMING] ═══════════════════════════════════════', tag: 'SplashController');
|
||||
|
||||
// 🔧 修复1.0:新增 - DEBUG模式下清理旧数据
|
||||
// ⚠️ 仅在DEBUG模式下执行,防止误删生产环境用户数据
|
||||
@ -85,15 +79,6 @@ class KRSplashController extends GetxController {
|
||||
print('🧹 清理域名检测状态...');
|
||||
KRDomain.kr_resetDomainState();
|
||||
|
||||
// 🔧 P2优化:网站配置不再后台执行,改为串行执行以确保域名验证完成
|
||||
// print('🌐 启动后台任务:网站配置加载...');
|
||||
// KRLogUtil.kr_i('🌐 后台任务:网站配置和设备登录', tag: 'SplashController');
|
||||
// _kr_initSiteConfig(); // 移至 _kr_continueInitialization 中串行执行
|
||||
|
||||
// 🔧 关键修复:先初始化日志收集器,再开始主流程
|
||||
if (kDebugMode) {
|
||||
print('🔧 初始化日志收集器...');
|
||||
}
|
||||
_initializeAndStart();
|
||||
}
|
||||
|
||||
@ -130,21 +115,15 @@ class KRSplashController extends GetxController {
|
||||
final totalMs = totalDuration.inMilliseconds;
|
||||
|
||||
print('[SPLASH_TIMING] ⏱️ $stepName 耗时: ${stepMs}ms | 累计: ${totalMs}ms');
|
||||
KRLogUtil.kr_i('[SPLASH_TIMING] ⏱️ $stepName 耗时: ${stepMs}ms | 累计: ${totalMs}ms', tag: 'SplashController');
|
||||
KRLogUtil.kr_i(
|
||||
'[SPLASH_TIMING] ⏱️ $stepName 耗时: ${stepMs}ms | 累计: ${totalMs}ms',
|
||||
tag: 'SplashController');
|
||||
|
||||
_stepStartTime = now; // 更新步骤开始时间
|
||||
}
|
||||
|
||||
/// 初始化网站配置(后台执行,不阻塞主流程)
|
||||
Future<void> _kr_initSiteConfig() async {
|
||||
print('[SPLASH_TIMING] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('[SPLASH_TIMING] 🌐 开始初始化网站配置...');
|
||||
print('[SPLASH_TIMING] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
KRLogUtil.kr_i('[SPLASH_TIMING] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('[SPLASH_TIMING] 🌐 初始化网站配置...', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('[SPLASH_TIMING] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
|
||||
try {
|
||||
// 🔧 Android 15 优化:延长超时保护到25秒,匹配网络请求超时(20秒 + 5秒缓冲)
|
||||
await Future.any([
|
||||
@ -162,116 +141,20 @@ class KRSplashController extends GetxController {
|
||||
}
|
||||
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'SplashController');
|
||||
}
|
||||
|
||||
/// 执行网站配置初始化
|
||||
Future<void> _executeSiteConfigInit() async {
|
||||
print('📞 准备调用 KRSiteConfigService().initialize()...');
|
||||
final success = await KRSiteConfigService().initialize();
|
||||
final crispId = await KRSiteConfigService().getCrispId();
|
||||
final device_limit = await KRSiteConfigService().getDeviceLimit();
|
||||
print('📞 KRSiteConfigService().initialize() 返回: $success');
|
||||
|
||||
if (success) {
|
||||
final config = AppConfig.getInstance();
|
||||
config.kr_website_id = crispId;
|
||||
config.device_limit = device_limit;
|
||||
print('📞 KRSiteConfigService().initialize() 返回: $crispId');
|
||||
print('AppConfig website_id 已更新为: ${config.kr_website_id}');
|
||||
print('AppConfig device_limit 已更新为: ${config.device_limit}');
|
||||
|
||||
// print('[SPLASH_TIMING] ✅ 网站配置初始化成功');
|
||||
// await _kr_checkAndPerformDeviceLogin();
|
||||
} else {
|
||||
print('⚠️ 网站配置初始化失败,将使用默认配置');
|
||||
KRLogUtil.kr_w('⚠️ 网站配置初始化失败,将使用默认配置', tag: 'SplashController');
|
||||
}
|
||||
print('📞 准备调用 KRDomain.kr_loadBaseDomain()...');
|
||||
await KRDomain.kr_loadBaseDomain();
|
||||
print('📞 KRDomain.kr_loadBaseDomain() 完成');
|
||||
}
|
||||
|
||||
/// 检查并执行设备登录(后台执行,有超时保护)
|
||||
Future<void> _kr_checkAndPerformDeviceLogin() async {
|
||||
try {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('🔍 检查是否支持设备登录...');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
final siteConfigService = KRSiteConfigService();
|
||||
|
||||
// 检查是否启用设备登录
|
||||
final isDeviceLoginEnabled = siteConfigService.isDeviceLoginEnabled();
|
||||
print('📱 设备登录状态: ${isDeviceLoginEnabled ? "已启用" : "未启用"}');
|
||||
KRLogUtil.kr_i('📱 设备登录状态: $isDeviceLoginEnabled', tag: 'SplashController');
|
||||
|
||||
if (!isDeviceLoginEnabled) {
|
||||
print('⚠️ 设备登录未启用,跳过自动登录');
|
||||
KRLogUtil.kr_w('⚠️ 设备登录未启用', tag: 'SplashController');
|
||||
return;
|
||||
}
|
||||
|
||||
print('✅ 设备登录已启用,开始初始化设备信息...');
|
||||
|
||||
// 🔧 Android 15 优化:延长设备登录超时到15秒,匹配网络请求超时
|
||||
await Future.any([
|
||||
_executeDeviceLogin(),
|
||||
Future.delayed(const Duration(seconds: 15), () {
|
||||
throw TimeoutException('设备登录超时(15秒)');
|
||||
}),
|
||||
]);
|
||||
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
} on TimeoutException catch (e) {
|
||||
print('⏱️ 设备登录超时,应用将继续启动: $e');
|
||||
KRLogUtil.kr_w('⏱️ 设备登录超时: $e', tag: 'SplashController');
|
||||
} catch (e, stackTrace) {
|
||||
print('❌ 设备登录检查异常: $e');
|
||||
print('📚 堆栈跟踪: $stackTrace');
|
||||
KRLogUtil.kr_e('❌ 设备登录检查异常: $e', tag: 'SplashController');
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行设备登录
|
||||
Future<void> _executeDeviceLogin() async {
|
||||
// 初始化设备信息服务
|
||||
await KRDeviceInfoService().initialize();
|
||||
|
||||
print('🔐 开始执行设备登录...');
|
||||
KRLogUtil.kr_i('🔐 开始执行设备登录', tag: 'SplashController');
|
||||
|
||||
// 执行设备登录
|
||||
final authApi = KRAuthApi();
|
||||
final result = await authApi.kr_deviceLogin();
|
||||
|
||||
result.fold(
|
||||
(error) {
|
||||
print('❌ 设备登录失败: ${error.msg}');
|
||||
KRLogUtil.kr_e('❌ 设备登录失败: ${error.msg}', tag: 'SplashController');
|
||||
},
|
||||
(token) async {
|
||||
print('✅ 设备登录成功!');
|
||||
print('🎫 Token: ${token.substring(0, min(20, token.length))}...');
|
||||
KRLogUtil.kr_i('✅ 设备登录成功', tag: 'SplashController');
|
||||
|
||||
// 使用 saveUserInfo 保存完整的用户信息
|
||||
// 设备登录使用特殊的账号标识,登录类型设为邮箱(后续可以绑定真实账号)
|
||||
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
await KRAppRunData.getInstance().kr_saveUserInfo(
|
||||
token,
|
||||
'device_$deviceId', // 使用设备ID作为临时账号
|
||||
KRLoginType.kr_email, // 默认登录类型
|
||||
null, // 设备登录无需区号
|
||||
);
|
||||
print('💾 用户信息已保存(包括Token)');
|
||||
print('✅ 已标记为登录状态');
|
||||
KRLogUtil.kr_i('✅ 设备登录流程完成', tag: 'SplashController');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _kr_initialize() async {
|
||||
try {
|
||||
_initLog.logPhaseStart('主初始化流程');
|
||||
_initLog.log('开始执行 _kr_initialize', tag: 'Splash');
|
||||
KRLogUtil.kr_i('🔧 开始执行 _kr_initialize', tag: 'SplashController');
|
||||
|
||||
// 🔧 Android 15 优化:添加总体超时保护(15秒),更快响应
|
||||
@ -304,7 +187,8 @@ class KRSplashController extends GetxController {
|
||||
_handleError('网络连接失败', '请检查您的网络连接是否正常,然后重试。');
|
||||
} on DioException catch (e) {
|
||||
// 🔧 P2优化:HTTP请求错误
|
||||
KRLogUtil.kr_e('❌ HTTP请求错误: ${e.type} - ${e.message}', tag: 'SplashController');
|
||||
KRLogUtil.kr_e('❌ HTTP请求错误: ${e.type} - ${e.message}',
|
||||
tag: 'SplashController');
|
||||
final errorMsg = _getDioErrorMessage(e);
|
||||
_handleError('服务请求失败', errorMsg);
|
||||
} catch (e) {
|
||||
@ -328,10 +212,13 @@ class KRSplashController extends GetxController {
|
||||
// 1. 设置默认域名(确保应用有可用域名)
|
||||
try {
|
||||
_initLog.log('步骤1: 设置默认域名', tag: 'Minimal');
|
||||
if (KRDomain.kr_currentDomain.isEmpty && KRDomain.kr_baseDomains.isNotEmpty) {
|
||||
if (KRDomain.kr_currentDomain.isEmpty &&
|
||||
KRDomain.kr_baseDomains.isNotEmpty) {
|
||||
KRDomain.kr_currentDomain = KRDomain.kr_baseDomains[0];
|
||||
_initLog.logSuccess('设置默认域名: ${KRDomain.kr_currentDomain}', tag: 'Minimal');
|
||||
KRLogUtil.kr_i('✅ 设置默认域名: ${KRDomain.kr_currentDomain}', tag: 'SplashController');
|
||||
_initLog.logSuccess('设置默认域名: ${KRDomain.kr_currentDomain}',
|
||||
tag: 'Minimal');
|
||||
KRLogUtil.kr_i('✅ 设置默认域名: ${KRDomain.kr_currentDomain}',
|
||||
tag: 'SplashController');
|
||||
if (kDebugMode) {
|
||||
print('✅ 设置默认域名: ${KRDomain.kr_currentDomain}');
|
||||
}
|
||||
@ -509,21 +396,15 @@ class KRSplashController extends GetxController {
|
||||
|
||||
Future<void> _kr_continueInitialization() async {
|
||||
try {
|
||||
_initLog.logSeparator();
|
||||
_initLog.log('🚀 启动页主流程开始', tag: 'Continue');
|
||||
_initLog.logSeparator();
|
||||
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('🚀 启动页主流程开始...', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
|
||||
// 🔧 Android 15 优化:网络连接检查改为非阻塞
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
_initLog.log('📱 检查网络连接状态(超时5秒)', tag: 'Continue');
|
||||
KRLogUtil.kr_i('📱 检查网络连接...', tag: 'SplashController');
|
||||
try {
|
||||
// 添加5秒超时保护
|
||||
final bool isConnected = await KRNetworkCheck.kr_checkNetworkConnection().timeout(
|
||||
final bool isConnected =
|
||||
await KRNetworkCheck.kr_checkNetworkConnection().timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () {
|
||||
_initLog.logWarning('网络连接检查超时(5秒),继续初始化', tag: 'Continue');
|
||||
@ -550,8 +431,6 @@ class KRSplashController extends GetxController {
|
||||
KRLogUtil.kr_i('💻 桌面平台,跳过网络连接检查', tag: 'SplashController');
|
||||
}
|
||||
|
||||
// 网络检查完成后执行设备登录(始终支持)
|
||||
|
||||
// 🔧 关键修复:在设备登录前先初始化站点配置,确保域名可用
|
||||
// 这样如果主域名不可用,initSiteConfig 会切换到备用域名
|
||||
// 后续的设备登录就能使用正确的域名
|
||||
@ -561,7 +440,8 @@ class KRSplashController extends GetxController {
|
||||
|
||||
_logStepTiming('开始设备登录检查');
|
||||
// 5️⃣ 执行设备登录
|
||||
final success = await KRAppRunData.getInstance().kr_checkAndPerformDeviceLogin();
|
||||
final success =
|
||||
await KRAppRunData.getInstance().kr_checkAndPerformDeviceLogin();
|
||||
|
||||
if (!success) {
|
||||
// 设备登录失败 → 提示用户重试
|
||||
@ -605,12 +485,7 @@ class KRSplashController extends GetxController {
|
||||
// 配置初始化成功后的后续步骤
|
||||
Future<void> _kr_continueAfterConfig() async {
|
||||
try {
|
||||
// 用户信息初始化完成后,立即初始化订阅服务
|
||||
print('[SPLASH_TIMING] 🔄 开始初始化订阅服务...');
|
||||
KRLogUtil.kr_i('[SPLASH_TIMING] 🔄 开始初始化订阅服务', tag: 'SplashController');
|
||||
_kr_ensureSubscribeServiceInitialized();
|
||||
|
||||
// 初始化SingBox
|
||||
// 初始化SingBox(必须先于订阅服务,避免目录未初始化)
|
||||
_logStepTiming('开始SingBox初始化');
|
||||
await KRSingBoxImp.instance.init();
|
||||
_logStepTiming('SingBox初始化完成');
|
||||
@ -620,20 +495,12 @@ class KRSplashController extends GetxController {
|
||||
final token = KRAppRunData.getInstance().kr_token;
|
||||
final hasToken = token != null && token.isNotEmpty;
|
||||
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('🎯 准备进入主页');
|
||||
print('📊 最终登录状态: $loginStatus');
|
||||
print('🎫 Token存在: $hasToken');
|
||||
if (hasToken) {
|
||||
print('🎫 Token前缀: ${token.substring(0, min(20, token.length))}...');
|
||||
}
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('🎯 准备进入主页', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('📊 最终登录状态: $loginStatus', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('🎫 Token存在: $hasToken', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
|
||||
// 🔧 记录最终状态到日志
|
||||
_initLog.logSeparator();
|
||||
@ -652,12 +519,13 @@ class KRSplashController extends GetxController {
|
||||
|
||||
// 直接导航到主页(无论是否登录,主页会根据登录状态显示不同内容)
|
||||
_logStepTiming('开始页面导航');
|
||||
if(loginStatus) {
|
||||
if (loginStatus) {
|
||||
// 直接导航到主页
|
||||
Get.offAllNamed(Routes.KR_HOME);
|
||||
}else {
|
||||
} else {
|
||||
kr_hasError.value = true;
|
||||
kr_errorMessage.value = '登录:${AppTranslations.kr_splash.kr_initializationFailed}';
|
||||
kr_errorMessage.value =
|
||||
'登录:${AppTranslations.kr_splash.kr_initializationFailed}';
|
||||
}
|
||||
_logStepTiming('页面导航完成');
|
||||
} catch (e) {
|
||||
@ -667,63 +535,22 @@ class KRSplashController extends GetxController {
|
||||
final totalMs = totalDuration.inMilliseconds;
|
||||
|
||||
print('❌ 启动失败时间: ${endTime.toIso8601String()}');
|
||||
print('🕐 启动失败总耗时: ${totalMs}ms (${totalDuration.inSeconds}.${(totalMs % 1000).toString().padLeft(3, '0')}s)');
|
||||
print(
|
||||
'🕐 启动失败总耗时: ${totalMs}ms (${totalDuration.inSeconds}.${(totalMs % 1000).toString().padLeft(3, '0')}s)');
|
||||
|
||||
// 后续步骤失败,显示错误信息
|
||||
_initLog.logError('启动初始化失败', tag: 'Splash', error: e);
|
||||
KRLogUtil.kr_e('启动初始化失败: $e', tag: 'SplashController');
|
||||
KRLogUtil.kr_e('⏰ 启动失败总耗时: ${totalMs}ms', tag: 'SplashController');
|
||||
kr_hasError.value = true;
|
||||
kr_errorMessage.value = '${AppTranslations.kr_splash.kr_initializationFailed}$e';
|
||||
kr_errorMessage.value =
|
||||
'${AppTranslations.kr_splash.kr_initializationFailed}$e';
|
||||
|
||||
// 🔧 错误情况下也不关闭日志,让后续的错误处理也能写入
|
||||
// await _initLog.finalize(); // ❌ 注释掉
|
||||
}
|
||||
}
|
||||
|
||||
/// 确保订阅服务初始化
|
||||
Future<void> _kr_ensureSubscribeServiceInitialized() async {
|
||||
try {
|
||||
// 检查订阅服务状态
|
||||
final currentStatus = kr_subscribeService.kr_currentStatus.value;
|
||||
KRLogUtil.kr_i('订阅服务当前状态: $currentStatus', tag: 'SplashController');
|
||||
|
||||
if (currentStatus == KRSubscribeServiceStatus.kr_none ||
|
||||
currentStatus == KRSubscribeServiceStatus.kr_error) {
|
||||
KRLogUtil.kr_i(
|
||||
'订阅服务未初始化或错误,开始初始化', tag: 'SplashController');
|
||||
|
||||
// 初始化订阅服务并等待完成
|
||||
// 设置加载状态
|
||||
KRHomeController().kr_currentListStatus.value = KRHomeViewsListStatus.kr_loading;
|
||||
try {
|
||||
await kr_subscribeService.kr_refreshAll();
|
||||
KRLogUtil.kr_i('订阅服务初始化完成', tag: 'SplashController');
|
||||
} catch (error) {
|
||||
KRLogUtil.kr_e('订阅服务初始化失败: $error', tag: 'SplashController');
|
||||
// 重新抛出异常,让调用方知道初始化失败
|
||||
KRHomeController().kr_currentListStatus.value = KRHomeViewsListStatus.kr_error;
|
||||
rethrow;
|
||||
}
|
||||
} else if (currentStatus == KRSubscribeServiceStatus.kr_loading) {
|
||||
KRLogUtil.kr_i('订阅服务正在初始化中', tag: 'SplashController');
|
||||
// 如果正在加载中,等待加载完成
|
||||
while (kr_subscribeService.kr_currentStatus.value == KRSubscribeServiceStatus.kr_loading) {
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
}
|
||||
KRLogUtil.kr_i('订阅服务加载完成,最终状态: ${kr_subscribeService.kr_currentStatus.value}', tag: 'SplashController');
|
||||
KRHomeController().kr_currentListStatus.value = KRHomeViewsListStatus.kr_loading;
|
||||
} else if (currentStatus == KRSubscribeServiceStatus.kr_success) {
|
||||
KRHomeController().kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||||
KRLogUtil.kr_i('订阅服务已成功初始化', tag: 'SplashController');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('确保订阅服务初始化失败: $e', tag: 'SplashController');
|
||||
KRHomeController().kr_currentListStatus.value = KRHomeViewsListStatus.kr_error;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// 重试按钮点击事件
|
||||
void kr_retry() {
|
||||
kr_hasError.value = false;
|
||||
@ -749,15 +576,15 @@ class KRSplashController extends GetxController {
|
||||
/// 防止旧的测试账号在新安装时被恢复
|
||||
Future<void> _kr_clearOldLocalData() async {
|
||||
try {
|
||||
KRLogUtil.kr_i('🧹 开始清理旧本地存储数据...', tag: 'SplashController');
|
||||
// KRLogUtil.kr_i('🧹 开始清理旧本地存储数据...', tag: 'SplashController');
|
||||
|
||||
// 清理用户信息存储
|
||||
await KRSecureStorage().kr_deleteData(key: 'USER_INFO');
|
||||
KRLogUtil.kr_i('✅ 已清理USER_INFO', tag: 'SplashController');
|
||||
// KRLogUtil.kr_i('✅ 已清理USER_INFO', tag: 'SplashController');
|
||||
|
||||
// 清理设备登录状态
|
||||
await KRSecureStorage().kr_deleteData(key: 'DEVICE_INFO');
|
||||
KRLogUtil.kr_i('✅ 已清理DEVICE_INFO', tag: 'SplashController');
|
||||
// KRLogUtil.kr_i('✅ 已清理DEVICE_INFO', tag: 'SplashController');
|
||||
|
||||
KRLogUtil.kr_i('✅ 旧本地存储数据已全部清理', tag: 'SplashController');
|
||||
} catch (e) {
|
||||
|
||||
@ -25,15 +25,14 @@ class BaseResponse<T> {
|
||||
final dataMap = json['data'] ?? Map<String, dynamic>();
|
||||
final cipherText = dataMap['data'] ?? "";
|
||||
final nonce = dataMap['time'] ?? "";
|
||||
print('明文${cipherText}');
|
||||
// 判断是否需要解密:根据站点配置的 enable_security 字段
|
||||
if (cipherText.isNotEmpty && nonce.isNotEmpty) {
|
||||
try {
|
||||
if (kDebugMode) {
|
||||
// print('═══════════════════════════════════════');
|
||||
// print('🔐 检测到加密响应,开始解密...');
|
||||
print('📥 加密数据长度: ${cipherText.length} 字符');
|
||||
print('⏰ 时间戳: $nonce');
|
||||
// print('📥 加密数据长度: ${cipherText.length} 字符');
|
||||
// print('⏰ 时间戳: $nonce');
|
||||
}
|
||||
|
||||
final decrypted = KRAesUtil.decryptData(cipherText, nonce, AppConfig.kr_encryptionKey);
|
||||
|
||||
@ -24,7 +24,6 @@ import 'package:loggy/loggy.dart';
|
||||
|
||||
import '../utils/kr_aes_util.dart';
|
||||
import '../utils/kr_log_util.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
// import 'package:video/app/utils/common_util.dart';
|
||||
// import 'package:video/app/utils/log_util.dart';
|
||||
@ -144,70 +143,11 @@ class HttpUtil {
|
||||
}
|
||||
|
||||
var map = <String, dynamic>{};
|
||||
// 判断是否需要加密:根据站点配置的 enable_security 字段
|
||||
final shouldEncrypt = KRSiteConfigService().isDeviceSecurityEnabled();
|
||||
if (shouldEncrypt) {
|
||||
KRLogUtil.kr_i('🔐 需要加密请求数据', tag: 'HttpUtil');
|
||||
final plainText = jsonEncode(params);
|
||||
map = KRAesUtil.encryptData(plainText, AppConfig.kr_encryptionKey);
|
||||
} else {
|
||||
map = params;
|
||||
}
|
||||
final plainText = jsonEncode(params);
|
||||
map = KRAesUtil.encryptData(plainText, AppConfig.kr_encryptionKey);
|
||||
|
||||
// 初始化请求头
|
||||
final headers = _initHeader('signature', 'userId', 'token');
|
||||
int? _kr_parseUserIdFromToken(String token) {
|
||||
try {
|
||||
// JWT格式: header.payload.signature
|
||||
final parts = token.split('.');
|
||||
if (parts.length != 3) {
|
||||
KRLogUtil.kr_e('JWT token格式错误', tag: 'AppRunData');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 解码payload部分(base64)
|
||||
String payload = parts[1];
|
||||
// 手动添加必要的padding(base64要求长度是4的倍数)
|
||||
switch (payload.length % 4) {
|
||||
case 0:
|
||||
break; // 不需要padding
|
||||
case 2:
|
||||
payload += '==';
|
||||
break;
|
||||
case 3:
|
||||
payload += '=';
|
||||
break;
|
||||
default:
|
||||
KRLogUtil.kr_e('JWT payload长度无效', tag: 'AppRunData');
|
||||
return null;
|
||||
}
|
||||
|
||||
final decodedBytes = base64.decode(payload);
|
||||
final decodedString = utf8.decode(decodedBytes);
|
||||
|
||||
// 解析JSON
|
||||
final Map<String, dynamic> payloadMap = jsonDecode(decodedString);
|
||||
|
||||
// 获取UserId
|
||||
if (payloadMap.containsKey('UserId')) {
|
||||
final userId = payloadMap['UserId'];
|
||||
KRLogUtil.kr_i('从JWT解析出userId: $userId', tag: 'AppRunData');
|
||||
return userId is int ? userId : int.tryParse(userId.toString());
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('解析JWT token失败: $e', tag: 'AppRunData');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final userId =
|
||||
_kr_parseUserIdFromToken(KRAppRunData().kr_token.toString());
|
||||
// 调试:打印请求头
|
||||
KRLogUtil.kr_i('🔍 请求头: $headers', tag: 'HttpUtil');
|
||||
KRLogUtil.kr_i('🔍 请求userId: $userId', tag: 'HttpUtil');
|
||||
KRLogUtil.kr_i('🔍 请求头map: $map', tag: 'HttpUtil');
|
||||
|
||||
Response<Map<String, dynamic>> responseTemp;
|
||||
if (method == HttpMethod.GET) {
|
||||
@ -316,193 +256,93 @@ class HttpUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/// 拦截器(简洁格式,无边框)
|
||||
class MyInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
print('>>> Request │ ${options.method} │ ${options.uri}');
|
||||
}
|
||||
if (options.data != null) {
|
||||
if (kDebugMode) {
|
||||
print('Body: ${options.data}');
|
||||
}
|
||||
}
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}');
|
||||
}
|
||||
if (response.data != null) {
|
||||
if (kDebugMode) {
|
||||
print('Body: ${response.data}');
|
||||
}
|
||||
}
|
||||
handler.next(response);
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'<<< Error │ ${err.requestOptions.method} │ ${err.requestOptions.uri}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('Error Type: ${err.type}');
|
||||
}
|
||||
if (err.message != null) {
|
||||
if (kDebugMode) {
|
||||
print('Error Message: ${err.message}');
|
||||
}
|
||||
}
|
||||
if (err.response?.data != null) {
|
||||
if (kDebugMode) {
|
||||
print('Response Data: ${err.response?.data}');
|
||||
}
|
||||
}
|
||||
handler.next(err);
|
||||
}
|
||||
}
|
||||
|
||||
/// 自定义简洁 HTTP 拦截器(无边框符号)
|
||||
class _KRSimpleHttpInterceptor extends Interceptor {
|
||||
/// 常量:手动控制是否打印拦截器日志与解密结果
|
||||
static const bool KR_HTTP_PRINT = true;
|
||||
final Dio _dio;
|
||||
_KRSimpleHttpInterceptor(this._dio);
|
||||
static String? _lastPath;
|
||||
static int _lastTsMs = 0;
|
||||
@override
|
||||
|
||||
/// 请求拦截器
|
||||
///
|
||||
/// 功能:在调试模式下优先打印请求的解密明文;若解密失败则打印原始数据并标识失败。
|
||||
/// 参数:
|
||||
/// - options: 请求选项(包含 data 与 queryParameters)
|
||||
/// - handler: 拦截器处理器
|
||||
/// 返回:void
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
if (KR_HTTP_PRINT) {
|
||||
print('>>> Request │ ${options.method} │ ${options.uri}');
|
||||
}
|
||||
if (options.data != null) {
|
||||
if (kDebugMode) {
|
||||
print('Body: ${options.data}');
|
||||
}
|
||||
|
||||
// 检查是否是加密数据(包含 data 和 time 字段)
|
||||
if (KR_HTTP_PRINT) {
|
||||
Map<String, dynamic>? m;
|
||||
if (options.data is Map<String, dynamic>) {
|
||||
final data = options.data as Map<String, dynamic>;
|
||||
if (data.containsKey('data') && data.containsKey('time')) {
|
||||
try {
|
||||
if (kDebugMode) {
|
||||
print('');
|
||||
}
|
||||
// 尝试解密并打印原始数据
|
||||
final encryptedData = data['data'] as String;
|
||||
final nonce = data['time'] as String;
|
||||
final decrypted = KRAesUtil.decryptData(
|
||||
encryptedData,
|
||||
nonce,
|
||||
AppConfig.kr_encryptionKey,
|
||||
);
|
||||
if (kDebugMode) {
|
||||
print('🔓 解密后的原始请求数据:');
|
||||
}
|
||||
// 尝试格式化 JSON
|
||||
try {
|
||||
final jsonData = jsonDecode(decrypted);
|
||||
final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData);
|
||||
if (kDebugMode) {
|
||||
print(prettyJson);
|
||||
}
|
||||
} catch (_) {
|
||||
if (kDebugMode) {
|
||||
print(decrypted);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 请求解密失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
m = options.data as Map<String, dynamic>;
|
||||
} else if (options.queryParameters.isNotEmpty) {
|
||||
m = options.queryParameters;
|
||||
}
|
||||
if (m != null) {
|
||||
final decrypted = _tryDecryptFromMap(m);
|
||||
_printDecryptedOrFallback(
|
||||
phase: 'Request', raw: m, decrypted: decrypted);
|
||||
}
|
||||
}
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
/// 响应拦截器
|
||||
///
|
||||
/// 功能:在调试模式下优先打印响应的解密明文;若解密失败则打印原始数据并标识失败。
|
||||
/// 参数:
|
||||
/// - response: 响应对象(优先识别 Map 格式)
|
||||
/// - handler: 拦截器处理器
|
||||
/// 返回:void
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
if (KR_HTTP_PRINT) {
|
||||
print(
|
||||
'<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}');
|
||||
}
|
||||
if (response.data != null) {
|
||||
if (kDebugMode) {
|
||||
print('Body: ${response.data}');
|
||||
}
|
||||
|
||||
// 检查响应是否是加密数据(包含 data 和 time 字段)
|
||||
if (response.data is Map<String, dynamic>) {
|
||||
final dataMap = response.data as Map<String, dynamic>;
|
||||
|
||||
// 检查是否包含嵌套的 data 字段(加密数据格式)
|
||||
final nestedData = dataMap['data'];
|
||||
if (nestedData is Map<String, dynamic> &&
|
||||
nestedData.containsKey('data') &&
|
||||
nestedData.containsKey('time')) {
|
||||
try {
|
||||
if (kDebugMode) {
|
||||
print('');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('🔐 检测到加密响应,正在解密...');
|
||||
}
|
||||
// 尝试解密并打印原始响应数据
|
||||
final encryptedData = nestedData['data'] as String;
|
||||
final nonce = nestedData['time'] as String;
|
||||
final decrypted = KRAesUtil.decryptData(
|
||||
encryptedData,
|
||||
nonce,
|
||||
AppConfig.kr_encryptionKey,
|
||||
);
|
||||
if (kDebugMode) {
|
||||
print('🔓 解密后的原始响应数据:');
|
||||
}
|
||||
// 尝试格式化 JSON
|
||||
try {
|
||||
// final jsonData = jsonDecode(decrypted);
|
||||
// final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData);
|
||||
// if (kDebugMode) {
|
||||
// print(prettyJson);
|
||||
// }
|
||||
} catch (_) {
|
||||
if (kDebugMode) {
|
||||
print(decrypted);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 响应解密失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (KR_HTTP_PRINT && response.data is Map<String, dynamic>) {
|
||||
final dataMap = response.data as Map<String, dynamic>;
|
||||
String? decrypted = _tryDecryptFromMap(dataMap);
|
||||
if (decrypted == null && dataMap['data'] is Map<String, dynamic>) {
|
||||
decrypted = _tryDecryptFromMap(dataMap['data'] as Map<String, dynamic>);
|
||||
}
|
||||
_printDecryptedOrFallback(
|
||||
phase: 'Response', raw: dataMap, decrypted: decrypted);
|
||||
}
|
||||
handler.next(response);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
/// 错误拦截器
|
||||
///
|
||||
/// 功能:打印错误信息并保持原有的 Unknown 错误重试与失败提示去重逻辑。
|
||||
/// 参数:
|
||||
/// - err: Dio 错误对象
|
||||
/// - handler: 拦截器处理器
|
||||
/// 返回:void
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
if (KR_HTTP_PRINT) {
|
||||
print(
|
||||
'<<< Error │ ${err.requestOptions.method} │ ${err.requestOptions.uri}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
if (KR_HTTP_PRINT) {
|
||||
print('Error Type: ${err.type}');
|
||||
}
|
||||
if (err.message != null) {
|
||||
if (kDebugMode) {
|
||||
if (KR_HTTP_PRINT) {
|
||||
print('Error Message: ${err.message}');
|
||||
}
|
||||
}
|
||||
if (err.response?.data != null) {
|
||||
if (kDebugMode) {
|
||||
if (KR_HTTP_PRINT) {
|
||||
print('Response Data: ${err.response?.data}');
|
||||
}
|
||||
}
|
||||
@ -545,4 +385,53 @@ class _KRSimpleHttpInterceptor extends Interceptor {
|
||||
}
|
||||
handler.next(err);
|
||||
}
|
||||
|
||||
/// 尝试从 Map 中解密,若存在 data/time 字段则返回明文,否则返回 null
|
||||
String? _tryDecryptFromMap(Map<String, dynamic> m) {
|
||||
final data = m['data'];
|
||||
final time = m['time'];
|
||||
if (data is String && time is String) {
|
||||
try {
|
||||
return KRAesUtil.decryptData(data, time, AppConfig.kr_encryptionKey);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 将字符串尽量按 JSON 缩进格式化,失败时返回原文
|
||||
String _prettyPrintJson(String s) {
|
||||
try {
|
||||
final jsonData = jsonDecode(s);
|
||||
return JsonEncoder.withIndent(' ').convert(jsonData);
|
||||
} catch (_) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
/// 统一输出逻辑:成功仅打印解密明文;失败打印失败标识与原文
|
||||
void _printDecryptedOrFallback({
|
||||
required String phase,
|
||||
required Object? raw,
|
||||
required String? decrypted,
|
||||
}) {
|
||||
if (!KR_HTTP_PRINT) return;
|
||||
if (decrypted != null) {
|
||||
print('🔓 $phase 明文:');
|
||||
print(_prettyPrintJson(decrypted));
|
||||
} else {
|
||||
print('⚠️ $phase 解密失败');
|
||||
print('原文:');
|
||||
if (raw is Map<String, dynamic>) {
|
||||
try {
|
||||
print(JsonEncoder.withIndent(' ').convert(raw));
|
||||
} catch (_) {
|
||||
print(raw.toString());
|
||||
}
|
||||
} else {
|
||||
print(raw?.toString() ?? 'null');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -379,23 +379,15 @@ class KRAuthApi {
|
||||
|
||||
print('📤 原始请求数据: $data');
|
||||
|
||||
// 检查是否需要加密
|
||||
final siteConfigService = KRSiteConfigService();
|
||||
final needEncryption = siteConfigService.isDeviceSecurityEnabled();
|
||||
|
||||
print('🔒 是否需要加密: $needEncryption');
|
||||
KRLogUtil.kr_i('🔒 是否需要加密: $needEncryption', tag: 'KRAuthApi');
|
||||
|
||||
final needEncryption = true;
|
||||
String? requestBody;
|
||||
if (needEncryption) {
|
||||
// 加密请求数据
|
||||
print('🔐 加密请求数据...');
|
||||
final encrypted = KRAesUtil.encryptJson(data, AppConfig.kr_encryptionKey);
|
||||
requestBody = '{"data":"${encrypted['data']}","time":"${encrypted['time']}"}';
|
||||
print('🔐 加密后请求体: $requestBody');
|
||||
KRLogUtil.kr_i('🔐 加密后请求体', tag: 'KRAuthApi');
|
||||
// print('🔐 加密后请求体: $requestBody');
|
||||
// KRLogUtil.kr_i('🔐 加密后请求体', tag: 'KRAuthApi');
|
||||
} else {
|
||||
// 使用明文
|
||||
requestBody = jsonEncode(data);
|
||||
print('📝 明文请求体: $requestBody');
|
||||
}
|
||||
@ -439,10 +431,10 @@ class KRAuthApi {
|
||||
),
|
||||
);
|
||||
|
||||
print('📥 响应状态码: ${response.statusCode}');
|
||||
print('📥 响应数据: ${response.data}');
|
||||
KRLogUtil.kr_i('📥 响应状态码: ${response.statusCode}', tag: 'KRAuthApi');
|
||||
KRLogUtil.kr_i('📥 响应数据: ${response.data}', tag: 'KRAuthApi');
|
||||
// print('📥 响应状态码: ${response.statusCode}');
|
||||
// print('📥 响应数据: ${response.data}');
|
||||
// KRLogUtil.kr_i('📥 响应状态码: ${response.statusCode}', tag: 'KRAuthApi');
|
||||
// KRLogUtil.kr_i('📥 响应数据: ${response.data}', tag: 'KRAuthApi');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
Map<String, dynamic> responseData = response.data as Map<String, dynamic>;
|
||||
|
||||
@ -28,28 +28,23 @@ class KRDeviceInfoService {
|
||||
/// 初始化设备信息
|
||||
Future<void> initialize() async {
|
||||
try {
|
||||
if (kDebugMode) {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('📱 开始初始化设备信息服务');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
}
|
||||
KRLogUtil.kr_i('📱 开始初始化设备信息', tag: 'KRDeviceInfoService');
|
||||
|
||||
_deviceId = await _getDeviceId();
|
||||
_deviceDetails = await _getDeviceDetails();
|
||||
|
||||
if (kDebugMode) {
|
||||
print('✅ 设备信息初始化成功');
|
||||
print('📱 设备ID: $_deviceId');
|
||||
print('📱 设备平台: ${getPlatformName()}');
|
||||
print('📱 设备型号: ${getDeviceModel()}');
|
||||
print('📱 系统版本: ${getOSVersion()}');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
// print('✅ 设备信息初始化成功');
|
||||
// print('📱 设备ID: $_deviceId');
|
||||
// print('📱 设备平台: ${getPlatformName()}');
|
||||
// print('📱 设备型号: ${getDeviceModel()}');
|
||||
// print('📱 系统版本: ${getOSVersion()}');
|
||||
// print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('✅ 设备信息初始化成功', tag: 'KRDeviceInfoService');
|
||||
KRLogUtil.kr_i('📱 设备ID - $_deviceId', tag: 'KRDeviceInfoService');
|
||||
KRLogUtil.kr_i('📱 设备详情 - $_deviceDetails', tag: 'KRDeviceInfoService');
|
||||
// KRLogUtil.kr_i('📱 设备ID - $_deviceId', tag: 'KRDeviceInfoService');
|
||||
// KRLogUtil.kr_i('📱 设备详情 - $_deviceDetails', tag: 'KRDeviceInfoService');
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('❌ 设备信息初始化失败: $e');
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -7,6 +8,7 @@ import '../common/app_config.dart';
|
||||
import '../utils/kr_log_util.dart';
|
||||
import '../utils/kr_http_adapter_util.dart';
|
||||
import 'singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'api_service/kr_api.user.dart';
|
||||
|
||||
/// 网站配置服务
|
||||
class KRSiteConfigService extends ChangeNotifier {
|
||||
@ -18,15 +20,6 @@ class KRSiteConfigService extends ChangeNotifier {
|
||||
_dio.options.sendTimeout = const Duration(seconds: 20);
|
||||
_dio.options.receiveTimeout = const Duration(seconds: 20);
|
||||
|
||||
// 🔧 关键修复:网站配置请求不使用代理
|
||||
// 原因:网站配置是应用启动的第一步,此时 SingBox 还未初始化
|
||||
// 必须直接连接服务器获取配置,避免循环依赖和初始化失败
|
||||
// 之前的代理配置会导致 DioExceptionType.unknown 错误
|
||||
KRLogUtil.kr_i(
|
||||
'🌐 网站配置服务:使用直连模式(不通过代理)',
|
||||
tag: 'KRSiteConfigService',
|
||||
);
|
||||
|
||||
// 🔧 关键修复:添加 SSL 证书验证跳过,强制直连
|
||||
_dio.httpClientAdapter = KRHttpAdapterUtil.createAdapter(
|
||||
useSingBoxProxy: false,
|
||||
@ -49,190 +42,126 @@ class KRSiteConfigService extends ChangeNotifier {
|
||||
/// 是否已初始化
|
||||
bool get isInitialized => _isInitialized;
|
||||
|
||||
/// 初始化站点配置
|
||||
Future<bool> initialize() async {
|
||||
int retryCount = 0;
|
||||
const int maxRetries = 5;
|
||||
return await initializeDomains();
|
||||
}
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
try {
|
||||
if (kDebugMode) {
|
||||
print('🔧 KRSiteConfigService.initialize() 开始执行 (尝试 ${retryCount + 1}/$maxRetries)');
|
||||
}
|
||||
KRLogUtil.kr_i('🔧 开始初始化网站配置 (尝试 ${retryCount + 1}/$maxRetries)', tag: 'KRSiteConfigService');
|
||||
|
||||
// Debug 模式下使用固定地址
|
||||
final baseUrl = AppConfig().baseUrl;
|
||||
if (kDebugMode) {
|
||||
print('📍 baseUrl = $baseUrl');
|
||||
}
|
||||
final url = '$baseUrl/v1/common/site/config';
|
||||
if (kDebugMode) {
|
||||
print('📍 完整URL = $url');
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('📤 请求网站配置 - $url', tag: 'KRSiteConfigService');
|
||||
if (kDebugMode) {
|
||||
print('📤 准备发送 GET 请求到: $url');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
// 🔧 修正日志,显示真实的超时配置
|
||||
print('⏱️ 超时配置: connectTimeout=${_dio.options.connectTimeout}, sendTimeout=${_dio.options.sendTimeout}, receiveTimeout=${_dio.options.receiveTimeout}');
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
print('⏳ 开始发送请求...');
|
||||
}
|
||||
final startTime = DateTime.now();
|
||||
final response = await _dio.get(url);
|
||||
final endTime = DateTime.now();
|
||||
final duration = endTime.difference(startTime).inMilliseconds;
|
||||
if (kDebugMode) {
|
||||
print('⏱️ 请求耗时: ${duration}ms');
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
print('✅ 请求完成,状态码: ${response.statusCode}');
|
||||
}
|
||||
KRLogUtil.kr_i('📥 响应状态码 - ${response.statusCode}', tag: 'KRSiteConfigService');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseData = response.data;
|
||||
if (kDebugMode) {
|
||||
print('📥 响应数据类型: ${responseData.runtimeType}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📥 响应数据: $responseData');
|
||||
}
|
||||
KRLogUtil.kr_i('📥 响应数据 - $responseData', tag: 'KRSiteConfigService');
|
||||
|
||||
if (responseData['code'] == 200) {
|
||||
_siteConfig = KRSiteConfig.fromJson(responseData['data']);
|
||||
_isInitialized = true;
|
||||
|
||||
final config = AppConfig.getInstance();
|
||||
config.kr_website_id = getCrispId();
|
||||
config.device_limit = getDeviceLimit();
|
||||
// 打印配置信息
|
||||
_printConfigInfo();
|
||||
|
||||
// 通知监听者配置已更新
|
||||
notifyListeners();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
KRLogUtil.kr_e('❌ API返回错误 - ${responseData['msg']}', tag: 'KRSiteConfigService');
|
||||
// API 返回业务错误,一般不重试,除非是特定错误码
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
KRLogUtil.kr_e('❌ HTTP错误 - ${response.statusCode}', tag: 'KRSiteConfigService');
|
||||
// HTTP 错误(如 404, 500)通常也意味着该域名有问题,尝试切换
|
||||
// 但这里为了稳妥,先只针对网络异常重试,如果需要也可以改为抛出异常进入 catch
|
||||
throw DioException(
|
||||
requestOptions: response.requestOptions,
|
||||
response: response,
|
||||
type: DioExceptionType.badResponse,
|
||||
error: 'HTTP Error ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
} on DioException catch (e, stackTrace) {
|
||||
if (kDebugMode) {
|
||||
print('❌ Dio请求异常: ${e.type}');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ Dio异常 - ${e.type}: ${e.message}', tag: 'KRSiteConfigService');
|
||||
|
||||
// 检查是否是可以重试的错误类型
|
||||
if (e.type == DioExceptionType.connectionTimeout ||
|
||||
e.type == DioExceptionType.receiveTimeout ||
|
||||
e.type == DioExceptionType.sendTimeout ||
|
||||
e.type == DioExceptionType.connectionError ||
|
||||
e.type == DioExceptionType.unknown || // unknown 有时也是网络问题
|
||||
e.type == DioExceptionType.badResponse) { // 503 等服务器错误也值得重试
|
||||
|
||||
KRLogUtil.kr_w('⚠️ 网络或服务器异常,尝试切换域名重试...', tag: 'KRSiteConfigService');
|
||||
|
||||
// 尝试切换到下一个域名
|
||||
final switchSuccess = await KRDomain.kr_switchToNextDomain();
|
||||
if (switchSuccess) {
|
||||
retryCount++;
|
||||
KRLogUtil.kr_i('✅ 域名切换成功,准备第 ${retryCount + 1} 次尝试', tag: 'KRSiteConfigService');
|
||||
continue; // 重新进入循环
|
||||
} else {
|
||||
KRLogUtil.kr_e('❌ 域名切换失败,无法继续重试', tag: 'KRSiteConfigService');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是不可重试的错误,直接返回失败
|
||||
KRLogUtil.kr_e('📚 堆栈: $stackTrace', tag: 'KRSiteConfigService');
|
||||
return false;
|
||||
} catch (e, stackTrace) {
|
||||
if (kDebugMode) {
|
||||
print('❌ 未知异常: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ 初始化失败 - $e', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_e('📚 堆栈: $stackTrace', tag: 'KRSiteConfigService');
|
||||
return false;
|
||||
}
|
||||
Future<bool> initializeDomains() async {
|
||||
try {
|
||||
await KRDomain.kr_loadBaseDomain();
|
||||
_isInitialized = true;
|
||||
notifyListeners();
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
KRLogUtil.kr_e('❌ 达到最大重试次数 ($maxRetries),初始化失败', tag: 'KRSiteConfigService');
|
||||
return false;
|
||||
Future<bool> fetchSiteConfig() async {
|
||||
try {
|
||||
final result = await KRUserApi().kr_config();
|
||||
return await result.fold(
|
||||
(error) async {
|
||||
return false;
|
||||
},
|
||||
(cfg) async {
|
||||
try {
|
||||
if (cfg.kr_config.isNotEmpty) {
|
||||
final Map<String, dynamic> root = jsonDecode(cfg.kr_config);
|
||||
_siteConfig = KRSiteConfig.fromJson(root);
|
||||
AppConfig.getInstance().device_limit = getDeviceLimit();
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
} catch (_) {}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 打印配置信息
|
||||
void _printConfigInfo() {
|
||||
if (_siteConfig == null) return;
|
||||
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('📊 网站配置信息:', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'KRSiteConfigService');
|
||||
|
||||
// 站点信息
|
||||
KRLogUtil.kr_i('🏠 站点名称: ${_siteConfig!.site.siteName}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🏠 站点描述: ${_siteConfig!.site.siteDesc}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🏠 站点域名: ${_siteConfig!.site.host}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('💬 Crisp ID: ${_siteConfig!.site.crispId}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🏠 站点名称: ${_siteConfig!.site.siteName}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🏠 站点描述: ${_siteConfig!.site.siteDesc}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🏠 站点域名: ${_siteConfig!.site.host}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('💬 Crisp ID: ${_siteConfig!.site.crispId}',
|
||||
tag: 'KRSiteConfigService');
|
||||
|
||||
// 注册相关
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('📝 注册配置:', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 开放注册: ${isRegisterEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 手机号注册: ${isMobileRegisterEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 邮箱注册: ${isEmailRegisterEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 设备登录: ${isDeviceLoginEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 开放注册: ${isRegisterEnabled() ? "是" : "否"}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 手机号注册: ${isMobileRegisterEnabled() ? "是" : "否"}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 邮箱注册: ${isEmailRegisterEnabled() ? "是" : "否"}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 设备登录: ${isDeviceLoginEnabled() ? "是" : "否"}',
|
||||
tag: 'KRSiteConfigService');
|
||||
|
||||
// 验证相关
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🔐 验证配置:', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 邮箱验证: ${isEmailVerificationEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 手机验证: ${isMobileVerificationEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 登录验证: ${isLoginVerificationEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 注册验证: ${isRegisterVerificationEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 重置密码验证: ${isResetPasswordVerificationEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 邮箱验证: ${isEmailVerificationEnabled() ? "是" : "否"}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 手机验证: ${isMobileVerificationEnabled() ? "是" : "否"}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 登录验证: ${isLoginVerificationEnabled() ? "是" : "否"}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 注册验证: ${isRegisterVerificationEnabled() ? "是" : "否"}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(
|
||||
' ✓ 重置密码验证: ${isResetPasswordVerificationEnabled() ? "是" : "否"}',
|
||||
tag: 'KRSiteConfigService');
|
||||
|
||||
// 邀请相关
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🎁 邀请配置:', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 强制邀请码: ${isForcedInvite() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 推荐比例: ${_siteConfig!.invite.referralPercentage}%', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 强制邀请码: ${isForcedInvite() ? "是" : "否"}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 推荐比例: ${_siteConfig!.invite.referralPercentage}%',
|
||||
tag: 'KRSiteConfigService');
|
||||
|
||||
// 货币相关
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('💰 货币配置:', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 货币单位: ${_siteConfig!.currency.currencyUnit}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 货币符号: ${_siteConfig!.currency.currencySymbol}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 货币单位: ${_siteConfig!.currency.currencyUnit}',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 货币符号: ${_siteConfig!.currency.currencySymbol}',
|
||||
tag: 'KRSiteConfigService');
|
||||
|
||||
// OAuth 方法
|
||||
if (_siteConfig!.oauthMethods.isNotEmpty) {
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🔑 OAuth 方法: ${_siteConfig!.oauthMethods.join(", ")}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🔑 OAuth 方法: ${_siteConfig!.oauthMethods.join(", ")}',
|
||||
tag: 'KRSiteConfigService');
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('✅ 网站配置初始化成功', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
||||
tag: 'KRSiteConfigService');
|
||||
}
|
||||
|
||||
/// 是否开启手机号注册
|
||||
@ -352,10 +281,12 @@ class KRSiteConfigService extends ChangeNotifier {
|
||||
return true;
|
||||
}
|
||||
|
||||
final allowedDomains = domainSuffixList.split(',').map((d) => d.trim()).toList();
|
||||
final allowedDomains =
|
||||
domainSuffixList.split(',').map((d) => d.trim()).toList();
|
||||
final emailDomain = email.split('@').last.toLowerCase();
|
||||
|
||||
return allowedDomains.any((domain) => emailDomain.endsWith(domain.toLowerCase()));
|
||||
return allowedDomains
|
||||
.any((domain) => emailDomain.endsWith(domain.toLowerCase()));
|
||||
}
|
||||
|
||||
/// 获取Crisp客服系统ID
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_config.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
|
||||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
|
||||
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
|
||||
|
||||
Future<bool> ensureAccountExists() async {
|
||||
final app = KRAppRunData.getInstance();
|
||||
final account = app.kr_account.value;
|
||||
final isLoggedIn = app.kr_isLogin.value;
|
||||
final config = AppConfig.getInstance();
|
||||
|
||||
if (account == null ||
|
||||
account.isEmpty ||
|
||||
!isLoggedIn
|
||||
) {
|
||||
await HIDialog.show(
|
||||
message: '未检测到账号信息,请重试初始化',
|
||||
confirmText: '重试',
|
||||
preventBackDismiss: true,
|
||||
onConfirm: () {
|
||||
Get.offAllNamed(Routes.KR_SPLASH);
|
||||
},
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
var crispId = config.kr_website_id;
|
||||
var deviceLimitText = config.device_limit;
|
||||
var deviceLimit = int.tryParse(deviceLimitText) ?? 0;
|
||||
if ((crispId.isEmpty || crispId == '0') || deviceLimit == 0) {
|
||||
KRSiteConfigService().initialize();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -30,7 +30,7 @@ class KRCommonUtil {
|
||||
return;
|
||||
}
|
||||
|
||||
final websiteId = AppConfig.getInstance().kr_website_id;
|
||||
final websiteId = '47fcc1ac-9674-4ab1-9e3c-6b5666f59a38'; // AppConfig.getInstance().kr_website_id;
|
||||
if (websiteId.isEmpty) {
|
||||
HIDialog.show(
|
||||
title: '提示',
|
||||
|
||||
@ -31,7 +31,7 @@ class KRHttpAdapterUtil {
|
||||
client.findProxy = (url) {
|
||||
try {
|
||||
final proxyConfig = KRSingBoxImp.instance.kr_buildProxyRule();
|
||||
KRLogUtil.kr_i('🔍 请求使用代理: $proxyConfig, url: $url', tag: 'KRHttpAdapterUtil');
|
||||
// KRLogUtil.kr_i('🔍 请求使用代理: $proxyConfig, url: $url', tag: 'KRHttpAdapterUtil');
|
||||
return proxyConfig;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ 获取代理配置异常,回退到 DIRECT: $e', tag: 'KRHttpAdapterUtil');
|
||||
|
||||
@ -4,7 +4,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
|
||||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||
import 'package:kaer_with_panels/app/utils/account_guard.dart';
|
||||
|
||||
/// 🔹 HIBaseScaffold
|
||||
/// 用于统一页面结构:
|
||||
@ -114,8 +113,6 @@ class HIBaseScaffold extends StatelessWidget {
|
||||
if (showMenuButton) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final ok = await ensureAccountExists();
|
||||
if (!ok) return;
|
||||
Get.toNamed(Routes.HI_MENU);
|
||||
},
|
||||
child: Container(
|
||||
|
||||
@ -58,7 +58,7 @@ void main() async {
|
||||
|
||||
|
||||
// 初始化主题
|
||||
await KRThemeService().init();
|
||||
// await KRThemeService().init();
|
||||
|
||||
// 初始化翻译
|
||||
final translations = GetxTranslations();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user