fix(mobile-app): 修复切换窗口期 NotificationBadge 混账号请求问题

问题(通过日志发现):
账号切换时存在一个 storage 空窗期(旧数据已清除、新数据尚未恢复完成)。
在此期间,NotificationBadgeNotifier 的 30s 定时器恰好触发,导致:
- _loadUnreadCount() 从 authProvider 内存读到旧账号 userSerialNum
- HTTP interceptor 从 storage 读到已恢复的新账号 accessToken
- 发出混账号请求:?userSerialNum=旧账号 + Authorization: Bearer 新账号token
日志证据:
  _restoreAccountData() 执行期间出现
  GET /notifications/unread-count?userSerialNum=D26022600000
  Authorization: Bearer [D26022600001的token]

修复:

1. notification_badge_provider.dart
   新增 stopAutoRefresh() 公开方法,取消 30s 定时器而不 dispose,
   Provider invalidate 重建后会自动重启定时器。

2. account_switch_page.dart - _switchToAccount
   在 onBeforeRestore 中补加:
     ref.read(notificationBadgeProvider.notifier).stopAutoRefresh()
   确保切换空窗期内 notificationBadge 定时器不触发。

   同时移除 UI 层冗余的 saveCurrentAccountData() 调用——
   switchToAccount() 内部已有此步骤,无需重复。

   日志步骤从 [1/6]...[6/6] 更新为 [1/5]...[5/5],
   并在 onBeforeRestore 注释中说明停止各定时器的原因。

切换空窗期现在所有定时器均已停止:
  ✓ walletStatusProvider (60s)
  ✓ pendingActionPollingService (4s)
  ✓ notificationBadgeProvider (30s)  ← 本次新增
  ✓ TelemetryUploader (30s)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-25 09:09:37 -08:00
parent 825c8a32e4
commit eda39b982d
2 changed files with 29 additions and 15 deletions

View File

@ -70,6 +70,15 @@ class NotificationBadgeNotifier extends StateNotifier<NotificationBadgeState>
const Duration(seconds: _refreshIntervalSeconds), const Duration(seconds: _refreshIntervalSeconds),
(_) => _loadUnreadCount(), (_) => _loadUnreadCount(),
); );
debugPrint('[NotificationBadge] 自动刷新已启动 (间隔: ${_refreshIntervalSeconds}s)');
}
///
/// disposeinvalidate Provider
void stopAutoRefresh() {
_refreshTimer?.cancel();
_refreshTimer = null;
debugPrint('[NotificationBadge] 自动刷新已停止(切换账号)');
} }
/// ///

View File

@ -72,51 +72,56 @@ class _AccountSwitchPageState extends ConsumerState<AccountSwitchPage> {
try { try {
final multiAccountService = ref.read(multiAccountServiceProvider); final multiAccountService = ref.read(multiAccountServiceProvider);
//
debugPrint('$_tag [1/6] 保存当前账号数据...');
await multiAccountService.saveCurrentAccountData();
// //
// onBeforeRestore: storage // switchToAccount() saveCurrentAccountData()
// onBeforeRestore: storage API
// Provider invalidate switchToAccount storage // Provider invalidate switchToAccount storage
debugPrint('$_tag [2/6] 调用 switchToAccount()...'); debugPrint('$_tag [1/5] 调用 switchToAccount()...');
final success = await multiAccountService.switchToAccount( final success = await multiAccountService.switchToAccount(
account.userSerialNum, account.userSerialNum,
onBeforeRestore: () async { onBeforeRestore: () async {
// ===== 1. ===== // ===== =====
debugPrint('$_tag [3/6] onBeforeRestore - 停止定时器...'); // storage clear restore
// userSerialNum + token
debugPrint('$_tag [2/5] 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();
debugPrint('$_tag ✓ pendingActionPollingService 已停止'); debugPrint('$_tag ✓ pendingActionPollingService 已停止');
// NotificationBadge 30s restore
// authProvider userSerialNum + token
ref.read(notificationBadgeProvider.notifier).stopAutoRefresh();
debugPrint('$_tag ✓ notificationBadgeProvider 自动刷新已停止');
// //
if (TelemetryService().isInitialized) { if (TelemetryService().isInitialized) {
await TelemetryService().pauseForLogout(); await TelemetryService().pauseForLogout();
debugPrint('$_tag ✓ TelemetryService 已暂停'); debugPrint('$_tag ✓ TelemetryService 已暂停');
} }
debugPrint('$_tag [3/6] onBeforeRestore - 定时器全部停止'); debugPrint('$_tag [2/5] onBeforeRestore - 定时器全部停止');
}, },
); );
if (success && mounted) { if (success && mounted) {
// ===== 2. invalidate Provider storage ===== // ===== invalidate Provider storage =====
debugPrint('$_tag [4/6] switchToAccount 成功invalidate Provider...'); // switchToAccount storage
// Provider
debugPrint('$_tag [3/5] switchToAccount 成功invalidate Provider...');
ref.invalidate(authProvider); ref.invalidate(authProvider);
debugPrint('$_tag ✓ authProvider invalidated'); debugPrint('$_tag ✓ authProvider invalidated');
ref.invalidate(walletStatusProvider); ref.invalidate(walletStatusProvider);
debugPrint('$_tag ✓ walletStatusProvider invalidated'); debugPrint('$_tag ✓ walletStatusProvider invalidated');
ref.invalidate(notificationBadgeProvider); ref.invalidate(notificationBadgeProvider);
debugPrint('$_tag ✓ notificationBadgeProvider invalidated'); debugPrint('$_tag ✓ notificationBadgeProvider invalidated(将重建并自动重启定时器)');
// ===== 3. ===== // ===== =====
debugPrint('$_tag [5/6] 恢复遥测上传...'); debugPrint('$_tag [4/5] 恢复遥测上传...');
if (TelemetryService().isInitialized) { if (TelemetryService().isInitialized) {
TelemetryService().resumeAfterLogin(); TelemetryService().resumeAfterLogin();
debugPrint('$_tag ✓ TelemetryService 已恢复'); debugPrint('$_tag ✓ TelemetryService 已恢复');
} }
// //
debugPrint('$_tag [6/6] 导航到 ranking 页面'); debugPrint('$_tag [5/5] 导航到 ranking 页面');
debugPrint('$_tag ========== 切换账号完成 =========='); debugPrint('$_tag ========== 切换账号完成 ==========');
context.go(RoutePaths.ranking); context.go(RoutePaths.ranking);
} else if (mounted) { } else if (mounted) {