fix(mobile-app): 修复账号切换后自动退出登录的问题
根因: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 <noreply@anthropic.com>
This commit is contained in:
parent
83ba9b7d54
commit
12004d1c2e
|
|
@ -52,7 +52,10 @@ class _AppState extends ConsumerState<App> {
|
||||||
|
|
||||||
/// 处理 token 过期
|
/// 处理 token 过期
|
||||||
Future<void> _handleTokenExpired(String? message) async {
|
Future<void> _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);
|
final multiAccountService = ref.read(multiAccountServiceProvider);
|
||||||
|
|
|
||||||
|
|
@ -76,14 +76,14 @@ class _AccountSwitchPageState extends ConsumerState<AccountSwitchPage> {
|
||||||
// switchToAccount() 内部会先调用 saveCurrentAccountData(),无需在此重复调用
|
// switchToAccount() 内部会先调用 saveCurrentAccountData(),无需在此重复调用
|
||||||
// onBeforeRestore: storage 已清空、新数据尚未恢复 → 只停定时器,禁止发起任何 API 请求
|
// onBeforeRestore: storage 已清空、新数据尚未恢复 → 只停定时器,禁止发起任何 API 请求
|
||||||
// Provider invalidate 必须在 switchToAccount 返回后做(此时 storage 已恢复新账号数据)
|
// Provider invalidate 必须在 switchToAccount 返回后做(此时 storage 已恢复新账号数据)
|
||||||
debugPrint('$_tag [1/5] 调用 switchToAccount()...');
|
debugPrint('$_tag [1/6] 调用 switchToAccount()...');
|
||||||
final success = await multiAccountService.switchToAccount(
|
final success = await multiAccountService.switchToAccount(
|
||||||
account.userSerialNum,
|
account.userSerialNum,
|
||||||
onBeforeRestore: () async {
|
onBeforeRestore: () async {
|
||||||
// ===== 停止所有用户相关的定时任务 =====
|
// ===== 停止所有用户相关的定时任务 =====
|
||||||
// 必须在此阶段停止,防止定时器在 storage 空窗期(clear 完成、restore 未完成)
|
// 必须在此阶段停止,防止定时器在 storage 空窗期(clear 完成、restore 未完成)
|
||||||
// 触发混账号请求(旧账号 userSerialNum + 新账号 token)
|
// 触发混账号请求(旧账号 userSerialNum + 新账号 token)
|
||||||
debugPrint('$_tag [2/5] onBeforeRestore - 停止全部定时器...');
|
debugPrint('$_tag [2/6] onBeforeRestore - 停止全部定时器...');
|
||||||
ref.read(walletStatusProvider.notifier).stopPolling();
|
ref.read(walletStatusProvider.notifier).stopPolling();
|
||||||
debugPrint('$_tag ✓ walletStatusProvider 轮询已停止');
|
debugPrint('$_tag ✓ walletStatusProvider 轮询已停止');
|
||||||
ref.read(pendingActionPollingServiceProvider).stop();
|
ref.read(pendingActionPollingServiceProvider).stop();
|
||||||
|
|
@ -97,7 +97,7 @@ class _AccountSwitchPageState extends ConsumerState<AccountSwitchPage> {
|
||||||
await TelemetryService().pauseForLogout();
|
await TelemetryService().pauseForLogout();
|
||||||
debugPrint('$_tag ✓ TelemetryService 已暂停');
|
debugPrint('$_tag ✓ TelemetryService 已暂停');
|
||||||
}
|
}
|
||||||
debugPrint('$_tag [2/5] onBeforeRestore - 定时器全部停止');
|
debugPrint('$_tag [2/6] onBeforeRestore - 定时器全部停止');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -105,7 +105,7 @@ class _AccountSwitchPageState extends ConsumerState<AccountSwitchPage> {
|
||||||
// ===== invalidate 所有账号相关 Provider(此时 storage 已是新账号数据)=====
|
// ===== invalidate 所有账号相关 Provider(此时 storage 已是新账号数据)=====
|
||||||
// 必须在 switchToAccount 返回后执行,确保 storage 已完整恢复,
|
// 必须在 switchToAccount 返回后执行,确保 storage 已完整恢复,
|
||||||
// Provider 重建时读到的是新账号数据
|
// Provider 重建时读到的是新账号数据
|
||||||
debugPrint('$_tag [3/5] switchToAccount 成功,invalidate Provider...');
|
debugPrint('$_tag [3/6] switchToAccount 成功,invalidate Provider...');
|
||||||
ref.invalidate(authProvider);
|
ref.invalidate(authProvider);
|
||||||
debugPrint('$_tag ✓ authProvider invalidated');
|
debugPrint('$_tag ✓ authProvider invalidated');
|
||||||
ref.invalidate(walletStatusProvider);
|
ref.invalidate(walletStatusProvider);
|
||||||
|
|
@ -113,15 +113,27 @@ class _AccountSwitchPageState extends ConsumerState<AccountSwitchPage> {
|
||||||
ref.invalidate(notificationBadgeProvider);
|
ref.invalidate(notificationBadgeProvider);
|
||||||
debugPrint('$_tag ✓ notificationBadgeProvider invalidated(将重建并自动重启定时器)');
|
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) {
|
if (TelemetryService().isInitialized) {
|
||||||
TelemetryService().resumeAfterLogin();
|
TelemetryService().resumeAfterLogin();
|
||||||
debugPrint('$_tag ✓ TelemetryService 已恢复');
|
debugPrint('$_tag ✓ TelemetryService 已恢复');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换成功,跳转到主页刷新状态
|
// 切换成功,跳转到主页刷新状态
|
||||||
debugPrint('$_tag [5/5] 导航到 ranking 页面');
|
debugPrint('$_tag [6/6] 导航到 ranking 页面');
|
||||||
debugPrint('$_tag ========== 切换账号完成 ==========');
|
debugPrint('$_tag ========== 切换账号完成 ==========');
|
||||||
context.go(RoutePaths.ranking);
|
context.go(RoutePaths.ranking);
|
||||||
} else if (mounted) {
|
} else if (mounted) {
|
||||||
|
|
@ -159,7 +171,7 @@ class _AccountSwitchPageState extends ConsumerState<AccountSwitchPage> {
|
||||||
await multiAccountService.saveCurrentAccountData();
|
await multiAccountService.saveCurrentAccountData();
|
||||||
|
|
||||||
// ===== 1. 停止所有用户相关的定时任务 =====
|
// ===== 1. 停止所有用户相关的定时任务 =====
|
||||||
debugPrint('$_tag [2/5] 停止定时器...');
|
debugPrint('$_tag [2/6] 停止定时器...');
|
||||||
ref.read(walletStatusProvider.notifier).stopPolling();
|
ref.read(walletStatusProvider.notifier).stopPolling();
|
||||||
debugPrint('$_tag ✓ walletStatusProvider 轮询已停止');
|
debugPrint('$_tag ✓ walletStatusProvider 轮询已停止');
|
||||||
ref.read(pendingActionPollingServiceProvider).stop();
|
ref.read(pendingActionPollingServiceProvider).stop();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue