783 lines
29 KiB
Dart
Executable File
783 lines
29 KiB
Dart
Executable File
import 'dart:convert';
|
||
import 'dart:math' show min;
|
||
|
||
import 'dart:async';
|
||
import 'package:flutter/widgets.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:kaer_with_panels/app/common/app_config.dart';
|
||
|
||
import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart';
|
||
import 'package:kaer_with_panels/app/services/kr_socket_service.dart';
|
||
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
|
||
import 'package:kaer_with_panels/app/services/kr_device_info_service.dart';
|
||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||
|
||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||
|
||
import '../services/api_service/kr_api.user.dart';
|
||
import '../services/singbox_imp/kr_sing_box_imp.dart';
|
||
import '../services/kr_site_config_service.dart';
|
||
import '../services/kr_subscribe_service.dart';
|
||
import '../utils/kr_event_bus.dart';
|
||
import '../../singbox/model/singbox_status.dart';
|
||
|
||
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
|
||
import 'package:kaer_with_panels/app/services/api_service/kr_auth_api.dart';
|
||
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
|
||
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
|
||
import 'package:kaer_with_panels/app/utils/kr_device_util.dart';
|
||
import 'package:openinstall_flutter_plugin/openinstall_flutter_plugin.dart';
|
||
import 'dart:io' show Platform;
|
||
|
||
class KRAppRunData {
|
||
static final KRAppRunData _instance = KRAppRunData._internal();
|
||
|
||
static const String _keyUserInfo = 'USER_INFO';
|
||
static const bool inviteDebugMode = true;
|
||
|
||
/// 登录token
|
||
String? kr_token;
|
||
|
||
/// 用户账号(使用响应式变量以便 UI 能监听变化)
|
||
final Rx<String?> kr_account = Rx<String?>(null);
|
||
|
||
/// 用户ID(使用响应式变量以便 UI 能监听变化)
|
||
final Rx<int?> kr_userId = Rx<int?>(null);
|
||
|
||
/// 用户邀请码(从用户信息接口获取)
|
||
final RxString kr_referCode = ''.obs;
|
||
|
||
/// 邀请人ID(谁邀请了当前用户)
|
||
final RxInt kr_refererId = 0.obs;
|
||
|
||
/// 用户余额
|
||
final RxInt kr_balance = 0.obs;
|
||
|
||
/// 佣金
|
||
final RxInt kr_commission = 0.obs;
|
||
|
||
/// 设备ID
|
||
String? deviceId;
|
||
|
||
/// 最后一次设备登录错误信息
|
||
String? kr_lastDeviceLoginError;
|
||
|
||
/// 区号
|
||
String? kr_areaCode;
|
||
|
||
// 需要被监听的属性,用 obs 包装
|
||
final kr_isLogin = false.obs;
|
||
|
||
// 存储临时待绑定的邀请码(处理唤醒数据比登录完成早的情况)
|
||
String? _kr_pendingInviteCode;
|
||
|
||
KRAppRunData._internal();
|
||
|
||
factory KRAppRunData() => _instance;
|
||
|
||
static KRAppRunData getInstance() {
|
||
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() {
|
||
// 设备登录的账号格式为 "device_设备ID"
|
||
return kr_account.value != null && kr_account.value!.startsWith('9000');
|
||
}
|
||
|
||
/// 🔧 修复2.1:验证Token格式是否有效
|
||
/// 检查Token是否符合JWT格式(header.payload.signature)
|
||
/// 这能有效防止被污染的或过期的Token数据
|
||
bool _kr_isValidToken(String token) {
|
||
try {
|
||
// JWT格式检查: header.payload.signature (三段,每段用.分隔)
|
||
final parts = token.split('.');
|
||
if (parts.length != 3) {
|
||
KRLogUtil.kr_w('❌ Token格式无效:分段数不对 (${parts.length} != 3)',
|
||
tag: 'AppRunData');
|
||
return false;
|
||
}
|
||
|
||
// 检查每一段是否为非空
|
||
for (var i = 0; i < parts.length; i++) {
|
||
if (parts[i].isEmpty) {
|
||
KRLogUtil.kr_w('❌ Token格式无效:第${i + 1}段为空', tag: 'AppRunData');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 尝试解码payload部分,验证是否是有效的base64和JSON
|
||
String payload = parts[1];
|
||
// 手动添加必要的padding
|
||
switch (payload.length % 4) {
|
||
case 0:
|
||
break;
|
||
case 2:
|
||
payload += '==';
|
||
break;
|
||
case 3:
|
||
payload += '=';
|
||
break;
|
||
default:
|
||
KRLogUtil.kr_w('❌ Token payload长度无效', tag: 'AppRunData');
|
||
return false;
|
||
}
|
||
|
||
// 尝试解码和解析
|
||
try {
|
||
final decodedBytes = base64.decode(payload);
|
||
final decodedString = utf8.decode(decodedBytes);
|
||
jsonDecode(decodedString); // 验证是否是有效JSON
|
||
|
||
KRLogUtil.kr_i('✅ Token格式验证通过', tag: 'AppRunData');
|
||
return true;
|
||
} catch (e) {
|
||
KRLogUtil.kr_w('❌ Token payload无法解析: $e', tag: 'AppRunData');
|
||
return false;
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('Token验证异常: $e', tag: 'AppRunData');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 从JWT token中解析userId
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// 保存用户信息
|
||
Future<void> kr_saveUserInfo(
|
||
String token,
|
||
String account,
|
||
) async {
|
||
KRLogUtil.kr_i('开始保存用户信息', tag: 'AppRunData');
|
||
|
||
try {
|
||
// 从JWT token中解析userId
|
||
kr_userId.value = _kr_parseUserIdFromToken(token);
|
||
KRLogUtil.kr_i('从JWT解析userId: ${kr_userId.value}', tag: 'AppRunData');
|
||
final accountText = account.startsWith('device_') ? '9000${kr_userId}' : account;
|
||
// 更新内存中的数据
|
||
kr_token = token;
|
||
kr_account.value = accountText;
|
||
|
||
|
||
final Map<String, dynamic> userInfo = {
|
||
'token': token,
|
||
'account': accountText,
|
||
};
|
||
|
||
KRLogUtil.kr_i('准备保存用户信息到存储', tag: 'AppRunData');
|
||
|
||
await KRSecureStorage().kr_saveData(
|
||
key: _keyUserInfo,
|
||
value: jsonEncode(userInfo),
|
||
);
|
||
|
||
// 验证保存是否成功
|
||
final savedData = await KRSecureStorage().kr_readData(key: _keyUserInfo);
|
||
KRLogUtil.kr_i('用户信息-kr_readData$savedData', tag: 'AppRunData');
|
||
if (savedData == null || savedData.isEmpty) {
|
||
KRLogUtil.kr_e('数据保存后无法读取,保存失败', tag: 'AppRunData');
|
||
kr_isLogin.value = false;
|
||
return;
|
||
}
|
||
|
||
KRLogUtil.kr_i('用户信息保存成功,设置登录状态为true', tag: 'AppRunData');
|
||
|
||
// 只有在保存成功后才设置登录状态
|
||
kr_isLogin.value = true;
|
||
KRLogUtil.kr_i('用户信息-kr_isLogin$kr_isLogin', tag: 'AppRunData');
|
||
|
||
KRLogUtil.kr_i('🔍 [AppRunData] 检查登录模式: account=$account',
|
||
tag: 'AppRunData');
|
||
|
||
// 不等待userinfo接口返回
|
||
_fetchUserInfo();
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('保存用户信息失败: $e', tag: 'AppRunData');
|
||
// 如果出错,重置登录状态
|
||
kr_isLogin.value = false;
|
||
rethrow; // 重新抛出异常,让调用者知道保存失败
|
||
}
|
||
}
|
||
|
||
/// 退出登录(其实是设备重新登录)
|
||
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');
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('停止 VPN 服务失败: $e', tag: 'Logout');
|
||
}
|
||
|
||
// 断开 Socket 连接
|
||
await _kr_disconnectSocket();
|
||
|
||
// 清理用户信息
|
||
kr_token = null;
|
||
kr_account.value = null;
|
||
kr_userId.value = null;
|
||
kr_areaCode = null;
|
||
|
||
// 删除存储的用户信息
|
||
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');
|
||
}
|
||
} catch (e) {
|
||
// 忽略异常:如果订阅服务未初始化或不可用,直接继续
|
||
KRLogUtil.kr_d('⚠️ 无法获取订阅服务,跳过清理: $e', tag: 'AppRunData');
|
||
}
|
||
|
||
// 5️⃣ 执行设备登录
|
||
final success = await kr_checkAndPerformDeviceLogin();
|
||
|
||
if (!success) {
|
||
// 设备登录失败 → 提示用户重试
|
||
HIDialog.show(
|
||
message: '设备登录失败\n\n原因:${kr_lastDeviceLoginError ?? "未知错误"}\n\n请检查网络或重试',
|
||
confirmText: '重试',
|
||
preventBackDismiss: true,
|
||
onConfirm: () async {
|
||
await kr_loginOut(); // 递归重试
|
||
},
|
||
);
|
||
return; // 阻止跳首页
|
||
}
|
||
|
||
// 等待一小段时间,确保登录状态已经更新
|
||
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');
|
||
}
|
||
|
||
Get.offAllNamed(Routes.KR_HOME);
|
||
});
|
||
}
|
||
|
||
Future<void> kr_loginOut_loading() async {
|
||
KRCommonUtil.kr_showLoading();
|
||
// 先将登录状态设置为 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) {
|
||
KRCommonUtil.kr_hideLoading();
|
||
KRLogUtil.kr_e('停止 VPN 服务失败: $e', tag: 'Logout');
|
||
}
|
||
|
||
// 断开 Socket 连接
|
||
await _kr_disconnectSocket();
|
||
|
||
// 清理用户信息
|
||
kr_token = null;
|
||
kr_account.value = null;
|
||
kr_userId.value = null;
|
||
kr_areaCode = null;
|
||
|
||
// 删除存储的用户信息
|
||
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');
|
||
}
|
||
} catch (e) {
|
||
KRCommonUtil.kr_hideLoading();
|
||
// 忽略异常:如果订阅服务未初始化或不可用,直接继续
|
||
KRLogUtil.kr_d('⚠️ 无法获取订阅服务,跳过清理: $e', tag: 'AppRunData');
|
||
}
|
||
|
||
// 5️⃣ 执行设备登录
|
||
final success = await kr_checkAndPerformDeviceLogin();
|
||
|
||
if (!success) {
|
||
KRCommonUtil.kr_hideLoading();
|
||
// 设备登录失败 → 提示用户重试
|
||
HIDialog.show(
|
||
message: '设备登录失败\n\n原因:${kr_lastDeviceLoginError ?? "未知错误"}\n\n请检查网络或重试',
|
||
confirmText: '重试',
|
||
preventBackDismiss: true,
|
||
onConfirm: () async {
|
||
await kr_loginOut(); // 递归重试
|
||
},
|
||
);
|
||
return; // 阻止跳首页
|
||
}
|
||
|
||
// 等待一小段时间,确保登录状态已经更新
|
||
await Future.delayed(const Duration(milliseconds: 300));
|
||
|
||
// 刷新订阅信息
|
||
KRLogUtil.kr_i('🔄 开始刷新订阅信息...', tag: 'DeviceManagement');
|
||
try {
|
||
await KRSubscribeService().kr_refreshAll();
|
||
KRLogUtil.kr_i('✅ 订阅信息刷新成功', tag: 'DeviceManagement');
|
||
} catch (e) {
|
||
KRCommonUtil.kr_hideLoading();
|
||
KRLogUtil.kr_e('订阅信息刷新失败: $e', tag: 'DeviceManagement');
|
||
}
|
||
KRCommonUtil.kr_hideLoading();
|
||
Get.offAllNamed(Routes.KR_HOME);
|
||
}
|
||
|
||
/// 检查并执行设备登录
|
||
Future<bool> kr_checkAndPerformDeviceLogin() async {
|
||
try {
|
||
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) {
|
||
kr_lastDeviceLoginError = error.msg;
|
||
print('❌ 设备登录失败: ${error.msg}');
|
||
KRLogUtil.kr_e('❌ 设备登录失败: ${error.msg}', tag: 'SplashController');
|
||
_logStepTiming('设备登录完成');
|
||
return false;
|
||
},
|
||
(token) async {
|
||
print('✅ 设备登录成功!Token: $token');
|
||
KRLogUtil.kr_i('✅ 设备登录成功', tag: 'SplashController');
|
||
|
||
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||
_logStepTiming('开始保存用户信息');
|
||
await kr_saveUserInfo(
|
||
token,
|
||
'device_$deviceId', // 临时账号
|
||
);
|
||
_logStepTiming('保存用户信息完成');
|
||
|
||
kr_isLogin.value = true;
|
||
print('✅ 已标记为登录状态');
|
||
|
||
// 静默邀请绑定
|
||
if (inviteDebugMode) {
|
||
// Debug 模式下等待调试弹窗完成,避免页面跳转
|
||
await _kr_handleSilentInvitation();
|
||
} else {
|
||
// 正式环境异步执行,不阻塞主流程
|
||
_kr_handleSilentInvitation();
|
||
}
|
||
|
||
_logStepTiming('设备登录完成');
|
||
return true;
|
||
},
|
||
);
|
||
} catch (e, stackTrace) {
|
||
print('❌ 设备登录检查异常: $e');
|
||
print('📚 堆栈跟踪: $stackTrace');
|
||
kr_lastDeviceLoginError = e.toString();
|
||
KRLogUtil.kr_e('❌ 设备登录检查异常: $e', tag: 'SplashController');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 初始化用户信息
|
||
Future<void> kr_initializeUserInfo() async {
|
||
KRLogUtil.kr_i('开始初始化用户信息', tag: 'AppRunData');
|
||
|
||
try {
|
||
deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||
final String? userInfoString =
|
||
await KRSecureStorage().kr_readData(key: _keyUserInfo);
|
||
|
||
if (userInfoString != null && userInfoString.isNotEmpty) {
|
||
KRLogUtil.kr_i('找到存储的用户信息,开始解析', tag: 'AppRunData');
|
||
|
||
try {
|
||
final Map<String, dynamic> userInfo = jsonDecode(userInfoString);
|
||
kr_token = userInfo['token'];
|
||
kr_account.value = userInfo['account'];
|
||
|
||
// 从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');
|
||
|
||
// 🔧 修复2:验证token有效性和账号信息完整性
|
||
// 防止恢复被污染的或过期的数据
|
||
if (kr_token != null &&
|
||
kr_token!.isNotEmpty &&
|
||
_kr_isValidToken(kr_token!)) {
|
||
// token格式验证通过(JWT格式检查)
|
||
if (kr_account.value != null && kr_account.value!.isNotEmpty) {
|
||
// 账号信息完整
|
||
KRLogUtil.kr_i('✅ Token和账号验证通过,设置登录状态为true', tag: 'AppRunData');
|
||
KRLogUtil.kr_i('📊 恢复账号: ${kr_account.value}', tag: 'AppRunData');
|
||
kr_isLogin.value = true;
|
||
|
||
// 🔧 新增:恢复登录状态后也尝试检测一次静默邀请(重要:针对已安装后启动的情况)
|
||
_kr_handleSilentInvitation();
|
||
} else {
|
||
// 账号信息为空,清理旧数据
|
||
KRLogUtil.kr_w('⚠️ 账号信息为空,清理该条用户数据', tag: 'AppRunData');
|
||
await kr_loginOut();
|
||
}
|
||
} else {
|
||
// 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');
|
||
}
|
||
await kr_loginOut();
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('解析用户信息失败: $e', tag: 'AppRunData');
|
||
await kr_loginOut();
|
||
}
|
||
} else {
|
||
KRLogUtil.kr_i('未找到存储的用户信息,设置为未登录状态', tag: 'AppRunData');
|
||
kr_isLogin.value = false;
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('初始化用户信息过程出错: $e', tag: 'AppRunData');
|
||
kr_isLogin.value = false;
|
||
}
|
||
|
||
KRLogUtil.kr_i('用户信息初始化完成,登录状态: ${kr_isLogin.value}', tag: 'AppRunData');
|
||
}
|
||
|
||
/// 建立 Socket 连接
|
||
/// /// ⚠️ 已遗弃:wsBaseUrl 不再使用
|
||
Future<void> _kr_connectSocket(String userId) async {
|
||
// 已遗弃,不再建立 Socket 连接
|
||
KRLogUtil.kr_i('Socket 连接已遗弃', tag: 'AppRunData');
|
||
// 如果需要实时消息,使用其他实现方式
|
||
}
|
||
|
||
/// 处理接收到的消息
|
||
void _kr_handleMessage(Map<String, dynamic> message) {
|
||
try {
|
||
final String method = message['method'] as String;
|
||
switch (method) {
|
||
case 'kicked_device':
|
||
KRLogUtil.kr_i('超出登录设备限制', tag: 'AppRunData');
|
||
kr_loginOut();
|
||
break;
|
||
case 'kicked_admin':
|
||
KRLogUtil.kr_i('强制退出', tag: 'AppRunData');
|
||
kr_loginOut();
|
||
break;
|
||
case 'subscribe_update':
|
||
KRLogUtil.kr_i('订阅信息已更新', tag: 'AppRunData');
|
||
// 发送订阅更新事件
|
||
KREventBus().kr_sendMessage(KRMessageType.kr_subscribe_update);
|
||
break;
|
||
default:
|
||
KRLogUtil.kr_w('收到未知类型的消息: $message', tag: 'AppRunData');
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('处理消息失败: $e', tag: 'AppRunData');
|
||
}
|
||
}
|
||
|
||
/// 处理连接状态变化
|
||
void _kr_handleConnectionState(bool isConnected) {
|
||
KRLogUtil.kr_i('WebSocket 连接状态: ${isConnected ? "已连接" : "已断开"}',
|
||
tag: 'AppRunData');
|
||
}
|
||
|
||
/// 断开 Socket 连接
|
||
Future<void> _kr_disconnectSocket() async {
|
||
await KrSocketService.instance.disconnect();
|
||
}
|
||
|
||
/// 获取用户详细信息(登录后调用)
|
||
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');
|
||
|
||
final result = await KRUserApi.kr_getUserInfo();
|
||
|
||
result.fold(
|
||
(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;
|
||
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');
|
||
|
||
// 保存到全局状态
|
||
kr_referCode.value = userInfo.referCode;
|
||
kr_refererId.value = userInfo.refererId;
|
||
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');
|
||
},
|
||
);
|
||
} catch (e, stackTrace) {
|
||
KRLogUtil.kr_e('💥 [AppRunData] 获取用户信息异常: $e', tag: 'AppRunData');
|
||
KRLogUtil.kr_e('📚 [AppRunData] 错误堆栈: $stackTrace', tag: 'AppRunData');
|
||
}
|
||
}
|
||
/// 处理静默邀请
|
||
Future<void> _kr_handleSilentInvitation() async {
|
||
KRLogUtil.kr_i('🚀 开始处理静默邀请...', tag: 'AppRunData');
|
||
String? inviteCode;
|
||
|
||
// 1. 先检查是否有之前通过唤醒/安装暂存的邀请码
|
||
if (_kr_pendingInviteCode != null && _kr_pendingInviteCode!.isNotEmpty) {
|
||
inviteCode = _kr_pendingInviteCode;
|
||
KRLogUtil.kr_i('📎 使用暂存的待绑定邀请码: $inviteCode', tag: 'AppRunData');
|
||
}
|
||
|
||
// 2. 如果没有暂存码,尝试从平台环境获取
|
||
if (inviteCode == null || inviteCode!.isEmpty) {
|
||
try {
|
||
if (Platform.isMacOS || Platform.isWindows) {
|
||
inviteCode = await KRDeviceUtil().kr_getDesktopInviteCode();
|
||
} else if (Platform.isAndroid || Platform.isIOS) {
|
||
final Completer<String?> completer = Completer<String?>();
|
||
OpeninstallFlutterPlugin().install((data) async {
|
||
final code = kr_parseInviteCodeFromData(data);
|
||
KRLogUtil.kr_i('收到 OpenInstall 安装数据: $data, 解析出邀请码: $code', tag: 'AppRunData');
|
||
if (!completer.isCompleted) completer.complete(code);
|
||
});
|
||
inviteCode = await completer.future
|
||
.timeout(const Duration(seconds: 8), onTimeout: () => null);
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('获取静默邀请码异常: $e', tag: 'AppRunData');
|
||
}
|
||
}
|
||
|
||
if (inviteCode != null && inviteCode!.isNotEmpty) {
|
||
KRLogUtil.kr_i('🔍 最终识别到邀请码: $inviteCode', tag: 'AppRunData');
|
||
|
||
if (inviteDebugMode) {
|
||
// Debug 模式下弹出对话框确认
|
||
final bool isDesktop = Platform.isMacOS || Platform.isWindows;
|
||
await HIDialog.show(
|
||
title: isDesktop ? '调试:唤醒识别到邀请码' : '调试:邀请码绑定确认',
|
||
message: isDesktop
|
||
? '桌面端识别到邀请码:$inviteCode\n是否进行绑定?'
|
||
: '识别到邀请码:$inviteCode\n是否进行绑定?',
|
||
confirmText: isDesktop ? '绑定' : '确认绑定',
|
||
cancelText: isDesktop ? '跳过' : '取消',
|
||
onConfirm: () async {
|
||
await _kr_performInviteBinding(inviteCode!);
|
||
_kr_pendingInviteCode = null; // 绑定后清除
|
||
},
|
||
onCancel: () {
|
||
_kr_pendingInviteCode = null; // 取消也清除,避免重复弹窗
|
||
},
|
||
);
|
||
} else {
|
||
// 正式环境静默绑定
|
||
await _kr_performInviteBinding(inviteCode!);
|
||
_kr_pendingInviteCode = null; // 绑定后清除
|
||
}
|
||
} else {
|
||
KRLogUtil.kr_i('⚠️ 未识别到有效的邀请码,跳外静默绑定', tag: 'AppRunData');
|
||
}
|
||
}
|
||
|
||
/// 执行邀请码绑定请求
|
||
Future<void> _kr_performInviteBinding(String inviteCode) async {
|
||
KRLogUtil.kr_i('🚀 准备执行邀请码绑定: $inviteCode', tag: 'AppRunData');
|
||
final result =
|
||
await KRUserApi().hi_inviteCode(inviteCode, isSilentInvite: true);
|
||
result.fold(
|
||
(error) => KRLogUtil.kr_w('❌ 邀请绑定失败: ${error.msg}', tag: 'AppRunData'),
|
||
(_) => KRLogUtil.kr_i('✅ 邀请绑定成功', tag: 'AppRunData'),
|
||
);
|
||
}
|
||
|
||
/// 公开方法:直接处理 OpenInstall 返回的原始数据(用于唤醒等场景)
|
||
Future<void> kr_handleOpenInstallData(Map<dynamic, dynamic> data) async {
|
||
final code = kr_parseInviteCodeFromData(data);
|
||
if (code != null && code.isNotEmpty) {
|
||
KRLogUtil.kr_i('🔗 收到 OpenInstall 原始参数并触发解析: $code', tag: 'AppRunData');
|
||
|
||
// 暂存该邀请码
|
||
_kr_pendingInviteCode = code;
|
||
|
||
// 如果当前已经是登录状态,则由于是唤醒(Hot Start)触发,直接按业务逻辑处理
|
||
if (kr_isLogin.value) {
|
||
KRLogUtil.kr_i('✅ 用户已登录,立即处理唤醒绑定', tag: 'AppRunData');
|
||
if (inviteDebugMode) {
|
||
await HIDialog.show(
|
||
title: '调试:唤醒识别到邀请码',
|
||
message: '唤醒数据解析到邀请码:$code\n是否进行绑定?',
|
||
confirmText: '绑定',
|
||
cancelText: '跳过',
|
||
onConfirm: () async {
|
||
await _kr_performInviteBinding(code);
|
||
_kr_pendingInviteCode = null;
|
||
},
|
||
onCancel: () => _kr_pendingInviteCode = null,
|
||
);
|
||
} else {
|
||
_kr_performInviteBinding(code).then((_) => _kr_pendingInviteCode = null);
|
||
}
|
||
} else {
|
||
KRLogUtil.kr_i('⏳ 用户未登录,已暂存邀请码,等待登录完成后自动绑定', tag: 'AppRunData');
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 从 OpenInstall 数据中解析邀请码
|
||
String? kr_parseInviteCodeFromData(Map<dynamic, dynamic> data) {
|
||
try {
|
||
if (data.containsKey('bindData')) {
|
||
final bindDataStr = data['bindData'] as String?;
|
||
if (bindDataStr != null && bindDataStr.isNotEmpty) {
|
||
final Map<String, dynamic> bindData = jsonDecode(bindDataStr);
|
||
return bindData['inviteCode']?.toString();
|
||
}
|
||
}
|
||
} catch (e) {
|
||
KRLogUtil.kr_e('解析 OpenInstall 数据中邀请码失败: $e', tag: 'AppRunData');
|
||
}
|
||
return null;
|
||
}
|
||
}
|