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:
parent
825c8a32e4
commit
eda39b982d
|
|
@ -70,6 +70,15 @@ class NotificationBadgeNotifier extends StateNotifier<NotificationBadgeState>
|
||||||
const Duration(seconds: _refreshIntervalSeconds),
|
const Duration(seconds: _refreshIntervalSeconds),
|
||||||
(_) => _loadUnreadCount(),
|
(_) => _loadUnreadCount(),
|
||||||
);
|
);
|
||||||
|
debugPrint('[NotificationBadge] 自动刷新已启动 (间隔: ${_refreshIntervalSeconds}s)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 停止自动刷新定时器(账号切换时调用,防止定时器在切换窗口期触发混账号请求)
|
||||||
|
/// 调用后不会立即 dispose,invalidate Provider 时会重建并自动重启
|
||||||
|
void stopAutoRefresh() {
|
||||||
|
_refreshTimer?.cancel();
|
||||||
|
_refreshTimer = null;
|
||||||
|
debugPrint('[NotificationBadge] 自动刷新已停止(切换账号)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 加载未读通知数量
|
/// 加载未读通知数量
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue