fix(mobile-app): 修复多账号切换数据串号问题,完善存储隔离与状态重置
问题:
多账号切换时,前一个账号的推荐码、种植省市、缓存数据等会串到下一个账号,
同时定时器(钱包轮询、通知刷新、遥测上传)未正确停止/重启,
导致旧账号的 API 请求混入新账号上下文。
修复内容:
1. StorageKeys 补充种植省市常量(storage_keys.dart)
- 新增 plantingProvinceName/Code、plantingCityName/Code 4个常量
- 将硬编码的 key 统一收口,确保隔离列表引用一致
2. MultiAccountService 补全隔离列表(multi_account_service.dart)
- _accountSecureKeys 新增 inviterReferralCode(修复邀请码串号)
- _accountLocalKeys 新增 4个种植省市key + cachedAppAssets +
cachedCustomerServiceContacts(修复种植/缓存数据串号)
- switchToAccount() 新增 onBeforeRestore 回调参数,用于在
storage清空后、恢复新数据前 停止定时器
3. AccountSwitchPage 三层状态重置(account_switch_page.dart)
- _switchToAccount: onBeforeRestore 内停止 walletStatus 轮询、
pendingAction 轮询、telemetry 上传;返回后 invalidate
authProvider/walletStatusProvider/notificationBadgeProvider,
最后 resumeAfterLogin 恢复遥测
- _addNewAccount: 退出前停止定时器,退出后 invalidate Provider
4. ProfilePage 退出登录补全清理(profile_page.dart)
- _performLogout: 退出前停止 walletStatus/pendingAction 轮询,
退出后 invalidate 三个 Provider
5. 页面 key 统一引用 StorageKeys 常量
- planting_location_page.dart 和 authorization_apply_page.dart
将硬编码 key 替换为 StorageKeys.plantingXxx 常量
关键时序(switchToAccount 内部):
save → clear → onBeforeRestore(停timer) → restore → 返回
→ invalidate Provider(此时storage已恢复新数据)→ resume telemetry → navigate
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a5a69645b4
commit
8f8a9230d0
|
|
@ -75,6 +75,7 @@ class MultiAccountService {
|
|||
StorageKeys.avatarUrl,
|
||||
StorageKeys.referralCode,
|
||||
StorageKeys.inviterSequence,
|
||||
StorageKeys.inviterReferralCode,
|
||||
StorageKeys.isAccountCreated,
|
||||
StorageKeys.phoneNumber,
|
||||
StorageKeys.isPasswordSet,
|
||||
|
|
@ -94,6 +95,14 @@ class MultiAccountService {
|
|||
StorageKeys.lastSyncTime,
|
||||
StorageKeys.cachedRankingData,
|
||||
StorageKeys.cachedMiningStatus,
|
||||
// 种植省市选择
|
||||
StorageKeys.plantingProvinceName,
|
||||
StorageKeys.plantingProvinceCode,
|
||||
StorageKeys.plantingCityName,
|
||||
StorageKeys.plantingCityCode,
|
||||
// 非敏感缓存(仍需隔离以保证干净环境)
|
||||
StorageKeys.cachedAppAssets,
|
||||
StorageKeys.cachedCustomerServiceContacts,
|
||||
];
|
||||
|
||||
/// 获取账号列表
|
||||
|
|
@ -206,15 +215,20 @@ class MultiAccountService {
|
|||
/// 切换到指定账号
|
||||
/// 返回 true 表示切换成功
|
||||
///
|
||||
/// [onBeforeRestore] 在清除旧数据之后、恢复新账号数据之前调用,
|
||||
/// 用于重置内存中的 Provider 状态(如 authProvider、walletStatusProvider 等),
|
||||
/// 确保不会有前一个账号的内存数据残留。
|
||||
///
|
||||
/// 切换流程:
|
||||
/// 1. 验证目标账号存在
|
||||
/// 2. 验证目标账号数据完整性
|
||||
/// 3. 保存当前账号数据到账号专用存储
|
||||
/// 4. 清除当前存储空间(确保干净环境)
|
||||
/// 4.5 调用 onBeforeRestore 重置内存状态
|
||||
/// 5. 从账号专用存储恢复目标账号数据
|
||||
/// 6. 设置当前账号标记
|
||||
/// 7. 更新遥测和错误追踪服务的用户信息
|
||||
Future<bool> switchToAccount(String userSerialNum) async {
|
||||
Future<bool> switchToAccount(String userSerialNum, {Future<void> Function()? onBeforeRestore}) async {
|
||||
debugPrint('$_tag switchToAccount() - 切换到账号: $userSerialNum');
|
||||
|
||||
// 1. 验证账号存在于列表中
|
||||
|
|
@ -243,6 +257,12 @@ class MultiAccountService {
|
|||
// 4. 清除当前存储空间(确保干净环境,避免数据残留)
|
||||
await _clearCurrentAccountData();
|
||||
|
||||
// 4.5 重置内存中的 Provider 状态(避免前账号数据残留在内存中)
|
||||
if (onBeforeRestore != null) {
|
||||
await onBeforeRestore();
|
||||
debugPrint('$_tag switchToAccount() - 已重置内存状态');
|
||||
}
|
||||
|
||||
// 5. 从账号专用存储恢复目标账号数据
|
||||
await _restoreAccountData(userSerialNum);
|
||||
|
||||
|
|
@ -395,9 +415,8 @@ class MultiAccountService {
|
|||
for (final key in _accountSecureKeys) {
|
||||
await _secureStorage.delete(key: key);
|
||||
}
|
||||
// 额外清除临时数据(不属于账号专用存储)
|
||||
await _secureStorage.delete(key: StorageKeys.inviterReferralCode);
|
||||
debugPrint('$_tag logoutCurrentAccount() - 已清除 ${_accountSecureKeys.length + 1} 个 SecureStorage 键');
|
||||
// inviterReferralCode 已包含在 _accountSecureKeys 中,无需额外清除
|
||||
debugPrint('$_tag logoutCurrentAccount() - 已清除 ${_accountSecureKeys.length} 个 SecureStorage 键');
|
||||
|
||||
// ===== 2. 清除 LocalStorage 中的缓存数据 =====
|
||||
for (final key in _accountLocalKeys) {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,12 @@ class StorageKeys {
|
|||
static const String deviceId = 'device_id';
|
||||
static const String deviceName = 'device_name';
|
||||
|
||||
// Planting Location (种植省市选择)
|
||||
static const String plantingProvinceName = 'planting_province_name';
|
||||
static const String plantingProvinceCode = 'planting_province_code';
|
||||
static const String plantingCityName = 'planting_city_name';
|
||||
static const String plantingCityCode = 'planting_city_code';
|
||||
|
||||
// Cache
|
||||
static const String lastSyncTime = 'last_sync_time';
|
||||
static const String cachedRankingData = 'cached_ranking_data';
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import 'package:go_router/go_router.dart';
|
|||
import '../../../../core/di/injection_container.dart';
|
||||
import '../../../../core/services/multi_account_service.dart';
|
||||
import '../../../../core/providers/notification_badge_provider.dart';
|
||||
import '../../../../core/telemetry/telemetry_service.dart';
|
||||
import '../../../auth/presentation/providers/auth_provider.dart';
|
||||
import '../../../auth/presentation/providers/wallet_status_provider.dart';
|
||||
import '../../../../routes/route_paths.dart';
|
||||
|
||||
/// 账号切换页面
|
||||
|
|
@ -65,11 +68,32 @@ class _AccountSwitchPageState extends ConsumerState<AccountSwitchPage> {
|
|||
await multiAccountService.saveCurrentAccountData();
|
||||
|
||||
// 切换到新账号
|
||||
final success = await multiAccountService.switchToAccount(account.userSerialNum);
|
||||
// onBeforeRestore: storage 已清空、新数据尚未恢复 → 只停定时器
|
||||
// Provider invalidate 必须在 switchToAccount 返回后做(此时 storage 已恢复新账号数据)
|
||||
final success = await multiAccountService.switchToAccount(
|
||||
account.userSerialNum,
|
||||
onBeforeRestore: () async {
|
||||
// ===== 1. 停止所有用户相关的定时任务 =====
|
||||
ref.read(walletStatusProvider.notifier).stopPolling();
|
||||
ref.read(pendingActionPollingServiceProvider).stop();
|
||||
// 遥测:清空旧账号事件队列、停止上传
|
||||
if (TelemetryService().isInitialized) {
|
||||
await TelemetryService().pauseForLogout();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (success && mounted) {
|
||||
// 刷新新账号的未读通知数量
|
||||
ref.read(notificationBadgeProvider.notifier).refresh();
|
||||
// ===== 2. invalidate 所有账号相关 Provider(此时 storage 已是新账号数据)=====
|
||||
ref.invalidate(authProvider);
|
||||
ref.invalidate(walletStatusProvider);
|
||||
ref.invalidate(notificationBadgeProvider);
|
||||
|
||||
// ===== 3. 恢复遥测上传(新账号上下文)=====
|
||||
if (TelemetryService().isInitialized) {
|
||||
TelemetryService().resumeAfterLogin();
|
||||
}
|
||||
|
||||
// 切换成功,跳转到主页刷新状态
|
||||
context.go(RoutePaths.ranking);
|
||||
} else if (mounted) {
|
||||
|
|
@ -102,11 +126,17 @@ class _AccountSwitchPageState extends ConsumerState<AccountSwitchPage> {
|
|||
// 保存当前账号数据
|
||||
await multiAccountService.saveCurrentAccountData();
|
||||
|
||||
// ===== 1. 停止所有用户相关的定时任务 =====
|
||||
ref.read(walletStatusProvider.notifier).stopPolling();
|
||||
ref.read(pendingActionPollingServiceProvider).stop();
|
||||
|
||||
// 退出当前账号但保留数据
|
||||
await multiAccountService.logoutCurrentAccount();
|
||||
|
||||
// 清空未读通知数量
|
||||
ref.read(notificationBadgeProvider.notifier).clearCount();
|
||||
// ===== 2. invalidate 所有账号相关 Provider =====
|
||||
ref.invalidate(authProvider);
|
||||
ref.invalidate(walletStatusProvider);
|
||||
ref.invalidate(notificationBadgeProvider);
|
||||
|
||||
// 跳转到向导页创建新账号
|
||||
if (mounted) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:city_pickers/city_pickers.dart';
|
||||
import '../../../../core/di/injection_container.dart';
|
||||
import '../../../../core/storage/storage_keys.dart';
|
||||
import '../../../../core/services/authorization_service.dart';
|
||||
|
||||
/// 授权类型枚举
|
||||
|
|
@ -50,11 +51,11 @@ class AuthorizationApplyPage extends ConsumerStatefulWidget {
|
|||
|
||||
class _AuthorizationApplyPageState
|
||||
extends ConsumerState<AuthorizationApplyPage> {
|
||||
/// 本地存储 key(与认种页面保持一致)
|
||||
static const String _keyProvinceName = 'planting_province_name';
|
||||
static const String _keyProvinceCode = 'planting_province_code';
|
||||
static const String _keyCityName = 'planting_city_name';
|
||||
static const String _keyCityCode = 'planting_city_code';
|
||||
/// 本地存储 key(统一使用 StorageKeys 常量,确保多账号隔离一致性)
|
||||
static const String _keyProvinceName = StorageKeys.plantingProvinceName;
|
||||
static const String _keyProvinceCode = StorageKeys.plantingProvinceCode;
|
||||
static const String _keyCityName = StorageKeys.plantingCityName;
|
||||
static const String _keyCityCode = StorageKeys.plantingCityCode;
|
||||
|
||||
/// 选中的授权类型
|
||||
AuthorizationType? _selectedType;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:city_pickers/city_pickers.dart';
|
|||
import '../widgets/planting_confirm_dialog.dart';
|
||||
import '../widgets/kyc_required_dialog.dart';
|
||||
import '../../../../core/di/injection_container.dart';
|
||||
import '../../../../core/storage/storage_keys.dart';
|
||||
import '../../../../routes/route_paths.dart';
|
||||
|
||||
/// 认种省市选择页面参数
|
||||
|
|
@ -40,11 +41,11 @@ class PlantingLocationPage extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _PlantingLocationPageState extends ConsumerState<PlantingLocationPage> {
|
||||
/// 本地存储 key 前缀
|
||||
static const String _keyProvinceName = 'planting_province_name';
|
||||
static const String _keyProvinceCode = 'planting_province_code';
|
||||
static const String _keyCityName = 'planting_city_name';
|
||||
static const String _keyCityCode = 'planting_city_code';
|
||||
/// 本地存储 key(统一使用 StorageKeys 常量,确保多账号隔离一致性)
|
||||
static const String _keyProvinceName = StorageKeys.plantingProvinceName;
|
||||
static const String _keyProvinceCode = StorageKeys.plantingProvinceCode;
|
||||
static const String _keyCityName = StorageKeys.plantingCityName;
|
||||
static const String _keyCityCode = StorageKeys.plantingCityCode;
|
||||
|
||||
/// 选中的省份名称
|
||||
String? _selectedProvinceName;
|
||||
|
|
|
|||
|
|
@ -4794,9 +4794,19 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
/// 执行退出登录
|
||||
Future<void> _performLogout() async {
|
||||
try {
|
||||
// ===== 1. 停止所有用户相关的定时任务 =====
|
||||
ref.read(walletStatusProvider.notifier).stopPolling();
|
||||
ref.read(pendingActionPollingServiceProvider).stop();
|
||||
|
||||
final multiAccountService = ref.read(multiAccountServiceProvider);
|
||||
// 退出当前账号(保留账号数据)
|
||||
await multiAccountService.logoutCurrentAccount();
|
||||
|
||||
// ===== 2. invalidate 所有账号相关 Provider =====
|
||||
ref.invalidate(authProvider);
|
||||
ref.invalidate(walletStatusProvider);
|
||||
ref.invalidate(notificationBadgeProvider);
|
||||
|
||||
// 导航到向导页
|
||||
if (mounted) {
|
||||
context.go(RoutePaths.guide);
|
||||
|
|
|
|||
Loading…
Reference in New Issue