366 lines
14 KiB
Markdown
366 lines
14 KiB
Markdown
# 架构设计
|
||
|
||
## 一、系统结构
|
||
|
||
目标架构是一套后端服务、两个面向不同角色的客户端:
|
||
|
||
```text
|
||
采购管理员
|
||
|
|
||
v
|
||
管理 Web(服务端渲染)
|
||
|
|
||
v
|
||
统一 Backend API ---------------------+
|
||
| |
|
||
+--> SQLite / 文件存储 +--> VLM Provider
|
||
^
|
||
|
|
||
Android 采购 App
|
||
|
|
||
+--> AccessibilityService
|
||
|
|
||
v
|
||
拼多多 App
|
||
```
|
||
|
||
- 管理 Web 与 API 同属 `backend-api/`,但页面层不能直接访问数据库。
|
||
- Android App 位于 `android-buyer/`,通过 API 领取任务、调用 AI 能力和回传证据。
|
||
- 拼多多是第三方受控边界,只能由 Android 设备在已登录会话中操作。
|
||
- VLM 密钥的正式路径保留在后端;技术探针如需端上临时调用,只能使用本地忽略配置。
|
||
|
||
## 二、模块职责
|
||
|
||
### 2.1 Backend API
|
||
|
||
建议分层:
|
||
|
||
```text
|
||
cmd/api/ # 进程入口、依赖装配、信号和优雅关闭
|
||
internal/transport/http/ # Gin 路由、中间件、页面和请求/响应 DTO
|
||
internal/usecase/ # 创建、领取、状态迁移和结果归档
|
||
internal/domain/ # 实体、状态机、权限和确定性约束
|
||
internal/repository/sqlite/ # database/sql 仓储实现
|
||
internal/platform/ # 配置、日志、文件存储和 VLM 适配器
|
||
```
|
||
|
||
职责:
|
||
|
||
- 验证管理会话、采购人员和设备身份。
|
||
- 创建任务并保存原始输入,不让模型结果覆盖原始事实。
|
||
- 原子领取下一条任务,签发有期限的 claim lease。
|
||
- 校验任务状态迁移、幂等键和设备归属。
|
||
- 代理 VLM 调用并把供应商响应转换为领域结构。
|
||
- 保存候选、事件、截图元数据和结果。
|
||
- 为管理 Web 提供任务列表、详情和取消能力。
|
||
|
||
不得:
|
||
|
||
- 根据 Web 按钮直接驱动某台手机点击。
|
||
- 在 Gin handler 中写任务状态机、SQL 或仓储业务逻辑。
|
||
- 保存拼多多密码、支付凭证或支付验证码。
|
||
|
||
### 2.2 Android App
|
||
|
||
建议模块:
|
||
|
||
```text
|
||
ui/ # 登录、任务列表、执行状态、设置
|
||
taskclient/ # 后端 API、认证、幂等和重试
|
||
tasksource/ # 统一任务源;本地探针与后端 API 实现
|
||
workflow/ # ProcurementWorkflow 状态机
|
||
automation/ # Accessibility 节点、截图、点击、输入、滑动
|
||
ai/ # 结构化 AI 请求/响应,不含供应商业务类型
|
||
evidence/ # 截图、步骤日志、脱敏和上传
|
||
```
|
||
|
||
职责:
|
||
|
||
- 检查无障碍、拼多多安装/登录可用性、网络和当前任务状态。
|
||
- 由用户点击后领取任务;一台设备一次只运行一条。
|
||
- 执行有界步骤:解析 -> 搜索 -> 浏览不超过 5 个候选 -> 判断 -> 停止确认。
|
||
- 显示当前步骤、停止原因和人工接管入口。
|
||
- 用前台服务承载执行中任务,进程重启后从服务端状态恢复或安全失败。
|
||
- 回传步骤事件、候选、截图和最终结果。
|
||
|
||
不得:
|
||
|
||
- 接收到通知就无条件开始采购。
|
||
- 只用固定屏幕坐标定位关键控件。
|
||
- 在验证码、登录、风险提示或未知页面上继续猜测操作。
|
||
- 在 MVP 点击最终“提交订单”或支付相关控件。
|
||
|
||
### 2.3 VLM Gateway
|
||
|
||
只负责两类能力:
|
||
|
||
1. **需求提取**:图片 + 标题 + 描述 -> 搜索词、属性、置信度、警告。
|
||
2. **候选评估**:候选截图/文本 + 原始约束 -> 匹配项、缺失项、拒绝原因、建议分。
|
||
|
||
模型输出是不可信建议,必须通过 schema 和确定性校验:
|
||
|
||
- `quantity` 和 `max_budget` 使用原始任务值。
|
||
- 价格未知、超预算或关键属性无法确认时不能判为可接受。
|
||
- 置信度低于配置阈值时进入人工处理,不自动扩大浏览范围。
|
||
- 模型不能返回点击坐标、状态迁移或“允许提交订单”等执行授权。
|
||
|
||
## 三、核心数据流
|
||
|
||
### 3.1 技术探针
|
||
|
||
```text
|
||
本机私有蝦皮订单目录
|
||
-> Debug 导入工具按订单号配对文本和参考图
|
||
-> 规范化为 ProbeTask
|
||
-> Android FixtureTaskSource
|
||
-> 校验设备就绪
|
||
-> 解析为固定/模型搜索词
|
||
-> 打开拼多多并搜索
|
||
-> 检查最多 5 个候选
|
||
-> 到达候选详情或确认页
|
||
-> 停止并记录结论
|
||
```
|
||
|
||
该阶段不依赖后台,用于尽早判断 Android 自动化是否可行。
|
||
|
||
`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 和参考图;不发送订单号或店铺名。
|
||
|
||
实现边界:
|
||
|
||
- `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 动作降级。
|
||
|
||
### 3.2 MVP 业务闭环
|
||
|
||
```text
|
||
管理员创建 PENDING 任务
|
||
-> 采购员在 App 点击“获取任务”
|
||
-> 后端事务内选取并置为 CLAIMED
|
||
-> App 确认后置为 RUNNING
|
||
-> 定期 heartbeat 续租并上报步骤
|
||
-> 需求提取和拼多多自动化
|
||
-> WAITING_CONFIRMATION
|
||
-> 采购员接受/拒绝候选
|
||
-> 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 分钟,运行时每 30 秒心跳续租;数值进入配置。
|
||
- 只有当前设备和有效 claim token 能启动、续租、上报或结束任务。
|
||
- 终态不可回退;重试创建新的 execution attempt,不篡改历史证据。
|
||
- 取消正在自动化的任务时,App 应在下一个安全检查点停止。
|
||
|
||
### 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 版本 |
|
||
| `pdd_version` | nullable | 已验证拼多多版本 |
|
||
| `last_seen_at` | nullable | 最近心跳 |
|
||
| `is_enabled` | NOT NULL | 后端开关 |
|
||
|
||
### `purchase_tasks`
|
||
|
||
| 字段 | 约束 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | PK | 任务 ID |
|
||
| `title` | NOT NULL | 原始标题 |
|
||
| `description` | NOT NULL | 原始说明,可为空字符串 |
|
||
| `image_asset_id` | FK, NOT NULL | 原始参考图 |
|
||
| `quantity` | `> 0` | 权威数量 |
|
||
| `max_budget` | `> 0`, nullable | 权威最高预算,币种为 CNY |
|
||
| `status` | NOT NULL | 任务状态枚举 |
|
||
| `created_by` | FK | 创建人 |
|
||
| `claimed_by_device_id` | FK, nullable | 当前设备 |
|
||
| `claim_expires_at` | nullable | 租约到期时间 |
|
||
| `version` | NOT NULL | 乐观锁/状态并发控制 |
|
||
| `created_at/updated_at` | NOT NULL | 审计时间 |
|
||
|
||
### `task_executions`
|
||
|
||
| 字段 | 约束 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | PK | 一次执行尝试 |
|
||
| `task_id` | FK | 所属任务 |
|
||
| `attempt_no` | UNIQUE(task, no) | 尝试序号 |
|
||
| `device_id/user_id` | FK | 执行设备和人员 |
|
||
| `extracted_requirements` | JSON | 已校验的模型结果 |
|
||
| `candidate_result` | JSON, nullable | 候选及匹配理由 |
|
||
| `outcome` | nullable | `CANDIDATE_ACCEPTED`、`CANDIDATE_REJECTED`、`NO_MATCH` 或 `MANUAL_REQUIRED` |
|
||
| `order_submitted` | NOT NULL, false | MVP 数据库约束必须为 false |
|
||
| `error_code/error_message` | nullable | 结构化失败 |
|
||
| `started_at/finished_at` | nullable | 执行耗时 |
|
||
|
||
### `execution_events` 与 `assets`
|
||
|
||
- `execution_events` 只追加,记录 step、事件类型、可读消息和时间;不保存密码或 token。
|
||
- `assets` 保存文件相对路径、媒体类型、大小、哈希、创建者和保留时间。
|
||
- 原图和截图必须通过鉴权接口读取,文件名不能直接作为公开 URL。
|
||
|
||
## 六、API 和并发边界
|
||
|
||
- 合约以 [`api.md`](api.md) 为准。
|
||
- `claim-next` 必须由 usecase 调用 repository,在一个数据库事务内完成
|
||
“选取 + 校验设备空闲 + 更新状态”。
|
||
- 创建、领取和完成接口支持 `Idempotency-Key`。
|
||
- App 对网络超时不能假定失败;必须用任务详情确认服务端最终状态。
|
||
- 后端拒绝非法状态迁移,即使客户端 UI 隐藏了对应按钮。
|
||
- SQLite MVP 使用单后端进程;切换多实例前先迁移 PostgreSQL 并验证领取竞争。
|
||
- Gin handler 只做鉴权上下文、binding、调用 usecase 和响应映射。
|
||
|
||
## 七、自动化与安全边界
|
||
|
||
- 只允许目标拼多多包处于前台时发送动作。
|
||
- 关键动作前后都校验包名、页面特征和任务取消标志。
|
||
- 节点选择要求可见、启用且唯一匹配;优先可访问性语义、稳定文本和受限祖先层级,
|
||
不能因资源 ID 重复而任取第一个节点。
|
||
- 已验证的首页、搜索输入和结果页路径禁止坐标降级;新页面必须先取得版本化真机
|
||
证据并在独立任务中定义识别条件。
|
||
- 截图发送给模型前裁剪无关区域并按配置脱敏。
|
||
- 每个动作记录抽象步骤,不默认记录完整输入文本。
|
||
- 发现验证码、风险控制、支付、生物识别或系统权限页面立即停止。
|
||
- 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 范围执行 |
|