Files
cmautobuy/docs/task/32-client-执行采集任务并提交-admin.md
T
chengmaandClaude Opus 5 4022feee43 docs: 记录 #31 #32 验收通过
两份归档都由实现方写好,本次只更新状态。

#31 遵守了 #20 立的「迁移只追加」规则:v4 用 ALTER TABLE 加 shop_name,
v1/v2 原文未动,CheckSchema 也覆盖了新列。

#32 的 skus 是颜色×尺码的笛卡尔积并带 price_observed_at,
与 #31 的 price_granularity 对得上——两侧独立得出同一结论。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:19:11 +08:00

91 lines
5.1 KiB
Markdown
Raw Blame History

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.
# 32 Client 执行采集任务并提交 Admin
- 类型:需求(自动化执行、跨模块联调)
- 父级大工单:#1
- 所属 MVP / 版本:#2 / MVP
- 状态:验收通过
- 日期:2026-08-07
- Gitea 工单:http://ilaer.eicp.net:8418/chengma/cmautobuy/issues/32
## 背景与目标
#30 已能领取采集任务,但任务只停留在本地 `claimed`。本任务接通单条只读采集闭环:本地任务优先、操作当前 Android 设备采集商品、结果先写 SQLite 和 Outbox、最后幂等提交 Admin。
本任务只浏览商品页、展开规格和选择颜色,不包含加购、下单或付款。
## 最终方案
- 新增 `CollectTaskService`,一次点击只处理一条工作:先补交 Outbox,再执行最早的本地 `claimed/retry_wait`,最后才领取新任务。
- 采集仍复用并补全现有 `PddCollectService`,没有再拆一个重复的页面解析模块。
- 任务执行继续使用 `QObject + moveToThread`;Worker 不访问 QWidget,结果通过信号回到主线程。
- 启动时把采集任务残留的 `running` 恢复为 `retry_wait`,把 Outbox 的 `sending` 恢复为 `pending`。
- 采集成功时在同一事务保存 `pdd_data`、运行记录和完整 Outbox 请求;只有 Admin 返回 `accepted: true` 才改为 `succeeded`。
- Admin 暂时不可用时保留 `result_pending` 和原幂等键,下次只重发已保存请求,不重新操作手机。
- HTTP Gateway 实现 `/result` 和 `/failure`,携带 `X-Client-Id`、`X-Request-Id` 和 `Idempotency-Key`。
- `pdd_data` 使用 Admin 已确认的顶层结构,`price_granularity` 固定为 `color`。
- 每个可用颜色只点击一次读取实付价和划线价;尺码只读取、不点击,再展开颜色×尺码笛卡尔积。
- 每条 SKU 保存 `price_observed_at`,说明该价格实际在哪个已选组合下读取。
- 标题按真实控件 bounds 拼接;规格名优先使用 `content-desc`,仍截断时整单失败。
- 店铺名允许为空;运行时把相关无障碍 XML 写到 `data/artifacts/`,结果只保存本地路径和 SHA-256,不上传文件。
- 整单采集上限为 10 分钟;登录、验证码、截断数据、设备断开和超时都有稳定错误代码。
## 改了哪些
- `client/src/collect_task_service.py`:单任务领取、采集、Outbox 和提交编排。
- `client/src/pdd_collect_service.py`:真实标题、规格、颜色价格、划线价、总超时、诊断文件和断线处理。
- `client/src/task_repository.py` / `task_models.py`:运行记录、结果与失败事务、Outbox 状态和启动恢复。
- `client/src/http_admin_gateway.py`:实现结果与失败 HTTP 提交。
- `client/src/pdd_ui_event.py`:把“获取任务”接到后台采集闭环并反馈状态。
- `client/image_xml/737116531267_*.xml`:真机导出的回归固件。
- `client/test/`:增加采集编排、Outbox、HTTP 提交和真实 XML 测试。
- `docs/client/02`、`04`、`05`:同步当前架构、接口和单任务界面状态。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 真实 XML 拼出完整标题 | 通过 |
| 真实规格 XML 枚举 8 个完整颜色名 | 通过 |
| 实付价 470 分、划线价 1990 分 | 通过 |
| 颜色逐个采价,尺码只读,输出完整笛卡尔积 | 通过(自动测试) |
| 每条 SKU 有 `price_observed_at` | 通过 |
| `price_granularity = color` | 通过 |
| 结果与 Outbox 同事务,成功确认后才完成 | 通过 |
| Admin 超时只重发、不重采 | 通过 |
| HTTP 提交带 Client ID 和幂等键 | 通过 |
| 10 分钟总超时及稳定失败代码 | 通过 |
| 工作线程不访问 QWidget | 通过 |
| 不含加购、下单、付款代码 | 通过 |
| 真机完整采集并提交真实 Admin | 未通过:见测试说明 |
## 测试
执行命令:
```powershell
# client 目录
C:/Python310/python.exe -m unittest discover -s test -p 'test_*.py' -q
# 界面离屏冒烟
$env:QT_QPA_PLATFORM='offscreen'
C:/Python310/python.exe -c "from src.ui_main import MainWindow; from PyQt5.QtWidgets import QApplication; app=QApplication([]); w=MainWindow(); print('OK'); w.close(); app.quit()"
# admin 目录
go test ./...
```
- Client 全量 143 项测试通过。
- Admin 全量 Go 测试通过。
- 界面离屏启动和关闭通过,输出 `OK`。
- 真机只读验证使用 USB 设备 `R28M30T2JWE`,在进入规格面板前设备从 ADB 列表消失,uiautomator2 返回 `device not found`。验证过程中没有执行加购、下单或付款;代码随后补充并测试了 `DEVICE_DISCONNECTED` 分类。
- **没验证到的部分**:尚未完成真机的全部颜色横向扫描、屏外尺码纵向扫描以及向真实 Admin 提交后的页面状态和规格表验收;原因是设备断开且本机 Admin 当时未运行。Windows 高对比度和 200% 缩放也未手工验证。
## 遗留问题
- 重新连接稳定的 Android 设备并启动 Admin 后,需要按工单的真机联调步骤完成最终验收。
- 当前底部状态显示整条任务的启动、完成或错误,尚未细分显示“第几个颜色”等逐步进度;不影响数据链路。
## 相关提交
- `2d1fdbe` `feat: 执行并提交 PDD 采集任务 (#32)`