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
185 lines
5.3 KiB
Dart
185 lines
5.3 KiB
Dart
// 日记仓库 — 抽象接口 + 内存实现(开发测试用)
|
||
//
|
||
// 设计思路:
|
||
// - 抽象接口 [JournalRepository] 定义数据操作契约
|
||
// - 后续实现 IsarJournalRepository(本地)和 RemoteJournalRepository(远程)
|
||
// - SyncEngine 负责协调本地和远程仓库之间的数据同步
|
||
// - 内存实现 [InMemoryJournalRepository] 用于开发阶段快速迭代
|
||
|
||
import '../models/journal_entry.dart';
|
||
import '../models/journal_element.dart';
|
||
|
||
/// 日记仓库抽象接口 — 所有数据操作的统一契约
|
||
///
|
||
/// 查询参数说明:
|
||
/// - [dateFrom]/[dateTo]: 日期范围过滤(闭区间)
|
||
/// - [page]/[pageSize]: 分页参数,从 1 开始
|
||
abstract class JournalRepository {
|
||
/// 获取日记列表(支持日期范围过滤和分页)
|
||
Future<List<JournalEntry>> getJournals({
|
||
DateTime? dateFrom,
|
||
DateTime? dateTo,
|
||
int? page,
|
||
int? pageSize,
|
||
});
|
||
|
||
/// 获取单篇日记(返回 null 表示不存在)
|
||
Future<JournalEntry?> getJournal(String id);
|
||
|
||
/// 创建新日记
|
||
Future<JournalEntry> createJournal(JournalEntry entry);
|
||
|
||
/// 更新日记(使用 version 字段做乐观锁冲突检测)
|
||
Future<JournalEntry> updateJournal(JournalEntry entry);
|
||
|
||
/// 删除日记(软删除,设置 deleted_at)
|
||
Future<void> deleteJournal(String id);
|
||
|
||
/// 获取指定日记的所有元素
|
||
Future<List<JournalElement>> getElements(String journalId);
|
||
|
||
/// 添加元素到日记
|
||
Future<JournalElement> addElement(JournalElement element);
|
||
|
||
/// 更新元素
|
||
Future<JournalElement> updateElement(JournalElement element);
|
||
|
||
/// 从日记中移除元素
|
||
Future<void> removeElement(String elementId);
|
||
}
|
||
|
||
/// 内存实现 — 用于开发阶段快速迭代和单元测试
|
||
///
|
||
/// 数据存储在内存中,应用重启后丢失。
|
||
/// 线程安全说明:Flutter 是单线程模型,无需加锁。
|
||
class InMemoryJournalRepository implements JournalRepository {
|
||
final Map<String, JournalEntry> _journals = {};
|
||
final Map<String, JournalElement> _elements = {};
|
||
|
||
@override
|
||
Future<List<JournalEntry>> getJournals({
|
||
DateTime? dateFrom,
|
||
DateTime? dateTo,
|
||
int? page,
|
||
int? pageSize,
|
||
}) async {
|
||
var results = _journals.values.toList();
|
||
|
||
// 日期范围过滤
|
||
if (dateFrom != null) {
|
||
results = results.where((j) => j.date.isAfter(dateFrom)).toList();
|
||
}
|
||
if (dateTo != null) {
|
||
results = results.where((j) => j.date.isBefore(dateTo)).toList();
|
||
}
|
||
|
||
// 按日期降序排列(最新在前)
|
||
results.sort((a, b) => b.date.compareTo(a.date));
|
||
|
||
// 分页
|
||
if (page != null && pageSize != null) {
|
||
final start = (page - 1) * pageSize;
|
||
if (start >= results.length) return [];
|
||
final end = (start + pageSize).clamp(0, results.length);
|
||
results = results.sublist(start, end);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
@override
|
||
Future<JournalEntry?> getJournal(String id) async {
|
||
return _journals[id];
|
||
}
|
||
|
||
@override
|
||
Future<JournalEntry> createJournal(JournalEntry entry) async {
|
||
_journals[entry.id] = entry;
|
||
return entry;
|
||
}
|
||
|
||
@override
|
||
Future<JournalEntry> updateJournal(JournalEntry entry) async {
|
||
final existing = _journals[entry.id];
|
||
if (existing == null) {
|
||
throw StateError('日记不存在: ${entry.id}');
|
||
}
|
||
|
||
// 乐观锁冲突检测:版本号必须匹配
|
||
if (existing.version != entry.version) {
|
||
throw StateError(
|
||
'版本冲突: 本地版本 ${entry.version}, 服务端版本 ${existing.version}',
|
||
);
|
||
}
|
||
|
||
// 版本号 +1,更新时间戳
|
||
final updated = entry.copyWith(
|
||
version: entry.version + 1,
|
||
updatedAt: DateTime.now(),
|
||
);
|
||
_journals[entry.id] = updated;
|
||
return updated;
|
||
}
|
||
|
||
@override
|
||
Future<void> deleteJournal(String id) async {
|
||
// 内存实现:直接移除(软删除由 Isar 实现处理)
|
||
_journals.remove(id);
|
||
// 同时移除关联元素
|
||
_elements.removeWhere((_, e) => e.journalId == id);
|
||
}
|
||
|
||
@override
|
||
Future<List<JournalElement>> getElements(String journalId) async {
|
||
return _elements.values
|
||
.where((e) => e.journalId == journalId)
|
||
.toList()
|
||
..sort((a, b) => a.zIndex.compareTo(b.zIndex));
|
||
}
|
||
|
||
@override
|
||
Future<JournalElement> addElement(JournalElement element) async {
|
||
_elements[element.id] = element;
|
||
return element;
|
||
}
|
||
|
||
@override
|
||
Future<JournalElement> updateElement(JournalElement element) async {
|
||
final existing = _elements[element.id];
|
||
if (existing == null) {
|
||
throw StateError('元素不存在: ${element.id}');
|
||
}
|
||
|
||
// 乐观锁冲突检测
|
||
if (existing.version != element.version) {
|
||
throw StateError(
|
||
'版本冲突: 本地版本 ${element.version}, 服务端版本 ${existing.version}',
|
||
);
|
||
}
|
||
|
||
final updated = element.copyWith(
|
||
version: element.version + 1,
|
||
updatedAt: DateTime.now(),
|
||
);
|
||
_elements[element.id] = updated;
|
||
return updated;
|
||
}
|
||
|
||
@override
|
||
Future<void> removeElement(String elementId) async {
|
||
_elements.remove(elementId);
|
||
}
|
||
|
||
/// 清空所有数据(测试辅助方法)
|
||
void clearAll() {
|
||
_journals.clear();
|
||
_elements.clear();
|
||
}
|
||
|
||
/// 获取日记总数(测试辅助方法)
|
||
int get journalCount => _journals.length;
|
||
|
||
/// 获取元素总数(测试辅助方法)
|
||
int get elementCount => _elements.length;
|
||
}
|