Files
CloudHome-Ops/lib/utils/credential_storage.dart

135 lines
4.7 KiB
Dart
Raw Normal View History

import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
/// 本地凭据持久化(基于 SharedPreferences,跨平台可靠持久化)。
///
/// 存储内容:
/// - 账号 / 密码(仅当用户勾选「记住密码」时保存)
/// - token / refreshToken / 用户信息(登录成功后保存,用于免登录恢复)
///
/// 历史坑:早期版本使用 dart:io 文件存储 + `Directory.current`,但 Android 上
/// `Directory.current` 指向不可写路径,会静默退化为「仅内存」存储,导致 App 重启后
/// 凭据与登录态全部丢失(表现即「记住账号密码不生效 / 每次都要重新登录」)。
/// 现统一改用 SharedPreferences(应用私有、沙箱隔离、必定可写、跨重启持久)。
class CredentialStorage {
CredentialStorage._();
/// 单例。
static final CredentialStorage instance = CredentialStorage._();
/// SharedPreferences 实例;[init] 完成后非 null(除非极端初始化失败)。
SharedPreferences? _prefs;
/// 是否已尝试初始化(无论成功失败都置 true,避免重复 await 同一 Future)。
bool _initialized = false;
/// 异步预初始化:必须在 runApp 前 await,使下方同步 getter 可读到持久化数据。
static Future<void> init() async {
await instance._init();
}
Future<void> _init() async {
if (_initialized) return;
try {
_prefs = await SharedPreferences.getInstance();
} catch (_) {
// 极端情况下 SharedPreferences 不可用:退化为内存态,不影响启动与登录。
_prefs = null;
} finally {
_initialized = true;
}
}
// ── 账号密码 ──────────────────────────────────────────
/// 是否记住密码。
bool get rememberPassword => _prefs?.getBool(_kRemember) ?? false;
/// 保存的账号(rememberPassword=true 时有值)。
String? get savedAccount => _prefs?.getString(_kAccount);
/// 保存的密码(rememberPassword=true 时有值)。
String? get savedPassword => _prefs?.getString(_kPassword);
/// 保存账号密码(用户勾选「记住密码」并登录成功后调用)。
Future<void> saveCredentials({
required String account,
required String password,
}) async {
final prefs = _prefs;
if (prefs == null) return;
await prefs.setString(_kAccount, account);
await prefs.setString(_kPassword, password);
await prefs.setBool(_kRemember, true);
}
/// 清除保存的账号密码(用户未勾选「记住密码」时调用)。
Future<void> clearCredentials() async {
final prefs = _prefs;
if (prefs == null) return;
await prefs.remove(_kAccount);
await prefs.remove(_kPassword);
await prefs.setBool(_kRemember, false);
}
// ── Token & 用户信息(用于免登录恢复) ──────────────────
/// 保存的 token。
String? get token => _prefs?.getString(_kToken);
/// 保存的 refreshToken。
String? get refreshToken => _prefs?.getString(_kRefreshToken);
/// 保存的用户信息 JSON 字符串。
String? get userJson => _prefs?.getString(_kUserJson);
/// 保存登录态(token + refreshToken + 用户信息)。
Future<void> saveSession({
required String token,
String? refreshToken,
String? userJson,
}) async {
final prefs = _prefs;
if (prefs == null) return;
await prefs.setString(_kToken, token);
if (refreshToken != null && refreshToken.isNotEmpty) {
await prefs.setString(_kRefreshToken, refreshToken);
}
if (userJson != null && userJson.isNotEmpty) {
await prefs.setString(_kUserJson, userJson);
}
}
/// 清除登录态(登出或会话失效时调用)。
Future<void> clearSession() async {
final prefs = _prefs;
if (prefs == null) return;
await prefs.remove(_kToken);
await prefs.remove(_kRefreshToken);
await prefs.remove(_kUserJson);
}
/// 是否有已保存的登录态。
bool get hasSession => (token ?? '').isNotEmpty;
/// 从 JSON 恢复用户信息 Map。
Map<String, dynamic>? get savedUserMap {
final json = userJson;
if (json == null || json.isEmpty) return null;
try {
return jsonDecode(json) as Map<String, dynamic>;
} catch (_) {
return null;
}
}
// key 常量(加前缀避免与 StoreLocalCache 等业务 key 冲突)。
static const String _kRemember = 'cred_remember';
static const String _kAccount = 'cred_account';
static const String _kPassword = 'cred_password';
static const String _kToken = 'cred_token';
static const String _kRefreshToken = 'cred_refreshToken';
static const String _kUserJson = 'cred_userJson';
}