ops-console v1.0.2 (versionCode 8) 源码入库
This commit is contained in:
134
lib/utils/credential_storage.dart
Normal file
134
lib/utils/credential_storage.dart
Normal file
@@ -0,0 +1,134 @@
|
||||
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';
|
||||
}
|
||||
Reference in New Issue
Block a user