refactor(network): 重构网络请求配置和API接口实现
- 将BASE_URL改为使用GlobalData.PROD_BASE_URL常量 - 更新ApiClient中的URL匹配规则以支持新的营养模块接口 - 将ApiServiceV2中的接口路径从/neglect/booth改为/nutrition/neglect/booth - 使用@Url注解动态传入完整URL地址 - 在CollectedFoodActivity中切换到NetViewModelV2和新API模型 - 更新采集功能的数据模型和接口调用方式 - 调整人脸数据获取逻辑以支持增量和全量同步 - 优化文件上传和删除操作的参数传递方式
This commit is contained in:
@@ -1,9 +1,18 @@
|
||||
package com.sw.dualscreen.viewmodel
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.arcsoft.face.ErrorInfo
|
||||
import com.sw.dualscreen.GlobalData
|
||||
import com.sw.dualscreen.model.request.UserNutritionParam
|
||||
import com.sw.dualscreen.model.request.v2.BindUserOrderRequest
|
||||
import com.sw.dualscreen.model.request.v2.PlaceOrderRequest
|
||||
import com.sw.dualscreen.model.response.ApiResponse
|
||||
import com.sw.dualscreen.model.response.DinnerType
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.model.response.FoodOrder
|
||||
import com.sw.dualscreen.model.response.FoodOrderModel
|
||||
import com.sw.dualscreen.model.response.FoodSearchReq
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
import com.sw.dualscreen.model.response.UserNutrition
|
||||
import com.sw.dualscreen.model.response.v2.CollectedFoodV2
|
||||
import com.sw.dualscreen.model.response.v2.FaceVO
|
||||
@@ -11,10 +20,20 @@ import com.sw.dualscreen.model.response.v2.NewFoodInfo
|
||||
import com.sw.dualscreen.model.response.v2.NewMemberInfo
|
||||
import com.sw.dualscreen.model.response.v2.SettlementOrder
|
||||
import com.sw.dualscreen.network.ApiClient
|
||||
import com.sw.dualscreen.utils.FileUtil
|
||||
import com.sw.dualscreen.utils.SpTool
|
||||
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.entity.FaceEntity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
class NetViewModelV2 : BaseViewModel() {
|
||||
|
||||
@@ -23,6 +42,16 @@ class NetViewModelV2 : BaseViewModel() {
|
||||
const val PAGE_SIZE = 100L
|
||||
}
|
||||
|
||||
private val faceApi: FaceApi = FaceApi()
|
||||
|
||||
/** 人脸加载完成 */
|
||||
private val _loadFaceResult = MutableStateFlow<Boolean>(false)
|
||||
val loadFaceResult: StateFlow<Boolean> = _loadFaceResult
|
||||
|
||||
/** 饭点类型 早餐/午餐/晚餐 */
|
||||
private val _dinnerTypeInfo = MutableStateFlow<DinnerType?>(null)
|
||||
val dinnerTypeInfo: StateFlow<DinnerType?> = _dinnerTypeInfo
|
||||
|
||||
fun getDeviceConfig(
|
||||
onSuccess: (config: com.sw.dualscreen.model.response.DeviceConfig?) -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
@@ -42,49 +71,82 @@ class NetViewModelV2 : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取人脸全量数据(递归分页 + FaceApi 集成) */
|
||||
fun getFacePage(
|
||||
pageNum: Long = 1L,
|
||||
pageSize: Long = PAGE_SIZE,
|
||||
onSuccess: (list: List<FaceVO>, hasNext: Boolean) -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
onSuccess: () -> Unit = {},
|
||||
onFailure: (String) -> Unit = {}
|
||||
) {
|
||||
var currentPageNum = pageNum
|
||||
Timber.tag(TAG).d("getFacePage index = $currentPageNum")
|
||||
launch {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getFacePage(pageNum, pageSize)
|
||||
if (parseResponse(response)) {
|
||||
val list = response.data ?: emptyList()
|
||||
val hasNext = list.size >= pageSize
|
||||
onSuccess(list, hasNext)
|
||||
} else {
|
||||
onFailure(response.msg ?: "获取人脸数据失败")
|
||||
_loadFaceResult.value = false
|
||||
SpTool.lastFaceTimestamp = 0
|
||||
FileUtil.saveLog("获取人脸全量数据开始,重置时间戳为0")
|
||||
val response = ApiClient.repositoryV2.getFacePage(currentPageNum, pageSize)
|
||||
if (!parseResponse(response)) {
|
||||
onFailure("获取人脸数据失败,${response.msg}(${response.code})")
|
||||
return@launch
|
||||
}
|
||||
withContext(Dispatchers.Default) {
|
||||
val list = response.data ?: emptyList()
|
||||
if (pageNum == 1L && list.isEmpty()) {
|
||||
onFailure("查询人脸数据为空")
|
||||
return@withContext
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "获取全量人脸数据异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
val faceEntity = list.map { vo ->
|
||||
FaceEntity(vo.userId, null, Base64.decode(vo.faceFeature)).also {
|
||||
it.userType = if (vo.member == true) "1" else "2"
|
||||
it.cardNo = vo.cardNo
|
||||
}
|
||||
}
|
||||
faceApi.updateFaceData(currentPageNum.toInt(), faceEntity)
|
||||
if (list.size >= pageSize) {
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
currentPageNum++
|
||||
getFacePage(currentPageNum, pageSize, onSuccess, onFailure)
|
||||
return@withContext
|
||||
}
|
||||
if (list.isNotEmpty()) {
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
}
|
||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||
FileUtil.saveLog("获取人脸[全量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||
_loadFaceResult.value = true
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取人脸增量数据(FaceApi 集成) */
|
||||
fun getFaceIncrement(
|
||||
pageNum: Long = 1L,
|
||||
pageSize: Long = PAGE_SIZE,
|
||||
timestamp: Long,
|
||||
onSuccess: (list: List<FaceVO>, hasNext: Boolean) -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
onAllQueryFinished: () -> Unit,
|
||||
onPageQueryFinished: (List<FaceVO>) -> Unit,
|
||||
onFailure: (String) -> Unit = {}
|
||||
) {
|
||||
Timber.tag(TAG).d("getFaceIncrement timestamp=$timestamp")
|
||||
launch {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.getFaceIncrement(pageNum, pageSize, timestamp)
|
||||
if (parseResponse(response)) {
|
||||
val list = response.data ?: emptyList()
|
||||
val hasNext = list.size >= pageSize
|
||||
onSuccess(list, hasNext)
|
||||
} else {
|
||||
onFailure(response.msg ?: "获取增量人脸数据失败")
|
||||
val response = ApiClient.repositoryV2.getFaceIncrement(pageNum, pageSize, timestamp)
|
||||
if (!parseResponse(response)) {
|
||||
onFailure(response.msg ?: "获取增量人脸数据失败")
|
||||
return@launch
|
||||
}
|
||||
withContext(Dispatchers.Default) {
|
||||
val list = response.data ?: emptyList()
|
||||
if (pageNum == 1L && list.isEmpty()) {
|
||||
return@withContext
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "获取增量人脸数据异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
onPageQueryFinished(list)
|
||||
if (list.isNotEmpty()) {
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
}
|
||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||
FileUtil.saveLog("获取人脸[增量]数据结束,时间戳为:$lastFaceTimestamp")
|
||||
onAllQueryFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,7 +252,7 @@ class NetViewModelV2 : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun getMemberInfoByPhone(
|
||||
fun getMemberInfoByPhoneV2(
|
||||
phone: String,
|
||||
password: String = "",
|
||||
onSuccess: (memberInfo: NewMemberInfo?) -> Unit,
|
||||
@@ -275,72 +337,326 @@ class NetViewModelV2 : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun uploadCollect(
|
||||
/** 上传采集图片(suspend,直接返回 picUrls 列表) */
|
||||
suspend fun uploadCollect(
|
||||
foodId: Long,
|
||||
foodName: String,
|
||||
version: String,
|
||||
foodVector: String,
|
||||
fileList: List<okhttp3.MultipartBody.Part>,
|
||||
onSuccess: (picUrls: List<String>) -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
fileList: List<File>
|
||||
): List<String>? {
|
||||
Timber.tag(TAG).d("uploadCollect foodId=$foodId, foodName=$foodName")
|
||||
val fileListNotNull = fileList.filter { it.exists() }
|
||||
if (fileListNotNull.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
val response = ApiClient.repositoryV2.uploadCollect(
|
||||
foodId, foodName, version, foodVector, fileListNotNull
|
||||
)
|
||||
if (!parseResponse(response)) {
|
||||
return null
|
||||
}
|
||||
return response.data
|
||||
}
|
||||
|
||||
/** 删除采集菜品 */
|
||||
fun deleteCollect(
|
||||
foodId: String?,
|
||||
version: String?,
|
||||
block: (Boolean) -> Unit
|
||||
) {
|
||||
Timber.tag(TAG).d("deleteCollect foodId=$foodId, version=$version")
|
||||
val id = foodId?.toLongOrNull() ?: run {
|
||||
block(false)
|
||||
return
|
||||
}
|
||||
launchWithLoading {
|
||||
try {
|
||||
val params = hashMapOf<String, okhttp3.RequestBody>()
|
||||
params["foodId"] = okhttp3.RequestBody.create(
|
||||
okhttp3.MediaType.parse("text/plain"),
|
||||
foodId.toString()
|
||||
)
|
||||
params["foodName"] = okhttp3.RequestBody.create(
|
||||
okhttp3.MediaType.parse("text/plain"),
|
||||
foodName
|
||||
)
|
||||
params["version"] = okhttp3.RequestBody.create(
|
||||
okhttp3.MediaType.parse("text/plain"),
|
||||
version
|
||||
)
|
||||
params["foodVector"] = okhttp3.RequestBody.create(
|
||||
okhttp3.MediaType.parse("text/plain"),
|
||||
foodVector
|
||||
)
|
||||
|
||||
val response = ApiClient.repositoryV2.uploadCollect(
|
||||
foodId, foodName, version, foodVector,
|
||||
fileList.map { it.body().contentType()?.let { ct ->
|
||||
okhttp3.RequestBody.create(ct, (it.body() as okhttp3.ResponseBody).bytes())
|
||||
} ?: it.body() } as List<File>
|
||||
)
|
||||
|
||||
val response = ApiClient.repositoryV2.deleteCollect(id, version ?: "")
|
||||
if (parseResponse(response)) {
|
||||
onSuccess(response.data ?: emptyList())
|
||||
block(true)
|
||||
} else {
|
||||
onFailure(response.msg ?: "上传采集数据失败")
|
||||
block(false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "上传采集数据异常")
|
||||
Timber.e(e, "删除采集菜品异常")
|
||||
block(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 以下方法暂用 V1 API 实现(待 V2 接口补充) ==========
|
||||
|
||||
/** 激活虹软人脸识别引擎 */
|
||||
fun activeEngine() {
|
||||
Timber.tag(TAG).d("activeEngine")
|
||||
faceApi.activeEngine(
|
||||
App.getContext(),
|
||||
GlobalData.appId,
|
||||
GlobalData.sdkKey,
|
||||
GlobalData.activeKey,
|
||||
object : FaceApi.ActiveCallback {
|
||||
override fun onSuccess(activeCode: Int) {
|
||||
Timber.tag(TAG).d("activeEngine activeCode = $activeCode")
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
when (activeCode) {
|
||||
ErrorInfo.MOK -> {
|
||||
ToastUtils.showToast("激活引擎成功")
|
||||
}
|
||||
ErrorInfo.MERR_ASF_ALREADY_ACTIVATED -> {
|
||||
// 引擎已激活,无需再次激活
|
||||
}
|
||||
else -> {
|
||||
ToastUtils.showToast("激活引擎失败($activeCode)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFail(e: Exception?) {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
ToastUtils.showToast("激活引擎异常,${e?.message}")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取支付二维码 */
|
||||
fun getQrCodeImg(
|
||||
orderId: String,
|
||||
userId: String? = null,
|
||||
totalFee: String? = null,
|
||||
block: (String?) -> Unit
|
||||
) {
|
||||
Timber.tag(TAG).d("getQrCodeImg orderId = $orderId, userId = $userId")
|
||||
launch {
|
||||
val response = repository.getQrCodeImg(
|
||||
orderNo = orderId, memberId = userId, totalFee = totalFee
|
||||
)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 扫码支付 */
|
||||
fun qrCodePay(
|
||||
authCode: String,
|
||||
orderNo: String,
|
||||
memberId: String?,
|
||||
block: (Boolean, String?) -> Unit
|
||||
) {
|
||||
launch {
|
||||
val response = repository.qrCodePay(authCode, orderNo, memberId)
|
||||
if (parseResponse(response)) {
|
||||
block(true, response.data)
|
||||
} else {
|
||||
block(false, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 现金支付 */
|
||||
fun cashPay(param: HashMap<String, String>, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("cashPay")
|
||||
launchWithLoading {
|
||||
val response = repository.cashPay(param)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data ?: false)
|
||||
} else {
|
||||
block(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 会员支付 */
|
||||
fun memberPay(param: HashMap<String, String?>, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("memberPay")
|
||||
launchWithLoading {
|
||||
val response = repository.memberPay(param)
|
||||
if (parseResponse(response)) {
|
||||
block(true)
|
||||
} else {
|
||||
block(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询订单支付状态 */
|
||||
suspend fun queryOrderState(orderId: String, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("queryOrderState")
|
||||
val response = repository.queryOrderState(orderNo = orderId)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data == "1")
|
||||
} else {
|
||||
block(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取饭点类型 */
|
||||
fun getDinnerType(
|
||||
onSuccess: (DinnerType?) -> Unit = {},
|
||||
onFailure: (message: String) -> Unit = {}
|
||||
) {
|
||||
_dinnerTypeInfo.value = null
|
||||
launch {
|
||||
try {
|
||||
val response = repository.getDinnerType()
|
||||
if (parseResponse(response)) {
|
||||
_dinnerTypeInfo.value = response.data
|
||||
onSuccess(response.data)
|
||||
} else {
|
||||
onFailure(response.msg ?: "获取饭点类型失败")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "获取饭点类型异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteCollect(
|
||||
foodId: Long,
|
||||
version: String,
|
||||
onSuccess: () -> Unit,
|
||||
onFailure: (message: String) -> Unit
|
||||
/** 提交就餐营养数据 */
|
||||
fun postUserNutritionData(
|
||||
param: List<UserNutritionParam>,
|
||||
onSuccess: () -> Unit = {},
|
||||
onFailure: (message: String) -> Unit = {}
|
||||
) {
|
||||
launchWithLoading {
|
||||
launch {
|
||||
try {
|
||||
val response = ApiClient.repositoryV2.deleteCollect(foodId, version)
|
||||
val response = repository.postUserNutritionData(param)
|
||||
if (parseResponse(response)) {
|
||||
onSuccess()
|
||||
} else {
|
||||
onFailure(response.msg ?: "删除采集菜品失败")
|
||||
onFailure(response.msg ?: "提交营养数据失败")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "删除采集菜品异常")
|
||||
Timber.e(e, "提交营养数据异常")
|
||||
onFailure("网络异常: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== V1 兼容包装方法(暂用 V1 API,后续逐步迁移至 V2) ==========
|
||||
|
||||
private var lastFaceTimestamp = 0L
|
||||
|
||||
/** 搜索食物 */
|
||||
fun searchByFoodName(foodName: String, action: (List<FoodInfo>) -> Unit = {}) {
|
||||
Timber.tag(TAG).d("searchByFoodName foodName = $foodName")
|
||||
launchWithLoading {
|
||||
val response = repository.getRestInfoFoodsByType(foodName = foodName)
|
||||
if (parseResponse(response)) {
|
||||
action(response.data ?: emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取用户就餐营养数据 */
|
||||
fun getUserNutritionData(
|
||||
userId: String,
|
||||
block: (UserNutrition?) -> Unit
|
||||
) {
|
||||
Timber.tag(TAG).d("getUserNutritionData userId = $userId")
|
||||
launch {
|
||||
val response = repository.getUserNutritionData(userId = userId)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索菜品信息 */
|
||||
fun getFoodInfo(foodName: String, action: (List<FoodInfo>) -> Unit = {}) {
|
||||
Timber.tag(TAG).d("getFoodInfo")
|
||||
launchWithLoading {
|
||||
val response = repository.getFoodInfo(foodName = foodName)
|
||||
if (parseResponse(response)) {
|
||||
action(response.data ?: emptyList())
|
||||
} else {
|
||||
action(emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 创建订单 */
|
||||
fun createOrder(order: FoodOrder, block: (String) -> Unit) {
|
||||
Timber.tag(TAG).d("createOrder")
|
||||
launchWithLoading {
|
||||
val response = repository.createOrder(order)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data.toString())
|
||||
} else {
|
||||
block("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取订单列表 */
|
||||
fun getFoodOrderList(userId: String, block: (FoodOrderModel?) -> Unit) {
|
||||
Timber.tag(TAG).d("getFoodOrderList")
|
||||
launchWithLoading {
|
||||
val response = repository.getFoodOrderList(userId)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 根据 ID 查询会员信息 */
|
||||
fun getMemberInfoById(memberId: String, block: (MemberInfo?) -> Unit) {
|
||||
Timber.tag(TAG).d("getMemberInfo")
|
||||
launchWithLoading {
|
||||
val response = repository.getMemberInfoById(memberId)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 根据手机号查询会员信息 */
|
||||
fun getMemberInfoByPhone(phone: String, key: String, block: (MemberInfo?) -> Unit) {
|
||||
Timber.tag(TAG).d("getMemberInfoByPhone")
|
||||
launchWithLoading {
|
||||
val response = repository.getMemberInfoByPhone(phone, key)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 绑定订单 */
|
||||
fun bindOrder(userId: String, orderId: String, mode: Int, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("bindOrder")
|
||||
launchWithLoading {
|
||||
val response = repository.bindOrder(userId = userId, orderId = orderId, mode = mode)
|
||||
if (parseResponse(response)) {
|
||||
block(true)
|
||||
} else {
|
||||
block(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取会员折扣 */
|
||||
fun getMemberDiscount(userId: String, block: (Double?) -> Unit) {
|
||||
Timber.tag(TAG).d("getMemberDiscount")
|
||||
launchWithLoading {
|
||||
val response = repository.getMemberDiscount(userId)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data)
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user