218 lines
8.3 KiB
Dart
218 lines
8.3 KiB
Dart
import 'dart:async';
|
||
import 'dart:convert';
|
||
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
import 'package:get_it/get_it.dart';
|
||
import 'package:maibu_satabot_v2/core/consts/tcp_consts.dart';
|
||
import 'package:maibu_satabot_v2/core/domain/entities/user_entity.dart';
|
||
import 'package:maibu_satabot_v2/core/network/net_message_dispatcher.dart';
|
||
|
||
import '../../../../core/app/app_user_cubit.dart';
|
||
import '../../../../core/infrastructure/logging/sentry_logger_impl.dart';
|
||
import '../../../../core/logging/i_logger_service.dart';
|
||
import '../../../../core/network/tcp/tcp_client.dart';
|
||
import '../../../../core/storage/user_storage.dart';
|
||
import '../../../devices/domain/usecases/get_user_device_usecase.dart';
|
||
import '../../../devices/domain/usecases/switch_device_usecase.dart';
|
||
import '../../../devices/presentation/bloc/devices_cubit.dart';
|
||
import '../../../devices/presentation/bloc/device_status_bloc.dart';
|
||
import '../../../devices/presentation/bloc/device_status_event.dart';
|
||
import '../../../devices/presentation/bloc/devices_state.dart';
|
||
import '../../../../features/v2/site/presentation/cubit/site_cubit.dart';
|
||
import '../../data/datasources/auth_tcp_datasource.dart';
|
||
import '../../data/datasources/impl/auth_tcp_datasource_impl.dart';
|
||
import 'auth_state.dart';
|
||
|
||
/// emit做的事情:
|
||
/// 1.修改状态标志:通过改变类的类型(从 Initial 变成 Authenticated)
|
||
/// 2.携带数据:把 user 对象塞进了状态里,让外部能拿到
|
||
/// 3.发送通知:自动触发监听(如 GoRouter 的刷新)。
|
||
class AuthCubit extends Cubit<AuthState> {
|
||
final UserStorage storage;
|
||
final TcpClient tcp;
|
||
final AppUserCubit appCubit;
|
||
final NetMessageDispatcher dispatcher;
|
||
final AuthTcpDatasource _authTcpDatasource;
|
||
final ILoggerService _logger = GetIt.I<ILoggerService>();
|
||
|
||
StreamSubscription? _kickOutSub; // 新增:用于管理监听生命周期
|
||
|
||
AuthCubit(
|
||
this.storage,
|
||
this.tcp,
|
||
this.appCubit,
|
||
this.dispatcher,
|
||
this._authTcpDatasource,
|
||
) : super(AuthInitial()) {
|
||
// Cubit 一启动就开始监听 TCP 的“自动逻辑”
|
||
_listenToAuthResponse();
|
||
}
|
||
|
||
/// App 启动时检查本地缓存
|
||
Future<void> appStarted() async {
|
||
final logger = GetIt.I<ILoggerService>() as SentryLoggerImpl;
|
||
try {
|
||
final user = await storage.getUser();
|
||
logger.logWithLevel(
|
||
'启动时检查本地缓存',
|
||
level: 'INFO',
|
||
data: {'user': user != null ? '找到用户: ${user.username}' : '未找到用户'},
|
||
);
|
||
|
||
if (user != null) {
|
||
// 🔥 冷启动时不再自动连接TCP,改为选择设备时再连接
|
||
// await tcp.initializeTcp(
|
||
// host: TCPConsts.TCP_IP,
|
||
// port: TCPConsts.TCP_PORT,
|
||
// );
|
||
|
||
// 2. 同步全局 App 状态
|
||
appCubit.setAuth(user);
|
||
// 3. 进入已登录状态
|
||
emit(AuthAuthenticated(user));
|
||
logger.logWithLevel('✅ [AUTH] 应用启动 - 已恢复登录状态(TCP将在选择设备时连接)', level: 'INFO');
|
||
} else {
|
||
logger.logWithLevel('⚠️ [AUTH] 应用启动 - 无本地缓存,进入未登录状态', level: 'INFO');
|
||
emit(AuthUnauthenticated());
|
||
}
|
||
} catch (e) {
|
||
logger.logWithLevel('❌ [AUTH] 应用启动检查失败: $e', level: 'ERROR');
|
||
emit(AuthUnauthenticated());
|
||
}
|
||
}
|
||
|
||
/// 当 HTTP 登录/注册成功后调用
|
||
Future<void> loginSuccess(UserEntity user) async {
|
||
await storage.saveUser(user);
|
||
|
||
// 🔥 登录后不再立即连接TCP,改为选择设备时再连接
|
||
// await tcp.initializeTcp(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT);
|
||
|
||
appCubit.setAuth(user);
|
||
emit(AuthAuthenticated(user));
|
||
}
|
||
|
||
/// 退出登录 (主动或被动)
|
||
Future<void> logout() async {
|
||
//print("退出登录");
|
||
|
||
// 🔥 关键修复:在断开TCP前,先清空所有业务状态,避免重连触发旧数据
|
||
_clearAllBusinessState();
|
||
|
||
await storage.deleteUser();
|
||
tcp.disconnect(); // 断开后会自动重连
|
||
appCubit.clearAuth();
|
||
emit(AuthUnauthenticated());
|
||
}
|
||
|
||
/// 🔥 新增:清空所有业务状态,防止数据泄露到新账户
|
||
void _clearAllBusinessState() {
|
||
try {
|
||
final devicesCubit = GetIt.I<DevicesCubit>();
|
||
final deviceStatusBloc = GetIt.I<DeviceStatusBloc>();
|
||
final siteCubit = GetIt.I<SiteCubit>();
|
||
|
||
// 1. 清空设备列表和选中设备
|
||
devicesCubit.emit(const DevicesState());
|
||
debugPrint('✅ [AUTH] 已清空 DevicesCubit 状态');
|
||
_logger.logWithLevel('✅ [AUTH] 已清空 DevicesCubit 状态');
|
||
|
||
// 2. 清空设备实时状态
|
||
deviceStatusBloc.add(DeviceStatusReset());
|
||
debugPrint('✅ [AUTH] 已重置 DeviceStatusBloc 状态');
|
||
_logger.logWithLevel('✅ [AUTH] 已重置 DeviceStatusBloc 状态');
|
||
|
||
// 3. 清空全局选中的场站
|
||
siteCubit.clearSelectedSite();
|
||
debugPrint('✅ [AUTH] 已清空 SiteCubit 选中状态');
|
||
_logger.logWithLevel('✅ [AUTH] 已清空 SiteCubit 选中状态');
|
||
|
||
debugPrint('✅ [AUTH] 所有业务状态已清空');
|
||
_logger.logWithLevel('✅ [AUTH] 所有业务状态已清空');
|
||
} catch (e) {
|
||
debugPrint('❌ [AUTH] 清空业务状态失败: $e');
|
||
_logger.logWithLevel('❌ [AUTH] 清空业务状态失败: $e', level: 'ERROR');
|
||
}
|
||
}
|
||
|
||
/// TCP 指令监听
|
||
void _listenToAuthResponse() {
|
||
debugPrint('>>> [AUTH] _listenToAuthResponse() 被调用,开始监听 0x12 指令');
|
||
_kickOutSub?.cancel(); // 防止重复监听
|
||
|
||
// 直接监听原始数据包,自己处理 JSON 解析(去掉 CRC 字节)
|
||
_kickOutSub = dispatcher.onCommand(0x12).listen((packet) {
|
||
debugPrint(
|
||
'>>> [AUTH] 收到 0x12 原始包,payload 长度=${packet.payload.length}, 内容=${packet.payload}',
|
||
);
|
||
try {
|
||
// 🔥 关键:手动去掉最后 2 个 CRC 字节
|
||
String jsonString;
|
||
if (packet.payload.length > 2) {
|
||
jsonString = utf8.decode(
|
||
packet.payload.sublist(0, packet.payload.length - 2),
|
||
);
|
||
} else {
|
||
jsonString = utf8.decode(packet.payload);
|
||
}
|
||
debugPrint('>>> [AUTH] 获取 JSON: "$jsonString"');
|
||
|
||
final jsonMap = jsonDecode(jsonString);
|
||
debugPrint('>>> [AUTH] 获取 JSON Map: $jsonMap');
|
||
|
||
final respond = jsonMap['respond'] ?? '';
|
||
debugPrint('>>> [AUTH] respond 字段值: "$respond"');
|
||
_logger.logWithLevel(
|
||
'>>> [AUTH] respond 字段值: "$respond"',
|
||
shouldLog: true,
|
||
);
|
||
debugPrint('>>> [AUTH] respond 类型: ${respond.runtimeType}');
|
||
debugPrint(
|
||
'>>> [AUTH] respond == "have_logged_in": ${respond == "have_logged_in"}',
|
||
);
|
||
|
||
if (respond == 'have_logged_in') {
|
||
debugPrint('>>> [AUTH] ⚠️ 检测到异地登录 (respond=have_logged_in),开始退出...');
|
||
_logger.logWithLevel(
|
||
'⚠️ [AUTH] 检测到异地登录 (respond=have_logged_in),开始退出...',
|
||
level: 'INFO',
|
||
);
|
||
logout();
|
||
} else {
|
||
debugPrint('>>> [AUTH] ℹ️ 收到 0x12 消息,respond="$respond",不处理');
|
||
}
|
||
} catch (e) {
|
||
debugPrint('>>> [AUTH] ❌ JSON 解析失败:$e');
|
||
}
|
||
});
|
||
_logger.logWithLevel('[AUTH] 监听 TCP 0x12 指令...', level: 'INFO');
|
||
}
|
||
|
||
@override
|
||
Future<void> close() {
|
||
_kickOutSub?.cancel(); // 销毁 Cubit 时关闭监听
|
||
return super.close();
|
||
}
|
||
|
||
/// 🔥 息屏/后台后恢复到前台时的重连方法
|
||
Future<void> reconnectAfterResume() async {
|
||
_logger.logWithLevel('[AUTH] 检测到应用恢复到前台,检查 TCP 连接状态...', level: 'INFO');
|
||
// 如果 TCP 未连接,则执行重连
|
||
if (!tcp.isConnected) {
|
||
_logger.logWithLevel('[AUTH] TCP 未连接,开始重连...', level: 'INFO');
|
||
try {
|
||
await tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT);
|
||
tcp.startHeartbeat(interval: const Duration(seconds: 4));
|
||
_logger.logWithLevel('[AUTH] TCP 重连成功!', level: 'INFO');
|
||
} catch (e) {
|
||
_logger.logWithLevel('[AUTH] TCP 重连失败:$e', level: 'ERROR');
|
||
}
|
||
} else {
|
||
_logger.logWithLevel('[AUTH] TCP 已连接,无需重连', level: 'INFO');
|
||
// 可选:发送一个心跳包确认连接有效
|
||
tcp.sendHeartbeat();
|
||
}
|
||
}
|
||
}
|