feat(agent): implement #91 controlled recollection

This commit is contained in:
QiuSW
2026-08-26 10:17:45 +08:00
parent 77fdbebc2d
commit 7b6cc00503
11 changed files with 349 additions and 8 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "cn.ilapage.goauto.agent"
minSdk = 23
targetSdk = 34
versionCode = 6
versionName = "0.4.1"
versionCode = 7
versionName = "0.5.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -92,6 +92,8 @@ data class CollectionHistoryDetail(
val missing: List<String>,
)
data class CollectionResetResult(val taskId: Long, val status: String, val replayed: Boolean)
data class PurchaseHistoryItem(
val taskId: Long,
val status: String,
@@ -242,6 +244,12 @@ class AgentApiClient(private val serverUrl: String) {
)
}
fun resetCollectionTask(taskId: Long, requestId: String, token: String): CollectionResetResult {
val payload = JSONObject().put("requestId", requestId)
val data = requireNotNull(request("POST", "/api/agent/v1/collection-tasks/$taskId/reset", payload, token)).getJSONObject("data")
return CollectionResetResult(data.getLong("taskId"), data.getString("status"), data.optBoolean("replayed"))
}
fun purchaseHistory(token: String, page: Int, status: String?, taskNo: String?): HistoryPage<PurchaseHistoryItem> {
val data = requireNotNull(request("GET", historyPath("/api/agent/v1/purchase-tasks", page, status, taskNo), null, token)).getJSONObject("data")
return HistoryPage(
@@ -20,9 +20,12 @@ import cn.ilapage.goauto.agent.network.CollectionHistoryItem
import cn.ilapage.goauto.agent.network.HistoryPage
import cn.ilapage.goauto.agent.network.PurchaseHistoryDetail
import cn.ilapage.goauto.agent.network.PurchaseHistoryItem
import cn.ilapage.goauto.agent.service.AgentForegroundService
import cn.ilapage.goauto.agent.service.AgentSettingsStore
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.util.Locale
import java.util.UUID
internal object TaskNumberParser {
fun normalize(raw: String, collection: Boolean): String? {
@@ -40,6 +43,18 @@ internal object TaskNumberParser {
}
}
internal object CollectionResetPolicy {
private val terminalStatuses = setOf("completed", "completed_partial", "failed")
fun isAllowed(status: String): Boolean = status in terminalStatuses
fun confirmationMessage(status: String): String = if (status == "failed") {
"重新采集会清除本任务现有的错误和采集结果,然后重新加入当前设备的任务队列。"
} else {
"重新采集会清除本任务现有的全部采集结果,然后重新加入当前设备的任务队列。"
}
}
class TaskHistoryFragment : Fragment() {
private val collection: Boolean get() = requireArguments().getBoolean(ARG_COLLECTION)
private lateinit var pageColumn: LinearLayout
@@ -291,6 +306,56 @@ class TaskHistoryFragment : Fragment() {
}), collectionCardParams())
if (detail.missing.isNotEmpty()) resultColumn.addView(context.centeredMessage("缺失项", detail.missing.joinToString("、")))
if (task.errorMessage != null) resultColumn.addView(context.centeredMessage(task.errorMessage, "错误代码:${task.errorCode ?: "—"}"))
if (CollectionResetPolicy.isAllowed(task.status)) {
resultColumn.addView(MaterialButton(context).apply {
text = "重新采集"
minimumHeight = context.dp(48)
contentDescription = "重新采集任务 ${task.taskId}"
setOnClickListener { confirmReset(task) }
}, collectionCardParams())
}
}
private fun confirmReset(task: CollectionHistoryItem) {
MaterialAlertDialogBuilder(requireContext())
.setTitle("重新采集 #${task.taskId}?")
.setMessage(CollectionResetPolicy.confirmationMessage(task.status))
.setNegativeButton("取消", null)
.setPositiveButton("确认重新采集") { _, _ -> resetCollectionTask(task.taskId) }
.show()
}
private fun resetCollectionTask(taskId: Long) {
val context = requireContext()
val credentials = runCatching { SecureDeviceStore(context).credentials() }.getOrNull()
val serverUrl = AgentSettingsStore(context).serverUrl()
if (credentials == null || serverUrl.isBlank()) {
showMessage("无法重新采集", "设备尚未连接服务端,请先检查设置。", "返回任务详情") { loadCollectionDetail(taskId) }
return
}
val generation = ++requestGeneration
showLoading("正在加入重新采集队列…")
Thread {
runCatching { AgentApiClient(serverUrl).resetCollectionTask(taskId, UUID.randomUUID().toString(), credentials.token) }
.onSuccess { result ->
resultColumn.post {
if (!isAdded || generation != requestGeneration) return@post
AgentForegroundService.start(requireContext())
showMessage(
"已加入重新采集队列",
"任务 #${result.taskId} 将由当前设备通过正常调度重新执行。",
"返回采集记录",
) { page = 1; load() }
}
}
.onFailure { error ->
resultColumn.post {
if (isAdded && generation == requestGeneration) {
showMessage("无法重新采集", error.message ?: "请检查设备状态后重试。", "返回任务详情") { loadCollectionDetail(taskId) }
}
}
}
}.start()
}
private fun renderPurchaseDetail(detail: PurchaseHistoryDetail) {
@@ -0,0 +1,23 @@
package cn.ilapage.goauto.agent
import cn.ilapage.goauto.agent.ui.CollectionResetPolicy
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class CollectionResetPolicyTest {
@Test
fun `only collection terminal states can reset`() {
assertTrue(CollectionResetPolicy.isAllowed("completed"))
assertTrue(CollectionResetPolicy.isAllowed("completed_partial"))
assertTrue(CollectionResetPolicy.isAllowed("failed"))
assertFalse(CollectionResetPolicy.isAllowed("pending"))
assertFalse(CollectionResetPolicy.isAllowed("running"))
}
@Test
fun `confirmation always explains result deletion`() {
assertTrue(CollectionResetPolicy.confirmationMessage("failed").contains("清除"))
assertTrue(CollectionResetPolicy.confirmationMessage("completed").contains("全部采集结果"))
}
}