626 lines
32 KiB
Markdown
626 lines
32 KiB
Markdown
# 架构设计
|
||
|
||
## 一、系统结构
|
||
|
||
目标架构是一套后端服务、两个面向不同角色的客户端:
|
||
|
||
```text
|
||
采购管理员
|
||
|
|
||
v
|
||
管理 Web(服务端渲染)
|
||
|
|
||
v
|
||
统一 Backend API
|
||
|
|
||
+--> SQLite / 文件存储
|
||
^
|
||
|
|
||
Android 采购 App --------------------> VLM Provider
|
||
|
|
||
+--> AccessibilityService
|
||
|
|
||
v
|
||
拼多多 App
|
||
```
|
||
|
||
- 管理 Web 与 API 同属 `backend-api/`,但页面层不能直接访问数据库。
|
||
- Android App 位于 `android-buyer/`,通过 API 领取任务和回传证据,在本地调用 AI。
|
||
- 拼多多是第三方受控边界,只能由 Android 设备在已登录会话中操作。
|
||
- VLM 配置和 Key 位于手机,Key 使用 Android Keystore 包装的加密存储;管理后端不
|
||
保存、下发或代理模型调用。
|
||
|
||
## 二、模块职责
|
||
|
||
### 2.1 Backend API
|
||
|
||
建议分层:
|
||
|
||
```text
|
||
cmd/api/ # 进程入口、依赖装配、信号和优雅关闭
|
||
internal/transport/http/ # Gin 路由、中间件、页面和请求/响应 DTO
|
||
internal/usecase/ # 创建、领取、状态迁移和结果归档
|
||
internal/domain/ # 实体、状态机、权限和确定性约束
|
||
internal/repository/sqlite/ # database/sql 仓储实现
|
||
internal/platform/ # 配置、日志和文件存储
|
||
```
|
||
|
||
职责:
|
||
|
||
- 验证管理会话、采购人员和设备身份。
|
||
- 创建任务并保存原始输入,不让模型结果覆盖原始事实。
|
||
- 原子领取下一条任务,签发有期限的 claim lease。
|
||
- 校验任务状态迁移、幂等键和设备归属。
|
||
- 保存候选、事件、截图元数据和结果。
|
||
- 为管理 Web 提供任务列表、详情和取消能力。
|
||
|
||
不得:
|
||
|
||
- 根据 Web 按钮直接驱动某台手机点击。
|
||
- 在 Gin handler 中写任务状态机、SQL 或仓储业务逻辑。
|
||
- 保存拼多多密码、支付凭证或支付验证码。
|
||
|
||
T-201 已建立的基础包只有 `config`、`platform/database`、`platform/migration` 和
|
||
`transport/httpapi`。HTTP 进程使用非零 read-header/read/write/idle timeout、
|
||
1 MiB header 上限和 10 秒有界关闭;默认监听 `127.0.0.1:8080`,扩大监听范围必须
|
||
显式配置。Gin 不启用默认 Logger/CORS/Recovery,异常恢复返回稳定 JSON 且不记录
|
||
Authorization、Cookie 或 panic 内容。
|
||
|
||
SQLite 默认文件为被忽略的 `backend-api/var/cmroubao.db`,连接启用 foreign keys、
|
||
5 秒 busy timeout、WAL 和 immediate transaction,并限制单连接以匹配单进程 MVP。
|
||
数据库由 `cmd` 显式打开和关闭,不存在包级单例。Goose SQL 通过 `embed.FS` 加载,
|
||
`cmd/migrate` 显式执行 `up/down/status`。API 启动前检查所有迁移均已应用,不在
|
||
服务进程内自动改表;T-203 已用 `00002_tasks_and_assets.sql` 建立任务、资产、事件
|
||
和幂等记录表。
|
||
|
||
T-204 已用正式服务端 session 替换临时来源门禁,不重写 T-203 业务规则。Web 写
|
||
操作校验浏览器 Cookie 与表单字段的随机双提交 CSRF token;Cookie 管理 API 写请求
|
||
校验 `X-CSRF-Token`。明文 HTTP 只能监听 loopback,非 loopback 必须配置服务端
|
||
TLS certificate/key。
|
||
|
||
### 2.2 Android App
|
||
|
||
建议模块:
|
||
|
||
```text
|
||
ui/ # 登录、任务列表、执行状态、设置
|
||
taskclient/ # 后端 API、认证、幂等和重试
|
||
tasksource/ # 统一任务源;本地探针与后端 API 实现
|
||
workflow/ # ProcurementWorkflow 状态机
|
||
automation/ # Accessibility 节点、截图、点击、输入、滑动
|
||
ai/ # 结构化 AI、OpenAI 兼容适配器和本地安全配置
|
||
evidence/ # 截图、步骤日志、脱敏和上传
|
||
```
|
||
|
||
职责:
|
||
|
||
- 检查无障碍、拼多多安装/登录可用性、网络和当前任务状态。
|
||
- 由用户点击后领取任务;一台设备一次只运行一条。
|
||
- 执行有界步骤:解析 -> 搜索 -> 浏览不超过 5 个候选 -> 判断 -> 停止确认。
|
||
- 直接调用用户在本机明确配置的 VLM;没有可用模型时允许显式人工优先模式。
|
||
- 显示当前步骤、停止原因和人工接管入口。
|
||
- 用前台服务承载执行中任务,以服务端授权截止时间支持有限离线;进程重启后从加密
|
||
本地状态与服务端核对恢复或安全失败。
|
||
- 回传步骤事件、候选、截图和最终结果。
|
||
|
||
不得:
|
||
|
||
- 接收到通知就无条件开始采购。
|
||
- 只用固定屏幕坐标定位关键控件。
|
||
- 在验证码、登录、风险提示或未知页面上继续猜测操作。
|
||
- 在 MVP 点击最终“提交订单”或支付相关控件。
|
||
|
||
### 2.3 VLM Gateway
|
||
|
||
只负责两类能力:
|
||
|
||
1. **需求提取**:参考图 + 标题 + SKU -> 类目、属性、置信度、警告;搜索词只用于
|
||
诊断和人工模式,不替代正式链路的参考图召回。
|
||
2. **候选评估**:候选详情截图/文本 + 原始约束 -> 颜色/尺码硬约束结果、匹配项、
|
||
缺失项、拒绝原因、建议分。
|
||
|
||
正式数据流固定为 `Android App -> VLM Provider`。管理后端只下发原始任务并接收
|
||
结构化结果,不保存/下发 provider 配置或 Key,也不实现 `/tasks/{id}/ai/*` 代理。
|
||
后台任务不能携带或覆盖本机 provider、Base URL、model、prompt 或 API Key。
|
||
|
||
App 支持两个显式模式:
|
||
|
||
- `MANUAL_FIRST`:从原始标题/SKU 产生有界搜索词,由人员判断候选,不要求 VLM。
|
||
- `AI_ASSISTED`:使用 App 本地配置的 OpenAI 兼容 provider 做需求提取和候选评估。
|
||
|
||
模式在 execution 开始时固定并写入结果;AI 失败后只能由人员明确切换,不能静默降级。
|
||
App 同时固定 provider ID、model、prompt/schema version 和证据 SHA-256,作为非秘密
|
||
provenance 回传。Key、Authorization、完整 endpoint 和供应商原始响应正文不回传。
|
||
|
||
provider Key 从旧设置迁移到 Android Keystore 包装的加密存储;迁移或解密失败时
|
||
拒绝真实调用。每台设备使用独立、可撤销、有限额度的 Key。Keystore 只降低静态泄露
|
||
风险,不能宣称可抵抗已 Root 或完全受控设备。
|
||
|
||
模型输出是不可信建议,必须通过 schema 和确定性校验:
|
||
|
||
- `sku`、`quantity` 和 `max_budget` 使用原始任务值;数量和预算不进入模型 prompt。
|
||
- 原始 SKU 可唯一提取的颜色、尺码由本地代码固定为硬约束。候选必须对每个硬约束
|
||
返回 `MATCH/MISMATCH/UNKNOWN`;只有全部为 `MATCH` 才能进入自动 Top 5。
|
||
- 价格未知、超预算或关键属性无法确认时不能判为可接受。
|
||
- T-103 探针阈值为 `0.75`;低于阈值或标题/图片冲突时进入人工处理,不自动扩大
|
||
浏览范围。
|
||
- 模型不能返回点击坐标、状态迁移或“允许提交订单”等执行授权。
|
||
|
||
T-104/T-211 按候选 ordinal 串行评估,每个候选最多一次结构化调用、整批最多 5 次;首个
|
||
网络失败或无效输出停止后续调用。模型响应只允许
|
||
`schema_version/candidate_index/decision/score/matched/missing_or_uncertain/
|
||
rejection_reasons/confidence/hard_constraint_results`,其中 `decision` 仅允许
|
||
`REVIEW/REJECT/MANUAL_REQUIRED`。`hard_constraint_results` 必须逐项原样回显本地给出的
|
||
颜色和尺码期望值,并返回 `MATCH/MISMATCH/UNKNOWN` 与证据说明;缺项、重复、改写期望值
|
||
或 `UNKNOWN` 均不能进入自动 Top 5。ordinal、证据 SHA-256、建议候选、provider provenance、
|
||
`manual_review_required=true` 和 `order_submitted=false` 由本地代码确定。任务没有
|
||
预算时,模型声称价格或预算匹配会被视为无效输出。
|
||
|
||
T-103 的模型响应只允许
|
||
`schema_version/search_query/category/attributes/confidence/warnings`。属性来源限定为
|
||
`TITLE/IMAGE/BOTH`;额外字段、无效 JSON、重复或越界属性、坐标/动作语义均视为无效
|
||
输出并转人工。最终领域结果再由确定性代码补回原 SKU、数量、空预算、人工复核原因和
|
||
`provider/model/prompt_version/reference_image_sha256`。
|
||
|
||
技术探针和后台 execution 的每个逻辑步骤最多发出一次 VLM 请求,不沿用通用 Agent
|
||
的重试循环。该结构化 HTTP 客户端关闭连接自动重试和 HTTP/HTTPS 重定向,整体调用
|
||
超时为 75 秒;协程取消
|
||
会取消底层 HTTP call,成功响应正文上限为 64 KiB,非成功响应不读取正文。
|
||
|
||
Release 远程 provider 只允许 HTTPS;HTTP 仅允许 Debug 下无 API Key 的 `localhost`、
|
||
`127.0.0.1` 或 `::1` 本机 mock。端点不得包含 userinfo、query 或 fragment。标题
|
||
和 SKU 在进入
|
||
prompt 前分别限制为 2048 和 512 个 UTF-8 字节。JPEG 在调用前复核媒体类型、20 MiB
|
||
上限、魔数和 SHA-256,按声明长度一次分配并精确读取,Android 解码后最长边限制为
|
||
2048 px。请求和响应正文、Base64、API Key 不写普通日志;Keystore-backed 密钥存储
|
||
不可用且存在 Key 时拒绝调用。
|
||
|
||
## 三、核心数据流
|
||
|
||
### 3.1 技术探针
|
||
|
||
```text
|
||
本机私有蝦皮订单目录
|
||
-> Debug 导入工具按订单号配对文本和参考图
|
||
-> 规范化为 ProbeTask
|
||
-> Android FixtureTaskSource
|
||
-> 校验设备就绪
|
||
-> 校验参考图并提取 SKU 颜色/尺码硬约束
|
||
-> 将一次执行专用参考图写入受控 MediaStore
|
||
-> 通过拼多多可见图片搜索入口选择该图
|
||
-> 检查图片结果页最多 5 个候选
|
||
-> 过滤并排序颜色/尺码均确认匹配的 0..5 个候选
|
||
-> 到达候选详情或确认页
|
||
-> 停止并记录结论
|
||
```
|
||
|
||
该阶段不依赖后台,用于尽早判断 Android 自动化是否可行。
|
||
|
||
图片搜索临时资产只保存匿名执行标识,不使用订单号或标题作为文件名。App 在写入前
|
||
复核任务参考图的媒体类型、长度和 SHA-256,并在完成、失败或安全停止后 best-effort
|
||
删除自己创建的媒体。拼多多图片入口、相册页、当前图片或结果页无法唯一确认时停止;
|
||
不得从用户相册中猜选其他图片。图片搜索只负责召回,VLM 不能改变原始 SKU,也不能
|
||
把 `UNKNOWN` 规格提升为通过。
|
||
|
||
`TaskSource` 是工作流读取任务的唯一边界:
|
||
|
||
- 第一层使用 `FixtureTaskSource`,消费 Debug 导入工具生成的本地私有样本。
|
||
- 第二层替换为 `HttpTaskSource`,通过 Go-Gin 的 `claim-next` 和资产接口读取任务。
|
||
- 两种实现必须产生相同的领域字段;workflow、AI 和 automation 不得判断任务来自
|
||
文件还是 HTTP。
|
||
|
||
Debug 导入工具运行在开发机上,Android App 不能假定能直接访问 Windows 绝对路径。
|
||
工具读取用户显式配置的目录,将数据写入被 Git 忽略的 `.local/` 生成目录,再由
|
||
Debug 构建或测试装载。导入必须:
|
||
|
||
- 用文件主名关联订单号,不能用文本模糊匹配图片。
|
||
- 首版严格解析 UTF-8 四行标签 `店铺名:`、`商品标题:`、`SKU:`、`数量:`。
|
||
- 校验店铺名、标题、SKU、正整数数量和唯一且可解码的 JPEG 参考图。
|
||
- 对缺失、重复、编码无法识别和字段格式未知返回结构化错误。
|
||
- 不把完整订单号、店铺名、原图路径写入普通日志。
|
||
- 仅在用户明确配置 VLM 后发送必要的标题、SKU 和参考图;不发送订单号或店铺名。
|
||
- `RequirementProbeSource` 只从固定 asset 根读取已导入任务和引用图片;随后立即进入
|
||
大小、JPEG 魔数和 SHA-256 复核,不接受任意 cache 或绝对路径。
|
||
|
||
实现边界:
|
||
|
||
- `task-contract/`:纯 Kotlin `ProbeTask`、`TaskSource` 和版本化 JSON codec。
|
||
- `tools/shopee-importer/`:开发机 CLI,严格校验并生成匿名资产名。
|
||
- `app/src/debug/.../FixtureTaskSource`:只在 Debug 代码中读取
|
||
`assets/probe-fixtures/tasks.json`。
|
||
- `ProbeWorkflowCoordinator`:只依赖 `TaskSource`,未来可无缝替换 `HttpTaskSource`。
|
||
|
||
私有 fixture 只有在构建显式设置 `-PprobeFixturesDir` 时注入 Debug APK;普通构建
|
||
必须同步清空生成资产,防止上一次真实样本残留。
|
||
|
||
T-101 的固定词搜索已落在以下运行边界:
|
||
|
||
```text
|
||
SearchProbeScreen
|
||
-> WorkflowRunner(timeout / retry / stop)
|
||
-> PinduoduoSearchAutomation(纯 Kotlin)
|
||
-> AndroidPinduoduoUiDriver
|
||
-> BuyerAccessibilityBridge
|
||
-> BuyerAccessibilityService
|
||
-> 拼多多可见、启用、唯一语义节点
|
||
```
|
||
|
||
结果页成功不是“点击已发送”,而是同时确认精确查询词、搜索结果头和至少 3 个排序
|
||
控件。`ACTION_SET_TEXT` 后必须重新读取当前根节点并精确比对输入;任何稳定未知页以及
|
||
登录、验证码、风控、订单或支付边界都终止 workflow。该路径不提供坐标、ADB、
|
||
Shizuku shell、OCR 或 VLM 动作降级。
|
||
|
||
T-102/T-104 在搜索结果后追加一个有界候选步骤:
|
||
|
||
```text
|
||
精确匹配固定词或当前结构化需求词的结果页
|
||
-> 最多 2 次滚动预算
|
||
-> 最多 5 个去重商品卡
|
||
-> 验证详情页
|
||
-> 详情页无障碍截图
|
||
-> 受控 DISCOVERY_INSPECT 规格入口
|
||
-> 选择目标颜色/尺码并读取组合价格
|
||
-> 规格弹层语义和截图
|
||
-> App cache/candidate-NN-{detail,specification}.png + manifest.json
|
||
-> 关闭规格弹层并复核详情页
|
||
-> 一次全局返回并复核固定词结果页
|
||
```
|
||
|
||
候选卡只保留语义指纹和计数,详情/规格截图保存在 App 内部 cache。manifest v3 记录
|
||
匿名文件名、截图 SHA-256、字节数、尺寸、有界规格语义、已选摘要和严格组合价格,
|
||
不保存商品标题或完整页面
|
||
原文。T-104/T-213 使用这些证据时必须通过受控 evidence 边界读取,不能让 VLM adapter
|
||
自行遍历 cache。Android 10/API 29 及以下不能运行当前截图探针,应在预检时明确
|
||
不支持,不使用媒体投影或 shell 绕过。
|
||
|
||
`CandidateEvidenceSource` 只接受当前 workflow 内存中的连续 ordinal 元数据,文件名
|
||
固定为 `candidate-01-detail.png`/`candidate-01-specification.png` 至第五组;每张
|
||
PNG 最多 8 MiB、全批最多 64 MiB、声明尺寸最长边不超过 10000 px,并复核规范路径、
|
||
PNG/IHDR、精确字节数、尺寸和 SHA-256。每个候选回传时必须恰好绑定
|
||
`DETAIL`/`SPECIFICATION` 两份证据,生成独立 asset ID;先验证全批 ordinal、类型和
|
||
哈希再写入加密 outbox,候选的 asset ID 数组固定按 `DETAIL`、`SPECIFICATION`
|
||
排序,重试沿用各自 idempotency key。Android gateway 再解码详情图并把最长边缩至
|
||
2048 px。当前需求快照与搜索词必须同时匹配候选 session,否则拒绝评估;另一个
|
||
关键词的结果页只允许重新进入搜索框,不能采集候选。
|
||
|
||
T-213 规格核验优先使用“选择规格/请选择规格/颜色分类/颜色款式/选择颜色/选择尺码”
|
||
或“已选/请选择”前缀的唯一入口;拼多多 8.17 没有直接入口时,`DISCOVERY_INSPECT`
|
||
允许从已验证详情页点击唯一“免拼购买”语义目标。弹层最多读取 160 个语义节点、
|
||
每组最多 30 个选项,目标颜色/尺码必须各自唯一、启用且每次点击后可复核选择状态。
|
||
选定后读取明确人民币组合价格;重复、缺组、禁用、滚动截断、状态未变化、多价格冲突
|
||
或无障碍语义不足一律 `UNKNOWN/MISMATCH`,不得继续到订单提交。
|
||
|
||
候选评估 schema/prompt v3 把本地规格结果作为权威输入;模型返回的颜色/尺码状态必须
|
||
逐项与本地结果相同,否则整批降级人工检查。详情和规格证据 SHA-256 同时绑定
|
||
assessment、重排身份和 outbox 资产,VLM 不能通过主图推断覆盖本地硬约束。
|
||
|
||
PKG110 上的拼多多 8.17.0 服装详情只暴露不可点击的“颜色/款式”预览;完整颜色/尺码
|
||
和组合价格位于“单独购买/免拼购买”后的规格弹层。业务方已确认最终流程需要自动创建
|
||
待付款订单,因此 T-213 重新定义两级能力中的第一层:`DISCOVERY_INSPECT` 可以从
|
||
已验证详情页进入规格弹层并选择目标颜色/尺码以读取组合价格,但不能加入购物车、
|
||
提交订单或触发支付。后续 `ORDER_CREATE` 必须由 Admin 对确定候选签发一次性授权,
|
||
使用独立状态机和幂等/对账边界,不能复用候选探查权限。
|
||
|
||
`DISCOVERY_INSPECT` 从任务 SKU 的本地规范化结果取得唯一颜色/尺码,按颜色后尺码
|
||
的固定顺序选择,每次动作后重新读取 selected 与“已选择”摘要;规格区域最多向前
|
||
滚动 2 次。价格只接受单一、完整的人民币文本并保存分值,区间、券后、条件价格、
|
||
多价格冲突或无价格一律降级 `UNKNOWN`。若唯一入口落到订单确认页,自动化只能执行
|
||
系统返回并跳过该候选。
|
||
|
||
评估结束后 automation 不再产生动作,UI 进入 `AWAITING_CONFIRMATION`、
|
||
`MANUAL_REVIEW` 或 `NO_MATCH`。人员可以把本地建议项标记为可用,或拒绝本次候选;
|
||
这只是验证结果,所有状态的 `order_submitted` 均为 `false`。
|
||
|
||
需求提取是独立探针,不启动拼多多,也不接入 `MobileAgent`。GUI-Owl 和 MAI-UI 输出
|
||
动作/坐标,明确不具备 `supportsRequirementExtraction` 能力。真实供应商未确认前,
|
||
普通测试只使用 Fake gateway;本机 OpenAI 兼容 mock 仅验证 Android 请求链路和隐私
|
||
边界,不作为真实模型效果证据。
|
||
|
||
### 3.2 MVP 业务闭环
|
||
|
||
```text
|
||
管理员创建 PENDING 任务
|
||
-> 采购员在 App 点击“获取任务”
|
||
-> 后端事务内选取并置为 CLAIMED
|
||
-> App 确认后置为 RUNNING,取得有限离线授权
|
||
-> best-effort heartbeat 同步步骤和取消
|
||
-> App 本地需求提取和拼多多自动化
|
||
-> WAITING_CONFIRMATION
|
||
-> 采购员接受/拒绝候选
|
||
-> 加密 outbox 幂等回传结果和证据
|
||
-> SUCCEEDED 或 FAILED/CANCELED
|
||
-> 管理端展示结果
|
||
```
|
||
|
||
## 四、状态机
|
||
|
||
### 4.1 任务状态
|
||
|
||
```text
|
||
PENDING
|
||
| claim
|
||
v
|
||
CLAIMED ---- lease expired/release ----> PENDING
|
||
| start
|
||
v
|
||
RUNNING
|
||
| candidate ready
|
||
v
|
||
WAITING_CONFIRMATION
|
||
| accept validation result
|
||
v
|
||
SUCCEEDED
|
||
|
||
PENDING/CLAIMED/RUNNING/WAITING_CONFIRMATION
|
||
+--> CANCELED(仅允许规则规定的主体取消)
|
||
|
||
CLAIMED/RUNNING/WAITING_CONFIRMATION
|
||
+--> FAILED(结构化错误)
|
||
```
|
||
|
||
规则:
|
||
|
||
- `SUCCEEDED` 是验证结果成功,不代表已下单;`order_submitted=false`。
|
||
- `CLAIMED` 默认租约为 10 分钟;`RUNNING/WAITING_CONFIRMATION` 默认授权为
|
||
30 分钟,允许范围 5 至 120 分钟。App 每 30 秒 best-effort heartbeat,成功后
|
||
按服务端时间滑动续期。
|
||
- 只有当前设备和有效 claim token 能启动、续租、上报或结束任务。
|
||
- 终态不可回退;重试创建新的 execution attempt,不篡改历史证据。
|
||
- App 在 claim 前生成并安全保存 256 bit Raw URL token;后端只存 SHA-256,
|
||
token 与 `Idempotency-Key` 相互独立。
|
||
- 过期 `CLAIMED` 可以原子回收;过期 `RUNNING/WAITING_CONFIRMATION` 保持原状态,
|
||
不自动回队列。App 到达服务端授权截止时间持久化 `SAFE_STOPPED` 并停止前台
|
||
执行;原设备重连 heartbeat 只同步状态且不延长已过期授权。T-207 才允许原设备
|
||
补报授权内已产生的终态结果并留下过期补报审计标志。
|
||
- 管理取消 `PENDING/CLAIMED` 立即终态;执行中只设置停止请求,App 在下一个安全
|
||
检查点停止并调用 `cancel-ack` 后才进入 `CANCELED`。离线期间不能保证即时取消,
|
||
App 恢复连接后必须先同步取消状态再继续页面动作。
|
||
|
||
T-206 Android 使用独立 `ProcurementSecureStore` 保存 backend URL、BUYER access
|
||
token、设备 secret、claim token、各操作幂等 key、任务快照和 execution。存储创建
|
||
失败时整个后台采购入口 fail closed,不回退普通 `SharedPreferences`。登录密码只
|
||
存在于当前 Compose 输入和一次请求中;后台任务不能读取或修改 Roubao provider。
|
||
`HttpTaskSource` 只把已领取任务和经过 MIME/JPEG/大小/SHA-256 校验的内部参考图
|
||
映射为现有 `ProbeTask`,不参与领取策略。
|
||
|
||
### 4.2 Android 工作流状态
|
||
|
||
```text
|
||
IDLE
|
||
-> PREFLIGHT
|
||
-> REQUIREMENT_ANALYSIS
|
||
-> OPEN_PDD
|
||
-> SEARCH
|
||
-> SCAN_RESULTS
|
||
-> REVIEW_CANDIDATE
|
||
-> WAITING_USER
|
||
-> FINISHED
|
||
|
||
任意执行态 -> STOPPING -> FAILED/CANCELED
|
||
```
|
||
|
||
每一步必须有:
|
||
|
||
- 进入条件和预期页面特征。
|
||
- 最大等待时间和最大重试次数。
|
||
- 可观察事件。
|
||
- 允许的下一步。
|
||
- 安全停止条件。
|
||
|
||
## 五、数据模型
|
||
|
||
所有主键使用服务端生成的 UUID;时间统一为 UTC 的 ISO 8601。
|
||
|
||
### `users`
|
||
|
||
| 字段 | 约束 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | PK | 用户 ID |
|
||
| `username` | UNIQUE, NOT NULL | 登录名 |
|
||
| `password_hash` | NOT NULL | 不保存明文 |
|
||
| `role` | NOT NULL | MVP 为 `ADMIN` 或 `BUYER` |
|
||
| `is_active` | NOT NULL | 禁用后现有和新凭证均不可使用 |
|
||
| `created_at` | NOT NULL | 创建时间 |
|
||
|
||
### `devices`
|
||
|
||
| 字段 | 约束 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | PK | 设备 ID |
|
||
| `name` | NOT NULL | 人类可识别名称 |
|
||
| `token_hash` | NOT NULL | 设备令牌哈希 |
|
||
| `bound_user_id` | FK, nullable | 当前绑定采购员 |
|
||
| `app_version` | nullable | App 版本 |
|
||
| `android_version` | nullable | Android 系统版本 |
|
||
| `pdd_version` | nullable | 已验证拼多多版本 |
|
||
| `last_seen_at` | nullable | 最近心跳 |
|
||
| `readiness_reported_at` | nullable | 最近一次就绪上报的服务端时间 |
|
||
| `accessibility_enabled` | NOT NULL | 无障碍连接就绪位 |
|
||
| `pdd_installed` | NOT NULL | 拼多多安装就绪位 |
|
||
| `is_enabled` | NOT NULL | 后端开关 |
|
||
|
||
设备必须先由本地管理命令预授权。首次 BUYER 联合登录可以把 `bound_user_id` 为空的
|
||
设备原子绑定给当前采购员,不支持客户端自助重新登记或抢占其他采购员的设备。
|
||
|
||
### `admin_sessions` / `access_tokens`
|
||
|
||
- session/access token 都使用至少 256 bit 的随机 opaque secret,原值只通过
|
||
`Set-Cookie` 或登录响应返回一次,数据库只存 SHA-256。
|
||
- 管理 session 固定 8 小时绝对有效;App access token 固定 1 小时。
|
||
- 每次鉴权都联查用户/设备启用状态,因此禁用立即生效,不等待 token 到期。
|
||
- 管理 Cookie 与 App Bearer token 使用不同 middleware,不能互相替代。
|
||
- T-203 的 `local-admin` 继续作为单管理共享资源 scope;真实 `user_id` 另作 actor,
|
||
避免切换身份后隐藏已有任务。
|
||
- 管理和 App 登录在 bcrypt 前共享一个内存有界限流器,但使用独立 scope + 服务端
|
||
`RemoteAddr` 键;成功登录清零,超限返回 `429` 与 `Retry-After`。
|
||
|
||
### `purchase_tasks`
|
||
|
||
| 字段 | 约束 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | PK | 任务 ID |
|
||
| `created_by_user_id` | FK, nullable | 新任务的真实 ADMIN actor;历史任务允许为空 |
|
||
| `title` | NOT NULL | 原始标题 |
|
||
| `description` | NOT NULL | 原始说明,可为空字符串 |
|
||
| `sku` | NOT NULL | 原始 SKU,不得由模型改写 |
|
||
| `image_asset_id` | FK, NOT NULL | 原始参考图 |
|
||
| `quantity` | `> 0` | 权威数量 |
|
||
| `max_budget` | `> 0`, nullable | 全部数量的权威最高商品总预算,币种为 CNY |
|
||
| `status` | NOT NULL | 任务状态枚举 |
|
||
| `claimed_by_user_id` | FK, nullable | 当前采购员 |
|
||
| `claimed_by_device_id` | FK, nullable | 当前设备 |
|
||
| `claim_generation` | `>= 0` | 每次领取递增,释放后保留用于审计 |
|
||
| `claim_token_hash` | nullable | 64 位小写 SHA-256;永不保存原 token |
|
||
| `claim_issued_at` | nullable | 当前 claim 签发时间 |
|
||
| `claim_expires_at` | nullable | 租约到期时间 |
|
||
| `cancel_reason` | nullable | 管理取消原因 |
|
||
| `cancel_requested_at` | nullable | 执行中停止请求时间 |
|
||
| `cancel_requested_by_user_id` | FK, nullable | 请求停止的 ADMIN |
|
||
| `canceled_at` | nullable | 取消终态时间 |
|
||
| `version` | NOT NULL | 乐观锁/状态并发控制 |
|
||
| `created_at/updated_at` | NOT NULL | 审计时间 |
|
||
|
||
### `task_events`
|
||
|
||
- 事件只追加;T-205 已实现 `TASK_CREATED`、`TASK_CLAIMED`、
|
||
`TASK_RECLAIMED`、`TASK_RELEASED`、`TASK_STARTED`、
|
||
`TASK_CANCEL_REQUESTED`、`TASK_CANCELED`。
|
||
- `actor_user_id` 是指向 `users` 的可空外键。新管理操作必须写入真实 ADMIN,
|
||
T-203 历史事件保持为空。
|
||
- `actor_device_id` 是指向 `devices` 的可空外键;App 状态迁移同时记录用户和设备。
|
||
- 设备/任务 heartbeat 不写事件,避免高频审计膨胀。
|
||
- 管理任务详情 API 可返回 actor;密码、session、设备 secret 和 access token 永不
|
||
进入事件。
|
||
|
||
### `task_executions`
|
||
|
||
| 字段 | 约束 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | PK | 一次执行尝试 |
|
||
| `task_id` | FK | 所属任务 |
|
||
| `attempt_no` | UNIQUE(task, no) | 尝试序号 |
|
||
| `claim_generation` | UNIQUE(task, generation) | 对应的 claim 代次 |
|
||
| `device_id/user_id` | FK | 执行设备和人员 |
|
||
| `current_step` | 1-64 bytes | 最近 heartbeat 的执行步骤 |
|
||
| `last_heartbeat_at` | NOT NULL | 最近运行 heartbeat |
|
||
| `order_submitted` | NOT NULL, false | MVP 数据库约束必须为 false |
|
||
| `started_at/finished_at` | started 必填 | 执行耗时和安全结束 |
|
||
|
||
候选、模型派生结果、outcome、结构化失败和 execution evidence 属于 T-207,不在
|
||
T-205 的最小 execution 表中提前伪造。
|
||
|
||
### `lifecycle_requests`
|
||
|
||
- 主键为 `user_id + device_id + operation + idempotency_key`。
|
||
- operation 为 `CLAIM_NEXT`、`START`、`RELEASE`、`CANCEL_ACK`;保存请求 SHA-256
|
||
与 task/generation/execution 结果引用。
|
||
- `NO_TASK` 也是稳定结果;同 key 重放不会因后来新增任务而改变。
|
||
- claim 原 token 不进入该表,请求 hash 只包含 token hash。
|
||
|
||
### `assets` 与后续 `execution_events`
|
||
|
||
- T-207 的 `execution_events` 只追加,记录 step、事件类型、可读消息和时间;不保存
|
||
密码或 token。
|
||
- `assets` 保存文件相对路径、媒体类型、大小、哈希、创建者和保留时间。
|
||
- 原图和截图必须通过鉴权接口读取,文件名不能直接作为公开 URL。
|
||
- 任务参考图上传可接受 JPEG/PNG/WebP,但后端必须先真实解码、限制字节与像素,再
|
||
统一规范化为匿名 JPEG;未来 Android 读取的资产不能依赖原始扩展名或声明 MIME。
|
||
|
||
### 后置候选决策数据集(T-208)
|
||
|
||
T-207 先完成候选、事件、截图和最小结果回传。T-208 在 `task_executions` 下增加
|
||
独立、不可变的数据层,不把整批候选塞进 `task_events`、`purchase_tasks` JSON 或
|
||
一段不可查询的自由文本:
|
||
|
||
| 表 | 主要内容 |
|
||
| --- | --- |
|
||
| `candidate_search_runs` | execution、需求快照 hash、搜索词、App/拼多多版本、开始/结束时间 |
|
||
| `candidate_observations` | run、ordinal、可选平台商品 ID/规范化 URL、可见标题/规格/价格、采集状态和截图 asset |
|
||
| `model_runs` | provider/model、prompt/schema/阈值版本、耗时、token/成本和请求/结果 hash |
|
||
| `candidate_evaluations` | observation/model run、decision、score/confidence、matched/missing/rejection reasons |
|
||
| `candidate_recommendations` | run、推荐 observation、conclusion、确定性策略版本和简短理由 |
|
||
| `candidate_human_reviews` | run、最终 outcome、选择项、actor、理由 schema、时间和可空 `supersedes_review_id` |
|
||
| `candidate_human_review_items` | review 下每个候选的 `ACCEPT/REJECT`、主要理由和可选备注 |
|
||
| `candidate_human_review_reasons` | review item 的多值结构化理由码 |
|
||
|
||
四层数据语义不可混用:
|
||
|
||
- observation 是 App 当时实际看到的页面事实。
|
||
- prediction 是特定模型版本的输出。
|
||
- recommendation 是本地确定性规则的结果。
|
||
- human label 是采购人员明确提交的结论,才可作为离线准确率标签。
|
||
|
||
现有 `assets.purpose` 在 T-207/T-208 migration 中按实际需要扩展
|
||
`CANDIDATE_SCREENSHOT`、`CANDIDATE_IMAGE` 和 `EXECUTION_EVIDENCE`。第三方商品或
|
||
图片 URL 只保存为长度受限、规范化的辅助观测值;后端不得盲目请求该 URL。主要证据
|
||
必须是 App 上传后由 assetstore 校验、脱敏和受鉴权访问的时间点快照。
|
||
|
||
人工理由使用版本化 allowlist。接受或拒绝至少有一个理由且指定主要理由;
|
||
`OTHER` 才要求 4-200 字备注。拒绝推荐后改选必须同时产生一条原推荐项负标签和一条
|
||
替代项正标签;全部无匹配时每个曝光候选都有负标签。人工修正追加新 review 并引用
|
||
旧 review,不能覆盖历史。模型理由可以展示给人员参考,但不能自动成为人工理由。
|
||
|
||
## 六、API 和并发边界
|
||
|
||
- 合约以 [`api.md`](api.md) 为准。
|
||
- `claim-next` 必须由 usecase 调用 repository,在一个数据库事务内完成
|
||
“幂等检查 + 就绪/单活校验 + FIFO 选取/过期回收 + 更新 + 事件”。
|
||
- 当前设备若有自己的过期 `CLAIMED`,优先原子回收该任务;没有时才在全局候选中
|
||
按 `created_at ASC, id ASC` 选择。该例外保持设备唯一归属并避免静默清理审计状态。
|
||
- SQLite DSN 使用 `_txlock=immediate`、WAL 和 busy timeout;跨两个独立数据库连接
|
||
的并发测试必须精确得到一个领取者。
|
||
- claim/start/release/cancel-ack 支持 `Idempotency-Key`;heartbeat 不需要幂等表。
|
||
- App 对网络超时不能假定失败;必须用任务详情确认服务端最终状态。
|
||
- 后端拒绝非法状态迁移,即使客户端 UI 隐藏了对应按钮。
|
||
- App 参考图通过 task-scoped URL 读取;每次校验当前 user/device/generation/token/
|
||
租约,不允许用可猜测 asset ID 越权读取其他任务。
|
||
- SQLite MVP 使用单后端进程;切换多实例前先迁移 PostgreSQL 并验证领取竞争。
|
||
- Gin handler 只做鉴权上下文、binding、调用 usecase 和响应映射。
|
||
|
||
## 七、自动化与安全边界
|
||
|
||
- 只允许目标拼多多包处于前台时发送动作。
|
||
- 关键动作前后都校验包名、页面特征和任务取消标志。
|
||
- 节点选择要求可见、启用且唯一匹配;优先可访问性语义、稳定文本和受限祖先层级,
|
||
不能因资源 ID 重复而任取第一个节点。
|
||
- 已验证的首页、搜索输入和结果页路径禁止坐标降级;新页面必须先取得版本化真机
|
||
证据并在独立任务中定义识别条件。
|
||
- 候选 session 的候选尝试与滚动预算在动作发出前扣减,retry 不得重置;详情动作
|
||
白名单只有截图和返回。
|
||
- 截图发送给模型前裁剪无关区域并按配置脱敏。
|
||
- 每个动作记录抽象步骤,不默认记录完整输入文本。
|
||
- 发现验证码、风险控制、支付、生物识别或系统权限页面立即停止。
|
||
- MVP 用代码级 allowlist 禁止所有已知“提交订单/支付”动作;不能仅靠提示词约束模型。
|
||
|
||
## 八、错误分类
|
||
|
||
| 错误码前缀 | 示例 | 处理 |
|
||
| --- | --- | --- |
|
||
| `DEVICE_` | 权限缺失、拼多多未安装 | 修复设备后再领取 |
|
||
| `AUTH_` | 登录失效、设备禁用 | 停止并重新认证 |
|
||
| `TASK_` | 租约失效、状态冲突 | 刷新服务端状态 |
|
||
| `AI_` | 超时、输出无效、低置信度 | 有界重试或转人工 |
|
||
| `PDD_` | 未知页面、验证码、风控 | 立即停止,不绕过 |
|
||
| `NETWORK_` | API 超时、上传失败 | 保留本地证据,幂等重试 |
|
||
| `SAFETY_` | 即将进入禁止动作 | 阻断并记录高优先级事件 |
|
||
|
||
## 九、开发顺序
|
||
|
||
1. 接入 Android 基线并记录真实构建方式。
|
||
2. 导入一条脱敏蝦皮样本,以固定搜索词跑通拼多多页面探针。
|
||
3. 接入需求提取和候选判断,验证结构化输出。
|
||
4. 建立后端数据模型、API 和简单管理 Web。
|
||
5. 接入 App 领取、租约、进度和结果。
|
||
6. 20 条真实任务试验;根据数据决定是否进入 V2。
|
||
|
||
## 十、关键风险
|
||
|
||
| 风险 | 影响 | 应对 |
|
||
| --- | --- | --- |
|
||
| 拼多多 UI/风控变化 | 自动化失败或账号受限 | 有界步骤、版本基线、未知即停、人工接管 |
|
||
| VLM 误判 | 候选不匹配或违反预算 | schema + 硬约束复核 + 人工确认 |
|
||
| Android 后台限制 | 任务中断 | 前台服务、心跳、恢复状态机 |
|
||
| 网络重复请求 | 重复领取或重复结果 | 幂等键、版本号、事务和状态查询 |
|
||
| 图片/截图隐私 | 数据泄露 | 鉴权存储、脱敏、保留期限、密钥后端化 |
|
||
| 需求膨胀 | 跳过核心可行性验证 | 严格按 Phase 和 P0 范围执行 |
|