chore: 初始化脚手架 — sunny_mochi 企业级 Flutter 模板

Core 基础设施:Flavor 三包体系、Drift+SQLCipher 加密数据库
7 拦截器 Dio 网络栈、CrashReporter 崩溃日志、Sealed Failure 错误体系
5 色板主题系统、Auth 认证骨架(Clean Architecture)、Dev Panel、Mock Adapter

通过验证:flutter analyze(0 errors)、flutter test(全绿)、staging APK 74.8MB
This commit is contained in:
SkyJourney
2026-05-14 12:51:05 +08:00
commit 61017f1c39
204 changed files with 15386 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
import 'dart:async';
/// Repository 层数据新鲜度策略 — 反馈文档 §"实时性分级的工程落地"。
///
/// **核心理念**:把"数据新鲜度"和"UI 可用性"解耦:
/// - 弱实时性场景(列表 / 档案)→ 优先返回缓存,后台刷新
/// - 强实时性场景(订单状态 / 支付结果)→ 优先实时,失败才降级
///
/// 在 Repository 方法签名里把策略当参数传入:
/// ```dart
/// Future<List<Order>> fetchOrders({
/// FreshnessPolicy policy = FreshnessPolicy.cacheFirst,
/// });
/// ```
/// UseCase / Notifier 决定使用哪种策略,Repository 内部用 [policyAwareFetch]
/// 实现统一的"读缓存 / 拉网络 / 写缓存"流程。
enum FreshnessPolicy {
/// 弱实时:先缓存,后台刷新。
/// 网络失败不影响 UI 显示(缓存兜底)。**列表页、档案展示首选**。
cacheFirst,
/// 强实时:先尝试网络,失败时才降级到缓存。
/// 适合订单状态、库存、消息计数等"过期数据会误导用户"的场景。
networkFirst,
/// 纯离线:只读缓存,不发起网络请求。
/// 适合电梯/地铁等已知无网场景,或用户主动选择离线模式。
cacheOnly,
/// 强制实时:只走网络,不读不写缓存。
/// 适合一次性操作(提交表单、确认下单)—— 缓存反而是污染源。
networkOnly,
}
/// 按 [FreshnessPolicy] 编排"读缓存 + 拉网络 + 写缓存"的统一流程。
///
/// **接入约定**
/// - [readCache]:从 Drift / Hive 读最近一份数据,**无缓存返回 null**(不要抛异常)
/// - [fetchRemote]:调 datasource 拉网络,失败抛 [Exception](被本函数 catch
/// - [writeCache]:把网络结果写回缓存(cacheOnly / networkOnly 不会触发)
///
/// **行为矩阵**
///
/// | 策略 | 读缓存 | 网络 | 网络失败 |
/// |------|------|------|---------|
/// | cacheFirst | 立即返回(如有)+ 后台刷新写回 | 后台 | 静默忽略(缓存已显示) |
/// | networkFirst | — | 前台 | 降级返回缓存(如有),无缓存则抛 |
/// | cacheOnly | 立即返回(如无返回 null) | 不发 | — |
/// | networkOnly | 不读 | 前台 | 抛 |
///
/// 返回 [Stream] 而非 Future 的原因:cacheFirst 场景 UI 需要先看到缓存、
/// 后台刷新后再看到最新值,自然是两次 emit。Drift 本身就支持 Stream
/// 让 UI 通过 StreamProvider 订阅即可。
Stream<T?> policyAwareFetch<T>({
required FreshnessPolicy policy,
required Future<T?> Function() readCache,
required Future<T> Function() fetchRemote,
required Future<void> Function(T value) writeCache,
}) async* {
switch (policy) {
case FreshnessPolicy.cacheOnly:
yield await readCache();
case FreshnessPolicy.networkOnly:
yield await fetchRemote();
case FreshnessPolicy.cacheFirst:
final cached = await readCache();
if (cached != null) yield cached;
try {
final fresh = await fetchRemote();
await writeCache(fresh);
yield fresh;
} on Object {
// 静默:UI 已显示缓存值,网络失败不打扰用户
// 业务侧若需提示"刷新失败",应在 Notifier 层用 ref.listen 单独监听网络层 Failure
if (cached == null) rethrow;
}
case FreshnessPolicy.networkFirst:
try {
final fresh = await fetchRemote();
await writeCache(fresh);
yield fresh;
} on Object catch (_) {
final cached = await readCache();
if (cached != null) {
yield cached;
} else {
rethrow;
}
}
}
}