1
T-270
ila edited this page 2026-08-07 16:37:10 +08:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

同步来源:docs/tasks/T-270.md · commit afc651f75a3a


id: T-270 title: 任务阶段结束后一次性切回 Roubao phase: 2 deps:

  • T-207 status: DOING created: 2026-08-01 context_ref: 243874a work_branch: null write_paths:
  • docs/tasks/T-270.md
  • docs/current-state.md
  • android-buyer/app/src/main/**
  • android-buyer/app/src/test/**

问题 / 背景

Roubao 把拼多多置于前台执行图片搜索、规格匹配或下单核对后,阶段结果虽然已经保存并 准备回传 Admin,手机仍停留在拼多多。采购人员无法直观看到任务已经进入等待确认、失败、 安全停止或待付款状态,容易误以为自动化仍在运行,也会增加手工切换 App 的操作成本。

本任务只处理任务阶段结束后的前台归位。状态持久化、候选/事件 outbox、Admin 回传和 支付边界仍是事实来源;切换失败不能改写业务结果,也不能诱发重复搜索或重复下单。

关联需求与交互

  • 功能:F-005、F-006、F-007
  • 用户故事:US-004、US-006
  • 交互:复用 Android 采购任务页,不新增采购步骤
  • 架构/API:Android 本地导航与通知;后端 API、数据库和任务状态机零改动

行为契约

1. 先保存结果,再请求切回

只有本地执行状态或 terminal outbox 已经可靠写入后,才允许请求把 Roubao 放到前台。 切回属于展示副作用,不参与候选成功、执行失败、安全停止或订单核对的事务判断。

允许触发的阶段:

  • WAITING_ADMIN_CONFIRMATION:本轮候选采集已经封板,等待 Admin 人工确认;
  • pending terminal FAIL:失败结果已进入 outbox;
  • AUTHORIZATION_EXPIRED:取消、授权到期或安全停止已持久化;
  • ORDER_SUBMISSION_MANUAL_REVIEW:订单结果无法自动确认,需要人工核对;
  • ORDER_RECONCILED:拼多多订单号和下单时间已经保存并准备/完成回传,等待付款。

以下情况不得触发:单条候选增量回传、第一/二/三轮之间、证据仍在保存、普通 RUNNING、 ORDER_DRY_RUN_RUNNING、ORDER_SUBMISSION_RECONCILING、空闲同步或页面恢复步骤。

2. 每个执行阶段只切回一次

幂等键为 executionId + terminalPhase。设备本地持久化最近的幂等键;同一状态重复发布、 Service 重启、Activity/Service 同时观察到状态时只能有一个调用取得执行权。不同阶段可以 各触发一次,例如先在候选封板时返回,Admin 授权下单后又在订单核对完成时返回。

持久化中只保存幂等键摘要,不记录页面文本、商品信息、token 或设备密钥,并对历史键设置 固定上限,避免无限增长。

3. 使用显式 Intent 返回采购任务页

  • 只启动本应用 MainActivity,使用显式 Intent 和 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP | FLAG_ACTIVITY_REORDER_TO_FRONT;
  • MainActivity 的既有 singleTask 保持不变,在 onNewIntent() 消费内部 action 并切到 采购任务页;不重建登录表单、不清空已填数据;
  • 不使用 Home/最近任务坐标、Shizuku 或无障碍去点击系统桌面;
  • 不携带候选标题、SKU、价格、图片、共享链接或其他业务数据到 Intent。

4. Android 后台启动限制的降级

Android 可能根据系统版本、厂商策略或用户设置拒绝后台 Activity 启动。因此触发时同步发布 一条非持续、可点击的任务结果通知,点击后使用同一个显式 Intent 进入采购任务页。后台拉起 失败不重试抢前台,不标记任务失败,不改变 outbox;通知文案仅包含本应用自有的阶段说明。

5. 安全边界

  • 返回 Roubao 后不得继续触发拼多多动作;后续只允许既有 outbox 后台同步;
  • 不放宽登录、验证码、风控、结算、提交订单或支付安全停止;
  • ORDER_RECONCILED 仍只提醒采购人员去拼多多确认和付款,Roubao 不点击支付;
  • App 不在前台、通知权限关闭或系统拦截启动时,业务结果仍必须保持已保存状态。

方案

  1. 在采购状态模型中派生不含敏感数据的 ForegroundReturnTarget,统一声明允许返回的阶段。
  2. 用 SharedPreferences 保存有限数量的幂等键摘要,并用进程内同步保证 Activity 和 Service 竞争时只有一个观察者取得该键。
  3. 增加前台返回协调器:先 claim 幂等键,再发布完成通知并发送显式 Intent。
  4. ProcurementExecutionService 观察状态,保证 Activity 已被系统回收或不活跃时仍能处理; MainActivity 同时观察以缩短在拼多多前台执行完成后的响应时间,二者由同一幂等门保护。
  5. MainActivity.onNewIntent() 只处理应用内部 action,并把 Compose 导航切到采购任务页。

验收要点

  • 候选封板并持久化后,Roubao 最多请求前台一次并显示采购任务页。
  • 同一 execution、同一阶段的重复状态发布或 Service 重启不会重复抢前台。
  • 同一 execution 从等待候选确认进入订单核对完成时,两个不同阶段各允许返回一次。
  • pending terminal FAIL、授权到期/安全停止、人工核对、订单已核对均有明确返回目标。
  • 中间轮次、单条候选、普通运行和下单核对过程中不会返回 Roubao。
  • 系统不允许后台启动时,可通过结果通知进入同一采购任务页;业务状态不受影响。
  • Intent、通知和日志不含任务商品数据、页面原文或认证秘密。
  • Android Debug/Release 单测、lint 和 APK 构建通过。
  • 真机让拼多多停留前台,分别验证候选封板与订单核对完成后的返回行为;若系统拦截, 验证通知降级且不重复启动。

边界

  • 不修改 Admin、Go API、数据库 migration、候选匹配、三轮搜索预算或下单自动化。
  • 不承诺绕过 Android/厂商对后台 Activity 启动的系统限制。
  • 不新增自动支付、免密支付或绕过登录/验证码/风控的能力。
  • 本任务不关闭 T-265/T-268/T-269 的独立真机验收项。

执行记录

  • 2026-08-01:根据阶段完成后手机仍停在拼多多的问题创建任务,状态 TODO;先提交行为 契约,尚未修改生产代码。
  • 2026-08-01:领取任务,状态改为 DOING,开始 Android 实现。
  • 2026-08-01:新增 ProcurementForegroundReturnPolicy,只从已持久化的候选等待确认、 pending FAIL、授权结束、人工对账和订单已核对状态派生返回目标;普通运行、各轮搜索、 dry-run 和提交对账中间状态均返回 null。ProcurementUiState 只暴露 execution ID 与自有 阶段枚举,不带商品和页面数据。
  • 2026-08-01:新增进程共享的 ProcurementForegroundReturnCoordinator。先以 SHA-256(executionId + phase) 同步提交最多 32 条本地 receipt,再发布可点击的采购结果 通知并发送显式 NEW_TASK | SINGLE_TOP | REORDER_TO_FRONT Intent;Activity 与前台 Service 同时观察同一状态,但共享 gate 保证只消费一次。MainActivity.onNewIntent() 只切换到既有采购任务页,未重建登录输入;Service 在同步前后也检查目标,避免 STOP 时 来不及返回。
  • 修改文件:App.kt、MainActivity.kt、ProcurementExecutionService.kt、 ProcurementForegroundReturn.kt、ProcurementModels.kt、ProcurementRepository.kt、 ProcurementForegroundReturnTest.kt、本任务和 docs/current-state.md。
  • 自动化验证:首次运行 .\gradlew.bat :app:compileDebugKotlin 因当前 shell 未设置 ANDROID_HOME,在依赖解析前失败;设置 $env:ANDROID_HOME="$env:LOCALAPPDATA\Android\Sdk" 和同值 ANDROID_SDK_ROOT 后, :app:compileDebugKotlin 成功。
  • 自动化验证:运行 .\gradlew.bat :app:testDebugUnitTest :app:testReleaseUnitTest :app:lintDebug :app:assembleDebug :app:assembleRelease 成功;Debug/Release 各 417 项单测,0 failure、 0 error,lint 成功,app-debug.apk 与 app-release-unsigned.apk 均生成。
  • 仓库验证:在项目根运行 .\init.ps1 成功;Android 全量测试和 Debug APK 构建通过, Go 后端所有 package 测试通过。
  • 真机 smoke:设备 PKG110,Android 16 / SDK 36,Roubao 1.4.18 (23),拼多多 8.17.0。 通过 adb install -r ...\app-debug.apk 覆盖安装成功;用包管理器解析并显式启动拼多多后, topResumedActivity 为 com.xunmeng.pinduoduo/.activity.NewPageActivity;再发送 com.roubao.autopilot.action.SHOW_PROCUREMENT 到 .MainActivity,前台变为 com.roubao.autopilot/.MainActivity。该 smoke 未操作商品、订单或支付。
  • 未验证范围:尚未用一条真实任务让候选封板/订单核对状态自动触发返回,也未在系统实际 拦截后台 Activity 启动的场景验证通知点击降级。因此状态保持 DOING,不改为 DONE。