Author SHA1 Message Date
mazengfei 86c3c07e42 隐藏测试代码 2026-03-12 11:10:26 +08:00
lvmengandClaude Sonnet 4.6 aa96455f66 refactor(face): 优化人脸特征存储与增量同步逻辑
- FaceDao 新增 queryAllByUserName 查询用户全部特征记录
- 改为查询所有记录后做去重判断,支持同一用户保存多条特征数据
- 将 Thread 替换为 lifecycleScope.launch(Dispatchers.IO) 协程
- 提取 getNewFaceEntity 方法消除重复代码
- 新增特征比对调试日志

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 16:02:13 +08:00
lvmengandClaude Sonnet 4.6 786f2a123c feat(face): 人脸库为空时自动进入新用户采集模式
- 新增 newUserCollect() 方法,人脸数据库记录为零时自动显示采集界面
- onResume 中异步查询人脸数量,据此决定进入采集模式或正常识别模式
- 修复 isFirstOpen 依赖 1000ms 延迟的竞态问题,改为立即置 false
- 顺带修改 FunActivity 按钮文案为「清除并拉取全量数据」

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 15:01:39 +08:00
6 changed files with 117 additions and 23 deletions
@@ -14,6 +14,7 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.google.gson.Gson
import com.sw.inbound.utils.DateTimeUtils
@@ -468,9 +469,9 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
public val recognizeViewModel by viewModels<RecognizeViewModel>()
private fun updateFaceData(list: List<UserFaceModel>) {
Thread {
lifecycleScope.launch(Dispatchers.IO) {
val faceList = mutableListOf<FaceEntity>()
val faceDao = FaceDatabase.getInstance(this).faceDao()
val faceDao = FaceDatabase.getInstance(this@BaseActivity).faceDao()
try {
list.forEach { model ->
if (model.faceDeleted == true) {
@@ -480,19 +481,14 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
// FaceEntity("-1", null, byteArrayOf(-1)).also { it.userType = "-1" }
// }
} else {
val faceData = faceDao.queryByUserName(model.userId)
if (faceData == null) {
//保存数据
val faceEntity = FaceEntity(
model.userId,
null,
Base64.decode(model.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (model.member) "1" else "2"
it.cardNo = model.cardNo
}
faceList.add(faceEntity)
// 获取该用户所有已存特征,判断当前特征是否已存在
val existList = faceDao.queryAllByUserName(model.userId)
val featureMatches = existList.map { Base64.encode(it.featureData) == model.faceFeatureStr }
Timber.d("userId=${model.userId}, 已存记录数=${existList.size}, 各条特征是否与服务器一致=$featureMatches")
val alreadyExists = featureMatches.any { it }
if (!alreadyExists) {
//当前特征不存在,保存
faceList.add(getNewFaceEntity(model))
}
}
}
@@ -501,13 +497,25 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
}
try {
if (faceList.isNotEmpty()) {
FaceDatabase.getInstance(this).faceDao().insert(faceList)
FaceDatabase.getInstance(this@BaseActivity).faceDao().insert(faceList)
}
recognizeViewModel.refreshFaceList()
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
}
private fun getNewFaceEntity(model: UserFaceModel) : FaceEntity {
return FaceEntity(
model.userId,
null,
Base64.decode(model.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (model.member) "1" else "2"
it.cardNo = model.cardNo
}
}
fun initPlateList() {
@@ -1,6 +1,8 @@
package com.sw.platecabinet.activity
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import com.sw.plate.utils.Base64
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.facedb.FaceDatabase
import com.sw.platecabinet.MyApp
@@ -10,10 +12,12 @@ import com.sw.platecabinet.dialog.CustomDialog
import com.sw.platecabinet.ext.clickWithDebounce
import com.sw.platecabinet.utils.PlateUtils
import com.sw.platecabinet.utils.SpTool
import com.sw.platecabinet.viewmodel.UserViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.getValue
class FunActivity : BaseActivity<ActivityFunBinding>() {
@@ -34,6 +38,26 @@ class FunActivity : BaseActivity<ActivityFunBinding>() {
binding.btnPutPlate.clickWithDebounce { putPlate() }
queryUserCount()
binding.btnTestFeature.clickWithDebounce {
testFeature()
}
}
private fun testFeature() {
userViewModel.getUserFace(1, 1) {list ->
lifecycleScope.launch {
withContext(Dispatchers.IO) {
if (list.isEmpty()) return@withContext
val userId = list[0].userId
val base64Feature = list[0].faceFeatureStr
val faceDao = FaceDatabase.getInstance(this@FunActivity).faceDao()
val entityList = faceDao.queryAllByUserName(userId)
val resultList = entityList.map { base64Feature == Base64.encode(it.featureData) }
val isEqual = resultList.any { true }
ToastUtils.showToast("比较结果:$isEqual")
}
}
}
}
private fun clearFace() {
@@ -59,6 +59,7 @@ import com.sw.platecabinet.utils.SoundPoolUtil
import com.sw.platecabinet.utils.SpTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -348,6 +349,20 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
binding.ivCollectPhoto.visible()
}
private fun newUserCollect() {
recognizeCountDownUtil.cancelCountDown()
binding.tvToInit.text = ""
binding.tvSimilarValue.text = ""
binding.llCollectButton.visible()
binding.btnRetry.gone()
binding.btnCollect.run {
visible()
text = "新用户采集"
}
binding.ivCollectPhoto.gone()
resumeCamera()
}
/**
* 查询餐盘数据
*/
@@ -674,13 +689,24 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
super.onResume()
try {
if (isFirstOpen) {
isFirstOpen = false
goInitActivity()
binding.root.postDelayed({
isFirstOpen = false
}, 1000)
return
}
resetRecognizeState()
lifecycleScope.launch {
try {
val faceDao = FaceDatabase.getInstance(this@LoginByFaceActivity).faceDao()
val faceCount = withContext(Dispatchers.IO) { faceDao.faceCount }
if (faceCount == 0) {
newUserCollect()
} else {
resetRecognizeState()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
@@ -51,6 +51,19 @@ class UserViewModel : BaseViewModel() {
// }
// }
fun getUserFace(
pageNo: Int,
pageSize: Int,
callback: (List<UserFaceModel>) -> Unit
) {
launchWithLoading{
val response = repository.getUserFaceCache(pageNum = pageNo, pageSize = pageSize)
val status = parseResponse(response)
if (status) callback(response.data?:emptyList())
else emptyList<UserFaceModel>()
}
}
/**
* 获取人脸数据
*/
@@ -84,7 +97,7 @@ class UserViewModel : BaseViewModel() {
//第一页数据删除本地数据库
faceApi.clearFaceData()
}
faceApi.updateFaceData( pageNo, faceEntityList)
faceApi.updateFaceData(pageNo, faceEntityList)
if (list.size >= pageSize) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
currentPageNo++
+15 -1
View File
@@ -65,7 +65,7 @@
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="清除并重新拉取数据"
android:text="清除并拉取全量数据"
android:textColor="#FFCC99" />
<TextView
@@ -93,4 +93,18 @@
android:background="@drawable/btn_outline"
android:text="模拟放置餐盘"
android:textColor="#FFCC99" />
<TextView
android:id="@+id/btnTestFeature"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="人脸特征比较"
android:textColor="#FFCC99"
android:visibility="gone"/>
</LinearLayout>
@@ -92,6 +92,15 @@ public interface FaceDao {
@Query("SELECT * FROM face WHERE user_name = :userName limit 1")
FaceEntity queryByUserName(String userName);
/**
* 查询指定用户的所有人脸记录(同一用户可能存有多条特征数据)
*
* @param userName 用户ID
* @return 该用户所有人脸记录列表
*/
@Query("SELECT * FROM face WHERE user_name = :userName")
List<FaceEntity> queryAllByUserName(String userName);
/**
* @return 删除临时用户人脸
*/