From 12004d1c2e2e38b453a89962c4fdb02183b58a4d Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 26 Feb 2026 11:39:31 -0800 Subject: [PATCH] =?UTF-8?q?fix(mobile-app):=20=E4=BF=AE=E5=A4=8D=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E5=88=87=E6=8D=A2=E5=90=8E=E8=87=AA=E5=8A=A8=E9=80=80?= =?UTF-8?q?=E5=87=BA=E7=99=BB=E5=BD=95=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:ref.invalidate(authProvider) 销毁旧 AuthNotifier 后,新实例的构造函数 仅设置 AuthState(status: AuthStatus.initial),从不自动调用 checkAuthStatus() 从 SecureStorage 重新加载认证数据。导致 auth 状态停留在 initial(未认证), 依赖 auth 状态的组件误判为"未登录",触发页面跳转到登录页。 修复: - account_switch_page: invalidate 后立即调用 loadAuthState() 从 storage 读取新账号数据,确保 auth 状态为 authenticated 后再导航 - account_switch_page: 切换后重置 ApiClient 的 tokenExpired 标记,防止 旧会话的 401 状态阻塞新账号的请求 - app.dart: _handleTokenExpired() 增加醒目日志和调用栈打印,便于排查 切换期间是否有 token 过期事件被误触发 切换流程更新为 6 步: [1/6] switchToAccount() - 保存旧账号、清空、恢复新账号 storage [2/6] onBeforeRestore - 停止所有定时器 [3/6] invalidate Provider - 销毁旧 Provider 实例 [4/6] loadAuthState() - 从 storage 加载新账号 auth 状态 ← 新增关键步骤 [5/6] 恢复遥测上传 [6/6] 导航到 ranking 页面 Co-Authored-By: Claude Opus 4.6 --- frontend/mobile-app/lib/app.dart | 5 +++- .../pages/account_switch_page.dart | 26 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/frontend/mobile-app/lib/app.dart b/frontend/mobile-app/lib/app.dart index a0bd7377..a0f09eb5 100644 --- a/frontend/mobile-app/lib/app.dart +++ b/frontend/mobile-app/lib/app.dart @@ -52,7 +52,10 @@ class _AppState extends ConsumerState { /// 处理 token 过期 Future _handleTokenExpired(String? message) async { - debugPrint('[App] Token expired, navigating to login page'); + debugPrint('[App] !!!! Token expired 事件触发,即将跳转登录页面 !!!!'); + debugPrint('[App] Token expired message: $message'); + // 打印调用栈,方便排查是谁触发了 token 过期事件 + debugPrint('[App] Token expired stack: ${StackTrace.current}'); // 清除当前账号状态(但保留账号列表和向导页标识) final multiAccountService = ref.read(multiAccountServiceProvider); diff --git a/frontend/mobile-app/lib/features/account/presentation/pages/account_switch_page.dart b/frontend/mobile-app/lib/features/account/presentation/pages/account_switch_page.dart index a54367c8..7b9673ad 100644 --- a/frontend/mobile-app/lib/features/account/presentation/pages/account_switch_page.dart +++ b/frontend/mobile-app/lib/features/account/presentation/pages/account_switch_page.dart @@ -76,14 +76,14 @@ class _AccountSwitchPageState extends ConsumerState { // switchToAccount() 内部会先调用 saveCurrentAccountData(),无需在此重复调用 // onBeforeRestore: storage 已清空、新数据尚未恢复 → 只停定时器,禁止发起任何 API 请求 // Provider invalidate 必须在 switchToAccount 返回后做(此时 storage 已恢复新账号数据) - debugPrint('$_tag [1/5] 调用 switchToAccount()...'); + debugPrint('$_tag [1/6] 调用 switchToAccount()...'); final success = await multiAccountService.switchToAccount( account.userSerialNum, onBeforeRestore: () async { // ===== 停止所有用户相关的定时任务 ===== // 必须在此阶段停止,防止定时器在 storage 空窗期(clear 完成、restore 未完成) // 触发混账号请求(旧账号 userSerialNum + 新账号 token) - debugPrint('$_tag [2/5] onBeforeRestore - 停止全部定时器...'); + debugPrint('$_tag [2/6] onBeforeRestore - 停止全部定时器...'); ref.read(walletStatusProvider.notifier).stopPolling(); debugPrint('$_tag ✓ walletStatusProvider 轮询已停止'); ref.read(pendingActionPollingServiceProvider).stop(); @@ -97,7 +97,7 @@ class _AccountSwitchPageState extends ConsumerState { await TelemetryService().pauseForLogout(); debugPrint('$_tag ✓ TelemetryService 已暂停'); } - debugPrint('$_tag [2/5] onBeforeRestore - 定时器全部停止'); + debugPrint('$_tag [2/6] onBeforeRestore - 定时器全部停止'); }, ); @@ -105,7 +105,7 @@ class _AccountSwitchPageState extends ConsumerState { // ===== invalidate 所有账号相关 Provider(此时 storage 已是新账号数据)===== // 必须在 switchToAccount 返回后执行,确保 storage 已完整恢复, // Provider 重建时读到的是新账号数据 - debugPrint('$_tag [3/5] switchToAccount 成功,invalidate Provider...'); + debugPrint('$_tag [3/6] switchToAccount 成功,invalidate Provider...'); ref.invalidate(authProvider); debugPrint('$_tag ✓ authProvider invalidated'); ref.invalidate(walletStatusProvider); @@ -113,15 +113,27 @@ class _AccountSwitchPageState extends ConsumerState { ref.invalidate(notificationBadgeProvider); debugPrint('$_tag ✓ notificationBadgeProvider invalidated(将重建并自动重启定时器)'); + // ===== 重新加载 auth 状态(从 SecureStorage 读取新账号数据)===== + // invalidate 后 AuthNotifier 重建,初始状态是 AuthStatus.initial(未认证) + // 必须主动调用 loadAuthState() 从 storage 读取新账号数据,否则 auth 状态为 initial, + // 会导致依赖 auth 状态的组件认为用户未登录 + debugPrint('$_tag [4/6] 重新加载 auth 状态(从 storage 读取新账号数据)...'); + await ref.read(authProvider.notifier).loadAuthState(); + final newAuthState = ref.read(authProvider); + debugPrint('$_tag ✓ auth 状态已加载: status=${newAuthState.status}, userSerialNum=${newAuthState.userSerialNum}'); + // 重置 ApiClient 的 token 过期标记,确保新账号的请求不会被旧的过期标记阻塞 + ref.read(apiClientProvider).resetTokenExpiredFlag(); + debugPrint('$_tag ✓ ApiClient tokenExpired 标记已重置'); + // ===== 恢复遥测上传(新账号上下文)===== - debugPrint('$_tag [4/5] 恢复遥测上传...'); + debugPrint('$_tag [5/6] 恢复遥测上传...'); if (TelemetryService().isInitialized) { TelemetryService().resumeAfterLogin(); debugPrint('$_tag ✓ TelemetryService 已恢复'); } // 切换成功,跳转到主页刷新状态 - debugPrint('$_tag [5/5] 导航到 ranking 页面'); + debugPrint('$_tag [6/6] 导航到 ranking 页面'); debugPrint('$_tag ========== 切换账号完成 =========='); context.go(RoutePaths.ranking); } else if (mounted) { @@ -159,7 +171,7 @@ class _AccountSwitchPageState extends ConsumerState { await multiAccountService.saveCurrentAccountData(); // ===== 1. 停止所有用户相关的定时任务 ===== - debugPrint('$_tag [2/5] 停止定时器...'); + debugPrint('$_tag [2/6] 停止定时器...'); ref.read(walletStatusProvider.notifier).stopPolling(); debugPrint('$_tag ✓ walletStatusProvider 轮询已停止'); ref.read(pendingActionPollingServiceProvider).stop();