software copyright
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* 自然写互动课堂电视端应用软件 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}个")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user