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 android.view.inputmethod.InputMethodManager
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.sw.plate.utils.ToastUtils
|
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.plate.utils.arcface.faceserver.FaceServer
|
||||||
import com.sw.platecabinet.databinding.ActivityPlcSettingBinding
|
import com.sw.platecabinet.databinding.ActivityPlcSettingBinding
|
||||||
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
|
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
|
||||||
@@ -60,6 +61,7 @@ class PlcSettingActivity : BaseActivity<ActivityPlcSettingBinding>() {
|
|||||||
initAlarmButton()
|
initAlarmButton()
|
||||||
initParamButtons()
|
initParamButtons()
|
||||||
initStatusButton()
|
initStatusButton()
|
||||||
|
initFaceCount()
|
||||||
binding.btnSwitchEnv.setOnClickListener {
|
binding.btnSwitchEnv.setOnClickListener {
|
||||||
EnvSwitchDialog(context = this, onEnvChanged = {
|
EnvSwitchDialog(context = this, onEnvChanged = {
|
||||||
showWaitingDialog("重置人脸数据中……")
|
showWaitingDialog("重置人脸数据中……")
|
||||||
@@ -334,6 +336,30 @@ class PlcSettingActivity : BaseActivity<ActivityPlcSettingBinding>() {
|
|||||||
""".trimIndent()
|
""".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.GlobalData
|
||||||
import com.sw.platecabinet.model.DeviceConfigV2
|
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.ApiResponse
|
||||||
import com.sw.platecabinet.model.response.UserFaceModel
|
import com.sw.platecabinet.model.response.UserFaceModel
|
||||||
import com.sw.platecabinet.model.response.UserFaceModelV2
|
import com.sw.platecabinet.model.response.UserFaceModelV2
|
||||||
@@ -46,11 +47,9 @@ interface ApiServiceV2 {
|
|||||||
* 添加人脸数据
|
* 添加人脸数据
|
||||||
*/
|
*/
|
||||||
@POST
|
@POST
|
||||||
@FormUrlEncoded
|
|
||||||
suspend fun addUserFace(
|
suspend fun addUserFace(
|
||||||
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/user/add-by-face",
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/user/add-by-face",
|
||||||
@Field("featureChar") faceData: String,
|
@Body faceData: FeatureBody
|
||||||
@Field("url") imageUr: String
|
|
||||||
): ApiResponse<UserFaceModelV2?>
|
): ApiResponse<UserFaceModelV2?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.sw.platecabinet.repository
|
package com.sw.platecabinet.repository
|
||||||
|
|
||||||
import com.sw.platecabinet.model.DeviceConfigV2
|
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.ApiResponse
|
||||||
import com.sw.platecabinet.model.response.UserFaceModel
|
import com.sw.platecabinet.model.response.UserFaceModel
|
||||||
import com.sw.platecabinet.model.response.UserFaceModelV2
|
import com.sw.platecabinet.model.response.UserFaceModelV2
|
||||||
@@ -25,8 +26,7 @@ class RemoteRepositoryV2 constructor(
|
|||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
apiService.getUserFaceCache(
|
apiService.getUserFaceCache(
|
||||||
param = mapOf(
|
param = mapOf(
|
||||||
"pageNum" to pageNum,
|
"pageNum" to pageNum, "pageSize" to pageSize
|
||||||
"pageSize" to pageSize
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -34,16 +34,12 @@ class RemoteRepositoryV2 constructor(
|
|||||||
|
|
||||||
|
|
||||||
suspend fun getFaceIncrementList(
|
suspend fun getFaceIncrementList(
|
||||||
pageNum: Long,
|
pageNum: Long, pageSize: Long = 100L, timestamp: Long
|
||||||
pageSize: Long = 100L,
|
|
||||||
timestamp: Long
|
|
||||||
): ApiResponse<List<UserFaceModelV2>?> {
|
): ApiResponse<List<UserFaceModelV2>?> {
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
apiService.getFaceIncrementList(
|
apiService.getFaceIncrementList(
|
||||||
param = mapOf(
|
param = mapOf(
|
||||||
"pageNum" to pageNum,
|
"pageNum" to pageNum, "pageSize" to pageSize, "timestamp" to timestamp
|
||||||
"pageSize" to pageSize,
|
|
||||||
"timestamp" to timestamp
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -53,8 +49,14 @@ class RemoteRepositoryV2 constructor(
|
|||||||
return safeApiCall { apiService.getDeviceConfig() }
|
return safeApiCall { apiService.getDeviceConfig() }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addUserFace(faceData: String, imageUr: String): ApiResponse<UserFaceModelV2?> {
|
suspend fun addUserFace(faceData: String, imageUrl: String): ApiResponse<UserFaceModelV2?> {
|
||||||
return safeApiCall { apiService.addUserFace(faceData = faceData, imageUr = imageUr) }
|
return safeApiCall {
|
||||||
|
apiService.addUserFace(
|
||||||
|
faceData = FeatureBody(
|
||||||
|
url = imageUrl, featureChar = faceData
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun uploadImage(file: File): ApiResponse<String?> {
|
suspend fun uploadImage(file: File): ApiResponse<String?> {
|
||||||
|
|||||||
@@ -82,12 +82,17 @@ class NetViewModelV2 : ViewModel() {
|
|||||||
}
|
}
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
if (currentPageNo == 1) {
|
if (currentPageNo == 1) {
|
||||||
//第一页数据删除本地数据库
|
//第一页数据删除本地数据库,同时重置时间戳
|
||||||
faceApi.clearFaceData()
|
faceApi.clearFaceData()
|
||||||
|
faceTimestamp = 0L
|
||||||
}
|
}
|
||||||
faceApi.updateFaceData(pageNo, faceEntityList)
|
faceApi.updateFaceData(pageNo, faceEntityList)
|
||||||
if (list.size >= pageSize) {
|
if (list.size >= pageSize) {
|
||||||
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
|
// 取当前页最大时间戳,与已累积的比较取最大值
|
||||||
|
val pageMaxTimestamp = maxFaceTimestamp(list)
|
||||||
|
if (pageMaxTimestamp > faceTimestamp) {
|
||||||
|
faceTimestamp = pageMaxTimestamp
|
||||||
|
}
|
||||||
currentPageNo++
|
currentPageNo++
|
||||||
getUserFaceCache(
|
getUserFaceCache(
|
||||||
pageNo = currentPageNo, callback = callback
|
pageNo = currentPageNo, callback = callback
|
||||||
@@ -95,7 +100,11 @@ class NetViewModelV2 : ViewModel() {
|
|||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
if (list.isNotEmpty()) {
|
if (list.isNotEmpty()) {
|
||||||
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
|
// 最后一页:取当前页最大时间戳,与已累积的比较取最大值
|
||||||
|
val pageMaxTimestamp = maxFaceTimestamp(list)
|
||||||
|
if (pageMaxTimestamp > faceTimestamp) {
|
||||||
|
faceTimestamp = pageMaxTimestamp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SpTool.lastFaceTimestamp = faceTimestamp
|
SpTool.lastFaceTimestamp = faceTimestamp
|
||||||
callback(true, "查询完成")
|
callback(true, "查询完成")
|
||||||
@@ -161,7 +170,7 @@ class NetViewModelV2 : ViewModel() {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取人脸数据
|
* 增量获取人脸数据(支持分页递归,取所有页中的最大时间戳)
|
||||||
*/
|
*/
|
||||||
fun getFaceIncrementList(
|
fun getFaceIncrementList(
|
||||||
pageNo: Int = 1,
|
pageNo: Int = 1,
|
||||||
@@ -169,9 +178,10 @@ class NetViewModelV2 : ViewModel() {
|
|||||||
timestamp: Long,
|
timestamp: Long,
|
||||||
refreshFaceList: () -> Unit
|
refreshFaceList: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
var currentPageNo = pageNo
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val response = repository.getFaceIncrementList(
|
val response = repository.getFaceIncrementList(
|
||||||
pageNum = pageNo.toLong(),
|
pageNum = currentPageNo.toLong(),
|
||||||
pageSize = pageSize.toLong(),
|
pageSize = pageSize.toLong(),
|
||||||
timestamp = timestamp
|
timestamp = timestamp
|
||||||
)
|
)
|
||||||
@@ -181,10 +191,27 @@ class NetViewModelV2 : ViewModel() {
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val list = response.data ?: emptyList()
|
val list = response.data ?: emptyList()
|
||||||
updateFaceData(list)
|
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 {
|
try {
|
||||||
refreshFaceList()
|
refreshFaceList()
|
||||||
} catch (e: Exception) {
|
} 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:textColor="#FFCC99"
|
||||||
android:textSize="22sp" />
|
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>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user