hi-client/lib/app/modules/kr_splash/controllers/kr_splash_controller.dart
Rust 442ea458b7 1. /lib/app/common/app_config.dart
- P0: 限制递归重试次数(最多5次)
    - P1: 减少超时时间(3s→2s, 6s→4s)
    - P1: 移除重复域名配置
  2.  /lib/app/services/kr_site_config_service.dart
    - P1: 减少网络超时(10s→5s)
  3.  /lib/app/modules/kr_splash/controllers/kr_splash_controller.dart
    - P0: 总体超时保护(30秒)
    - P2: 非阻塞初始化
    - P2: 后台任务超时保护(网站配置10s, 设备登录8s)
    - P2: 完善错误处理(区分超时/网络/HTTP错误)
    - P3: 跳过初始化功能
  4.  /lib/app/modules/kr_splash/views/kr_splash_view.dart
    - P3: 添加"跳过"按钮

(cherry picked from commit 0a9fe429919fe9ae85b7769df123b72d7e33c6b1)
2025-10-31 19:21:18 -07:00

505 lines
22 KiB
Dart
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:get/get.dart';
import 'dart:convert';
import 'dart:io' show Platform, SocketException;
import 'dart:math';
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:kaer_with_panels/app/utils/kr_network_check.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/common/app_config.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
import 'package:kaer_with_panels/app/services/kr_device_info_service.dart';
import 'package:kaer_with_panels/app/services/api_service/kr_auth_api.dart';
import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart';
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
import 'package:flutter/material.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
import 'package:kaer_with_panels/app/modules/kr_home/models/kr_home_views_status.dart';
import 'dart:async';
class KRSplashController extends GetxController {
// 加载状态
final RxBool kr_isLoading = true.obs;
// 错误状态
final RxBool kr_hasError = false.obs;
// 错误信息
final RxString kr_errorMessage = ''.obs;
/// 订阅服务
final KRSubscribeService kr_subscribeService = KRSubscribeService();
// 倒计时
// final count = 0.obs;
// 是否正在加载
final isLoading = true.obs;
// // 是否初始化成功
// final isInitialized = false.obs;
// 启动开始时间(用于计算总耗时)
late final DateTime _startTime;
late DateTime _stepStartTime;
@override
void onInit() {
super.onInit();
// 记录启动开始时间
_startTime = DateTime.now();
_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');
// 🔧 P2优化网站配置和设备登录在后台执行不阻塞主流程
print('🌐 启动后台任务:网站配置加载...');
KRLogUtil.kr_i('🌐 后台任务:网站配置和设备登录', tag: 'SplashController');
_kr_initSiteConfig(); // 不等待完成,在后台执行
// 立即开始主初始化流程
print('🔧 立即开始主初始化流程...');
_kr_initialize();
}
// 记录步骤耗时的辅助方法
void _logStepTiming(String stepName) {
final now = DateTime.now();
final stepDuration = now.difference(_stepStartTime);
final totalDuration = now.difference(_startTime);
final stepMs = stepDuration.inMilliseconds;
final totalMs = totalDuration.inMilliseconds;
print('[SPLASH_TIMING] ⏱️ $stepName 耗时: ${stepMs}ms | 累计: ${totalMs}ms');
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 {
// 🔧 P2优化为后台任务添加10秒超时保护
await Future.any([
_executeSiteConfigInit(),
Future.delayed(const Duration(seconds: 10), () {
throw TimeoutException('网站配置加载超时10秒');
}),
]);
} on TimeoutException catch (e) {
print('⏱️ 网站配置加载超时,应用将使用默认配置: $e');
KRLogUtil.kr_w('⏱️ 网站配置加载超时: $e', tag: 'SplashController');
} catch (e) {
print('❌ 网站配置初始化异常: $e');
KRLogUtil.kr_e('❌ 网站配置初始化异常: $e', tag: 'SplashController');
}
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
}
/// 执行网站配置初始化
Future<void> _executeSiteConfigInit() async {
print('📞 准备调用 KRSiteConfigService().initialize()...');
final success = await KRSiteConfigService().initialize();
final crispId = await KRSiteConfigService().getCrispId();
print('📞 KRSiteConfigService().initialize() 返回: $success');
if (success) {
final config = AppConfig.getInstance();
config.kr_website_id = crispId;
print('📞 KRSiteConfigService().initialize() 返回: $crispId');
print('AppConfig website_id 已更新为: ${config.kr_website_id}');
print('[SPLASH_TIMING] ✅ 网站配置初始化成功');
await _kr_checkAndPerformDeviceLogin();
} else {
print('⚠️ 网站配置初始化失败,将使用默认配置');
KRLogUtil.kr_w('⚠️ 网站配置初始化失败,将使用默认配置', tag: 'SplashController');
}
}
/// 检查并执行设备登录(后台执行,有超时保护)
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('✅ 设备登录已启用,开始初始化设备信息...');
// 🔧 P2优化为设备登录添加8秒超时保护
await Future.any([
_executeDeviceLogin(),
Future.delayed(const Duration(seconds: 8), () {
throw TimeoutException('设备登录超时8秒');
}),
]);
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 {
KRLogUtil.kr_i('🔧 开始执行 _kr_initialize', tag: 'SplashController');
// 🔧 P0修复添加总体超时保护30秒
await Future.any([
_executeInitialization(),
Future.delayed(const Duration(seconds: 30), () {
throw TimeoutException('初始化超时30秒内未完成');
}),
]);
} on TimeoutException catch (e) {
// 🔧 P2优化超时错误提供更友好的提示
KRLogUtil.kr_e('⏱️ 初始化超时: $e', tag: 'SplashController');
print('⏱️ 初始化超时,直接跳转到主页');
// 超时后直接跳转到主页,让用户可以手动重试
Get.offAllNamed(Routes.KR_MAIN);
} on SocketException catch (e) {
// 🔧 P2优化网络连接错误
KRLogUtil.kr_e('❌ 网络连接错误: $e', tag: 'SplashController');
_handleError('网络连接失败', '请检查您的网络连接是否正常,然后重试。');
} on DioException catch (e) {
// 🔧 P2优化HTTP请求错误
KRLogUtil.kr_e('❌ HTTP请求错误: ${e.type} - ${e.message}', tag: 'SplashController');
final errorMsg = _getDioErrorMessage(e);
_handleError('服务请求失败', errorMsg);
} catch (e) {
// 🔧 P2优化其他未知错误
KRLogUtil.kr_e('❌ 未知初始化异常: $e', tag: 'SplashController');
_handleError('初始化失败', '应用启动时发生错误,请尝试重启应用或清除缓存。');
}
}
/// 处理错误并显示
void _handleError(String title, String message) {
kr_hasError.value = true;
kr_errorMessage.value = '$title\n\n$message';
}
/// 获取Dio错误的友好提示
String _getDioErrorMessage(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return '请求超时,请检查网络连接后重试。';
case DioExceptionType.badResponse:
final statusCode = e.response?.statusCode;
if (statusCode == 404) {
return '服务地址未找到404请联系管理员。';
} else if (statusCode == 500) {
return '服务器内部错误500请稍后重试。';
} else if (statusCode == 401 || statusCode == 403) {
return '访问被拒绝($statusCode),请检查配置。';
}
return '服务器返回错误($statusCode),请稍后重试。';
case DioExceptionType.cancel:
return '请求已取消';
case DioExceptionType.connectionError:
return '无法连接到服务器,请检查网络设置。';
default:
return '网络请求失败,请检查网络后重试。';
}
}
// 🔧 P0修复将原来的初始化逻辑提取到单独方法
Future<void> _executeInitialization() async {
// 只在手机端检查网络权限
if (Platform.isIOS || Platform.isAndroid) {
KRLogUtil.kr_i('📱 移动平台,检查网络权限...', tag: 'SplashController');
final bool hasNetworkPermission = await KRNetworkCheck.kr_initialize(
Get.context!,
onPermissionGranted: () async {
await _kr_continueInitialization();
},
);
if (!hasNetworkPermission) {
kr_hasError.value = true;
kr_errorMessage.value = AppTranslations.kr_splash.kr_networkPermissionFailed;
KRLogUtil.kr_e('❌ 网络权限检查失败', tag: 'SplashController');
return;
}
} else {
// 非手机端直接继续初始化
KRLogUtil.kr_i('💻 桌面平台,直接执行初始化', tag: 'SplashController');
await _kr_continueInitialization();
}
}
Future<void> _kr_continueInitialization() async {
try {
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
KRLogUtil.kr_i('🚀 启动页主流程开始...', tag: 'SplashController');
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
// 只在手机端检查网络连接
if (Platform.isIOS || Platform.isAndroid) {
KRLogUtil.kr_i('📱 检查网络连接...', tag: 'SplashController');
final bool isConnected = await KRNetworkCheck.kr_checkNetworkConnection();
if (!isConnected) {
kr_hasError.value = true;
kr_errorMessage.value = AppTranslations.kr_splash.kr_networkConnectionFailed;
KRLogUtil.kr_e('❌ 网络连接失败', tag: 'SplashController');
return;
}
KRLogUtil.kr_i('✅ 网络连接正常', tag: 'SplashController');
} else {
KRLogUtil.kr_i('💻 桌面平台,跳过网络连接检查', tag: 'SplashController');
}
// 网络检查完成后执行设备登录(始终支持)
_logStepTiming('开始设备登录检查');
// 5⃣ 执行设备登录
final success = await KRAppRunData.getInstance().kr_checkAndPerformDeviceLogin();
if (!success) {
// 设备登录失败 → 提示用户重试
HIDialog.show(
message: '设备登录失败,请检查网络或重试',
confirmText: '重试',
preventBackDismiss: true,
onConfirm: () async {
await _kr_initialize(); // 递归重试
},
);
return;
}
_logStepTiming('设备登录检查完成');
// 初始化配置
KRLogUtil.kr_i('⚙️ 开始初始化应用配置...', tag: 'SplashController');
await AppConfig().initConfig(
onSuccess: () async {
// 配置初始化成功,继续后续步骤
KRLogUtil.kr_i('✅ 应用配置初始化成功,继续后续步骤', tag: 'SplashController');
await _kr_continueAfterConfig();
},
);
} catch (e) {
// 配置初始化失败,显示错误信息
KRLogUtil.kr_e('❌ 启动页初始化异常: $e', tag: 'SplashController');
kr_hasError.value = true;
kr_errorMessage.value = '${AppTranslations.kr_splash.kr_initializationFailed}$e';
}
}
// 配置初始化成功后的后续步骤
Future<void> _kr_continueAfterConfig() async {
try {
// 用户信息初始化完成后,立即初始化订阅服务
print('[SPLASH_TIMING] 🔄 开始初始化订阅服务...');
KRLogUtil.kr_i('[SPLASH_TIMING] 🔄 开始初始化订阅服务', tag: 'SplashController');
_kr_ensureSubscribeServiceInitialized();
// 初始化SingBox
_logStepTiming('开始SingBox初始化');
await KRSingBoxImp.instance.init();
_logStepTiming('SingBox初始化完成');
// 验证登录状态是否已正确设置
final loginStatus = KRAppRunData.getInstance().kr_isLogin.value;
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');
// 直接导航到主页(无论是否登录,主页会根据登录状态显示不同内容)
_logStepTiming('开始页面导航');
if(loginStatus) {
// 直接导航到主页
Get.offAllNamed(Routes.KR_HOME);
}else {
kr_hasError.value = true;
kr_errorMessage.value = '登录:${AppTranslations.kr_splash.kr_initializationFailed}';
}
_logStepTiming('页面导航完成');
} catch (e) {
// 计算错误时的总耗时
final endTime = DateTime.now();
final totalDuration = endTime.difference(_startTime);
final totalMs = totalDuration.inMilliseconds;
print('❌ 启动失败时间: ${endTime.toIso8601String()}');
print('🕐 启动失败总耗时: ${totalMs}ms (${totalDuration.inSeconds}.${(totalMs % 1000).toString().padLeft(3, '0')}s)');
// 后续步骤失败,显示错误信息
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';
}
}
/// 确保订阅服务初始化
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;
kr_errorMessage.value = '';
_kr_initialize();
}
// 🔧 P3优化跳过初始化直接进入主页
void kr_skipInitialization() {
KRLogUtil.kr_i('⏭️ 用户选择跳过初始化', tag: 'SplashController');
print('⏭️ 用户跳过初始化,直接进入主页');
// 重置状态
kr_hasError.value = false;
kr_errorMessage.value = '';
kr_isLoading.value = false;
// 直接跳转到主页
Get.offAllNamed(Routes.KR_MAIN);
}
@override
void onReady() {
super.onReady();
}
@override
void onClose() {
super.onClose();
}
}