feat(app): Isar 本地数据库集成 — Collection + Repository + 编辑器持久化 + SyncEngine 队列

新增文件:
- data/local/collections/ 3 个 Isar Collection 定义 + 生成 Schema
- data/repositories/isar_journal_repository.dart 完整 CRUD + 乐观锁

修改文件:
- app.dart: IsarJournalRepository 注册为主 JournalRepository + SyncEngine 注入
- editor_page.dart: onSave 接入 JournalRepository,笔画/元素自动保存到 Isar
- sync_engine.dart: 新增 persistPendingQueue/restorePendingQueue Isar 持久化
- isar_database.dart: 注册 3 个 Collection Schema
- main.dart: 启动时初始化 Isar

架构: 离线优先 — Isar 为本地主仓库,Remote 供 SyncEngine 推送
This commit is contained in:
iven
2026-06-01 14:41:40 +08:00
parent e07da7addb
commit 2481c8fce6
12 changed files with 6876 additions and 46 deletions

View File

@@ -1,41 +1,36 @@
// Isar 数据库初始化 — 本地持久化存储
//
// Isar 3.x 要求 open() 时传入 List<CollectionSchema> 位置参数
// 由于我们使用手写不可变类而非 isar_generator 代码生成,
// 需要在调用 [init] 时传入 schema 列表。
// 当前阶段使用 [ensureInitialized] 占位,待后续添加 Isar Collection 后正式注册。
// Isar 3.x 要求 open() 时传入 List<CollectionSchema>。
// 通过 build_runner 生成 Schema在 main.dart 启动时调用 init()。
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'collections/journal_entry_collection.dart';
import 'collections/journal_element_collection.dart';
import 'collections/pending_operation_collection.dart';
/// Isar 数据库单例管理
///
/// 使用方式Phase 1 — 无 schema 时):
/// ```dart
/// // 直接使用,不初始化 Isar内存仓库模式
/// ```
///
/// 使用方式Phase 2 — 有 schema 后):
/// ```dart
/// final isar = await IsarDatabase.init(schemas: [JournalEntrySchema]);
/// ```
class IsarDatabase {
IsarDatabase._();
static Isar? _instance;
static bool _initialized = false;
/// 所有 Collection Schema由 build_runner 生成)
static final List<CollectionSchema<dynamic>> schemas = [
JournalEntryCollectionSchema,
JournalElementCollectionSchema,
PendingOperationCollectionSchema,
];
/// 是否已初始化
static bool get isInitialized => _initialized;
/// 初始化数据库(需在 app 启动时调用,传入所有 CollectionSchema
/// 初始化数据库
///
/// - [schemas]: Isar Collection Schema 列表(由 isar_generator 生成)
/// - 在应用文档目录下创建 isar 数据库文件
/// - 开发模式开启 inspectorflutter pub global run isar_inspector
static Future<Isar> init({
required List<CollectionSchema<dynamic>> schemas,
}) async {
/// 在 main() 中调用open 之前需确保 WidgetsFlutterBinding 已初始化。
static Future<Isar> init() async {
if (_instance != null && _instance!.isOpen) return _instance!;
final dir = await getApplicationDocumentsDirectory();
@@ -50,20 +45,14 @@ class IsarDatabase {
}
/// 获取 Isar 实例(必须先调用 [init]
///
/// 如果未初始化会抛出 [StateError]。
static Isar get instance {
if (_instance == null || !_instance!.isOpen) {
throw StateError(
'IsarDatabase 未初始化,请先调用 IsarDatabase.init(schemas: [...])',
);
throw StateError('IsarDatabase 未初始化,请先调用 IsarDatabase.init()');
}
return _instance!;
}
/// 关闭数据库连接
///
/// 通常只在应用退出时调用。
static Future<void> close() async {
if (_instance != null && _instance!.isOpen) {
await _instance!.close();
@@ -76,7 +65,7 @@ class IsarDatabase {
static Future<void> clearAll() async {
if (_instance == null || !_instance!.isOpen) return;
await _instance!.writeTxn(() async {
// TODO: 清空所有 collection
await _instance!.clear();
});
}
}