1.优化补足了首页下拉刷新中对获取场站接口的加入,确保能够实时获取数据并渲染。

2.优化修改了tcp重连机制,具体操作:断开重连时候不再建立新链接 而是将旧链接重置,并重新发送数据。
3.大面积的给项目中的各个页面 手动接入了多语言的支持。同时也调整了相应带来的问题英文长度较大影响原有的样式的问题,已解决了对应的样式不和谐问题。保证了外观的统一性。
4.给项目中的主要操作加了几个核心的操作比如远程遥控和路径规划添加了双层的保护,设置里安全模式,即在此期间对服务器推送的重复登录操作后的自动退出登录做一个拦截 保证操作的安全性。
This commit is contained in:
2026-07-02 16:04:30 +08:00
parent d5a83c1221
commit 0db64a96e7
69 changed files with 5018 additions and 1532 deletions

View File

@@ -39,6 +39,13 @@ class AuthCubit extends Cubit<AuthState> {
final ILoggerService _logger = GetIt.I<ILoggerService>();
StreamSubscription? _kickOutSub; // 新增:用于管理监听生命周期
// 🔥 登录验证 Completer:用于等待登录阶段的 have_logged_in 推送
Completer<bool>? _loginVerificationCompleter;
// 🔥 安全模式标志位:保护关键操作阶段不被异地登录踢下线
bool _isSafeMode = false;
Timer? _safeModeExitTimer;
AuthCubit(
this.storage,
@@ -63,17 +70,14 @@ class AuthCubit extends Cubit<AuthState> {
);
if (user != null) {
// 🔥 冷启动时不再自动连接TCP,改为选择设备时再连接
// await tcp.initializeTcp(
// host: TCPConsts.TCP_IP,
// port: TCPConsts.TCP_PORT,
// );
// 🔥 冷启动时不创建TCP连接,改为用户选择设备时再连接
// 避免发送 0x03 触发服务端残留 session 的 have_logged_in
// 2. 同步全局 App 状态
appCubit.setAuth(user);
// 3. 进入已登录状态
emit(AuthAuthenticated(user));
logger.logWithLevel('✅ [AUTH] 应用启动 - 已恢复登录状态(TCP将在选择设备时连接)', level: 'INFO');
logger.logWithLevel('✅ [AUTH] 应用启动 - 已恢复登录状态', level: 'INFO');
} else {
logger.logWithLevel('⚠️ [AUTH] 应用启动 - 无本地缓存,进入未登录状态', level: 'INFO');
emit(AuthUnauthenticated());
@@ -88,8 +92,44 @@ class AuthCubit extends Cubit<AuthState> {
Future<void> loginSuccess(UserEntity user) async {
await storage.saveUser(user);
// 🔥 登录后不再立即连接TCP,改为选择设备时再连接
// await tcp.initializeTcp(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT);
// 🔥 登录后立即创建TCP连接并验证
try {
debugPrint('>>> [AUTH] 登录成功,开始建立TCP连接...');
_logger.logWithLevel('>>> [AUTH] 登录成功,开始建立TCP连接...', shouldLog: true);
await tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT);
tcp.startHeartbeat(interval: const Duration(seconds: 4));
// 🔥 关键:等待 2.5 秒,看是否收到 have_logged_in
bool isKicked = await _waitForLoginVerification();
if (isKicked) {
// 🔥 不能进入 APP,必须清除所有登录信息
debugPrint('>>> [AUTH] ⚠️ 登录验证失败,清除登录信息...');
_logger.logWithLevel('>>> [AUTH] ⚠️ 登录验证失败,清除登录信息', shouldLog: true);
// 1. 删除已保存的用户信息
await storage.deleteUser();
debugPrint('✅ [AUTH] 已删除用户信息');
// 2. 断开 TCP 连接
tcp.forceDisconnect();
debugPrint('✅ [AUTH] 已断开 TCP 连接');
// 3. 显示 Toast
_showLoginFailedToast("账号已在其他设备登录");
debugPrint('>>> [AUTH] ⚠️ 登录验证失败,停留在登录页');
_logger.logWithLevel('>>> [AUTH] ⚠️ 登录验证失败,停留在登录页', shouldLog: true);
return; // 停留在登录页
}
debugPrint('✅ [AUTH] TCP连接成功,验证通过');
_logger.logWithLevel('✅ [AUTH] TCP连接成功,验证通过', shouldLog: true);
} catch (e) {
debugPrint('❌ [AUTH] TCP连接失败:$e');
_logger.logWithLevel('❌ [AUTH] TCP连接失败:$e', level: 'ERROR');
// TCP 连接失败不影响登录流程,继续执行
}
appCubit.setAuth(user);
emit(AuthAuthenticated(user));
@@ -97,7 +137,8 @@ class AuthCubit extends Cubit<AuthState> {
/// 退出登录 (主动或被动)
Future<void> logout() async {
//print("退出登录");
print('>>> [AUTH] 🚨🚨🚨 logout() 被调用!时间: ${DateTime.now()}');
print('>>> [AUTH] 调用栈:\n${StackTrace.current}');
// 🔥 关键修复:在断开TCP前,先清空所有业务状态,避免重连触发旧数据
_clearAllBusinessState();
@@ -108,6 +149,25 @@ class AuthCubit extends Cubit<AuthState> {
emit(AuthUnauthenticated());
}
/// 🔥 新增:显示登录失败 Toast(黑色背景,和登录错误提示一致)
void _showLoginFailedToast(String message) {
try {
final context = navigatorKey.currentContext;
if (context != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.black,
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
),
);
}
} catch (e) {
debugPrint('>>> [AUTH] ❌ 显示Toast失败:$e');
}
}
/// 🔥 新增:显示异地登录提示弹窗
void _showKickOutDialog() {
try {
@@ -177,6 +237,21 @@ class AuthCubit extends Cubit<AuthState> {
}
}
/// 🔥 等待登录验证结果(2.5 秒内看是否收到 have_logged_in)
Future<bool> _waitForLoginVerification() async {
_loginVerificationCompleter = Completer<bool>();
// 等待 2.5 秒
await Future.delayed(const Duration(milliseconds: 2500));
// 如果 completer 还没完成,说明没收到 have_logged_in,返回 false(可以进入)
if (!_loginVerificationCompleter!.isCompleted) {
_loginVerificationCompleter!.complete(false);
}
return _loginVerificationCompleter!.future;
}
/// TCP 指令监听
void _listenToAuthResponse() {
debugPrint('>>> [AUTH] _listenToAuthResponse() 被调用,开始监听 0x12 指令');
@@ -214,13 +289,36 @@ class AuthCubit extends Cubit<AuthState> {
);
if (respond == 'have_logged_in') {
debugPrint('>>> [AUTH] ⚠️ 检测到异地登录 (respond=have_logged_in),开始退出...');
// 🔥 登录阶段策略:HTTP 已成功,TCP 认证阶段的推送视为服务端状态同步,直接放行
if (_loginVerificationCompleter != null && !_loginVerificationCompleter!.isCompleted) {
debugPrint('>>> [AUTH] 🛡️ 登录阶段收到 have_logged_in,视为服务端状态同步,允许进入 APP');
_logger.logWithLevel(
'🛡️ [AUTH] 登录阶段收到 have_logged_in,视为服务端状态同步,允许进入',
shouldLog: true,
);
_loginVerificationCompleter!.complete(false); // 标记为可以进入
return;
}
// 🔥 已登录阶段策略:检查是否处于安全模式
if (_isSafeMode) {
print('>>> [AUTH] 🛡️ 安全模式下拦截 have_logged_in 推送,防止控制中断');
_logger.logWithLevel(
'🛡️ [AUTH] 安全模式下拦截异地登录推送',
level: 'WARN',
);
return; // 拦截退出逻辑
}
print('>>> [AUTH] ⚠️ 已登录状态下收到 TCP 0x12 指令 respond=have_logged_in');
debugPrint('>>> [AUTH] ℹ️ 暂时忽略该推送,观察是否影响业务操作...');
_logger.logWithLevel(
'⚠️ [AUTH] 检测到异地登录 (respond=have_logged_in),开始退出...',
level: 'INFO',
'⚠️ [AUTH] 已登录状态收到 have_logged_in,暂不处理,观察业务影响',
level: 'WARN',
);
// 🔥 修复:先显示友好提示,再执行退出
_showKickOutDialog();
// 如果你希望依然保持严格的安全策略,可以取消下面注释恢复退出逻辑:
// _showKickOutDialog();
} else {
debugPrint('>>> [AUTH] ℹ️ 收到 0x12 消息,respond="$respond",不处理');
}
@@ -231,9 +329,92 @@ class AuthCubit extends Cubit<AuthState> {
_logger.logWithLevel('[AUTH] 监听 TCP 0x12 指令...', level: 'INFO');
}
/// 🔥 安全模式管理:进入关键页面时调用
void enterSafeMode() {
_isSafeMode = true;
_safeModeExitTimer?.cancel(); // 取消之前的退出倒计时
debugPrint('>>> [AUTH] 🛡️ 已进入安全模式,异地登录推送将被拦截');
_logger.logWithLevel('>>> [AUTH] 🛡️ 已进入安全模式', shouldLog: true);
_showSafeModeEnteredToast(); // 🔥 显示进入提示
}
/// 🔥 安全模式管理:离开关键页面时调用
void exitSafeMode() {
// 启动 90 秒倒计时
_safeModeExitTimer?.cancel();
_safeModeExitTimer = Timer(const Duration(seconds: 90), () {
if (_isSafeMode) {
_isSafeMode = false;
debugPrint('>>> [AUTH] ⏰ 90秒无操作,已自动关闭安全模式');
_logger.logWithLevel('>>> [AUTH] ⏰ 90秒无操作,已自动关闭安全模式', shouldLog: true);
_showSafeModeClosedToast();
}
});
debugPrint('>>> [AUTH] 🕒 已离开安全页面,90秒后若无操作将关闭安全模式');
_showSafeModeCountdownToast();
}
/// 🔥 显示“已进入安全模式”提示
void _showSafeModeEnteredToast() {
try {
final context = navigatorKey.currentContext;
if (context != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('已进入安全模式,防止异地登录干扰'),
backgroundColor: Colors.green,
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
),
);
}
} catch (e) {
debugPrint('>>> [AUTH] ❌ 显示安全模式提示失败:$e');
}
}
/// 🔥 显示“90秒后关闭”提示
void _showSafeModeCountdownToast() {
try {
final context = navigatorKey.currentContext;
if (context != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('90秒后无操作将关闭安全模式'),
backgroundColor: Colors.orange,
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
),
);
}
} catch (e) {
debugPrint('>>> [AUTH] ❌ 显示倒计时提示失败:$e');
}
}
/// 🔥 显示“安全模式已关闭”提示
void _showSafeModeClosedToast() {
try {
final context = navigatorKey.currentContext;
if (context != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('安全模式已关闭,恢复异地登录检测'),
backgroundColor: Colors.grey,
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
),
);
}
} catch (e) {
debugPrint('>>> [AUTH] ❌ 显示关闭提示失败:$e');
}
}
@override
Future<void> close() {
_kickOutSub?.cancel(); // 销毁 Cubit 时关闭监听
_safeModeExitTimer?.cancel(); // 清理倒计时
return super.close();
}