docs: import wiki at afc651f75a3a

ila
2026-08-07 16:37:04 +08:00
parent b1ab455686
commit c37ba20c6c
+251
@@ -0,0 +1,251 @@
<!-- docs-wiki-sync:docs/tasks/T-260.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
> 同步来源:[`docs/tasks/T-260.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/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/**
---
## 问题 / 背景
采购人员反馈:因下单数量多、操作速度快,拼多多账号已被禁用。需要把自动化的操作节奏
放慢,并允许按实际情况调整。
当前节奏写死在代码里:
```kotlin
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 会让结果更差,必须与轮次
预算联动调整。**
### 失败动作完全不停顿
```kotlin
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% 校验、诊断键格式、
后端下发字段被忽略。
## 验收要点
- [x] 未修改设置时,停顿区间与轮次预算与本任务前逐值一致,有回归测试。
- [x] 下限低于 500 毫秒、上限高于 30 秒、`min > max` 均被拒绝并给出可修复提示。
- [x] 失败动作同样触发停顿,有单测证明。
- [x] 配置组合导致第 1 轮跑不完 1 个候选时拒绝保存。
- [x] 三轮预算之和加余量超过执行授权窗口 60% 时拒绝保存。
- [x] 诊断 message 出现 `paceMin` / `paceMax` / `browseTimeout` 且不含商品信息。
- [x] 后端 payload 中的节奏字段被忽略,不覆盖本地设置,有单测证明。
- [x] 验证码、风控、登录失效、未知页面和支付边界的安全停止行为未改变,有回归测试。
- [x] 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):
```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`。
```powershell
.\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 构建成功。
根目录只读/收尾命令:
```powershell
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`。