docs(architecture): adopt authorized single-pass purchase
This commit is contained in:
+241
-359
@@ -1,482 +1,364 @@
|
||||
# 架构设计
|
||||
|
||||
> 本文讲「怎么把技术栈搭起来」:系统结构、职责划分、数据模型、技术难点、开发顺序。
|
||||
> 具体用了哪些框架 / 库 / 数据库 / 部署方式,见 [技术栈](03-tech-stack.md)。
|
||||
> 本文定义系统结构、职责、单趟采购流程、安全边界和数据模型。框架与运行命令见
|
||||
> [技术栈](03-tech-stack.md),双端线协议以 [API 合约](api.md)为唯一权威。
|
||||
|
||||
## 一、系统结构
|
||||
|
||||
```text
|
||||
第三方 ERP(顺运宝) Excel 表格 人工填链接
|
||||
│ │ │
|
||||
└────────────┬───────────┴────────────────────┘
|
||||
v
|
||||
人工填链接(MVP) Excel / ERP(V2)
|
||||
│ │
|
||||
└──────────────┬───────────────┘
|
||||
v
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 采购服务(admin/,Go 单二进制) │
|
||||
│ · 建单、批量开始试选与任务生命周期 │
|
||||
│ · 候选确认与下单授权(唯一决策权威) │
|
||||
│ · 证据存储与审计 │
|
||||
│ · 管理 Web(服务端渲染) │
|
||||
└─────────────────────────────────────────┘
|
||||
^ HTTP / JSON
|
||||
│ Bearer Token + 设备绑定
|
||||
v
|
||||
│ 采购服务(admin/,Go) │
|
||||
│ · 建单、查询、开始采购授权与任务状态 │
|
||||
│ · 提交围栏、结果调和、内部证据与审计 │
|
||||
│ · 服务端渲染管理页面 │
|
||||
└───────────────────┬─────────────────────┘
|
||||
│ HTTPS / JSON
|
||||
│ Bearer + 设备绑定
|
||||
v
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 采购工具(client/,Python + PySide6) │
|
||||
│ · 领任务、跑流程、回传结果 │
|
||||
│ · 本地执行轨迹与证据落盘 │
|
||||
│ · AI 辅助调用(P1) │
|
||||
└─────────────────────────────────────────┘
|
||||
│ ADB(USB / WiFi)
|
||||
v
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Android 手机(拼多多 App) │
|
||||
└─────────────────────────────────────────┘
|
||||
│ 采购工具(client/,Python + PySide6) │
|
||||
│ · 轮询领取、单趟执行、回传状态与证据 │
|
||||
│ · 本地完整节点树与执行轨迹 │
|
||||
└───────────────────┬─────────────────────┘
|
||||
│ ADB(USB / WiFi)
|
||||
v
|
||||
Android 手机(拼多多 App)
|
||||
```
|
||||
|
||||
组件落位:
|
||||
|
||||
- 采购服务:Go + gin,入口 `admin/cmd/server/main.go`,模板
|
||||
`admin/internal/transport/webui/templates/`
|
||||
- 采购工具:Python,入口 `client/src/main.py`,真机流程 `client/src/android/pdd_flow.py`
|
||||
- 数据库:SQLite,迁移由 goose 管理
|
||||
- 证据存储:采购服务本地文件系统,SHA-256 寻址
|
||||
- 外部服务:顺运宝 ERP(只读)、AI provider(P1,仅采购工具调用)
|
||||
- 采购服务:Go + gin,SQLite,goose migration,本地 SHA-256 证据存储。
|
||||
- 采购工具:Python + uiautomator2 + PySide6;执行器只依赖 `TaskSource` / `ResultSink` 抽象。
|
||||
- 页面判据与拼多多 App 版本绑定;版本不同即停止,不把前序项目页面结构当作事实。
|
||||
|
||||
## 二、职责划分
|
||||
|
||||
### 采购服务(网页端,`admin/`)
|
||||
### 采购服务(`admin/`)
|
||||
|
||||
**独占**:
|
||||
独占以下权威:
|
||||
|
||||
- 任务的创建、状态流转和终态判定
|
||||
- 候选商品的接收与展示
|
||||
- **下单授权的签发与作废**——这是唯一的资金决策权威
|
||||
- 金额上限的判定
|
||||
- 证据资产的存储与访问控制
|
||||
- 管理员会话与设备凭据
|
||||
- 任务创建、批量开始采购、状态流转和终态判定;
|
||||
- **一次性采购授权的签发**:管理员点击“开始采购”是唯一的人类授权动作;
|
||||
- 任务不可变字段和最高总价校验;
|
||||
- 提交订单前的原子围栏与点击后结果调和;
|
||||
- 管理员会话、设备凭据、内部截图证据和审计记录。
|
||||
|
||||
**不做**:
|
||||
采购服务不连接手机、不发 ADB 命令、不解析拼多多页面,也不持有支付或 AI provider 凭据。
|
||||
|
||||
- 不连接手机、不发 ADB 命令
|
||||
- 不保存、代理或下发任何 AI provider 凭据
|
||||
- 不解析拼多多页面
|
||||
### 采购工具(`client/`)
|
||||
|
||||
### 采购工具(桌面端,`client/`)
|
||||
独占以下设备能力:
|
||||
|
||||
**独占**:
|
||||
- ADB 连接、设备健康检查和已取证 App 版本校验;
|
||||
- 打开商品、识别页面、精确选规格、设置数量、读取价格;
|
||||
- 在满足全部门禁并取得服务端围栏后,精确点击一次“提交订单”;
|
||||
- 截图、完整节点树和执行日志的本地采集,显式上传内部截图。
|
||||
|
||||
- ADB 连接与设备健康检查
|
||||
- 拼多多页面识别、点击、选规格、设数量
|
||||
- 页面截图与节点树采集
|
||||
- AI 调用(P1)
|
||||
- 完整执行轨迹的本地留档
|
||||
|
||||
**不做**:
|
||||
|
||||
- **不自行决定买哪个候选**——必须等采购服务的授权
|
||||
- **不自行放宽金额上限**——本地校验只能更严,不能更松
|
||||
- 不直接读 Excel 或访问 ERP
|
||||
- 不在没有授权的情况下执行任何创建订单的动作
|
||||
采购工具不自行修改任务约束、不扩大金额上限、不领取 `DRAFT`,也不能签发授权。没有服务端
|
||||
明确返回 `click_permitted=true` 时,任何本地判断都不能创建订单。
|
||||
|
||||
### 权威冲突规则
|
||||
|
||||
两端都会校验规格、数量、金额。**判定不一致时一律转人工,不取任一方结论。**
|
||||
这条是硬规则:双闸门的价值在于分歧能被发现,自动选一边等于把双闸门降级成单闸门。
|
||||
服务端校验锁定的任务约束,客户端校验当前真机事实。任一端拒绝或两端摘要不一致,一律停止并
|
||||
转人工;不能为了“继续跑”选择相信其中一端。
|
||||
|
||||
## 三、两趟执行
|
||||
## 三、单趟采购执行
|
||||
|
||||
这是本项目最核心的结构决策。**MVP 只做 A 路径(任务自带商品链接),分两趟跑完。**
|
||||
MVP 只做任务自带商品链接的 A 路径。创建任务与开始采购分离,但管理员开始后不再插入试选确认:
|
||||
|
||||
```text
|
||||
┌────────── 采购服务开始第一趟 ───────────┐
|
||||
│ 新任务先保存为 DRAFT │
|
||||
│ 管理员在任务表格勾选一条或多条 │
|
||||
│ 原子转为 PENDING,只进入试选队列 │
|
||||
└────────────────────┬─────────────────────┘
|
||||
v
|
||||
┌──────────────── 第一趟:试选 ────────────────┐
|
||||
│ 采购工具轮询领取 PENDING 任务 │
|
||||
│ 1. open_product(url) │
|
||||
│ 2. 经版本绑定、精确唯一的受控入口打开规格面板 │
|
||||
│ 3. 按维度精确勾选颜色分类、尺码 │
|
||||
│ 4. 【闸门一】读该 SKU 单价,算合计 │
|
||||
│ 5. 截图 │
|
||||
│ 6. 退出商品,释放手机 │
|
||||
│ 7. 回传标题 / 选中规格 / 单价 / 合计 / 截图 │
|
||||
└────────────────────┬─────────────────────────┘
|
||||
v
|
||||
任务转 WAITING_CONFIRMATION
|
||||
│
|
||||
┌────────────────────┴─────────────────────────┐
|
||||
│ 人在采购服务确认:机器选对了吗 │
|
||||
│ 看:需求 vs 选中规格、单价、合计、截图 │
|
||||
│ 点「确认下单(不付款)」→ 签发授权,锁定授权价 │
|
||||
│ 或「退回,不买」→ 任务终止 │
|
||||
└────────────────────┬─────────────────────────┘
|
||||
v
|
||||
┌──────────────── 第二趟:下单 ────────────────┐
|
||||
│ 采购工具轮询拿到授权 │
|
||||
│ 1. 重新 open_product(url) │
|
||||
│ 2. 重新按维度精确勾选同一规格 │
|
||||
│ 3. 【闸门二】重读单价,必须与授权价一致 │
|
||||
│ 4. 设数量并复核 │
|
||||
│ 5. 进订单确认页 │
|
||||
│ 6. 【闸门三】读「实付款」,不得超授权上限 │
|
||||
│ 7. 三个闸门全过 → 点一次「提交订单」 │
|
||||
│ 8. 回传订单截图 │
|
||||
└────────────────────┬─────────────────────────┘
|
||||
v
|
||||
任务转 WAITING_PAYMENT
|
||||
│
|
||||
人工在拼多多核对后付款
|
||||
管理员创建 DRAFT
|
||||
│
|
||||
├─ 勾选 DRAFT,查看选中数与最高总额
|
||||
└─ 点击“开始采购(只创建待付款订单)”
|
||||
│ 同一事务:校验版本 + 创建一次性授权 + PENDING
|
||||
v
|
||||
采购工具领取授权任务
|
||||
│ 1. 打开 canonical 商品链接
|
||||
│ 2. 通过证据/版本绑定、精确唯一的受控入口打开规格面板
|
||||
│ 3. 按维度精确选择并读回颜色、尺码
|
||||
│ 4. 【闸门一】读 SKU 单价;单价×数量不得超过最高总价
|
||||
│ 5. 设置数量并精确读回
|
||||
│ 6. 【闸门二】重读规格与单价;规格不变且价格等于闸门一
|
||||
│ 7. 进入订单确认页
|
||||
│ 8. 【闸门三】规格/数量一致,应付总额不超最高总价
|
||||
│ 9. 上传验证摘要并申请服务端提交围栏
|
||||
│ 10. 仅在 click_permitted=true 且提交控件唯一时点击一次
|
||||
│ 11. 回传观察结果;不确定时只调和,不重试
|
||||
v
|
||||
WAITING_PAYMENT ──人核对与付款──> SUCCEEDED
|
||||
或
|
||||
RECONCILIATION_REQUIRED ──人工核查同一提交──> WAITING_PAYMENT / FAILED
|
||||
```
|
||||
|
||||
### 为什么分两趟而不是停在面板上等人
|
||||
“开始采购”锁定的是管理员填写的 `goods_id`、颜色、尺码、数量和**最高总价**,不是一张旧页面
|
||||
截图里观察到的价格。价格在执行时实时读取,因此取消试选确认不会取消价格保护。
|
||||
|
||||
一台手机是瓶颈。若第一趟停在规格面板等人确认,手机被占住跑不了别的任务,面板还可能
|
||||
超时或被拼多多重置。**第一趟必须退出并释放手机**,第二趟重新进入。
|
||||
### 受控规格面板入口
|
||||
|
||||
代价是同一商品走两遍,但第二趟很快,而且换来两个好处:手机可以在人思考时继续跑别的
|
||||
任务的试选;价格变动能被第二趟抓住。
|
||||
T-103 真机证据表明:拼多多 `8.17.0`、goods_id `937122477375` 通过精确文本“快要抢光”打开
|
||||
规格面板。T-110 已批准把该**特定证据、版本和页面状态**绑定的点击定义为受控导航。
|
||||
|
||||
### 第一趟受控规格面板入口
|
||||
|
||||
T-103 的真机证据推翻了“商品详情页存在独立规格入口”的假设:拼多多 8.17.0、goods_id
|
||||
`937122477375` 只能从“快要抢光”打开规格面板。项目所有者于 2026-08-04 批准把这一点击定义为
|
||||
**可逆且能力受限的规格面板导航**,不把它当作下单授权,也不把购买语义文案整体加入白名单。
|
||||
|
||||
第一趟执行器只能持有以下 capability:
|
||||
|
||||
1. 打开 canonical 商品链接。
|
||||
2. 点击与证据哈希、拼多多版本和页面状态绑定的精确唯一 `快要抢光` 入口。
|
||||
3. 在已确认的维度容器中精确选择规格并读回选中态。
|
||||
4. 从规格面板读取单价、保存原始截图、关闭面板并退出商品页。T-103 真机 spike 先在本机验证;
|
||||
T-204 再把原始截图接入采购服务供管理员查看。
|
||||
|
||||
第一趟 capability **不得包含**通用 `click`、数量增减、进入订单确认页、提交订单或付款能力。
|
||||
“免拼购买 / 单独购买 / 直接拼成”等其他文案即使人工认为行为相同,也必须各自重新取证后才能评审;
|
||||
入口缺失、重复、版本失配、打开后面板判据不唯一,或出现“提交订单”以外的未知终态按钮时立即停止。
|
||||
第一趟的静态依赖检查必须证明 `set_quantity()`、`go_to_order_confirm()`、`submit_order()` 和任何
|
||||
支付函数不可达。
|
||||
- 只能精确唯一匹配;缺失、重复、版本失配或打开后面板不唯一时零后续点击。
|
||||
- “免拼购买 / 单独购买 / 直接拼成”等其他文案不能用包含、前缀、同义或坐标兜底。
|
||||
- 受控入口只负责进入已取证面板,不等于支付授权,也不能暴露通用任意点击能力。
|
||||
- T-103 的隔离验证 capability 只包含开商品、开面板、选规格、读价和安全退出;数量、确认页、
|
||||
提交和支付仍由后续真机任务分别取证后才能接入生产单趟执行器。
|
||||
|
||||
### 三道价格闸门
|
||||
|
||||
| 闸门 | 位置 | 作用 | 不通过时 |
|
||||
| 闸门 | 当前页面 | 判据 | 拒绝条件 |
|
||||
| --- | --- | --- | --- |
|
||||
| 一 | 第一趟规格面板 | 读该 SKU 单价,算合计,回传给人看 | 读不到即停,转人工 |
|
||||
| 二 | 第二趟规格面板 | 重读单价,**必须与授权时锁定的价格一致** | 不一致即停,转人工 |
|
||||
| 三 | 订单确认页 | 读「实付款」,不得超授权总额上限 | 超出即停,转人工 |
|
||||
| 一 | 规格面板,选中目标规格后 | 单价唯一可读;`单价 × 授权数量 <= 最高总价` | 不可读、有歧义或超上限 |
|
||||
| 二 | 规格面板,数量读回后 | 颜色、尺码仍正确;重读单价与闸门一完全相等 | 规格漂移或价格变化 |
|
||||
| 三 | 订单确认页 | 规格、数量正确;应付总额唯一可读且不超最高总价 | 任一不一致、不可读或超上限 |
|
||||
|
||||
**闸门二不可省略。** 人确认的是「32.50 元这一单」,不是「这个商品」。拼多多价格波动
|
||||
常见,不能因为「人已经确认过」就照下不误。
|
||||
金额一律使用十进制字符串和十进制定点运算,不用浮点数。价格只从规格面板和订单确认页读取;
|
||||
详情正文、搜索卡片、底部购买/提交按钮的数字不作为价格来源。
|
||||
|
||||
**价格只在规格面板和订单确认页读。** 商品详情页正文和搜索结果卡片上的价格文本在真机上
|
||||
被拆成多个节点(`¥` 与数字分离)、带 `券后` 一类前缀、实付价 / 原价 / 促销价难以区分
|
||||
——前序项目在这里耗掉大量时间且无可靠结论。
|
||||
闸门一与闸门二发生在同一设备会话中。它们用于发现选择或设置数量造成的页面变化,不需要管理员
|
||||
在中间确认。旧截图、缓存值和发布前 dry-run 都不能替代这次实时读取。
|
||||
|
||||
### B 路径(图片搜索)——V2,MVP 不做
|
||||
### B 路径(V2)
|
||||
|
||||
任务只有参考图、没有链接时,需要先搜图找出商品。**该路径推迟到 V2**,MVP 阶段建单必须
|
||||
提供商品链接。
|
||||
|
||||
V2 实现时仍遵守:**图搜的唯一产出是 goods_id**,不在搜索结果卡片上读价格或据价筛选,
|
||||
拿到 goods_id 后汇入本节的两趟流程。
|
||||
没有商品链接时由图片搜索只产出 `goods_id`,再汇入上述流程。不在搜索结果页读取价格或规格。
|
||||
|
||||
## 四、安全边界(硬约束)
|
||||
|
||||
以下每一条都必须有单元测试证明,且不得在任务中「顺手放宽」。
|
||||
以下每条都必须有测试证明,不得在任务中顺手放宽:
|
||||
|
||||
| 边界 | 规则 | 违反后果 |
|
||||
| 边界 | 规则 | 防止什么 |
|
||||
| --- | --- | --- |
|
||||
| 不付款 | 任何路径都不点击支付、免密支付、先用后付或扣款控件 | 真实资金损失 |
|
||||
| **提交订单四条件** | 见下方专节。四者缺一不可,且**只允许点击一次** | 误下单 / 重复下单 |
|
||||
| 订单确认页其余零点击 | 除「提交订单」与返回外,不点击确认页上任何控件 | 误触发未知动作 |
|
||||
| 规格精确匹配 | 按维度等值匹配,防前缀碰撞(`红`/`粉红`、`1`/`10`);找不到即停 | 买错货 |
|
||||
| 提交订单控件唯一 | 文本精确等于「提交订单」且可点击祖先唯一,否则停 | 点到未知控件 |
|
||||
| 数量必须复核 | 设置后读回确认精确等于要求值,否则停 | 买错数量 |
|
||||
| 价格三道闸门 | 见第三节。任一道读不到或不通过即停,**不用其他位置的数字凑合** | 超预算采购 |
|
||||
| 第一趟不下单 | 只允许点击证据/版本绑定的精确唯一受控入口打开规格面板,当前仅为 `快要抢光`;随后只选规格、读价、截图和返回。数量、确认页、`提交订单`、付款与通用点击能力均不可达 | 无授权下单 |
|
||||
| 外部支付页 | 检测到微信等外部支付交接立即停止、转人工、保留证据 | 凭据泄露 |
|
||||
| 安全校验 | 检测到验证码、风控、人脸、短信校验立即停止,不尝试绕过 | 封号 / 违规 |
|
||||
| 页面个人信息 | 内部原始截图可包含并上传页面已显示的地址、手机号,供已登录管理员查看;不得把这些内容解析成业务字段或写入普通日志、Git、Vikunja。支付凭据仍不得读取或上传 | 非必要扩散 / 凭据泄露 |
|
||||
| 授权一次性 | 一笔授权只能产生一笔订单,重复提交幂等 | 重复采购 |
|
||||
| 服务端提交围栏 | 真机点击前必须由采购服务原子冻结授权并创建唯一提交记录;失败或响应不明不得点击 | 并发 / 断网导致重复下单 |
|
||||
| App 版本失配即停 | 运行版本与本项目已取证版本不一致时停止领取真机任务,先重新取证 | 旧判据误点新页面 |
|
||||
| 不付款 | 不点击支付、免密支付、先用后付或任何扣款控件 | 真实资金损失 |
|
||||
| 提交四条件 | 授权+围栏、闸门二、闸门三、控件唯一同时成立,只点一次 | 误下单 / 重复下单 |
|
||||
| 确认页零点击 | 除返回和满足四条件后的“提交订单”外不点击任何控件 | 未知副作用 |
|
||||
| 规格精确匹配 | 维度内等值唯一匹配,防 `红/粉红`、`1/10` 前缀碰撞 | 买错规格 |
|
||||
| 数量读回复核 | 设置后精确读回,不一致即停 | 买错数量 |
|
||||
| 三道价格闸门 | 任一道不可读、有歧义或不通过都停,不用别处数字凑 | 超预算 |
|
||||
| 受控页面能力 | 页面动作按任务与证据分层;不得把通用 `click` 传入业务流程 | 边界扩散 |
|
||||
| 外部支付页 | 检测到外部支付交接立即停止,不读取、保存或输入凭据 | 凭据泄露 |
|
||||
| 安全校验 | 验证码、风控、人脸、短信出现即停止,不绕过 | 封号 / 违规 |
|
||||
| 内部截图 | 可上传页面已显示的地址/手机号;不解析成字段或日志,完整 XML 不上传 | 非必要扩散 |
|
||||
| 授权一次性 | 一条任务版本只有一份有效授权;幂等重放不生成第二份 | 重复采购 |
|
||||
| 服务端提交围栏 | 点击前原子创建唯一提交记录;失败或响应不明不得点击 | 并发 / 断网重复下单 |
|
||||
| App 版本绑定 | 运行版本不同于证据版本时停止并重新取证 | 旧判据误点 |
|
||||
|
||||
### 提交订单的四个前置条件
|
||||
|
||||
这是本项目唯一会创建真实待付款订单的动作。**四者同时满足才允许点击,且只点一次:**
|
||||
“提交订单”是系统唯一会创建真实待付款订单的动作。以下四项同时满足才允许点击一次:
|
||||
|
||||
1. **授权存在且未消费,并已建立服务端提交围栏**——采购服务已签发、采购工具已 ack;
|
||||
真机点击前,采购服务在一个原子事务中把授权从可执行态冻结为本次唯一
|
||||
`order_submission`。围栏接口失败或响应不明时不得点击。
|
||||
2. **闸门二通过**——第二趟重读的单价与授权时锁定的价格一致。
|
||||
3. **闸门三通过**——订单确认页「实付款」不超过授权总额上限。
|
||||
4. **控件唯一**——文本精确等于「提交订单」且可点击祖先唯一。
|
||||
1. **一次性授权有效且服务端提交围栏已建立**:围栏把授权、任务、领取和本次验证摘要原子绑定到
|
||||
唯一 `order_submission`;明确响应包含 `click_permitted=true`。
|
||||
2. **闸门二通过**:目标规格未漂移,第二次规格面板单价等于第一次。
|
||||
3. **闸门三通过**:确认页规格、数量正确,应付总额不超过授权最高总价。
|
||||
4. **提交控件唯一**:文本精确等于“提交订单”,可点击祖先唯一。
|
||||
|
||||
点击之后,**无论发生什么都不重试**:
|
||||
围栏请求超时、断网、冲突或响应不明时不得点击。围栏建立后:
|
||||
|
||||
| 点击后观察到 | 处置 |
|
||||
| 观察结果 | 处置 |
|
||||
| --- | --- |
|
||||
| 正常进入订单结果页 | 回传订单截图,任务转 `WAITING_PAYMENT` |
|
||||
| 跳转微信等外部支付 | 立即停止,转人工,提示「订单可能已创建、支付未完成」 |
|
||||
| 安全校验 | 立即停止,转人工,保留证据 |
|
||||
| 超时或页面无法判定 | 转人工,**预留金额额度**,提示订单状态不明 |
|
||||
| 明确进入订单结果 / 待付款页 | 上报 `SUBMITTED`,任务转 `WAITING_PAYMENT` |
|
||||
| 跳转外部支付 | 立即停止,上报结果不明确,不执行支付 |
|
||||
| 出现验证码 / 风控 / 人脸 / 短信 | 立即停止,上报结果不明确,不绕过 |
|
||||
| 超时、断连、页面无法判定 | 转 `RECONCILIATION_REQUIRED`,保留围栏和金额额度 |
|
||||
|
||||
后三种情况一律**禁止自动重试点击**。授权保持永久围栏,结果明确后再记为已消费;在此之前
|
||||
也绝不能重新开放——宁可人工核实一遍,不可能重复下单。
|
||||
后三种情况都禁止释放围栏、重新授权、重新领取或再次点击。只能调和同一提交记录。
|
||||
|
||||
### dry-run、提交围栏与结果调和
|
||||
### 发布前 dry-run 与生产围栏的区别
|
||||
|
||||
下单被拆成三个不可逆程度不同的阶段,任何客户端本地判断都不能替代服务端围栏:
|
||||
|
||||
1. **dry-run(只读演练)**:进入订单确认页,读取规格、数量和「实付款」,确认提交控件
|
||||
唯一,上传证据后退出。该阶段绝不点击「提交订单」,也不消费授权。
|
||||
2. **提交围栏**:真实第二趟再次读取并通过三道闸门后,采购工具向采购服务申请围栏。采购服务
|
||||
原子校验任务版本、命令、未消费授权和唯一性,创建 `order_submissions` 记录并冻结授权。
|
||||
只有明确收到成功响应,采购工具才可点击一次。
|
||||
3. **结果调和**:点击后只上报观察结果。明确创建则转 `WAITING_PAYMENT`;超时、外部支付、
|
||||
安全校验或断连均转 `RECONCILIATION_REQUIRED`,保留额度并由人核查。**不得释放围栏、
|
||||
重新签发授权或自动重试点击。**
|
||||
|
||||
授权超时和主动放弃只允许发生在提交围栏建立之前。围栏之后即使租约过期,也只能恢复
|
||||
同一提交记录并进入调和,不能把任务重新放回可领取队列。
|
||||
首次启用某一 App 版本的真实提交能力前,必须用独立真机任务完成只读 dry-run:进入确认页,验证
|
||||
规格、数量、金额与提交控件唯一,然后退出且不点击。它用于证明判据和不可达测试,不是每笔采购的
|
||||
“第一趟”,也不产生可复用页面事实。生产任务仍在同一趟内重新通过三道闸门并申请服务端围栏。
|
||||
|
||||
## 五、数据模型
|
||||
|
||||
### 5.1 核心实体
|
||||
|
||||
```sql
|
||||
-- 采购任务:创建后业务约束不可变
|
||||
CREATE TABLE tasks (
|
||||
id TEXT PRIMARY KEY, -- UUID
|
||||
source TEXT NOT NULL, -- MANUAL | EXCEL | ERP
|
||||
source_ref TEXT, -- 外部单号(如虾皮订单号),非内部 id
|
||||
title TEXT NOT NULL,
|
||||
goods_id TEXT NOT NULL, -- MVP 必填;B 路径(可为空)推迟到 V2
|
||||
sku_color TEXT NOT NULL,
|
||||
sku_size TEXT NOT NULL,
|
||||
quantity INTEGER NOT NULL CHECK (quantity > 0),
|
||||
max_total_price TEXT NOT NULL, -- 十进制字符串,资金边界不得为空
|
||||
reference_asset_id TEXT, -- 参考图,MVP 可选;B 路径(V2)必填
|
||||
status TEXT NOT NULL,
|
||||
version INTEGER NOT NULL DEFAULT 1,-- 乐观锁
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- 第一趟试选结果:人做确认决策的依据
|
||||
CREATE TABLE spec_trials (
|
||||
id TEXT PRIMARY KEY,
|
||||
task_id TEXT NOT NULL REFERENCES tasks(id),
|
||||
attempt INTEGER NOT NULL,
|
||||
product_title TEXT NOT NULL, -- 商品页读到的标题
|
||||
selected_color TEXT NOT NULL, -- 实际勾选到的颜色分类
|
||||
selected_size TEXT NOT NULL, -- 实际勾选到的尺码
|
||||
unit_price TEXT NOT NULL, -- 闸门一读到的单价
|
||||
total_price TEXT NOT NULL, -- unit_price × quantity
|
||||
evidence_sha256 TEXT NOT NULL, -- 规格面板截图
|
||||
source TEXT NOT NULL, -- MANUAL | EXCEL | ERP
|
||||
source_ref TEXT,
|
||||
title TEXT NOT NULL,
|
||||
goods_id TEXT NOT NULL,
|
||||
sku_color TEXT NOT NULL,
|
||||
sku_size TEXT NOT NULL,
|
||||
quantity INTEGER NOT NULL CHECK (quantity > 0),
|
||||
max_total_price TEXT NOT NULL, -- 十进制字符串
|
||||
reference_asset_id TEXT,
|
||||
status TEXT NOT NULL,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
UNIQUE (task_id, attempt)
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- 下单授权:唯一的资金决策记录
|
||||
-- 管理员点击“开始采购”产生;锁定任务约束,不锁定旧观察价
|
||||
CREATE TABLE order_authorizations (
|
||||
id TEXT PRIMARY KEY,
|
||||
task_id TEXT NOT NULL REFERENCES tasks(id),
|
||||
spec_trial_id TEXT NOT NULL REFERENCES spec_trials(id),
|
||||
version INTEGER NOT NULL,
|
||||
task_version INTEGER NOT NULL,
|
||||
start_key TEXT NOT NULL,
|
||||
goods_id TEXT NOT NULL,
|
||||
sku_color TEXT NOT NULL,
|
||||
sku_size TEXT NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
authorized_unit_price TEXT NOT NULL, -- 锁定价:闸门二据此比对
|
||||
total_price_cap TEXT NOT NULL, -- 授权总额上限:闸门三据此比对
|
||||
note TEXT, -- 可选备注
|
||||
status TEXT NOT NULL,
|
||||
total_price_cap TEXT NOT NULL,
|
||||
status TEXT NOT NULL, -- ACTIVE | CLAIMED | FENCED | CONSUMED | EXPIRED | ABANDONED
|
||||
created_by TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
expires_at TEXT NOT NULL, -- 围栏前超时自动作废;围栏后不再释放
|
||||
UNIQUE (task_id, version)
|
||||
expires_at TEXT NOT NULL,
|
||||
UNIQUE (task_id, task_version),
|
||||
UNIQUE (start_key, task_id)
|
||||
);
|
||||
|
||||
-- 真实点击前的服务端一次性围栏;一笔授权最多一条
|
||||
-- 一次领取产生一条可恢复执行;保存步骤摘要,不接收完整 XML
|
||||
CREATE TABLE purchase_attempts (
|
||||
id TEXT PRIMARY KEY,
|
||||
task_id TEXT NOT NULL REFERENCES tasks(id),
|
||||
authorization_id TEXT NOT NULL REFERENCES order_authorizations(id),
|
||||
claim_generation INTEGER NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
gate1_unit_price TEXT,
|
||||
gate2_unit_price TEXT,
|
||||
quantity_read INTEGER,
|
||||
confirm_amount TEXT,
|
||||
failure_code TEXT,
|
||||
started_at TEXT NOT NULL,
|
||||
finished_at TEXT,
|
||||
UNIQUE (task_id, claim_generation)
|
||||
);
|
||||
|
||||
-- 真机真实点击前建立;一份授权最多一条
|
||||
CREATE TABLE order_submissions (
|
||||
id TEXT PRIMARY KEY,
|
||||
task_id TEXT NOT NULL REFERENCES tasks(id),
|
||||
authorization_id TEXT NOT NULL REFERENCES order_authorizations(id),
|
||||
command_id TEXT NOT NULL,
|
||||
dry_run_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL, -- FENCED | SUBMITTED | RECONCILIATION_REQUIRED | MANUAL_RESOLVED
|
||||
verified_unit_price TEXT NOT NULL,
|
||||
quantity_read INTEGER NOT NULL,
|
||||
confirm_page_amount TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
resolved_at TEXT,
|
||||
attempt_id TEXT NOT NULL REFERENCES purchase_attempts(id),
|
||||
status TEXT NOT NULL, -- FENCED | SUBMITTED | RECONCILIATION_REQUIRED | MANUAL_RESOLVED
|
||||
gate1_unit_price TEXT NOT NULL,
|
||||
gate2_unit_price TEXT NOT NULL,
|
||||
quantity_read INTEGER NOT NULL,
|
||||
confirm_amount TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
resolved_at TEXT,
|
||||
UNIQUE (authorization_id),
|
||||
UNIQUE (command_id)
|
||||
UNIQUE (attempt_id)
|
||||
);
|
||||
```
|
||||
|
||||
`authorized_unit_price` 是第二趟闸门二的比对基准,**必须来自人确认时看到的那个试选
|
||||
结果**,不能在签发时重新取值。`expires_at` 见 5.3 节。
|
||||
|
||||
MVP 的授权没有「选择理由 / 拒绝理由」——那是从多个候选里挑一个时的留档需求。这里人
|
||||
只回答「机器选对了吗」,保留一个可选 `note` 即可。
|
||||
|
||||
金额一律用**十进制字符串**存储和传输,不用浮点数。
|
||||
MVP 不再用 `spec_trials` 作为审批记录,也不存在 `authorized_unit_price`。实际读价属于
|
||||
`purchase_attempts` / `order_submissions` 的执行与审计事实;管理员授权的资金边界始终是
|
||||
`total_price_cap`。
|
||||
|
||||
### 5.2 状态机
|
||||
|
||||
任务状态(采购服务权威)。创建与开始试选分离;两趟执行对应两次 `CLAIMED → RUNNING`:
|
||||
|
||||
```text
|
||||
DRAFT ─start trial→ PENDING ─┐
|
||||
PENDING_RETRIAL ─────────────┴─claim→ CLAIMED ─start→ RUNNING(TRIAL)
|
||||
↑ │ ├→ NEEDS_MANUAL
|
||||
└────────release──────┘ └→ WAITING_CONFIRMATION
|
||||
├→ CANCELED
|
||||
└→ AUTHORIZED
|
||||
└claim→ ORDERING
|
||||
├→ NEEDS_MANUAL(围栏前失败)
|
||||
└→ [submission FENCED]
|
||||
├→ WAITING_PAYMENT
|
||||
│ └→ SUCCEEDED
|
||||
└→ RECONCILIATION_REQUIRED
|
||||
└→ 人工核查 / 调和
|
||||
DRAFT
|
||||
└─开始采购(创建授权)→ PENDING
|
||||
└─claim→ CLAIMED ─start→ ORDERING
|
||||
├─围栏前验证失败→ NEEDS_MANUAL ─人工处理/重置→ DRAFT
|
||||
├─围栏前授权过期/安全释放→ DRAFT
|
||||
└─submission FENCED
|
||||
├─明确创建→ WAITING_PAYMENT ─人工付款并标记→ SUCCEEDED
|
||||
└─结果不明→ RECONCILIATION_REQUIRED
|
||||
└─人工调和同一提交→ WAITING_PAYMENT / FAILED
|
||||
|
||||
DRAFT / PENDING / NEEDS_MANUAL ─管理员取消(围栏前)→ CANCELED
|
||||
```
|
||||
|
||||
| 状态 | 含义 |
|
||||
| 状态 | 含义与安全下一步 |
|
||||
| --- | --- |
|
||||
| `DRAFT` | 已保存、等待管理员开始试选;设备不可领取,也不存在下单授权 |
|
||||
| `RUNNING(TRIAL)` | 第一趟试选中:正在勾选规格、读价、截图 |
|
||||
| `WAITING_CONFIRMATION` | 试选已回传,**等人确认机器选对了没** |
|
||||
| `PENDING_RETRIAL` | 旧授权已过期或在围栏前被放弃,必须重新跑第一趟取得新价格 |
|
||||
| `AUTHORIZED` | 已签发授权,等采购工具下一轮轮询领走 |
|
||||
| `ORDERING` | 第二趟下单中:重新选规格、过闸门二三、提交订单 |
|
||||
| `WAITING_PAYMENT` | 订单已创建,等人在拼多多付款。**这不是成功** |
|
||||
| `RECONCILIATION_REQUIRED` | 已建立提交围栏,但点击结果不明确;可能已创建订单,只能核查,不能重试 |
|
||||
| `NEEDS_MANUAL` | 围栏前的转人工情形(规格不匹配、价格不符、页面识别失败等) |
|
||||
| `SUCCEEDED` | 订单已付款且核对通过 |
|
||||
| `DRAFT` | 已保存,未授权;管理员可编辑/取消或点击开始采购;设备不可领取 |
|
||||
| `PENDING` | 已有有效一次性授权,等待采购工具领取 |
|
||||
| `CLAIMED` | 已由一个设备实例持有租约,尚未开始页面操作 |
|
||||
| `ORDERING` | 单趟执行中,正在选规格、过闸门或申请围栏 |
|
||||
| `NEEDS_MANUAL` | 围栏前失败;显示原因,由人核查后重置为 DRAFT 或取消 |
|
||||
| `WAITING_PAYMENT` | 订单已明确创建,等待人在拼多多付款;**不是成功** |
|
||||
| `RECONCILIATION_REQUIRED` | 围栏后结果不明;只能核查同一提交,不能重试 |
|
||||
| `SUCCEEDED` | 人已付款并完成核对 |
|
||||
| `FAILED` | 人工调和确认订单未创建或任务无法完成 |
|
||||
| `CANCELED` | 围栏前由管理员取消,不再执行 |
|
||||
|
||||
授权状态:`PENDING_DELIVERY → DELIVERED → ACKNOWLEDGED → EXECUTING → FENCED → CONSUMED`。
|
||||
人退回或重新确认时旧授权转 `SUPERSEDED`;超时转 `EXPIRED`。
|
||||
批量 `DRAFT → PENDING` 必须全有或全无。服务端同时创建授权;“先改状态、稍后补授权”无效。
|
||||
`WAITING_CONFIRMATION`、`PENDING_RETRIAL`、`AUTHORIZED` 和 `RUNNING(TRIAL)` 不再属于 MVP 状态。
|
||||
|
||||
`DRAFT → PENDING` 只能由管理端“开始试选”动作触发。批量开始在一个事务中校验全部任务仍为
|
||||
`DRAFT` 且版本一致后统一流转;任一冲突时整批不变,避免用户误以为选中的任务都已开始。
|
||||
这个动作只开放第一趟领取资格,不创建 `order_authorizations` 或 `order_submissions`。
|
||||
### 5.3 授权、租约与恢复
|
||||
|
||||
### 5.3 授权超时(MVP 必做,不得推后)
|
||||
|
||||
> **前序项目的教训**:曾出现 `EXECUTING` 授权永不推进,导致确认表单被永久隐藏、任务
|
||||
> 锁死,只能新建任务绕过。
|
||||
|
||||
规则:
|
||||
|
||||
- 每笔授权带 `expires_at`。**仅在尚未建立提交围栏时**,超时自动转 `EXPIRED`。
|
||||
- 授权 `EXPIRED` 后任务转 `PENDING_RETRIAL`,先重新跑第一趟取得新价格,再回到人工确认;
|
||||
不允许在旧 `spec_trials` 上直接重新确认。
|
||||
- 采购服务在围栏建立前提供「放弃当前授权」入口;围栏建立后改为「进入人工核查」,不得
|
||||
作废或释放授权。
|
||||
- **任何时候都不允许出现「任务停在某状态且界面上没有任何可用动作」的组合。**
|
||||
这是验收项,不是实现细节。
|
||||
|
||||
授权过期后重新确认时,必须重新走第一趟试选取得新的 `spec_trials` 记录——不能复用旧的
|
||||
锁定价,因为价格可能已经变了。已建立围栏的授权不参与本超时流程。
|
||||
- 授权带 `expires_at`,只有围栏前可转 `EXPIRED` / `ABANDONED`;任务回到 `DRAFT`,必须重新点击
|
||||
开始采购。旧授权永不复活。
|
||||
- 设备租约丢失不等于授权可安全重用。只有服务端确认该 attempt 未建立围栏,才能关闭 attempt 并
|
||||
回到 `DRAFT` / `NEEDS_MANUAL`;不能自动重新领取并重复页面动作。
|
||||
- 围栏建立后即使租约过期也只恢复同一 `order_submission` 的调和,不能回到可领取队列。
|
||||
- 每种非终态都必须给出安全下一步,不能出现隐藏表单导致任务永久锁死。
|
||||
|
||||
### 5.4 证据分层
|
||||
|
||||
| 数据 | 位置 | 理由 |
|
||||
| 数据 | 位置 | 边界 |
|
||||
| --- | --- | --- |
|
||||
| 原始商品页 / 规格页 screenshot | 采购工具本机 + 采购服务内部证据存储(T-204) | 内部系统允许保留页面中已显示的地址和手机号;设备鉴权后上传,只有已登录管理员可查看,不做遮罩或裁剪 |
|
||||
| 原始商品页 / 规格页完整 XML | **仅采购工具本机隔离目录** | 自动化运行时在内存中读取规格/价格,完整 XML 不上传、不进入日志、Git、Vikunja 或 fixture |
|
||||
| T-103 真机 spike 证据 | 先在采购工具本机;T-204 接入上传 | T-103 只验证选择与读价,不实现 HTTP 证据链;这是一项任务拆分,不是禁止原始截图上传 |
|
||||
| 最小脱敏 XML fixture | 采购工具测试 / 可提交 Git | 只保留页面判据所需结构;自动复检无地址、手机号、支付凭据后才可发布 |
|
||||
| 原始订单确认页截图 | 采购服务内部证据存储 | 授权后核对与审计必须留;允许页面已显示的地址/手机号,禁止外部支付凭据 |
|
||||
| 原始订单核对截图 | 采购服务内部证据存储 | 资金核对证据;只有已认证设备上传、已登录管理员查看 |
|
||||
| AI 调用记录(P1) | **仅采购工具本地** | 含 prompt / 响应全文,脱敏成本高 |
|
||||
| 失败现场快照 | 原始物仅采购工具本机;只能手工导出脱敏派生物 | 同上 |
|
||||
| 商品 / 规格 / 确认页原始 screenshot | 采购工具本机 + 采购服务内部证据存储 | 可含页面已显示地址/手机号;设备鉴权上传、管理员登录查看,不遮罩 |
|
||||
| 完整 XML | 仅采购工具本机隔离目录 | 可在内存解析页面判据;不上传、不写日志、Git、Vikunja |
|
||||
| 最小 XML fixture | 采购工具测试 / Git | 只保留判据所需结构,确认无地址、手机号、支付凭据 |
|
||||
| 外部支付页或支付凭据 | 不保存、不上传 | 检测到交接立即停止 |
|
||||
| AI 调用记录(V2) | 仅采购工具本地 | 不进入采购服务 |
|
||||
|
||||
截图上传器只能接收调用方显式传入的截图文件,不能枚举整个原始目录,也不能上传 XML、manifest 中的
|
||||
本机路径或其他文件。采购工具可以在真机流程内存中读取当前页面树,但只返回规格、选中态、价格和页面
|
||||
状态摘要;不得把地址或手机号解析成结构化业务字段。原始 XML 不进入 HTTP、日志、Vikunja、Git 或
|
||||
fixture;仓库只允许进入与页面判据有关的最小 XML fixture。
|
||||
|
||||
T-204 负责原始截图上传、SHA-256 校验、内部访问控制和保留策略,不再实现遮罩或裁剪。证据响应使用
|
||||
`Cache-Control: no-store`,不得暴露为无需登录的静态目录。外部支付页及任何支付凭据不属于“内部原图
|
||||
可上传”的范围,遇到支付交接仍立即停止。
|
||||
截图上传器只能接收调用方显式指定的截图,不能枚举证据目录或顺带上传 XML/manifest。证据响应
|
||||
使用 `Cache-Control: no-store`,不能暴露为免登录静态目录。
|
||||
|
||||
## 六、关键技术难点
|
||||
|
||||
| 难点 | 说明 | 应对 |
|
||||
| 难点 | 风险 | 应对 |
|
||||
| --- | --- | --- |
|
||||
| 拼多多页面结构随版本变化 | 前序项目已观察到详情页无独立规格入口、价格节点拆分等变化 | **每条判据先做真机 spike 取证再写代码**;判据与 App 版本一并记录 |
|
||||
| 规格面板安全入口 | T-103 已在拼多多 8.17.0 真机确认衣服商品只能通过购买语义入口打开规格面板 | T-110 已批准仅使用当前证据证明的精确唯一 `快要抢光` 作为受控导航;其他文案不泛化,第一趟下单能力保持不可达 |
|
||||
| 规格面板上的价格位置 | 选中 SKU 后价格显示在哪、是否含券后前缀,未取证 | **T-103 必须一并取证**,闸门一依赖它;读不到就转人工,不用详情页数字凑合 |
|
||||
| 同一商品两趟结果不一致 | 第二趟价格变了、规格选项变了或商品下架 | 闸门二拦截;一律转人工,不自动放弃也不自动继续 |
|
||||
| 图搜结果含跨类目商品(V2) | 搜服装出现纸巾 | B 路径只产 goods_id 且限 5 个;后续用 VLM 看截图筛同款 |
|
||||
| WiFi ADB 稳定性 | 息屏、换网、DHCP 续租会断连 | 超时可配置;断连视为技术失败并保留现场,不重试点击 |
|
||||
| 同一手机 USB + WiFi 同时在线 | `adb devices` 列出两条,自动选设备会失败 | serial 必填;多在线通道必须读到 `ro.serialno` 或 `ro.boot.serialno` 才能比对。身份一致或任一身份读取失败时都 fail closed,不能以相同 model/product 猜测后继续 |
|
||||
| 不可逆动作的重试 | 点击「现在买」后超时,无法判断订单是否已创建 | 一律转人工并预留金额额度,**禁止自动重试点击** |
|
||||
| 双端契约漂移 | 两端独立演进会静默不兼容 | 契约改动必跑完整门禁;[api.md](api.md) 是唯一权威 |
|
||||
|
||||
**高风险功能先做最小原型。** Phase 1 的真机 spike 必须先于 Phase 2 的界面开发完成。
|
||||
| 页面结构随版本变化 | 旧选择器误点新页面 | 每条判据先真机取证,记录 App 版本、截图、XML、goods_id |
|
||||
| 购买语义入口才打开面板 | 能力范围易扩散 | 只批准证据绑定的精确唯一入口;每种文案单独取证 |
|
||||
| 当前价与原价/按钮价混杂 | 读错价格 | 限定已取证结构和语义;价格只在面板/确认页读,歧义即停 |
|
||||
| 单趟页面状态变化 | 数量或促销导致价格变化 | 同一趟两次读规格面板价,再以确认页总额兜底 |
|
||||
| WiFi ADB / 双通道 | 断连或操作错设备 | serial 必填;USB/WiFi 同设备或身份不明时 fail closed |
|
||||
| 不可逆动作超时 | 可能已创建订单 | 服务端围栏 + 点击一次 + 只调和,不重试 |
|
||||
| 双端契约漂移 | 静默不兼容 | [api.md](api.md) 唯一权威;契约改动跑完整双端门禁 |
|
||||
|
||||
## 七、推荐开发顺序
|
||||
|
||||
1. **Phase 0 地基**:两端骨架、测试命令、`init` 脚本可运行。
|
||||
2. **Phase 1 真机取证**:WiFi ADB 连通;先验证第一趟的打开商品 → 受控打开规格面板 → 按维度
|
||||
精确勾选颜色分类和尺码 → **读到该 SKU 单价** → 原始截图取证 → 退出;再以独立的第二趟 spike
|
||||
验证设数量 → 进订单确认页 → 读「实付款」。第一趟 capability 不含后三项。
|
||||
**结论写入文档,判据带拼多多 App 版本。**
|
||||
3. **Phase 2 采购服务核心**:数据模型与状态机、手工建单、任务查询、试选结果接收、
|
||||
确认页与授权签发、**授权超时与放弃**。
|
||||
4. **Phase 3 双端打通**:设备侧 API、采购工具 `HttpTaskSource`/`HttpResultSink`、
|
||||
定时轮询、第一趟试选端到端。
|
||||
5. **Phase 4 闭环收尾**:第二趟下单(含三道闸门与提交)、失败分类、完整验收、打包。
|
||||
6. **V2 及以后**:图片搜索路径、候选对照台、Excel 导入、ERP 建单、订单自动核对、AI 辅助。
|
||||
1. **Phase 0 地基**:双端骨架、测试命令、初始化脚本和原型。
|
||||
2. **Phase 1 真机取证**:逐段验证商品打开、受控面板入口、精确规格与读价、数量、确认页和提交
|
||||
控件;真实点击前先完成独立 dry-run。每个 spike 的 capability 只覆盖当期动作。
|
||||
3. **Phase 2 采购服务核心**:DRAFT 建单/列表、批量开始采购与授权、状态/证据/围栏/调和接口。
|
||||
4. **Phase 3 双端打通**:设备身份、原子领取、单趟执行到围栏前、事件与截图。
|
||||
5. **Phase 4 闭环**:经明确真机授权验证一次提交、待付款收口、失败分类、打包。
|
||||
6. **V2**:图搜、Excel、ERP、自动核对、AI、多设备。
|
||||
|
||||
**不要在 Phase 1 结论出来之前写 Phase 2 的页面**——确认页要显示什么,取决于真机上
|
||||
究竟能读到什么。尤其是闸门一的单价,如果规格面板上读不可靠,整个确认页的设计要改。
|
||||
|
||||
> 2026-08-04:Phase 1 证明已验证衣服商品没有独立规格入口。项目所有者随后批准 T-110 的受控入口
|
||||
> 方案:当前只允许证据绑定的精确唯一 `快要抢光` 打开规格面板,并以 capability 隔离保证数量、确认页、
|
||||
> 提交订单和付款在第一趟不可达。项目所有者随后决定加速 MVP:停止截图遮罩器开发,T-103 使用现有
|
||||
> 安全最小 XML 继续实现选择与读价;T-204 直接把内部原始截图上传采购服务,不遮罩地址或手机号。
|
||||
T-103 继续作为“规格选择与读价”的隔离前置,不含数量、确认页或提交。生产单趟并不意味着在一个
|
||||
任务里跳过逐段取证;它只意味着这些已验证能力集成后,每笔业务任务不再等待中途人工确认。
|
||||
|
||||
## 八、项目结构
|
||||
|
||||
```text
|
||||
cmbuyer/
|
||||
├── docs/
|
||||
├── admin/ # 采购服务(Go)
|
||||
├── admin/
|
||||
│ ├── cmd/server/
|
||||
│ ├── internal/
|
||||
│ │ ├── domain/ # 实体与状态机,无外部依赖
|
||||
│ │ ├── usecase/ # 业务用例
|
||||
│ │ ├── transport/
|
||||
│ │ │ ├── httpapi/ # 设备侧 API
|
||||
│ │ │ └── webui/ # 管理页面 + 模板 + 静态资源
|
||||
│ │ └── storage/ # SQLite 与证据资产
|
||||
│ ├── internal/domain/
|
||||
│ ├── internal/usecase/
|
||||
│ ├── internal/transport/httpapi/
|
||||
│ ├── internal/transport/webui/
|
||||
│ ├── internal/storage/
|
||||
│ └── migrations/
|
||||
├── client/ # 采购工具(Python)
|
||||
│ ├── src/
|
||||
│ │ ├── android/ # adb / device / pdd_flow
|
||||
│ │ ├── core/ # models / task_runner / sources 抽象
|
||||
│ │ ├── remote/ # HttpTaskSource / HttpResultSink
|
||||
│ │ └── app/ # PySide6 GUI
|
||||
├── client/
|
||||
│ ├── src/cmbuyer_client/device/
|
||||
│ ├── src/cmbuyer_client/pdd/
|
||||
│ ├── src/cmbuyer_client/core/
|
||||
│ ├── src/cmbuyer_client/remote/
|
||||
│ ├── src/cmbuyer_client/app/
|
||||
│ └── tests/
|
||||
└── scripts/
|
||||
```
|
||||
|
||||
`client/src/core/sources.py` 必须保留 `TaskSource` / `ResultSink` 抽象,执行器只依赖抽象。
|
||||
这样离线 Excel 模式可作为降级路径存在,且执行器不因来源变化而改动。
|
||||
执行器依赖 `TaskSource` / `ResultSink`,不直接读取 Excel 或拼接 HTTP。来源变化不得改变安全执行器。
|
||||
|
||||
## 九、架构纪律
|
||||
|
||||
- 业务事实和 schema 变化必须同步更新本文与 [api.md](api.md)。
|
||||
- 不在代码里发明文档没有的接口、字段和状态。
|
||||
- 第四节的安全边界不得在任务中放宽;确需变更时先改本文并说明理由。
|
||||
- 高风险模块先单独真机验证,再接入完整流程。
|
||||
- 前序项目 `cmroubao` / `cmpdd` 是设计依据,**不是事实来源**;引用其结论时必须在本项目
|
||||
重新验证。
|
||||
- 业务事实和 schema 变化同步本文与 [api.md](api.md),代码不得另起一套字段或状态。
|
||||
- 第四节安全边界只能收紧。需要变更时先更新架构、任务边界和理由。
|
||||
- 页面高风险能力先真机取证并隔离测试,再接入完整流程。
|
||||
- 前序项目 `cmroubao` / `cmpdd` 只提供设计理由,不提供可直接复用的页面事实。
|
||||
|
||||
Reference in New Issue
Block a user