feat(face): 新增人脸库展示与增量更新支持
- 在PLC设置界面新增人脸库统计模块,显示人脸总数并支持刷新 - 人脸总数数据从本地数据库异步查询并更新界面 - 修改API接口调用,采用POST请求体方式提交人脸数据(包含url和特征字符串) - 在网络调用中实现人脸数据的增量分页获取,递归拉取所有数据页 - 维护人脸数据的最大时间戳,确保增量更新只处理最新数据 - 优化人脸数据缓存逻辑,第一页重置数据并同步时间戳 - 添加FeatureBody数据类封装人脸特征请求体参数 - 修正相关仓库层方法以适配新的API接口参数和返回结构
This commit is contained in:
@@ -5,6 +5,7 @@ import android.content.Intent
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import com.sw.plate.utils.arcface.facedb.FaceDatabase
|
||||
import com.sw.plate.utils.arcface.faceserver.FaceServer
|
||||
import com.sw.platecabinet.databinding.ActivityPlcSettingBinding
|
||||
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
|
||||
@@ -60,6 +61,7 @@ class PlcSettingActivity : BaseActivity<ActivityPlcSettingBinding>() {
|
||||
initAlarmButton()
|
||||
initParamButtons()
|
||||
initStatusButton()
|
||||
initFaceCount()
|
||||
binding.btnSwitchEnv.setOnClickListener {
|
||||
EnvSwitchDialog(context = this, onEnvChanged = {
|
||||
showWaitingDialog("重置人脸数据中……")
|
||||
@@ -334,6 +336,30 @@ class PlcSettingActivity : BaseActivity<ActivityPlcSettingBinding>() {
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
// ==================== 人脸库统计 ====================
|
||||
|
||||
/**
|
||||
* 初始化人脸库统计区:页面加载时查询并显示人脸总数,绑定刷新按钮
|
||||
*/
|
||||
private fun initFaceCount() {
|
||||
refreshFaceCount()
|
||||
binding.btnRefreshFaceCount.clickWithDebounce {
|
||||
refreshFaceCount()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库查询人脸总数并更新 UI
|
||||
*/
|
||||
private fun refreshFaceCount() {
|
||||
lifecycleScope.launch {
|
||||
val count = withContext(Dispatchers.IO) {
|
||||
FaceDatabase.getInstance(this@PlcSettingActivity).faceDao().faceCount
|
||||
}
|
||||
binding.tvFaceCount.text = "人脸总数:${count}"
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.sw.platecabinet.model.request
|
||||
|
||||
data class FeatureBody(
|
||||
val url:String,
|
||||
val featureChar:String
|
||||
)
|
||||
@@ -2,6 +2,7 @@ package com.sw.platecabinet.network.api
|
||||
|
||||
import com.sw.platecabinet.GlobalData
|
||||
import com.sw.platecabinet.model.DeviceConfigV2
|
||||
import com.sw.platecabinet.model.request.FeatureBody
|
||||
import com.sw.platecabinet.model.response.ApiResponse
|
||||
import com.sw.platecabinet.model.response.UserFaceModel
|
||||
import com.sw.platecabinet.model.response.UserFaceModelV2
|
||||
@@ -46,11 +47,9 @@ interface ApiServiceV2 {
|
||||
* 添加人脸数据
|
||||
*/
|
||||
@POST
|
||||
@FormUrlEncoded
|
||||
suspend fun addUserFace(
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/user/add-by-face",
|
||||
@Field("featureChar") faceData: String,
|
||||
@Field("url") imageUr: String
|
||||
@Body faceData: FeatureBody
|
||||
): ApiResponse<UserFaceModelV2?>
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sw.platecabinet.repository
|
||||
|
||||
import com.sw.platecabinet.model.DeviceConfigV2
|
||||
import com.sw.platecabinet.model.request.FeatureBody
|
||||
import com.sw.platecabinet.model.response.ApiResponse
|
||||
import com.sw.platecabinet.model.response.UserFaceModel
|
||||
import com.sw.platecabinet.model.response.UserFaceModelV2
|
||||
@@ -25,8 +26,7 @@ class RemoteRepositoryV2 constructor(
|
||||
return safeApiCall {
|
||||
apiService.getUserFaceCache(
|
||||
param = mapOf(
|
||||
"pageNum" to pageNum,
|
||||
"pageSize" to pageSize
|
||||
"pageNum" to pageNum, "pageSize" to pageSize
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -34,16 +34,12 @@ class RemoteRepositoryV2 constructor(
|
||||
|
||||
|
||||
suspend fun getFaceIncrementList(
|
||||
pageNum: Long,
|
||||
pageSize: Long = 100L,
|
||||
timestamp: Long
|
||||
pageNum: Long, pageSize: Long = 100L, timestamp: Long
|
||||
): ApiResponse<List<UserFaceModelV2>?> {
|
||||
return safeApiCall {
|
||||
apiService.getFaceIncrementList(
|
||||
param = mapOf(
|
||||
"pageNum" to pageNum,
|
||||
"pageSize" to pageSize,
|
||||
"timestamp" to timestamp
|
||||
"pageNum" to pageNum, "pageSize" to pageSize, "timestamp" to timestamp
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -53,8 +49,14 @@ class RemoteRepositoryV2 constructor(
|
||||
return safeApiCall { apiService.getDeviceConfig() }
|
||||
}
|
||||
|
||||
suspend fun addUserFace(faceData: String, imageUr: String): ApiResponse<UserFaceModelV2?> {
|
||||
return safeApiCall { apiService.addUserFace(faceData = faceData, imageUr = imageUr) }
|
||||
suspend fun addUserFace(faceData: String, imageUrl: String): ApiResponse<UserFaceModelV2?> {
|
||||
return safeApiCall {
|
||||
apiService.addUserFace(
|
||||
faceData = FeatureBody(
|
||||
url = imageUrl, featureChar = faceData
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uploadImage(file: File): ApiResponse<String?> {
|
||||
|
||||
@@ -82,12 +82,17 @@ class NetViewModelV2 : ViewModel() {
|
||||
}
|
||||
withContext(Dispatchers.Default) {
|
||||
if (currentPageNo == 1) {
|
||||
//第一页数据删除本地数据库
|
||||
//第一页数据删除本地数据库,同时重置时间戳
|
||||
faceApi.clearFaceData()
|
||||
faceTimestamp = 0L
|
||||
}
|
||||
faceApi.updateFaceData(pageNo, faceEntityList)
|
||||
if (list.size >= pageSize) {
|
||||
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
|
||||
// 取当前页最大时间戳,与已累积的比较取最大值
|
||||
val pageMaxTimestamp = maxFaceTimestamp(list)
|
||||
if (pageMaxTimestamp > faceTimestamp) {
|
||||
faceTimestamp = pageMaxTimestamp
|
||||
}
|
||||
currentPageNo++
|
||||
getUserFaceCache(
|
||||
pageNo = currentPageNo, callback = callback
|
||||
@@ -95,7 +100,11 @@ class NetViewModelV2 : ViewModel() {
|
||||
return@withContext
|
||||
}
|
||||
if (list.isNotEmpty()) {
|
||||
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
|
||||
// 最后一页:取当前页最大时间戳,与已累积的比较取最大值
|
||||
val pageMaxTimestamp = maxFaceTimestamp(list)
|
||||
if (pageMaxTimestamp > faceTimestamp) {
|
||||
faceTimestamp = pageMaxTimestamp
|
||||
}
|
||||
}
|
||||
SpTool.lastFaceTimestamp = faceTimestamp
|
||||
callback(true, "查询完成")
|
||||
@@ -161,7 +170,7 @@ class NetViewModelV2 : ViewModel() {
|
||||
|
||||
|
||||
/**
|
||||
* 获取人脸数据
|
||||
* 增量获取人脸数据(支持分页递归,取所有页中的最大时间戳)
|
||||
*/
|
||||
fun getFaceIncrementList(
|
||||
pageNo: Int = 1,
|
||||
@@ -169,9 +178,10 @@ class NetViewModelV2 : ViewModel() {
|
||||
timestamp: Long,
|
||||
refreshFaceList: () -> Unit
|
||||
) {
|
||||
var currentPageNo = pageNo
|
||||
viewModelScope.launch {
|
||||
val response = repository.getFaceIncrementList(
|
||||
pageNum = pageNo.toLong(),
|
||||
pageNum = currentPageNo.toLong(),
|
||||
pageSize = pageSize.toLong(),
|
||||
timestamp = timestamp
|
||||
)
|
||||
@@ -181,10 +191,27 @@ class NetViewModelV2 : ViewModel() {
|
||||
withContext(Dispatchers.IO) {
|
||||
val list = response.data ?: emptyList()
|
||||
updateFaceData(list)
|
||||
if (list.isNotEmpty()) {
|
||||
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
|
||||
// 取当前页最大时间戳,与已累积的比较取最大值
|
||||
val pageMaxTimestamp = maxFaceTimestamp(list)
|
||||
if (pageMaxTimestamp > faceTimestamp) {
|
||||
faceTimestamp = pageMaxTimestamp
|
||||
}
|
||||
if (list.size >= pageSize) {
|
||||
// 还有下一页,继续递归拉取
|
||||
currentPageNo++
|
||||
getFaceIncrementList(
|
||||
pageNo = currentPageNo,
|
||||
pageSize = pageSize,
|
||||
timestamp = timestamp,
|
||||
refreshFaceList = refreshFaceList
|
||||
)
|
||||
return@withContext
|
||||
}
|
||||
// 所有页拉取完毕,保存最终时间戳(只升不降)
|
||||
val storedTimestamp = SpTool.lastFaceTimestamp
|
||||
if (faceTimestamp > storedTimestamp) {
|
||||
SpTool.lastFaceTimestamp = faceTimestamp
|
||||
}
|
||||
SpTool.lastFaceTimestamp = faceTimestamp
|
||||
try {
|
||||
refreshFaceList()
|
||||
} catch (e: Exception) {
|
||||
@@ -246,4 +273,11 @@ class NetViewModelV2 : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从人脸数据列表中取出最大的 faceUpdateTimestamp(跳过 null 值)
|
||||
*/
|
||||
private fun maxFaceTimestamp(list: List<UserFaceModelV2>): Long {
|
||||
return list.mapNotNull { it.faceUpdateTimestamp }.maxOrNull() ?: 0L
|
||||
}
|
||||
|
||||
}
|
||||
@@ -324,6 +324,54 @@
|
||||
android:textColor="#FFCC99"
|
||||
android:textSize="22sp" />
|
||||
|
||||
<!-- ===== 人脸库 ===== -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="人脸库"
|
||||
android:textColor="#FFCC99"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFaceCount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/btn_outline"
|
||||
android:gravity="center"
|
||||
android:text="人脸总数:--"
|
||||
android:textColor="#FFCC99"
|
||||
android:textSize="20sp" />
|
||||
|
||||
<View
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnRefreshFaceCount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/btn_outline"
|
||||
android:gravity="center"
|
||||
android:text="刷新"
|
||||
android:textColor="#FFCC99"
|
||||
android:textSize="20sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user