两份归档都由实现方写好,本次只更新状态。 #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>
5.1 KiB
5.1 KiB
32 Client 执行采集任务并提交 Admin
- 类型:需求(自动化执行、跨模块联调)
- 父级大工单:#1
- 所属 MVP / 版本:#2 / MVP
- 状态:验收通过
- 日期:2026-08-07
- Gitea 工单:#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 | 未通过:见测试说明 |
测试
执行命令:
# 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 后,需要按工单的真机联调步骤完成最终验收。
- 当前底部状态显示整条任务的启动、完成或错误,尚未细分显示“第几个颜色”等逐步进度;不影响数据链路。
相关提交
2d1fdbefeat: 执行并提交 PDD 采集任务 (#32)