Files
nj/app/lib/data/repositories/journal_repository.dart
iven 749ef55b89
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
feat: Week 4 收尾 + 架构治理 — 搜索/家长中心/Feature Flag/Docker/环境配置
架构治理:
- Feature Flag 落地: Cargo.toml [features] default=["diary"] + main.rs cfg 条件编译
- 环境配置统一: AppConfig 类 + --dart-define 注入 + SSE 端口 8080→3000 修复

搜索替代方案 (无 FTS):
- SearchBloc + 标签/心情筛选接入后端 API
- JournalRepository 扩展 mood/tag 筛选参数
- 搜索页 UI 接入实际数据(替换占位文本)

家长中心最小集 (PIPL 合规):
- 后端: parent_service (绑定/查看/导出/删除/解绑) + parent_handler (6 个 API 端点)
- 前端: ParentBloc + ParentPage 功能完整实现
- 绑定孩子、只读查看日记、导出数据、删除数据、解绑

Docker 部署:
- verify.sh 健康检查脚本 (Axum/PG/Redis/OpenAPI 四项检查)

测试修复:
- home_bloc_test / calendar_bloc_test 适配 JournalRepository 新参数

验证: flutter test 84/84 pass, cargo test 76/76 pass, cargo check pass
2026-06-01 23:53:34 +08:00

199 lines
5.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 日记仓库 — 抽象接口 + 内存实现(开发测试用)
//
// 设计思路:
// - 抽象接口 [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,
String? mood,
String? tag,
});
/// 获取单篇日记(返回 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,
String? mood,
String? tag,
}) 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();
}
// 心情过滤
if (mood != null) {
results = results.where((j) => j.mood.value == mood).toList();
}
// 标签过滤(日记 tags 列表包含指定标签)
if (tag != null) {
results = results.where((j) => j.tags.contains(tag)).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;
}