From c37ba20c6c6397bd76b435ce186626611090141e Mon Sep 17 00:00:00 2001 From: ila Date: Fri, 7 Aug 2026 16:37:04 +0800 Subject: [PATCH] docs: import wiki at afc651f75a3a --- T-260.-.md | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 T-260.-.md diff --git a/T-260.-.md b/T-260.-.md new file mode 100644 index 0000000..fdd75a8 --- /dev/null +++ b/T-260.-.md @@ -0,0 +1,251 @@ + +> 同步来源:[`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= paceMax= browseTimeout= +``` + +沿用既有脱敏口径:只记数值,不记任何商品信息。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= paceMax= browseTimeout=`,只包含数值。 +- 新增/扩展单测,覆盖默认回归、边界与可修复错误、第 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`。