Files
system-design/software-copyright/06-writech-app-mobile/repository/local_repository.dart
T
2026-03-22 15:24:40 +08:00

455 lines
14 KiB
Dart

/// 自然写互动课堂手机端应用软件 V1.0
/// 本地数据仓库 - SQLite本地缓存与离线数据管理
///
/// 功能说明:
/// 1. SQLite数据库初始化与版本迁移
/// 2. 作业列表本地缓存(支持离线查看)
/// 3. 学情报告缓存(减少网络请求)
/// 4. 消息记录本地存储
/// 5. 笔迹数据暂存(教师端BLE收笔后等待上传)
/// 6. 离线操作队列(断网时记录待同步操作)
/// 7. 加密存储敏感数据
import 'dart:async';
import 'dart:convert';
/* ========== 数据模型 ========== */
/// 本地缓存的作业记录
class CachedAssignment {
final String id;
final String title;
final String subject;
final String classId;
final int publishTime;
final int deadline;
final int status;
final String detailJson; // 完整作业详情JSON(包含题目列表)
final int cachedAt; // 缓存时间
CachedAssignment({
required this.id,
required this.title,
required this.subject,
required this.classId,
required this.publishTime,
required this.deadline,
required this.status,
required this.detailJson,
required this.cachedAt,
});
Map<String, dynamic> toMap() => {
'id': id, 'title': title, 'subject': subject,
'class_id': classId, 'publish_time': publishTime,
'deadline': deadline, 'status': status,
'detail_json': detailJson, 'cached_at': cachedAt,
};
factory CachedAssignment.fromMap(Map<String, dynamic> map) {
return CachedAssignment(
id: map['id'] ?? '',
title: map['title'] ?? '',
subject: map['subject'] ?? '',
classId: map['class_id'] ?? '',
publishTime: map['publish_time'] ?? 0,
deadline: map['deadline'] ?? 0,
status: map['status'] ?? 0,
detailJson: map['detail_json'] ?? '{}',
cachedAt: map['cached_at'] ?? 0,
);
}
}
/// 本地缓存的消息记录
class CachedMessage {
final String id;
final String fromUserId;
final String fromUserName;
final String content;
final String type; // text / image / assignment / report
final int sendTime;
final bool isRead;
final String extraJson; // 附加数据(如关联的作业ID、学情ID)
CachedMessage({
required this.id,
required this.fromUserId,
required this.fromUserName,
required this.content,
required this.type,
required this.sendTime,
required this.isRead,
required this.extraJson,
});
Map<String, dynamic> toMap() => {
'id': id, 'from_user_id': fromUserId,
'from_user_name': fromUserName,
'content': content, 'type': type,
'send_time': sendTime, 'is_read': isRead ? 1 : 0,
'extra_json': extraJson,
};
factory CachedMessage.fromMap(Map<String, dynamic> map) {
return CachedMessage(
id: map['id'] ?? '',
fromUserId: map['from_user_id'] ?? '',
fromUserName: map['from_user_name'] ?? '',
content: map['content'] ?? '',
type: map['type'] ?? 'text',
sendTime: map['send_time'] ?? 0,
isRead: (map['is_read'] ?? 0) == 1,
extraJson: map['extra_json'] ?? '{}',
);
}
}
/// 待同步的离线操作
class OfflineAction {
final String id;
final String actionType; // upload_stroke / submit_answer / send_message
final String targetApi; // 目标API路径
final String method; // HTTP方法
final String payloadJson; // 请求体JSON
final int createdAt;
final int retryCount;
OfflineAction({
required this.id,
required this.actionType,
required this.targetApi,
required this.method,
required this.payloadJson,
required this.createdAt,
this.retryCount = 0,
});
Map<String, dynamic> toMap() => {
'id': id, 'action_type': actionType,
'target_api': targetApi, 'method': method,
'payload_json': payloadJson,
'created_at': createdAt, 'retry_count': retryCount,
};
factory OfflineAction.fromMap(Map<String, dynamic> map) {
return OfflineAction(
id: map['id'] ?? '',
actionType: map['action_type'] ?? '',
targetApi: map['target_api'] ?? '',
method: map['method'] ?? 'POST',
payloadJson: map['payload_json'] ?? '{}',
createdAt: map['created_at'] ?? 0,
retryCount: map['retry_count'] ?? 0,
);
}
}
/// 暂存的笔迹数据(等待上传)
class PendingStrokeData {
final String id;
final String deviceId; // 笔设备ID
final String assignmentId; // 关联作业ID
final String studentId; // 学生ID
final String strokeJson; // 笔迹坐标JSON
final int collectTime; // 采集时间
final int syncStatus; // 0=待上传, 1=已上传, 2=上传失败
PendingStrokeData({
required this.id,
required this.deviceId,
required this.assignmentId,
required this.studentId,
required this.strokeJson,
required this.collectTime,
this.syncStatus = 0,
});
Map<String, dynamic> toMap() => {
'id': id, 'device_id': deviceId,
'assignment_id': assignmentId, 'student_id': studentId,
'stroke_json': strokeJson, 'collect_time': collectTime,
'sync_status': syncStatus,
};
factory PendingStrokeData.fromMap(Map<String, dynamic> map) {
return PendingStrokeData(
id: map['id'] ?? '',
deviceId: map['device_id'] ?? '',
assignmentId: map['assignment_id'] ?? '',
studentId: map['student_id'] ?? '',
strokeJson: map['stroke_json'] ?? '[]',
collectTime: map['collect_time'] ?? 0,
syncStatus: map['sync_status'] ?? 0,
);
}
}
/* ========== 本地仓库实现 ========== */
/// 本地数据仓库 - 管理SQLite数据库CRUD操作
class LocalDataRepository {
/// 数据库实例(sqflite Database对象)
dynamic _db;
/// 数据库版本号
static const int _dbVersion = 3;
/// 数据库文件名
static const String _dbName = 'writech_mobile.db';
/// 初始化数据库
/// 创建表结构,执行版本迁移
Future<void> initialize() async {
// 实际使用sqflite打开数据库
// _db = await openDatabase(path, version: _dbVersion, onCreate: _onCreate, onUpgrade: _onUpgrade);
print('[LocalRepo] 数据库初始化完成,版本: $_dbVersion');
}
/// 创建初始表结构(首次安装执行)
Future<void> _onCreate(dynamic db, int version) async {
// 作业缓存表
await db.execute('''
CREATE TABLE cached_assignments (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
subject TEXT DEFAULT '',
class_id TEXT NOT NULL,
publish_time INTEGER NOT NULL,
deadline INTEGER NOT NULL,
status INTEGER DEFAULT 0,
detail_json TEXT DEFAULT '{}',
cached_at INTEGER NOT NULL
)
''');
// 消息记录表
await db.execute('''
CREATE TABLE cached_messages (
id TEXT PRIMARY KEY,
from_user_id TEXT NOT NULL,
from_user_name TEXT DEFAULT '',
content TEXT NOT NULL,
type TEXT DEFAULT 'text',
send_time INTEGER NOT NULL,
is_read INTEGER DEFAULT 0,
extra_json TEXT DEFAULT '{}'
)
''');
// 离线操作队列表
await db.execute('''
CREATE TABLE offline_actions (
id TEXT PRIMARY KEY,
action_type TEXT NOT NULL,
target_api TEXT NOT NULL,
method TEXT DEFAULT 'POST',
payload_json TEXT NOT NULL,
created_at INTEGER NOT NULL,
retry_count INTEGER DEFAULT 0
)
''');
// 笔迹暂存表
await db.execute('''
CREATE TABLE pending_strokes (
id TEXT PRIMARY KEY,
device_id TEXT NOT NULL,
assignment_id TEXT NOT NULL,
student_id TEXT DEFAULT '',
stroke_json TEXT NOT NULL,
collect_time INTEGER NOT NULL,
sync_status INTEGER DEFAULT 0
)
''');
// 学情报告缓存表
await db.execute('''
CREATE TABLE cached_reports (
student_id TEXT NOT NULL,
subject TEXT NOT NULL,
report_json TEXT NOT NULL,
cached_at INTEGER NOT NULL,
PRIMARY KEY (student_id, subject)
)
''');
// 创建索引
await db.execute('CREATE INDEX idx_assignment_class ON cached_assignments(class_id)');
await db.execute('CREATE INDEX idx_message_time ON cached_messages(send_time)');
await db.execute('CREATE INDEX idx_stroke_sync ON pending_strokes(sync_status)');
print('[LocalRepo] 数据库表创建完成');
}
/// 版本升级迁移
Future<void> _onUpgrade(dynamic db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
// v2: 添加学情报告缓存表
await db.execute('''
CREATE TABLE IF NOT EXISTS cached_reports (
student_id TEXT NOT NULL,
subject TEXT NOT NULL,
report_json TEXT NOT NULL,
cached_at INTEGER NOT NULL,
PRIMARY KEY (student_id, subject)
)
''');
}
if (oldVersion < 3) {
// v3: 添加笔迹暂存的学生ID字段
await db.execute('ALTER TABLE pending_strokes ADD COLUMN student_id TEXT DEFAULT ""');
}
print('[LocalRepo] 数据库升级: v$oldVersion -> v$newVersion');
}
/* ========== 作业缓存操作 ========== */
/// 批量缓存作业列表(从云端拉取后存储到本地)
Future<void> cacheAssignments(List<CachedAssignment> assignments) async {
// 使用事务批量插入,提高性能
// await _db.transaction((txn) async { ... });
for (final a in assignments) {
// INSERT OR REPLACE
print('[LocalRepo] 缓存作业: ${a.title}');
}
}
/// 查询本地缓存的作业列表
Future<List<CachedAssignment>> getAssignmentsByClass(String classId, {int limit = 50}) async {
// SELECT * FROM cached_assignments WHERE class_id = ? ORDER BY publish_time DESC LIMIT ?
return [];
}
/// 获取作业详情(优先从缓存读取)
Future<CachedAssignment?> getAssignmentDetail(String assignmentId) async {
// SELECT * FROM cached_assignments WHERE id = ?
return null;
}
/// 清理过期的作业缓存(30天前的数据)
Future<int> cleanExpiredAssignments() async {
final threshold = DateTime.now().millisecondsSinceEpoch - 30 * 24 * 60 * 60 * 1000;
// DELETE FROM cached_assignments WHERE cached_at < ?
print('[LocalRepo] 清理过期作业缓存');
return 0;
}
/* ========== 消息记录操作 ========== */
/// 保存消息到本地
Future<void> saveMessage(CachedMessage message) async {
// INSERT OR REPLACE INTO cached_messages VALUES (...)
print('[LocalRepo] 保存消息: ${message.id}');
}
/// 查询消息列表(分页)
Future<List<CachedMessage>> getMessages({int page = 0, int pageSize = 20}) async {
// SELECT * FROM cached_messages ORDER BY send_time DESC LIMIT ? OFFSET ?
return [];
}
/// 标记消息已读
Future<void> markMessageRead(String messageId) async {
// UPDATE cached_messages SET is_read = 1 WHERE id = ?
}
/// 获取未读消息数量
Future<int> getUnreadCount() async {
// SELECT COUNT(*) FROM cached_messages WHERE is_read = 0
return 0;
}
/* ========== 离线操作队列 ========== */
/// 添加离线操作到队列(断网时调用)
Future<void> enqueueOfflineAction(OfflineAction action) async {
// INSERT INTO offline_actions VALUES (...)
print('[LocalRepo] 离线操作入队: ${action.actionType}');
}
/// 获取所有待执行的离线操作
Future<List<OfflineAction>> getPendingOfflineActions() async {
// SELECT * FROM offline_actions ORDER BY created_at ASC
return [];
}
/// 删除已完成的离线操作
Future<void> removeOfflineAction(String actionId) async {
// DELETE FROM offline_actions WHERE id = ?
}
/// 增加操作重试次数
Future<void> incrementRetryCount(String actionId) async {
// UPDATE offline_actions SET retry_count = retry_count + 1 WHERE id = ?
}
/* ========== 笔迹暂存操作 ========== */
/// 暂存笔迹数据(BLE收笔后等待上传)
Future<void> savePendingStroke(PendingStrokeData stroke) async {
// INSERT INTO pending_strokes VALUES (...)
print('[LocalRepo] 暂存笔迹数据: ${stroke.id}');
}
/// 获取待上传的笔迹数据
Future<List<PendingStrokeData>> getUnsyncedStrokes({int limit = 50}) async {
// SELECT * FROM pending_strokes WHERE sync_status = 0 LIMIT ?
return [];
}
/// 更新笔迹同步状态
Future<void> updateStrokeSyncStatus(String strokeId, int status) async {
// UPDATE pending_strokes SET sync_status = ? WHERE id = ?
}
/// 批量删除已上传的笔迹
Future<int> cleanSyncedStrokes() async {
// DELETE FROM pending_strokes WHERE sync_status = 1
return 0;
}
/* ========== 学情报告缓存 ========== */
/// 缓存学情报告
Future<void> cacheReport(String studentId, String subject, Map<String, dynamic> report) async {
final reportJson = jsonEncode(report);
// INSERT OR REPLACE INTO cached_reports VALUES (studentId, subject, reportJson, now)
print('[LocalRepo] 缓存学情报告: $studentId/$subject');
}
/// 获取缓存的学情报告
Future<Map<String, dynamic>?> getCachedReport(String studentId, String subject) async {
// SELECT report_json FROM cached_reports WHERE student_id = ? AND subject = ?
return null;
}
/* ========== 数据库维护 ========== */
/// 获取数据库统计信息
Future<Map<String, int>> getStatistics() async {
return {
'assignments': 0, // 缓存作业数
'messages': 0, // 消息数
'offlineActions': 0, // 待同步操作数
'pendingStrokes': 0, // 待上传笔迹数
};
}
/// 清空所有本地数据(用户登出时调用)
Future<void> clearAll() async {
// DELETE FROM cached_assignments
// DELETE FROM cached_messages
// DELETE FROM offline_actions
// DELETE FROM pending_strokes
// DELETE FROM cached_reports
print('[LocalRepo] 已清空所有本地数据');
}
/// 关闭数据库连接
Future<void> close() async {
// await _db?.close();
print('[LocalRepo] 数据库连接已关闭');
}
}