Table of Contents
同步来源:
docs/tasks/T-260.md· commitafc651f75a3a
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 需要能判断某次任务是用什么节奏 跑的。
七、安全边界一律不变
验证码、登录失效、风控提示、未知页面、订单确认页和支付边界的安全停止行为完全不 改。本任务只延长自身动作之间的间隔,不改变任何停止条件。
方案
SettingsManager新增停顿下限、上限和三轮browseTimeoutMillis配置及校验。SettingsScreen增加编辑入口,非法组合拒绝保存并给出具体原因。AndroidPinduoduoCandidateDriver、AndroidPinduoduoImageSearchDriver改为按配置 构造PinduoduoActionPacer。pacedAction改为成功与失败均停顿。PinduoduoImageCandidateRoundPolicy改为接受配置值,默认值不变。- 新增可行性校验与授权窗口占用校验,估算系数集中为常量。
- 生效配置并入
CandidateSearchDiagnostics,追加三个键。 - 单测覆盖:默认值行为与本任务前一致、边界值接受与拒绝、
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。