docs: import wiki at afc651f75a3a

ila
2026-08-07 16:36:56 +08:00
parent 798da820bf
commit e037a4e530
+157
@@ -0,0 +1,157 @@
<!-- docs-wiki-sync:docs/tasks/T-246.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
> 同步来源:[`docs/tasks/T-246.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-246.md) · commit `afc651f75a3a`
---
id: T-246
title: 自动接单与 goods_id 直达采购闭环
phase: 2
deps:
- T-218
- T-245
status: DONE
created: 2026-07-29
context_ref: 97b2a18
work_branch: null
write_paths:
- docs/tasks/T-246.md
- docs/current-state.md
- docs/api.md
- docs/04-architecture.md
- backend-api/migrations/**
- backend-api/internal/domain/**
- backend-api/internal/usecase/**
- backend-api/internal/repository/sqlite/**
- backend-api/internal/transport/httpapi/**
- backend-api/internal/transport/webui/**
- android-buyer/app/src/main/**
- android-buyer/app/src/test/**
---
## 需求 / 背景
低客单价采购不能依赖采购人员在手机上反复领取、开始、重新搜索和定位商品。Admin 创建
采购任务后,Roubao 应在登录、设备就绪且无其他有效任务时自动领取并开始图片搜索,将
最多 5 个候选的拼多多商品身份、链接、本地图片、标题、规格和价格保存到后台。采购员
只在 Admin 选择一次商品;之后 Roubao 自动进入该商品页、选择规格和数量、提交一笔
待付款订单并回传订单号与下单时间,付款仍由人员完成。
现有实现有四个缺口:
1. `IDLE` 只在点击“获取任务”后调用 claim,`CLAIMED` 仍需人工确认开始。
2. `RUNNING/WAITING_CONFIRMATION` 在授权过期后仍永久占用设备;App 的
`SAFE_STOPPED` 手动同步在真正请求后端前返回,后台取消无法完成。
3. 候选 observation 虽预留 `product_url/image_url`,Android 实际始终提交空字符串;
候选图片只有受控详情/规格证据。
4. 下单授权和设备命令没有 `goods_id` 或规范 URL,dry-run 会再次图片搜索,不能直接
进入 Admin 已确认的商品。
参考 Obsidian:
- `ila/项目文档/cmroubao/cmroubao-候选商品链接回传与goods_id方案.md`
- `Projetos/cmroubao/Regras/goodsid.md`
## 修订契约
### 自动调度
- App 登录且无本地任务、无后台有效任务、设备 readiness 通过时,自动 heartbeat 并
claim 最早的 `PENDING` 任务;无任务按有界退避轮询。
- claim 后完成参考图下载、SHA-256 校验和 2 秒稳定窗口,自动开始 execution;本地安全
VLM 配置可用时采用 `AI_ASSISTED`,否则使用确定性 `MANUAL_FIRST`,不能因模型配置
缺失阻塞采购。
- 自动流程同一时刻只允许一个 claim、execution、候选探针或下单动作;进程恢复复用原
幂等 key,不得重复领取、重复开始或重复提交订单。
- 当前任务进入后台确认、终态或安全失败后停止外部动作;终态清理完成后自动领取下一条。
- “获取任务”和“立即同步”保留为故障重试,不再是正常流程必需操作。
### 过期任务恢复
- `CLAIMED` 仍可按现有租约重新领取;`RUNNING/WAITING_CONFIRMATION` 授权过期后不得继续
自动化,也不得永久占用设备。
- 后台已请求取消且 claim 已过期时,后端在受控恢复事务中直接转 `CANCELED`、结束未完成
execution、清空 claim 所有权并记录审计事件。
- App 的 `SAFE_STOPPED` 手动/自动同步必须真正查询后台;后台任务已终止或不再属于当前
设备时清理本地加密任务并回到自动接单。
- 不把过期的执行中任务重新排入 `PENDING`,避免在未知外部状态下重复执行。
### 候选商品身份和图片
- 每个真实候选提取纯数字 `goods_id`,并生成唯一规范 URL:
`https://mobile.yangkeduo.com/goods.html?goods_id=<goods_id>`。
- 首选无障碍语义中的“商品编号/goods_id”,本地 OCR 仅作受控兜底;不能可靠提取时保持
空值、不得猜测。缺少 `goods_id` 的候选可以作为审计观察,但不能创建自动下单授权。
- 候选回传保存 `goods_id`、规范 URL、标题、当前选中规格摘要、颜色/尺码目标、组合价格、
采集时间、PDD 版本、卡片/详情指纹以及 DETAIL/SPECIFICATION 本地证据资产。
- Admin 展示本地鉴权图片并提供规范外链。外链使用新窗口及 `noopener/noreferrer`;
后端只执行严格语法/域名/路径/参数校验,绝不请求 PDD URL 或保存浏览器会话。
- `image_url` 只允许作辅助字段;主要候选图片继续由本地证据 asset 提供,外链失效不影响
审计和授权。
### 授权和直达下单
- `candidate_key` 仍是 Admin 选择主键;新身份版本同时绑定 execution、ordinal、
`goods_id`、详情指纹和规格证据 hash。
- Admin 授权快照、设备命令和 command SHA-256 必须不可变地包含 `goods_id`、规范 URL、
原始 SKU、数量、候选规格/价格及现有证据指纹;URL 或 ordinal 不能单独授权。
- App 收到授权后使用显式拼多多包名的 `ACTION_VIEW` 打开规范 URL。只能在确认进入
PRODUCT_DETAIL 且重新提取的 `goods_id` 与授权一致后继续。
- 直达失败可使用现有参考图搜索作一次受控兜底,但最终仍必须唯一复核 `goods_id`;
不一致、跳转、下架或提取失败立即停止。
- 颜色、尺码和数量仍按唯一可访问控件选择,每次动作后复读状态;价格、订单确认页商品
身份和总额必须通过现有 dry-run。
- 最终继续使用 T-218 submission fence,最多点击一次“提交订单”,只回读待付款订单,
不点击支付、不保存 PDD Cookie/Token。
## 数据与接口方案
1. 新迁移为 observation/identity/authorization 增加结构化 `goods_id` 和规范 URL;
历史记录以空值兼容,新授权只接受完整身份。
2. 候选请求 schema 增加 `goods_id`,`product_url` 改为严格 PDD 规范 URL;候选键升级
`cmroubao-candidate-v2`。
3. order authorization 与 device command schema 升级,command hash 覆盖新增字段。
4. Android 安全存储升级并兼容旧 schema;新字段在 ACK 前落盘。
5. Admin API/Web 展示 `goods_id`、外链和本地证据图;人工理由与 candidate key 流程不变。
6. App 自动调度使用进程内单协程、有界退避和现有 repository mutex;不引入 FCM、
WebSocket 或后台常驻推送服务。
## 验收要点
- [x] 新 Admin 任务在 App 已登录且 readiness 通过时无需点击即可 claim、start 并搜索。
- [x] 无任务轮询有界;单设备/并发恢复不重复 claim、start、探针或下单。
- [x] 过期 `WAITING_CONFIRMATION` 加后台取消可原子终止并释放设备,App 清理旧任务后继续。
- [x] Top 5 每项保存标题、SKU、价格、两张本地证据;可读项同时保存纯数字 `goods_id`
和唯一规范 PDD URL。
- [x] 非规范域名、路径、额外参数、非数字 ID 和伪造 URL 被拒绝;后端不 fetch 外链。
- [x] Admin 可查看本地图片并安全打开候选商品;无 `goods_id` 候选不能授权自动下单。
- [x] 授权和设备 command hash 覆盖 `goods_id`/URL,网络重放仍返回同一不可变命令。
- [x] Roubao 优先直达商品页并复核相同 `goods_id`,再唯一选择颜色、尺码和数量。
- [x] 直达失败或 ID 未能复核时安全停止,不下单。
- [x] 一次性 submission fence、待付款回读和禁止支付边界不回归。
- [x] 上下迁移、Go 标准/race/vet/入口构建和 Android 单测/Debug/Release 构建通过。
## 边界
- 不承诺始终返回 5 个候选;只能返回实际曝光且证据完整的 `0..5` 项,禁止凑数。
- 不使用分享短链、推广参数、剪贴板账号信息或服务端 PDD 抓取。
- 不把 `product_url` 当作商品身份,不绕过 candidate key、Admin review 或授权版本。
- 不因 VLM 输出直接点击规格;VLM 只辅助语义映射,动作仍需确定性唯一匹配和复读。
- 不自动付款,不在订单已提交状态不确定时重复点击。
- Admin 工作站浏览器登录 PDD 是外部运营前提,本系统不保存其凭证。
## 执行记录
- 2026-07-29:读取 Obsidian 方案并复核当前代码。确认 observation 已有 URL 字段但 Android
两条候选构造路径均未赋值,Admin 只渲染本地证据;授权/设备命令没有商品 ID/URL,
dry-run 使用参考图重新搜索。确认需要后端契约和迁移,不能按旧结论“后端零改动”实施。
- 2026-07-29:现场数据库确认旧 `WAITING_CONFIRMATION` claim 已过期且继续占用设备;
新任务保持 `PENDING`。App `SAFE_STOPPED` 同步提前返回,后台取消不能被确认,构成自动
接单前必须修复的恢复死锁。
- 2026-07-29:新增 v18 候选商品/授权商品快照,候选键升级为 v2;Admin 只允许授权
完整 `goods_id` 候选,schema v2 设备命令和哈希绑定规范 URL。App 增加语义+本地 OCR
ID 提取、自动 claim/start/VLM 或确定性候选链路,以及显式 PDD `ACTION_VIEW` 直达。
- 2026-07-29:后端 heartbeat 已原子恢复“后台请求取消且租约过期”的旧执行,App
`AUTHORIZATION_EXPIRED` 会真实 heartbeat 并清理服务端已结束任务。单测覆盖恢复后
同设备继续领取下一任务。
- 2026-07-29:`go test ./...`、`go test -race ./...`、`go vet ./...` 和三个 Go 入口
构建通过;Android `testDebugUnitTest`、Debug/Release 构建通过。Debug APK 已通过
`adb install -r` 安装到 PKG110,保留本地数据;未在迁移 v18 应用前自动启动真实流程。