*
This commit is contained in:
38
core/database/build.gradle.kts
Normal file
38
core/database/build.gradle.kts
Normal file
@@ -0,0 +1,38 @@
|
||||
// core:database 本地存储:Room AppDatabase + 上传队列(离线优先参考实现)
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.hilt)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.stec.cmd.core.database"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:common"))
|
||||
|
||||
api(libs.androidx.room.runtime)
|
||||
api(libs.androidx.room.ktx)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
|
||||
implementation(libs.hilt.android)
|
||||
ksp(libs.hilt.compiler)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.stec.cmd.core.database
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
|
||||
/**
|
||||
* 应用数据库骨架。
|
||||
*
|
||||
* S0 仅注册上传队列一张表作为离线优先参考实现;
|
||||
* S1+ 新业务实体(任务缓存、测点、统计数据等)在 entities 中追加并升 version。
|
||||
* 骨架期 exportSchema=false(无迁移需求);开启迁移管理时再导出 schema。
|
||||
*/
|
||||
@Database(
|
||||
entities = [
|
||||
UploadQueueEntity::class,
|
||||
],
|
||||
version = 1,
|
||||
exportSchema = false,
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun uploadQueueDao(): UploadQueueDao
|
||||
|
||||
companion object {
|
||||
const val DATABASE_NAME = "stec-cmd.db"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.stec.cmd.core.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/** 数据库 Hilt 装配:全工程共享一个 AppDatabase。 */
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DatabaseModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppDatabase(
|
||||
@ApplicationContext context: Context,
|
||||
): AppDatabase = Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java,
|
||||
AppDatabase.DATABASE_NAME,
|
||||
).build()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUploadQueueDao(
|
||||
database: AppDatabase,
|
||||
): UploadQueueDao = database.uploadQueueDao()
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.stec.cmd.core.database
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* 上传队列 DAO:演示离线优先的入队/取待传/回写状态闭环。
|
||||
* S1+ 各数据类型 DAO 照此挂载到 [AppDatabase]。
|
||||
*/
|
||||
@Dao
|
||||
interface UploadQueueDao {
|
||||
|
||||
/** 入队一条待上传数据。 */
|
||||
@Insert
|
||||
suspend fun enqueue(entity: UploadQueueEntity): Long
|
||||
|
||||
/** 按入队顺序观察待上传(含失败待重试)队列。 */
|
||||
@Query(
|
||||
"SELECT * FROM upload_queue " +
|
||||
"WHERE status IN (:statuses) ORDER BY created_at ASC",
|
||||
)
|
||||
fun observeByStatuses(vararg statuses: Int): Flow<List<UploadQueueEntity>>
|
||||
|
||||
/** 一次性取待上传列表(上传调度器使用)。 */
|
||||
@Query(
|
||||
"SELECT * FROM upload_queue " +
|
||||
"WHERE status IN (:statuses) ORDER BY created_at ASC LIMIT :limit",
|
||||
)
|
||||
suspend fun takePending(statuses: List<Int>, limit: Int = DEFAULT_BATCH): List<UploadQueueEntity>
|
||||
|
||||
/** 标记上传中。 */
|
||||
@Query("UPDATE upload_queue SET status = :status WHERE id = :id")
|
||||
suspend fun updateStatus(id: Long, status: Int)
|
||||
|
||||
/** 标记成功并记录时间。 */
|
||||
@Query("UPDATE upload_queue SET status = :status, uploaded_at = :uploadedAt WHERE id = :id")
|
||||
suspend fun markSuccess(id: Long, status: Int, uploadedAt: Long)
|
||||
|
||||
/** 失败重试:状态回待传并累计重试次数。 */
|
||||
@Query(
|
||||
"UPDATE upload_queue SET status = :status, retry_count = retry_count + 1 WHERE id = :id",
|
||||
)
|
||||
suspend fun markRetry(id: Long, status: Int)
|
||||
|
||||
/** 清理已成功且早于截止时间的记录(队列瘦身)。 */
|
||||
@Query("DELETE FROM upload_queue WHERE status = :status AND uploaded_at IS NOT NULL AND uploaded_at < :before")
|
||||
suspend fun purgeSuccessBefore(status: Int, before: Long): Int
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_BATCH = 50
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.stec.cmd.core.database
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
/**
|
||||
* 采集数据上传队列(离线优先参考实现,对应功能明细 M4-07)。
|
||||
*
|
||||
* 设计口径:所有采集数据先入本队列再尝试上传,网络失败留在队列内自动补传;
|
||||
* S1+ 各数据类型(水平位移/测斜/轴力/水位…)照此模式挂载自己的实体,
|
||||
* payloadJson 统一存接口文档约定的上传 JSON 结构。
|
||||
*/
|
||||
@Entity(tableName = "upload_queue")
|
||||
data class UploadQueueEntity(
|
||||
/** 自增主键。 */
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "id")
|
||||
val id: Long = 0L,
|
||||
|
||||
/** 设备类型:IE1000 / VM208(见 core:ble DeviceType)。 */
|
||||
@ColumnInfo(name = "device_type")
|
||||
val deviceType: String,
|
||||
|
||||
/** 设备蓝牙标识(蓝牙名或 MAC)。 */
|
||||
@ColumnInfo(name = "device_id")
|
||||
val deviceId: String,
|
||||
|
||||
/** 业务数据类型(水平位移/测斜/轴力/水位等,S1+ 扩展枚举)。 */
|
||||
@ColumnInfo(name = "data_type")
|
||||
val dataType: String,
|
||||
|
||||
/** 按接口文档组装好的上传 JSON。 */
|
||||
@ColumnInfo(name = "payload_json")
|
||||
val payloadJson: String,
|
||||
|
||||
/** 队列状态,见 [UploadStatus]。 */
|
||||
@ColumnInfo(name = "status")
|
||||
val status: Int = UploadStatus.PENDING,
|
||||
|
||||
/** 已重试次数(429/403 限流退避依据)。 */
|
||||
@ColumnInfo(name = "retry_count")
|
||||
val retryCount: Int = 0,
|
||||
|
||||
/** 入队时间(epoch 毫秒)。 */
|
||||
@ColumnInfo(name = "created_at")
|
||||
val createdAt: Long,
|
||||
|
||||
/** 上传成功时间(epoch 毫秒),未上传为 null。 */
|
||||
@ColumnInfo(name = "uploaded_at")
|
||||
val uploadedAt: Long? = null,
|
||||
)
|
||||
|
||||
/** 队列状态常量(S0 用 Int 避免 Room TypeConverter 样板,S1 可按需替换为枚举)。 */
|
||||
object UploadStatus {
|
||||
/** 待上传。 */
|
||||
const val PENDING = 0
|
||||
|
||||
/** 上传中。 */
|
||||
const val UPLOADING = 1
|
||||
|
||||
/** 上传成功(保留一段时间后由清理任务删除)。 */
|
||||
const val SUCCESS = 2
|
||||
|
||||
/** 失败待重试。 */
|
||||
const val FAILED = 3
|
||||
}
|
||||
Reference in New Issue
Block a user