LighthouseApp/lib/app/common/app_run_data.dart
speakeloudest 75d4c48e41
Some checks failed
Build Windows / build (push) Has been cancelled
feat: 源码提交
2025-10-19 23:30:54 -07:00

302 lines
9.6 KiB
Dart
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

import 'dart:convert';
import 'dart:async';
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/utils/kr_device_util.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 '../utils/kr_event_bus.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);
/// 登录类型
KRLoginType? kr_loginType;
/// 区号
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('device_');
}
/// 从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 {
// 更新内存中的数据
kr_token = token;
kr_account.value = account;
kr_loginType = loginType;
kr_areaCode = areaCode;
// 从JWT token中解析userId
kr_userId.value = _kr_parseUserIdFromToken(token);
KRLogUtil.kr_i('从JWT解析userId: ${kr_userId.value}', tag: 'AppRunData');
final Map<String, dynamic> userInfo = {
'token': token,
'account': account,
'loginType': loginType.value,
'areaCode': areaCode ?? "",
};
KRLogUtil.kr_i('准备保存用户信息到存储', tag: 'AppRunData');
await KRSecureStorage().kr_saveData(
key: _keyUserInfo,
value: jsonEncode(userInfo),
);
// 验证保存是否成功
final savedData = await KRSecureStorage().kr_readData(key: _keyUserInfo);
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;
// 设备登录模式不再调用用户信息接口
// Socket 连接将在需要时建立
KRLogUtil.kr_i('用户信息已保存,跳过用户信息接口调用', tag: 'AppRunData');
} catch (e) {
KRLogUtil.kr_e('保存用户信息失败: $e', tag: 'AppRunData');
// 如果出错,重置登录状态
kr_isLogin.value = false;
rethrow; // 重新抛出异常,让调用者知道保存失败
}
}
/// 退出登录
Future<void> kr_loginOut() async {
// 先将登录状态设置为 false防止重连
kr_isLogin.value = false;
// 断开 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();
// 重置主页面
Get.find<KRMainController>().kr_setPage(0);
}
/// 初始化用户信息
Future<void> kr_initializeUserInfo() async {
KRLogUtil.kr_i('开始初始化用户信息', tag: 'AppRunData');
try {
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 连接
Future<void> _kr_connectSocket(String userId) async {
// 如果已存在连接,先断开
await _kr_disconnectSocket();
final deviceId = await KRDeviceUtil().kr_getDeviceId();
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'AppRunData');
KrSocketService.instance.kr_init(
baseUrl: AppConfig.getInstance().wsBaseUrl,
userId: userId,
deviceNumber: deviceId,
token: kr_token ?? "",
);
// 设置消息处理回调
KrSocketService.instance.setOnMessageCallback(_kr_handleMessage);
// 设置连接状态回调
KrSocketService.instance.setOnConnectionStateCallback(_kr_handleConnectionState);
// 建立连接
KrSocketService.instance.connect();
}
/// 处理接收到的消息
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();
}
}