1
T-260
ila edited this page 2026-08-07 16:37:04 +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-260.md · commit afc651f75a3a


id: T-260 title: 可配置操作节奏与失败停顿并联动轮次预算 phase: 2 deps:

  • T-259 status: DOING created: 2026-07-31 context_ref: 21dc617 work_branch: null write_paths:
  • docs/tasks/T-260.md
  • docs/current-state.md
  • android-buyer/app/src/main/**
  • android-buyer/app/src/test/**

问题 / 背景

采购人员反馈:因下单数量多、操作速度快,拼多多账号已被禁用。需要把自动化的操作节奏 放慢,并允许按实际情况调整。

当前节奏写死在代码里:

class PinduoduoActionPacer(
    private val minimumMillis: Long = DEFAULT_MINIMUM_MILLIS,   // 1_000
    private val maximumMillis: Long = DEFAULT_MAXIMUM_MILLIS,   // 3_000
    ...
)

构造参数本就存在,但两个驱动(AndroidPinduoduoCandidateDriver、 AndroidPinduoduoImageSearchDriver)都以 PinduoduoActionPacer() 硬编码默认值实例化。

三个时间常量互相耦合,不能单独调

常量 当前值
单次成功动作后停顿 1–3 秒随机
单轮候选浏览超时 browseTimeoutMillis 100 秒(第 1、2 轮)/ 45 秒(第 3 轮)
离线执行授权 30 分钟(服务端下发)

处理一个候选约需 6 次受限速动作(打开候选、开规格、选颜色、选尺码、关规格、返回)。 当前均值 2 秒即约 12 秒/候选,100 秒预算实际只够 2 个候选——这与真机实测 attempts=2 完全吻合:不是没有候选可试,是时间不够。

若只把停顿改成 5–10 秒(均值 7.5 秒),单候选升至约 45 秒,100 秒预算连 1 个候选都 未必跑得完,三轮下来可能一个都采不到。单独放慢 pacer 会让结果更差,必须与轮次 预算联动调整。

失败动作完全不停顿

private suspend fun pacedAction(action: suspend () -> Boolean): Boolean {
    val succeeded = action()
    if (succeeded) { actionPacer.pause() }   // 失败时直接返回,无停顿
    return succeeded
}

动作失败时以最快速度连续重试。从行为特征看,这比「成功动作间隔短」更像机器人—— 正常人点不动会停顿或放弃。这是现存缺陷,本任务一并修复。

明确的能力边界

本任务不是绕过风控。 遇到验证码、登录失效、风控提示时的安全停止行为一律不变, 仍然立即停止并上报。这里只调整自身发起动作的密度。

同时如实记录:放慢节奏未必能解决封号问题。平台风控通常综合订单量、支付方式、 地址复用、账号年龄和设备指纹等信号,操作间隔只是其中权重可能较低的一项。本任务提供 可调节能力,不承诺规避账号风险。降低单位时间订单总量属于运营决策,不在本任务范围。

关联需求与交互

  • 功能:F-005、F-009
  • 用户故事:US-004、US-009
  • 交互:Roubao 设置页新增节奏配置项,沿用现有 SettingsManager 与 SettingsScreen
  • 架构/API:后端零改动;执行节奏属设备本地自治配置

行为契约

一、节奏可配置且设备本地自治

在现有 SettingsManager 中新增操作停顿区间配置(下限毫秒、上限毫秒),由 SettingsScreen 提供编辑入口。两个驱动改为读取该配置构造 PinduoduoActionPacer。

必须是设备本地设置。 依据 F-009 第 2 条「后台任务也不能覆盖 App 的本地设置」, 后端不得下发、代理或覆盖执行节奏;任务 payload 中出现相关字段一律忽略。

默认值保持 1000 / 3000 不变,未修改设置的设备行为与本任务前完全一致。

二、取值校验

约束 值
下限最小值 500 毫秒(防止设为 0 等同关闭限速)
上限最大值 30_000 毫秒(防止单个动作拖垮整轮)
不变式 min <= max(PinduoduoActionPacer.init 已有 require,不得移除)

非法输入在设置页拒绝保存并给出可修复提示,不得静默截断或回退到默认值。

三、失败动作同样停顿

pacedAction 改为无论成功失败都停顿,使用同一区间。

不在本任务实现指数退避——退避会改变重试语义并影响既有超时预算,如需另开任务。

四、轮次预算联动

browseTimeoutMillis 改为可配置(沿用 PinduoduoImageCandidateRoundPolicy 的分轮 结构),默认值保持 100_000 / 45_000 不变。

必须实现可行性校验:保存设置时按「单候选约 6 次受限速动作」估算,若配置组合导致 第 1 轮预算连 1 个候选都无法完成,拒绝保存并提示。估算系数以常量形式集中定义,便于 后续按实测调整。

五、不得突破离线执行授权

离线执行授权 30 分钟由服务端下发,本任务不修改,也不请求延长。

保存设置时必须校验最坏情况总时长(三轮预算之和加固定余量)不超过执行授权窗口的 60%,超出则拒绝保存并提示。这样可以避免放慢节奏后任务在上传结果前就撞上授权 到期——该故障刚由 T-259 修复过,不应因配置不当重新引入。

六、可审计

本次执行实际生效的停顿区间与轮次预算,必须随 T-254 的诊断 message 机制记录,键名:

paceMin=<n> paceMax=<n> browseTimeout=<n>

沿用既有脱敏口径:只记数值,不记任何商品信息。Admin 需要能判断某次任务是用什么节奏 跑的。

七、安全边界一律不变

验证码、登录失效、风控提示、未知页面、订单确认页和支付边界的安全停止行为完全不 改。本任务只延长自身动作之间的间隔,不改变任何停止条件。

方案

  1. SettingsManager 新增停顿下限、上限和三轮 browseTimeoutMillis 配置及校验。
  2. SettingsScreen 增加编辑入口,非法组合拒绝保存并给出具体原因。
  3. AndroidPinduoduoCandidateDriver、AndroidPinduoduoImageSearchDriver 改为按配置 构造 PinduoduoActionPacer。
  4. pacedAction 改为成功与失败均停顿。
  5. PinduoduoImageCandidateRoundPolicy 改为接受配置值,默认值不变。
  6. 新增可行性校验与授权窗口占用校验,估算系数集中为常量。
  7. 生效配置并入 CandidateSearchDiagnostics,追加三个键。
  8. 单测覆盖:默认值行为与本任务前一致、边界值接受与拒绝、min > max 拒绝、 失败动作也停顿、可行性校验拒绝过慢组合、授权窗口 60% 校验、诊断键格式、 后端下发字段被忽略。

验收要点

  • 未修改设置时,停顿区间与轮次预算与本任务前逐值一致,有回归测试。
  • 下限低于 500 毫秒、上限高于 30 秒、min > max 均被拒绝并给出可修复提示。
  • 失败动作同样触发停顿,有单测证明。
  • 配置组合导致第 1 轮跑不完 1 个候选时拒绝保存。
  • 三轮预算之和加余量超过执行授权窗口 60% 时拒绝保存。
  • 诊断 message 出现 paceMin / paceMax / browseTimeout 且不含商品信息。
  • 后端 payload 中的节奏字段被忽略,不覆盖本地设置,有单测证明。
  • 验证码、风控、登录失效、未知页面和支付边界的安全停止行为未改变,有回归测试。
  • Android Debug/Release 单测、lintDebug、assembleDebug、assembleRelease 全部通过。
  • 真机验证:把停顿调到较慢档位跑一条任务,从 Admin 读到实际生效的三个键,并确认 任务在授权窗口内完成上传。

边界

  • 不绕过风控:验证码、登录失效、风控提示、未知页面的安全停止一律不改。
  • 不修改离线执行授权时长,不请求续期,不改后端任何代码或 API 契约。
  • 不实现指数退避或自适应节奏。
  • 不改候选卡片识别、签名算法、跨轮去重、SKU 硬约束或价格解析。
  • 不改 T-254/T-255/T-257 诊断字段语义、T-256 提交门槛、T-258 后端校验、 T-259 安全停止补报。
  • 不改下单 dry-run、订单提交、授权价格策略或支付边界。
  • 不新增第四轮搜索,不改三轮门禁。
  • 不承诺本任务能规避账号封禁;降低单位时间订单总量属运营决策,不在范围内。

执行记录

2026-07-31

状态保持 DOING。代码和自动化验证已完成,最终真机验收未执行,不能标记 DONE。

修改:

  • SettingsManager 新增设备本地 PinduoduoPacingSettings,持久化停顿下限、上限和 三轮候选浏览超时。默认值保持 1000 / 3000 / 100000 / 100000 / 45000。
  • 设置页沿用现有 Compose 设置项和对话框,新增五个有标签的毫秒整数输入;空值、 非数字、范围或组合错误均留在对话框内显示可修复提示,保存失败不关闭对话框。
  • 校验系数集中为命名常量:每候选 6 次受限速动作、每轮 50 秒图片搜索固定步骤预算、 三轮、120 秒执行余量、30 分钟授权参考窗口和 60% 上限。第 1 轮不足一个候选的最坏 动作时长时拒绝;三轮浏览超时、三轮固定步骤预算和余量合计超过 60% 时拒绝。
  • MainActivity 在每轮开始前只从 SettingsManager 捕获本地快照,以该快照构造两个 driver 的 PinduoduoActionPacer,并把当前轮超时传给既有 round policy。任务 payload 没有配置入口,额外 pacing 字段由客户端任务解析忽略。
  • 两个 driver 的布尔动作统一为成功或失败都暂停一次;未增加指数退避、自适应节奏、 新重试或续期逻辑。
  • CandidateSearchDiagnostics 在实际自动轮次 message 末尾追加 paceMin=<n> paceMax=<n> browseTimeout=<n>,只包含数值。
  • 新增/扩展单测,覆盖默认回归、边界与可修复错误、第 1 轮单候选可行性、三轮 60% 上限及等号边界、失败动作停顿、自定义 round timeout、诊断格式和后台 payload pacing 字段忽略。既有验证码、登录失效、风控、未知页、订单确认和支付边界测试均 随 Debug/Release 全套单测通过;相关分类器、停止条件和授权时长未修改。
  • UI 实现参考 ui-ux-pro-max 的移动表单建议,保留 Material 原生控件、可见标签、 就地错误与现有主题;没有引入新视觉系统。

命令与结果(工作目录 D:\chengma\cmroubao\android-buyer,Windows PowerShell):

.\gradlew.bat testDebugUnitTest --tests 'com.roubao.autopilot.data.PinduoduoPacingSettingsTest' --tests 'com.roubao.autopilot.pinduoduo.PinduoduoActionPacerTest' --tests 'com.roubao.autopilot.pinduoduo.PinduoduoImageCandidateRoundPolicyTest' --tests 'com.roubao.autopilot.procurement.CandidateSearchDiagnosticsTest' --tests 'com.roubao.autopilot.procurement.ProcurementApiClientTest'

首次运行:失败,26 项中 1 项失败;新诊断测试错误地把既有固定键 skuMismatch 当成商品信息。修正断言为拒绝商品文字、goods_id 和 URL 后以同一 命令重跑:通过,BUILD SUCCESSFUL。

.\gradlew.bat testDebugUnitTest
.\gradlew.bat testReleaseUnitTest
.\gradlew.bat lintDebug
.\gradlew.bat assembleDebug
.\gradlew.bat assembleRelease

结果:五条均 BUILD SUCCESSFUL。Debug/Release 各 272 项单测,0 failure、0 error、 0 skipped;lint 报告生成成功;Debug/Release APK 构建成功。

根目录只读/收尾命令:

git diff --check
git status --short

结果:git diff --check 通过;修改仅位于本任务 write_paths。开工前已有的 .playwright-mcp/、本地 JSON、PNG 和账号文本等未跟踪文件未读取、修改或删除。 所有本任务修改/新增文件已检查为 LF。未运行 init.ps1,未运行任何 Go/backend 命令,未修改 backend-api,未 commit、未 push。

Blocker:

  • 仍需采购人员在测试设备把节奏调到较慢档位跑一条任务,从 Admin 验证三个诊断键 与实际值一致,并确认任务在授权窗口内完成上传。该真机证据缺失,因此任务保持 DOING。