From e037a4e530ffc1be84eac182b8138fc78da50a63 Mon Sep 17 00:00:00 2001 From: ila Date: Fri, 7 Aug 2026 16:36:56 +0800 Subject: [PATCH] docs: import wiki at afc651f75a3a --- T-246.-.md | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 T-246.-.md diff --git a/T-246.-.md b/T-246.-.md new file mode 100644 index 0000000..be32929 --- /dev/null +++ b/T-246.-.md @@ -0,0 +1,157 @@ + +> 同步来源:[`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”,本地 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 应用前自动启动真实流程。