feat(diary): 数据层 + 班级系统 (Phase F1 + B3)

Flutter 数据层 (Phase F1):
- journal_entry.dart: 日记数据模型 (Mood/Weather/tags/version)
- journal_element.dart: 元素模型 (text/image/sticker/handwriting_ref/tape)
- school_class.dart: 班级模型
- user_settings.dart: 用户设置 (主题/画笔/字号)
- isar_database.dart: Isar 初始化
- api_client.dart: Dio + JWT注入 + 离线感知 + 401处理
- journal_repository.dart: 抽象接口 + InMemory实现 (乐观锁)
- sync_engine.dart: WiFi同步 + 操作队列 + 重试(5次) + 快照持久化

Rust 班级系统 (Phase B3):
- class_service.rs: 创建班级(6位码) + 加入班级 + 成员管理
- topic_service.rs: 老师布置主题 + 主题列表
- comment_service.rs: 老师点评 + 评语列表
- class_handler.rs: 5个API端点 + 权限守卫
- topic_handler.rs: 2个API端点
- comment_handler.rs: 2个API端点
- dto.rs: 新增5个DTO (ClassMemberResp/CreateTopicReq/TopicResp/CreateCommentReq/CommentResp)
- 6条新路由注册

验证: cargo check 通过, 433测试全绿, flutter analyze 1 warning
This commit is contained in:
iven
2026-06-01 00:55:51 +08:00
parent d0653614e0
commit 5e6c6fdd62
18 changed files with 2205 additions and 1 deletions

View File

@@ -0,0 +1,82 @@
// Isar 数据库初始化 — 本地持久化存储
//
// Isar 3.x 要求 open() 时传入 List<CollectionSchema> 位置参数。
// 由于我们使用手写不可变类而非 isar_generator 代码生成,
// 需要在调用 [init] 时传入 schema 列表。
// 当前阶段使用 [ensureInitialized] 占位,待后续添加 Isar Collection 后正式注册。
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.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;
/// 是否已初始化
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 {
if (_instance != null && _instance!.isOpen) return _instance!;
final dir = await getApplicationDocumentsDirectory();
_instance = await Isar.open(
schemas,
directory: dir.path,
inspector: true, // 开发模式,发布时关闭
);
_initialized = true;
return _instance!;
}
/// 获取 Isar 实例(必须先调用 [init]
///
/// 如果未初始化会抛出 [StateError]。
static Isar get instance {
if (_instance == null || !_instance!.isOpen) {
throw StateError(
'IsarDatabase 未初始化,请先调用 IsarDatabase.init(schemas: [...])',
);
}
return _instance!;
}
/// 关闭数据库连接
///
/// 通常只在应用退出时调用。
static Future<void> close() async {
if (_instance != null && _instance!.isOpen) {
await _instance!.close();
_instance = null;
_initialized = false;
}
}
/// 清空所有数据(仅用于测试)
static Future<void> clearAll() async {
if (_instance == null || !_instance!.isOpen) return;
await _instance!.writeTxn(() async {
// TODO: 清空所有 collection
});
}
}