/** * 自然写互动课堂电视端应用软件 V1.0 * Room数据库 - 本地数据缓存与持久化 * * 功能说明: * 1. Room数据库定义(Entity、DAO、Database) * 2. 课堂笔迹数据缓存(当前课堂的实时笔迹) * 3. 学情报告本地缓存(减少网络请求) * 4. 课件资源元数据索引 * 5. 设备配置持久化(网关绑定、显示设置) * 6. 数据库版本迁移 */ package com.writech.tv.data import android.content.Context import android.util.Log import java.util.concurrent.ConcurrentHashMap /* ========== Entity定义 ========== */ /** * 课堂笔迹缓存实体 * 缓存当前课堂接收到的学生笔迹数据 */ data class StrokeCacheEntity( val id: String, // 记录ID val classroomId: String, // 课堂ID val studentId: String, // 学生ID val studentName: String, // 学生姓名 val pageId: Int, // 点阵纸页面ID val strokeData: String, // 笔迹坐标JSON数据 val strokeCount: Int, // 笔画数量 val collectTime: Long, // 采集时间 val thumbnailPath: String = "" // 缩略图路径 ) /** * 学情报告缓存实体 * 缓存从云端拉取的学情报告数据,避免频繁网络请求 */ data class ReportCacheEntity( val studentId: String, // 学生ID(联合主键) val subject: String, // 科目(联合主键) val studentName: String, // 学生姓名 val overallScore: Double, // 综合评分 val writingScore: Double, // 书写评分 val knowledgeScore: Double, // 知识掌握评分 val reportJson: String, // 完整报告JSON val cachedAt: Long // 缓存时间 ) /** * 课件资源元数据实体 * 索引本地缓存的课件文件 */ data class ResourceCacheEntity( val resourceId: String, // 资源ID val title: String, // 资源标题 val type: String, // 类型: ppt/pdf/image/copybook val subject: String, // 科目 val grade: String, // 年级 val localPath: String, // 本地文件路径 val fileSize: Long, // 文件大小(字节) val downloadTime: Long, // 下载时间 val lastAccessTime: Long, // 最后访问时间 val cloudUrl: String // 云端原始URL ) /** * 设备配置实体 * 持久化TV端运行配置 */ data class DeviceConfigEntity( val key: String, // 配置键 val value: String, // 配置值 val updatedAt: Long // 更新时间 ) /* ========== DAO定义 ========== */ /** * 笔迹数据DAO - 管理笔迹缓存的增删改查 */ class StrokeCacheDao { /** 内存缓存(模拟Room查询) */ private val cache = ConcurrentHashMap() /** 插入笔迹缓存记录 */ fun insert(entity: StrokeCacheEntity) { cache[entity.id] = entity } /** 批量插入 */ fun insertAll(entities: List) { for (entity in entities) { cache[entity.id] = entity } } /** 按课堂ID查询所有笔迹 */ fun getByClassroom(classroomId: String): List { return cache.values.filter { it.classroomId == classroomId } .sortedBy { it.collectTime } } /** 按学生ID查询笔迹 */ fun getByStudent(classroomId: String, studentId: String): List { return cache.values.filter { it.classroomId == classroomId && it.studentId == studentId }.sortedBy { it.collectTime } } /** 获取课堂中所有有笔迹的学生ID列表 */ fun getActiveStudentIds(classroomId: String): List { return cache.values.filter { it.classroomId == classroomId } .map { it.studentId } .distinct() } /** 获取课堂笔迹总数 */ fun getStrokeCount(classroomId: String): Int { return cache.values.filter { it.classroomId == classroomId } .sumOf { it.strokeCount } } /** 删除指定课堂的所有笔迹(课堂结束后清理) */ fun deleteByClassroom(classroomId: String) { val keysToRemove = cache.entries .filter { it.value.classroomId == classroomId } .map { it.key } for (key in keysToRemove) { cache.remove(key) } } /** 清空所有缓存 */ fun deleteAll() { cache.clear() } /** 获取缓存记录总数 */ fun count(): Int = cache.size } /** * 学情报告DAO - 管理报告缓存 */ class ReportCacheDao { private val cache = ConcurrentHashMap() /** 键生成(studentId + subject) */ private fun makeKey(studentId: String, subject: String) = "${studentId}_$subject" /** 插入或更新报告缓存 */ fun upsert(entity: ReportCacheEntity) { cache[makeKey(entity.studentId, entity.subject)] = entity } /** 查询学生某科目的报告 */ fun getReport(studentId: String, subject: String): ReportCacheEntity? { return cache[makeKey(studentId, subject)] } /** 查询学生所有科目的报告 */ fun getStudentReports(studentId: String): List { return cache.values.filter { it.studentId == studentId } } /** 获取所有缓存的学生报告摘要(按综合分数排序) */ fun getAllReportsSorted(): List { return cache.values.sortedByDescending { it.overallScore } } /** 清理过期缓存(超过指定时间的记录) */ fun cleanExpired(maxAgeMs: Long): Int { val threshold = System.currentTimeMillis() - maxAgeMs val keysToRemove = cache.entries .filter { it.value.cachedAt < threshold } .map { it.key } for (key in keysToRemove) { cache.remove(key) } return keysToRemove.size } /** 清空所有缓存 */ fun deleteAll() { cache.clear() } } /** * 资源缓存DAO */ class ResourceCacheDao { private val cache = ConcurrentHashMap() /** 插入资源记录 */ fun insert(entity: ResourceCacheEntity) { cache[entity.resourceId] = entity } /** 按资源ID查询 */ fun getById(resourceId: String): ResourceCacheEntity? { return cache[resourceId] } /** 按类型和科目查询 */ fun getByTypeAndSubject(type: String, subject: String): List { return cache.values.filter { it.type == type && it.subject == subject } .sortedByDescending { it.lastAccessTime } } /** 获取最近访问的资源 */ fun getRecent(limit: Int = 20): List { return cache.values.sortedByDescending { it.lastAccessTime }.take(limit) } /** 更新最后访问时间 */ fun updateAccessTime(resourceId: String) { cache[resourceId]?.let { old -> cache[resourceId] = old.copy(lastAccessTime = System.currentTimeMillis()) } } /** 获取缓存总大小(字节) */ fun getTotalCacheSize(): Long { return cache.values.sumOf { it.fileSize } } /** 按LRU策略清理缓存(超出容量限制时删除最久未访问的) */ fun evictLRU(maxSizeBytes: Long): List { val evicted = mutableListOf() var totalSize = getTotalCacheSize() if (totalSize <= maxSizeBytes) return evicted // 按最后访问时间排序,优先删除最旧的 val sorted = cache.values.sortedBy { it.lastAccessTime } for (entity in sorted) { if (totalSize <= maxSizeBytes) break cache.remove(entity.resourceId) totalSize -= entity.fileSize evicted.add(entity.localPath) } return evicted } fun deleteAll() { cache.clear() } } /** * 设备配置DAO */ class DeviceConfigDao { private val configs = ConcurrentHashMap() /** 设置配置项 */ fun set(key: String, value: String) { configs[key] = DeviceConfigEntity(key, value, System.currentTimeMillis()) } /** 获取配置项 */ fun get(key: String, defaultValue: String = ""): String { return configs[key]?.value ?: defaultValue } /** 删除配置项 */ fun delete(key: String) { configs.remove(key) } /** 获取所有配置 */ fun getAll(): Map { return configs.mapValues { it.value.value } } } /* ========== Database定义 ========== */ /** * TV端本地数据库 * 聚合所有DAO,提供统一的数据访问入口 */ class TvDatabase private constructor(context: Context) { companion object { private const val TAG = "TvDatabase" private const val DB_VERSION = 2 @Volatile private var instance: TvDatabase? = null /** 获取数据库单例 */ fun getInstance(context: Context): TvDatabase { return instance ?: synchronized(this) { instance ?: TvDatabase(context.applicationContext).also { instance = it } } } } /** 笔迹缓存DAO */ val strokeDao = StrokeCacheDao() /** 报告缓存DAO */ val reportDao = ReportCacheDao() /** 资源缓存DAO */ val resourceDao = ResourceCacheDao() /** 设备配置DAO */ val configDao = DeviceConfigDao() init { Log.i(TAG, "数据库初始化完成,版本: $DB_VERSION") } /** 获取数据库统计信息 */ fun getStatistics(): Map { return mapOf( "stroke_records" to strokeDao.count(), "resource_cache_size" to resourceDao.getTotalCacheSize(), "db_version" to DB_VERSION ) } /** 清理所有缓存数据 */ fun clearAllCaches() { strokeDao.deleteAll() reportDao.deleteAll() resourceDao.deleteAll() Log.i(TAG, "所有缓存已清理") } /** 定期维护(清理过期数据) */ fun performMaintenance() { // 清理超过7天的报告缓存 val reportCleaned = reportDao.cleanExpired(7L * 24 * 60 * 60 * 1000) // 清理超出500MB的资源缓存 val evicted = resourceDao.evictLRU(500L * 1024 * 1024) Log.i(TAG, "数据库维护完成: 清理报告${reportCleaned}条, 清理资源${evicted.size}个") } }