Files
2026-03-22 15:24:40 +08:00

350 lines
10 KiB
Kotlin
Raw Permalink 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.
/**
* 自然写互动课堂电视端应用软件 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<String, StrokeCacheEntity>()
/** 插入笔迹缓存记录 */
fun insert(entity: StrokeCacheEntity) {
cache[entity.id] = entity
}
/** 批量插入 */
fun insertAll(entities: List<StrokeCacheEntity>) {
for (entity in entities) {
cache[entity.id] = entity
}
}
/** 按课堂ID查询所有笔迹 */
fun getByClassroom(classroomId: String): List<StrokeCacheEntity> {
return cache.values.filter { it.classroomId == classroomId }
.sortedBy { it.collectTime }
}
/** 按学生ID查询笔迹 */
fun getByStudent(classroomId: String, studentId: String): List<StrokeCacheEntity> {
return cache.values.filter {
it.classroomId == classroomId && it.studentId == studentId
}.sortedBy { it.collectTime }
}
/** 获取课堂中所有有笔迹的学生ID列表 */
fun getActiveStudentIds(classroomId: String): List<String> {
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<String, ReportCacheEntity>()
/** 键生成(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<ReportCacheEntity> {
return cache.values.filter { it.studentId == studentId }
}
/** 获取所有缓存的学生报告摘要(按综合分数排序) */
fun getAllReportsSorted(): List<ReportCacheEntity> {
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<String, ResourceCacheEntity>()
/** 插入资源记录 */
fun insert(entity: ResourceCacheEntity) {
cache[entity.resourceId] = entity
}
/** 按资源ID查询 */
fun getById(resourceId: String): ResourceCacheEntity? {
return cache[resourceId]
}
/** 按类型和科目查询 */
fun getByTypeAndSubject(type: String, subject: String): List<ResourceCacheEntity> {
return cache.values.filter { it.type == type && it.subject == subject }
.sortedByDescending { it.lastAccessTime }
}
/** 获取最近访问的资源 */
fun getRecent(limit: Int = 20): List<ResourceCacheEntity> {
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<String> {
val evicted = mutableListOf<String>()
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<String, DeviceConfigEntity>()
/** 设置配置项 */
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<String, String> {
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<String, Any> {
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}个")
}
}