302 lines
9.6 KiB
Dart
Executable File
302 lines
9.6 KiB
Dart
Executable File
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];
|
||
// 手动添加必要的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,
|
||
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();
|
||
}
|
||
}
|