fix(mobile-app): 修复账号切换的严重bug和数据隔离问题

问题修复:
1. 键列表不一致 - 统一定义 _accountSecureKeys 和 _accountLocalKeys
2. 缺少 phoneNumber/isPasswordSet/biometricEnabled - 补充到键列表
3. 切换前未清除旧数据 - 新增 _clearCurrentAccountData 方法
4. 缓存数据未按账号隔离 - LocalStorage 数据也按账号保存/恢复
5. 遥测队列未隔离 - 切换时清除遥测事件队列

新增功能:
- _validateAccountData: 切换前验证目标账号数据完整性
- _clearCurrentAccountData: 切换前清除当前存储空间

优化:
- switchToAccount: 完整的切换流程(验证→保存→清除→恢复)
- saveCurrentAccountData: 同时保存 SecureStorage 和 LocalStorage
- _restoreAccountData: 同时恢复 SecureStorage 和 LocalStorage
- deleteAccount: 同时删除 SecureStorage 和 LocalStorage 专用键
- logoutCurrentAccount: 使用统一键列表,确保一致性

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-27 10:01:11 -08:00
parent 6216a1563a
commit 8eecc4c55f
1 changed files with 184 additions and 116 deletions

View File

@ -60,6 +60,42 @@ class MultiAccountService {
this._telemetryStorage,
);
// ===== =====
/// SecureStorage
///
static const List<String> _accountSecureKeys = [
// Token
StorageKeys.accessToken,
StorageKeys.refreshToken,
//
StorageKeys.userSerialNum,
StorageKeys.username,
StorageKeys.avatarSvg,
StorageKeys.avatarUrl,
StorageKeys.referralCode,
StorageKeys.inviterSequence,
StorageKeys.isAccountCreated,
StorageKeys.phoneNumber,
StorageKeys.isPasswordSet,
//
StorageKeys.walletAddressBsc,
StorageKeys.walletAddressKava,
StorageKeys.walletAddressDst,
StorageKeys.mnemonic,
StorageKeys.isWalletReady,
StorageKeys.isMnemonicBackedUp,
//
StorageKeys.biometricEnabled,
];
/// LocalStorage
static const List<String> _accountLocalKeys = [
StorageKeys.lastSyncTime,
StorageKeys.cachedRankingData,
StorageKeys.cachedMiningStatus,
];
///
Future<List<AccountSummary>> getAccountList() async {
debugPrint('$_tag getAccountList() - 获取账号列表');
@ -136,31 +172,57 @@ class MultiAccountService {
///
/// true
///
///
/// 1.
/// 2.
/// 3.
/// 4.
/// 5.
/// 6.
/// 7.
Future<bool> switchToAccount(String userSerialNum) async {
debugPrint('$_tag switchToAccount() - 切换到账号: $userSerialNum');
//
// 1.
final accounts = await getAccountList();
final account = accounts.where((a) => a.userSerialNum == userSerialNum).firstOrNull;
if (account == null) {
debugPrint('$_tag switchToAccount() - 账号不存在');
debugPrint('$_tag switchToAccount() - 账号不列表中');
return false;
}
//
// 2. accessToken mnemonic
final hasValidData = await _validateAccountData(userSerialNum);
if (!hasValidData) {
debugPrint('$_tag switchToAccount() - 账号数据不完整,无法切换');
return false;
}
// 3.
final currentId = await getCurrentAccountId();
if (currentId != null && currentId != userSerialNum) {
await saveCurrentAccountData();
debugPrint('$_tag switchToAccount() - 已保存当前账号数据: $currentId');
}
// 4.
await _clearCurrentAccountData();
// 5.
await _restoreAccountData(userSerialNum);
//
// 6.
await setCurrentAccountId(userSerialNum);
// ID使userSerialNumD25121400005
// 7. ID
if (TelemetryService().isInitialized) {
TelemetryService().setUserId(userSerialNum);
debugPrint('$_tag switchToAccount() - 设置TelemetryService userId: $userSerialNum');
}
// Sentry
// 8. Sentry
if (SentryService().isInitialized) {
SentryService().setUser(userId: userSerialNum);
debugPrint('$_tag switchToAccount() - 设置SentryService userId: $userSerialNum');
@ -170,7 +232,57 @@ class MultiAccountService {
return true;
}
///
///
Future<bool> _validateAccountData(String accountId) async {
// userSerialNum
final userSerialNumKey = StorageKeys.withAccountPrefix(accountId, StorageKeys.userSerialNum);
final userSerialNum = await _secureStorage.read(key: userSerialNumKey);
if (userSerialNum == null || userSerialNum.isEmpty) {
debugPrint('$_tag _validateAccountData() - 缺少 userSerialNum');
return false;
}
// token 使
final accessTokenKey = StorageKeys.withAccountPrefix(accountId, StorageKeys.accessToken);
final mnemonicKey = StorageKeys.withAccountPrefix(accountId, StorageKeys.mnemonic);
final accessToken = await _secureStorage.read(key: accessTokenKey);
final mnemonic = await _secureStorage.read(key: mnemonicKey);
if ((accessToken == null || accessToken.isEmpty) &&
(mnemonic == null || mnemonic.isEmpty)) {
debugPrint('$_tag _validateAccountData() - 缺少 accessToken 和 mnemonic');
return false;
}
return true;
}
///
///
Future<void> _clearCurrentAccountData() async {
debugPrint('$_tag _clearCurrentAccountData() - 清除当前存储空间');
// SecureStorage
for (final key in _accountSecureKeys) {
await _secureStorage.delete(key: key);
}
// LocalStorage
for (final key in _accountLocalKeys) {
await _localStorage.remove(key);
}
//
await _telemetryStorage.clearUserData();
debugPrint('$_tag _clearCurrentAccountData() - 清除完成');
}
///
/// SecureStorage LocalStorage
Future<void> saveCurrentAccountData() async {
final currentId = await getCurrentAccountId();
if (currentId == null) {
@ -180,71 +292,60 @@ class MultiAccountService {
debugPrint('$_tag saveCurrentAccountData() - 保存账号数据: $currentId');
//
final keysToSave = [
StorageKeys.userSerialNum,
StorageKeys.username,
StorageKeys.avatarSvg,
StorageKeys.avatarUrl,
StorageKeys.referralCode,
StorageKeys.inviterSequence,
StorageKeys.isAccountCreated,
StorageKeys.walletAddressBsc,
StorageKeys.walletAddressKava,
StorageKeys.walletAddressDst,
StorageKeys.mnemonic,
StorageKeys.isWalletReady,
StorageKeys.isMnemonicBackedUp,
StorageKeys.accessToken,
StorageKeys.refreshToken,
];
for (final key in keysToSave) {
// SecureStorage
int secureCount = 0;
for (final key in _accountSecureKeys) {
final value = await _secureStorage.read(key: key);
if (value != null) {
final prefixedKey = StorageKeys.withAccountPrefix(currentId, key);
await _secureStorage.write(key: prefixedKey, value: value);
secureCount++;
}
}
debugPrint('$_tag saveCurrentAccountData() - 保存完成');
// LocalStorage
int localCount = 0;
for (final key in _accountLocalKeys) {
final value = _localStorage.getString(key);
if (value != null) {
final prefixedKey = StorageKeys.withAccountPrefix(currentId, key);
await _localStorage.setString(prefixedKey, value);
localCount++;
}
}
debugPrint('$_tag saveCurrentAccountData() - 保存完成 (Secure: $secureCount, Local: $localCount)');
}
///
/// SecureStorage LocalStorage
Future<void> _restoreAccountData(String accountId) async {
debugPrint('$_tag _restoreAccountData() - 恢复账号数据: $accountId');
//
final keysToRestore = [
StorageKeys.userSerialNum,
StorageKeys.username,
StorageKeys.avatarSvg,
StorageKeys.avatarUrl,
StorageKeys.referralCode,
StorageKeys.inviterSequence,
StorageKeys.isAccountCreated,
StorageKeys.walletAddressBsc,
StorageKeys.walletAddressKava,
StorageKeys.walletAddressDst,
StorageKeys.mnemonic,
StorageKeys.isWalletReady,
StorageKeys.isMnemonicBackedUp,
StorageKeys.accessToken,
StorageKeys.refreshToken,
];
for (final key in keysToRestore) {
// SecureStorage
int secureCount = 0;
for (final key in _accountSecureKeys) {
final prefixedKey = StorageKeys.withAccountPrefix(accountId, key);
final value = await _secureStorage.read(key: prefixedKey);
if (value != null) {
await _secureStorage.write(key: key, value: value);
} else {
//
await _secureStorage.delete(key: key);
secureCount++;
}
// _clearCurrentAccountData
}
// LocalStorage
int localCount = 0;
for (final key in _accountLocalKeys) {
final prefixedKey = StorageKeys.withAccountPrefix(accountId, key);
final value = _localStorage.getString(prefixedKey);
if (value != null) {
await _localStorage.setString(key, value);
localCount++;
}
}
debugPrint('$_tag _restoreAccountData() - 恢复完成');
debugPrint('$_tag _restoreAccountData() - 恢复完成 (Secure: $secureCount, Local: $localCount)');
}
/// 退
@ -259,49 +360,20 @@ class MultiAccountService {
//
await setCurrentAccountId(null);
// ===== 1. SecureStorage =====
final secureKeysToClear = [
// Token
StorageKeys.accessToken,
StorageKeys.refreshToken,
//
StorageKeys.userSerialNum,
StorageKeys.username,
StorageKeys.avatarSvg,
StorageKeys.avatarUrl,
StorageKeys.referralCode,
StorageKeys.inviterSequence,
StorageKeys.inviterReferralCode, //
StorageKeys.isAccountCreated,
StorageKeys.phoneNumber,
StorageKeys.isPasswordSet,
//
StorageKeys.walletAddressBsc,
StorageKeys.walletAddressKava,
StorageKeys.walletAddressDst,
StorageKeys.mnemonic,
StorageKeys.isWalletReady,
StorageKeys.isMnemonicBackedUp,
//
StorageKeys.biometricEnabled,
];
for (final key in secureKeysToClear) {
// ===== 1. SecureStorage =====
// 使
for (final key in _accountSecureKeys) {
await _secureStorage.delete(key: key);
}
debugPrint('$_tag logoutCurrentAccount() - 已清除 ${secureKeysToClear.length} 个 SecureStorage 键');
//
await _secureStorage.delete(key: StorageKeys.inviterReferralCode);
debugPrint('$_tag logoutCurrentAccount() - 已清除 ${_accountSecureKeys.length + 1} 个 SecureStorage 键');
// ===== 2. LocalStorage =====
final localKeysToClear = [
StorageKeys.lastSyncTime,
StorageKeys.cachedRankingData,
StorageKeys.cachedMiningStatus,
];
for (final key in localKeysToClear) {
for (final key in _accountLocalKeys) {
await _localStorage.remove(key);
}
debugPrint('$_tag logoutCurrentAccount() - 已清除 ${localKeysToClear.length} 个 LocalStorage 缓存');
debugPrint('$_tag logoutCurrentAccount() - 已清除 ${_accountLocalKeys.length} 个 LocalStorage 缓存');
// ===== 3. =====
await _telemetryStorage.clearUserData();
@ -318,7 +390,7 @@ class MultiAccountService {
debugPrint('$_tag logoutCurrentAccount() - 清除 SentryService userId');
}
final totalCleared = secureKeysToClear.length + localKeysToClear.length;
final totalCleared = _accountSecureKeys.length + _accountLocalKeys.length + 1;
debugPrint('$_tag logoutCurrentAccount() - 退出完成,共清除 $totalCleared 个存储键 + 遥测数据');
}
@ -326,42 +398,38 @@ class MultiAccountService {
Future<void> deleteAccount(String userSerialNum) async {
debugPrint('$_tag deleteAccount() - 删除账号: $userSerialNum');
// 退
final currentId = await getCurrentAccountId();
if (currentId == userSerialNum) {
//
await setCurrentAccountId(null);
await _clearCurrentAccountData();
// Sentry
if (TelemetryService().isInitialized) {
TelemetryService().clearUserId();
}
if (SentryService().isInitialized) {
SentryService().clearUser();
}
}
//
await removeAccount(userSerialNum);
//
final keysToDelete = [
StorageKeys.userSerialNum,
StorageKeys.username,
StorageKeys.avatarSvg,
StorageKeys.avatarUrl,
StorageKeys.referralCode,
StorageKeys.inviterSequence,
StorageKeys.isAccountCreated,
StorageKeys.phoneNumber,
StorageKeys.isPasswordSet,
StorageKeys.walletAddressBsc,
StorageKeys.walletAddressKava,
StorageKeys.walletAddressDst,
StorageKeys.mnemonic,
StorageKeys.isWalletReady,
StorageKeys.isMnemonicBackedUp,
StorageKeys.accessToken,
StorageKeys.refreshToken,
];
for (final key in keysToDelete) {
// SecureStorage
for (final key in _accountSecureKeys) {
final prefixedKey = StorageKeys.withAccountPrefix(userSerialNum, key);
await _secureStorage.delete(key: prefixedKey);
}
//
final currentId = await getCurrentAccountId();
if (currentId == userSerialNum) {
await logoutCurrentAccount();
// LocalStorage
for (final key in _accountLocalKeys) {
final prefixedKey = StorageKeys.withAccountPrefix(userSerialNum, key);
await _localStorage.remove(prefixedKey);
}
debugPrint('$_tag deleteAccount() - 删除完成');
debugPrint('$_tag deleteAccount() - 删除完成,已清除 ${_accountSecureKeys.length + _accountLocalKeys.length} 个账号专用键');
}
///