feat: 添加缓存作用域支持多用户数据隔离

This commit is contained in:
zl-q
2026-03-30 09:06:24 +08:00
parent 4285b4ec80
commit 41f35d6e22
4 changed files with 72 additions and 14 deletions
+28
View File
@@ -0,0 +1,28 @@
typedef CacheScopeProvider = String? Function();
class CacheScope {
CacheScope._();
static CacheScopeProvider? _provider;
static void configureProvider(CacheScopeProvider provider) {
_provider = provider;
}
static void resetProvider() {
_provider = null;
}
static String token() {
final raw = _provider?.call()?.trim();
if (raw == null || raw.isEmpty) {
return 'anonymous';
}
return raw;
}
static String scopedKey(String key, {String? scopeToken}) {
final scope = scopeToken ?? token();
return 'cache:$scope:$key';
}
}
+3 -1
View File
@@ -3,6 +3,8 @@ import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'cache_scope.dart';
class CacheEntry<T> { class CacheEntry<T> {
const CacheEntry({required this.value, required this.fetchedAt}); const CacheEntry({required this.value, required this.fetchedAt});
@@ -196,7 +198,7 @@ class CacheInvalidator {
void invalidate(String key) { void invalidate(String key) {
final store = _store; final store = _store;
if (store != null) { if (store != null) {
unawaited(store.remove(key)); unawaited(store.remove(CacheScope.scopedKey(key)));
} }
} }
+24 -13
View File
@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'cache_scope.dart';
import 'cache_policy.dart'; import 'cache_policy.dart';
import 'cache_store.dart'; import 'cache_store.dart';
@@ -31,18 +32,21 @@ abstract class CachedRepository<T> {
bool Function(T loaded)? shouldWriteLoaded, bool Function(T loaded)? shouldWriteLoaded,
bool forceRefresh = false, bool forceRefresh = false,
}) async { }) async {
final scopeToken = CacheScope.token();
final scopedKey = CacheScope.scopedKey(key, scopeToken: scopeToken);
if (forceRefresh) { if (forceRefresh) {
return _refreshAndWrite( return _refreshAndWrite(
key, scopedKey,
loadFromRemote, loadFromRemote,
shouldWriteLoaded: shouldWriteLoaded, shouldWriteLoaded: shouldWriteLoaded,
); );
} }
final cached = await readCacheEntry(key); final cached = await _readDecodedEntry(scopedKey);
if (cached == null) { if (cached == null) {
return _refreshAndWrite( return _refreshAndWrite(
key, scopedKey,
loadFromRemote, loadFromRemote,
shouldWriteLoaded: shouldWriteLoaded, shouldWriteLoaded: shouldWriteLoaded,
); );
@@ -51,14 +55,14 @@ abstract class CachedRepository<T> {
final decision = policy.evaluate(now: now(), fetchedAt: cached.fetchedAt); final decision = policy.evaluate(now: now(), fetchedAt: cached.fetchedAt);
if (decision.shouldRefreshInBackground) { if (decision.shouldRefreshInBackground) {
refreshInBackground( refreshInBackground(
key: key, key: scopedKey,
loadFromRemote: loadFromRemote, loadFromRemote: loadFromRemote,
shouldWriteLoaded: shouldWriteLoaded, shouldWriteLoaded: shouldWriteLoaded,
); );
} }
if (decision.mustBlockForNetwork || !decision.canUseCached) { if (decision.mustBlockForNetwork || !decision.canUseCached) {
return _refreshAndWrite( return _refreshAndWrite(
key, scopedKey,
loadFromRemote, loadFromRemote,
shouldWriteLoaded: shouldWriteLoaded, shouldWriteLoaded: shouldWriteLoaded,
); );
@@ -66,13 +70,13 @@ abstract class CachedRepository<T> {
return cached.value; return cached.value;
} }
Future<CacheEntry<T>?> readCacheEntry(String key) { Future<CacheEntry<T>?> readCacheEntry(String key, {String? scopeToken}) {
return _readDecodedEntry(key); return _readDecodedEntry(CacheScope.scopedKey(key, scopeToken: scopeToken));
} }
Future<void> writeCacheEntry(String key, T value) { Future<void> writeCacheEntry(String key, T value, {String? scopeToken}) {
return store.write<CacheEntry<Object?>>( return store.write<CacheEntry<Object?>>(
key, _scopedKey(key, scopeToken: scopeToken),
CacheEntry<Object?>(value: encodeValue(value), fetchedAt: now()), CacheEntry<Object?>(value: encodeValue(value), fetchedAt: now()),
); );
} }
@@ -93,8 +97,8 @@ abstract class CachedRepository<T> {
} }
} }
Future<void> removeCacheKey(String key) { Future<void> removeCacheKey(String key, {String? scopeToken}) {
return store.remove(key); return store.remove(CacheScope.scopedKey(key, scopeToken: scopeToken));
} }
void refreshInBackground({ void refreshInBackground({
@@ -118,7 +122,7 @@ abstract class CachedRepository<T> {
} }
Future<T> _refreshAndWrite( Future<T> _refreshAndWrite(
String key, String scopedKey,
Future<T> Function() loadFromRemote, { Future<T> Function() loadFromRemote, {
bool Function(T loaded)? shouldWriteLoaded, bool Function(T loaded)? shouldWriteLoaded,
}) async { }) async {
@@ -126,7 +130,14 @@ abstract class CachedRepository<T> {
if (shouldWriteLoaded != null && !shouldWriteLoaded(remote)) { if (shouldWriteLoaded != null && !shouldWriteLoaded(remote)) {
return remote; return remote;
} }
await writeCacheEntry(key, remote); await store.write<CacheEntry<Object?>>(
scopedKey,
CacheEntry<Object?>(value: encodeValue(remote), fetchedAt: now()),
);
return remote; return remote;
} }
String _scopedKey(String key, {String? scopeToken}) {
return CacheScope.scopedKey(key, scopeToken: scopeToken);
}
} }
+17
View File
@@ -0,0 +1,17 @@
class DialCode {
const DialCode(this.value);
final String value;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is DialCode &&
runtimeType == other.runtimeType &&
value == other.value;
@override
int get hashCode => value.hashCode;
}
const kDialCodes = <DialCode>[DialCode('+86')];