software copyright

This commit is contained in:
jiahong
2026-03-22 15:24:40 +08:00
parent e303bb868a
commit 60f336e345
155 changed files with 127262 additions and 0 deletions
@@ -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}")
}
}