package com.sw.platecabinet.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.arcsoft.face.ErrorInfo import com.sw.inbound.utils.SPUtil import com.sw.plate.App import com.sw.plate.utils.Base64 import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.arcface.FaceApi import com.sw.plate.utils.arcface.facedb.FaceDatabase import com.sw.plate.utils.arcface.facedb.entity.FaceEntity import com.sw.platecabinet.GlobalData import com.sw.platecabinet.GlobalKey import com.sw.platecabinet.model.DeviceConfigV2 import com.sw.platecabinet.model.response.UserFaceModel import com.sw.platecabinet.model.response.UserFaceModelV2 import com.sw.platecabinet.network.ApiClient import com.sw.platecabinet.repository.RemoteRepositoryV2 import com.sw.platecabinet.utils.SpTool import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber import java.io.File /** * 用户viewmodel */ class NetViewModelV2 : ViewModel() { companion object { const val PAGE_SIZE = 100 } private val faceApi: FaceApi = FaceApi() private val repository = RemoteRepositoryV2(ApiClient.apiServiceV2) //fun getUserFace( // pageNo: Int, // pageSize: Int, // callback: (Boolean, List) -> Unit //) { // viewModelScope.launch { // val response = repository.getUserFaceCache(pageNum = pageNo, pageSize = pageSize) // if (response.isSuccess()) callback(true, response.data ?: emptyList()) // else callback(false, emptyList()) // } //} /** * 获取人脸数据 */ fun getUserFaceCache( pageNo: Int = 1, pageSize: Int = PAGE_SIZE, callback: (Boolean, String?) -> Unit = { _, _ -> } ) { var currentPageNo = pageNo viewModelScope.launch { val response = repository.getUserFaceCache(pageNo, pageSize) if (response.isSuccess().not()) { callback(false, response.msg) return@launch } // 获取成功一次后缓存状态 SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true) val list = response.data ?: emptyList() if (pageNo == 1 && list.isEmpty()) { callback(false, "暂无数据") return@launch } val faceEntityList = list.mapNotNull { face -> val featureBase64 = face.resolveFeatureStr() if (featureBase64.isNullOrBlank()) null else { val featureData = base64ToByteArray(featureBase64) if (featureData == null) null else face.toFaceEntity() } } withContext(Dispatchers.Default) { if (currentPageNo == 1) { //第一页数据删除本地数据库,同时重置时间戳 faceApi.clearFaceData() faceTimestamp = 0L } faceApi.updateFaceData(pageNo, faceEntityList) if (list.size >= pageSize) { // 取当前页最大时间戳,与已累积的比较取最大值 val pageMaxTimestamp = maxFaceTimestamp(list) if (pageMaxTimestamp > faceTimestamp) { faceTimestamp = pageMaxTimestamp } currentPageNo++ getUserFaceCache( pageNo = currentPageNo, callback = callback ) return@withContext } if (list.isNotEmpty()) { // 最后一页:取当前页最大时间戳,与已累积的比较取最大值 val pageMaxTimestamp = maxFaceTimestamp(list) if (pageMaxTimestamp > faceTimestamp) { faceTimestamp = pageMaxTimestamp } } SpTool.setLastFaceTimestamp(faceTimestamp) callback(true, "查询完成") } } } private var faceTimestamp = 0L /** * 激活人脸识别引擎 */ fun activeEngine() { Timber.d("activeEngine") faceApi.activeEngine( App.getContext(), GlobalData.appId, GlobalData.sdkKey, GlobalData.activeKey, object : FaceApi.ActiveCallback { override fun onSuccess(activeCode: Int) { Timber.d("activeEngine activeCode = $activeCode") // ThreadUtils.launch { viewModelScope.launch(Dispatchers.Main) { when (activeCode) { ErrorInfo.MOK -> { ToastUtils.showToast("激活引擎成功") } ErrorInfo.MERR_ASF_ALREADY_ACTIVATED -> { // ToastUtils.showToast("引擎已激活,无需再次激活") } else -> { ToastUtils.showToast("激活引擎失败($activeCode)") } } } } override fun onFail(e: Exception?) { viewModelScope.launch(Dispatchers.Main) { ToastUtils.showToast("激活引擎异常,${e?.message}") } } }) } fun getDeviceConfig(onSuccess: (DeviceConfigV2?) -> Unit, onFailure: (String?) -> Unit) { // Timber.tag(TAG).d("getDeviceConfig") viewModelScope.launch { val response = repository.getDeviceConfig() if (response.isSuccess()) { onSuccess(response.data) } else { onFailure(response.msg) } } } /** * 增量获取人脸数据(支持分页递归,取所有页中的最大时间戳) */ fun getFaceIncrementList( pageNo: Int = 1, pageSize: Int = PAGE_SIZE, timestamp: Long, refreshFaceList: () -> Unit ) { var currentPageNo = pageNo viewModelScope.launch { val response = repository.getFaceIncrementList( pageNum = currentPageNo.toLong(), pageSize = pageSize.toLong(), timestamp = timestamp ) if (response.isSuccess().not()) { return@launch } withContext(Dispatchers.IO) { val list = response.data ?: emptyList() updateFaceData(list) // 取当前页最大时间戳,与已累积的比较取最大值 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.getLastFaceTimestamp() if (faceTimestamp > storedTimestamp) { SpTool.setLastFaceTimestamp(faceTimestamp) } try { refreshFaceList() } catch (e: Exception) { e.printStackTrace() } } } } private fun updateFaceData(list: List) { try { list.forEach { model -> if (model.faceDeleted == true) { //删除数据 // 仅删除 userFaceId 对应的单条人脸记录,避免误删该用户名下所有特征 val userFaceId = model.userFaceId if (!userFaceId.isNullOrEmpty()) { faceApi.deleteByUserFaceId(userFaceId) } } else { // 优先按 userFaceId 精确判重,避免特征字段不一致导致重复入库 val userFaceId = model.userFaceId if (!userFaceId.isNullOrEmpty() && faceApi.queryByUserFaceId(userFaceId) != null) { // 本地已存在该 userFaceId,跳过插入 return@forEach } // userFaceId 为空时,回退到特征比较去重(修正字段来源,保证与入库一致) val featureStr = model.resolveFeatureStr() val existList = faceApi.queryAllByUserName(model.userId) val alreadyExists = existList.any { entity -> featureStr != null && Base64.encode(entity.featureData) == featureStr } if (!alreadyExists) { faceApi.insert(model.toFaceEntity()) } } } } catch (e: Exception) { e.printStackTrace() } } /** * 添加人脸数据 */ fun addUserFace(faceData: String, imageFile: File, block: (Boolean, UserFaceModelV2?) -> Unit) { viewModelScope.launch { try { val imageResp = repository.uploadImage(imageFile) val imageUrl = imageResp.data ?: "" if (imageUrl.isBlank()) { block(false, null) return@launch } val faceResp = repository.addUserFace(faceData, imageUrl) if (faceResp.isSuccess().not()) { block(false, null) return@launch } block(true, faceResp.data) } catch (e: Exception) { // 网络或解析异常时同样回调失败,避免 block 永不触发导致上层无反馈 e.printStackTrace() block(false, null) } } } /** * 取餐盘上报(P-09a):记录用户开始就餐时刻,作为后续就餐记录(P-09)的强前置。 * 失败仅回调通知,不阻断吐盘等业务主流程。 * * @param userId 用户 id(餐盘号 = userId) * @param callback 上报结果回调:Boolean 是否成功,String? 成功为 recordNo、失败为错误信息 */ fun platePickup( userId: Long, callback: (Boolean, String?) -> Unit = { _, _ -> } ) { viewModelScope.launch { try { val response = repository.platePickup(userId) if (response.isSuccess()) { callback(true, response.data) } else { callback(false, response.msg) } } catch (e: Exception) { e.printStackTrace() callback(false, e.message) } } } private fun base64ToByteArray(base64: String?): ByteArray? { base64 ?: return null return try { Base64.decode(base64) } catch (e: Exception) { null } } /** * 从人脸数据列表中取出最大的 faceUpdateTimestamp(跳过 null 值) */ private fun maxFaceTimestamp(list: List): Long { return list.mapNotNull { it.faceUpdateTimestamp }.maxOrNull() ?: 0L } }