hi-client/lib/app/common/app_run_data.dart
speakeloudest 445b1e0352
Some checks failed
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (iOS/tvOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 构建 iOS (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
feat: 完成代码
2025-10-31 04:00:26 -07:00

453 lines
16 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 'dart:convert';
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/modules/kr_main/controllers/kr_main_controller.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/kr_announcement_service.dart';
import '../services/singbox_imp/kr_sing_box_imp.dart';
import '../services/kr_site_config_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';
class KRAppRunData {
static final KRAppRunData _instance = KRAppRunData._internal();
static const String _keyUserInfo = 'USER_INFO';
/// 登录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;
/// 用户余额
final RxInt kr_balance = 0.obs;
/// 佣金
final RxInt kr_commission = 0.obs;
/// 登录类型
KRLoginType? kr_loginType;
/// 设备ID
String? deviceId;
/// 区号
String? kr_areaCode;
// 需要被监听的属性,用 obs 包装
final kr_isLogin = false.obs;
KRAppRunData._internal();
factory KRAppRunData() => _instance;
static KRAppRunData getInstance() {
return _instance;
}
/// 判断是否是设备登录(游客模式)
bool isDeviceLogin() {
// 设备登录的账号格式为 "device_设备ID"
return kr_account.value != null && kr_account.value!.startsWith('9000');
}
/// 从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];
// 手动添加必要的paddingbase64要求长度是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,
KRLoginType loginType,
String? areaCode) 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;
kr_loginType = loginType;
kr_areaCode = areaCode;
final Map<String, dynamic> userInfo = {
'token': token,
'account': accountText,
'loginType': loginType.value,
'areaCode': areaCode ?? "",
};
// _kr_connectSocket(kr_userId.value.toString());
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');
// 🔧 非游客模式下,调用用户信息接口获取 refer_code 等信息
KRLogUtil.kr_i('🔍 [AppRunData] 检查登录模式: account=$account', tag: 'AppRunData');
await _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_loginType = null;
kr_areaCode = null;
// 删除存储的用户信息
await KRSecureStorage().kr_deleteData(key: _keyUserInfo);
// 重置公告显示状态
KRAnnouncementService().kr_reset();
// 5⃣ 执行设备登录
final success = await kr_checkAndPerformDeviceLogin();
if (!success) {
// 设备登录失败 → 提示用户重试
HIDialog.show(
message: '设备登录失败,请检查网络或重试',
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<bool> kr_checkAndPerformDeviceLogin() async {
try {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('🔍 开始执行设备登录...');
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
// 初始化设备信息服务
await KRDeviceInfoService().initialize();
KRLogUtil.kr_i('🔐 开始执行设备登录', tag: 'AppRunData');
// 执行设备登录
final authApi = KRAuthApi();
final result = await authApi.kr_deviceLogin();
return await result.fold(
(error) {
print('❌ 设备登录失败: ${error.msg}');
KRLogUtil.kr_e('❌ 设备登录失败: ${error.msg}', tag: 'SplashController');
return false;
},
(token) async {
print('✅ 设备登录成功Token: $token');
KRLogUtil.kr_i('✅ 设备登录成功', tag: 'SplashController');
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
await kr_saveUserInfo(
token,
'device_$deviceId', // 临时账号
KRLoginType.kr_email,
null, // 设备登录无需区号
);
kr_isLogin.value = true;
print('✅ 已标记为登录状态');
return true;
},
);
} catch (e, stackTrace) {
print('❌ 设备登录检查异常: $e');
print('📚 堆栈跟踪: $stackTrace');
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'];
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');
// 验证token有效性
if (kr_token != null && kr_token!.isNotEmpty) {
KRLogUtil.kr_i('设置登录状态为true', tag: 'AppRunData');
kr_isLogin.value = true;
// 设备登录模式不需要调用用户信息接口
// 用户ID将从订阅信息或其他途径获取
KRLogUtil.kr_i('已登录,跳过用户信息接口调用', tag: 'AppRunData');
} else {
KRLogUtil.kr_w('Token为空设置为未登录状态', tag: 'AppRunData');
kr_isLogin.value = false;
}
} 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_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_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');
}
}
}