From 51c2292be1433e323cf4f9cde062e565f25eae9e Mon Sep 17 00:00:00 2001 From: chengma Date: Fri, 7 Aug 2026 17:39:16 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=BD=92=E6=A1=A3=E4=BB=BB=E5=8A=A1=20?= =?UTF-8?q?#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../32-client-执行采集任务并提交-admin.md | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 docs/task/32-client-执行采集任务并提交-admin.md diff --git a/docs/task/32-client-执行采集任务并提交-admin.md b/docs/task/32-client-执行采集任务并提交-admin.md new file mode 100644 index 0000000..e233847 --- /dev/null +++ b/docs/task/32-client-执行采集任务并提交-admin.md @@ -0,0 +1,90 @@ +# 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)`