Files
cmroubao/docs/04-architecture.md
T

19 KiB
Raw Blame History

架构设计

一、系统结构

目标架构是一套后端服务、两个面向不同角色的客户端:

采购管理员
   |
   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

建议分层:

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 或仓储业务逻辑。
  • 保存拼多多密码、支付凭证或支付验证码。

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;T-201 只验证迁移链路,业务表由后续任务 按 API 和领域模型建立。

2.2 Android App

建议模块:

ui/                  # 登录、任务列表、执行状态、设置
taskclient/          # 后端 API、认证、幂等和重试
tasksource/          # 统一任务源;本地探针与后端 API 实现
workflow/            # ProcurementWorkflow 状态机
automation/          # Accessibility 节点、截图、点击、输入、滑动
ai/                  # 结构化 AI 请求/响应,不含供应商业务类型
evidence/            # 截图、步骤日志、脱敏和上传

职责:

  • 检查无障碍、拼多多安装/登录可用性、网络和当前任务状态。
  • 由用户点击后领取任务;一台设备一次只运行一条。
  • 执行有界步骤:解析 -> 搜索 -> 浏览不超过 5 个候选 -> 判断 -> 停止确认。
  • 显示当前步骤、停止原因和人工接管入口。
  • 用前台服务承载执行中任务,进程重启后从服务端状态恢复或安全失败。
  • 回传步骤事件、候选、截图和最终结果。

不得:

  • 接收到通知就无条件开始采购。
  • 只用固定屏幕坐标定位关键控件。
  • 在验证码、登录、风险提示或未知页面上继续猜测操作。
  • 在 MVP 点击最终“提交订单”或支付相关控件。

2.3 VLM Gateway

只负责两类能力:

  1. 需求提取:参考图 + 标题 + SKU -> 搜索词、类目、属性、置信度、警告。
  2. 候选评估:候选截图/文本 + 原始约束 -> 匹配项、缺失项、拒绝原因、建议分。

模型输出是不可信建议,必须通过 schema 和确定性校验:

  • sku、quantity 和 max_budget 使用原始任务值;数量和预算不进入模型 prompt。
  • 价格未知、超预算或关键属性无法确认时不能判为可接受。
  • T-103 探针阈值为 0.75;低于阈值或标题/图片冲突时进入人工处理,不自动扩大 浏览范围。
  • 模型不能返回点击坐标、状态迁移或“允许提交订单”等执行授权。

T-104 按候选 ordinal 串行评估,每个候选最多一次结构化调用、整批最多 5 次;首个 网络失败或无效输出停止后续调用。模型响应只允许 schema_version/candidate_index/decision/score/matched/missing_or_uncertain/ rejection_reasons/confidence,其中 decision 仅允许 REVIEW/REJECT/ MANUAL_REQUIRED。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。

技术探针每次点击最多发出一次 VLM 请求,不沿用通用 Agent 的重试循环。该结构化 HTTP 客户端关闭连接自动重试、HTTP/HTTPS 重定向,整体调用超时为 75 秒;协程取消 会取消底层 HTTP call,成功响应正文上限为 64 KiB,非成功响应不读取正文。

远程 provider 只允许 HTTPS;HTTP 仅允许无 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 不写普通日志;加密密钥存储不可用且存在 密钥时拒绝调用。

三、核心数据流

3.1 技术探针

本机私有蝦皮订单目录
 -> 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 和参考图;不发送订单号或店铺名。
  • 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 的固定词搜索已落在以下运行边界:

SearchProbeScreen
 -> WorkflowRunner(timeout / retry / stop)
 -> PinduoduoSearchAutomation(纯 Kotlin)
 -> AndroidPinduoduoUiDriver
 -> BuyerAccessibilityBridge
 -> BuyerAccessibilityService
 -> 拼多多可见、启用、唯一语义节点

结果页成功不是“点击已发送”,而是同时确认精确查询词、搜索结果头和至少 3 个排序 控件。ACTION_SET_TEXT 后必须重新读取当前根节点并精确比对输入;任何稳定未知页以及 登录、验证码、风控、订单或支付边界都终止 workflow。该路径不提供坐标、ADB、 Shizuku shell、OCR 或 VLM 动作降级。

T-102/T-104 在搜索结果后追加一个有界候选步骤:

精确匹配固定词或当前结构化需求词的结果页
 -> 最多 2 次滚动预算
 -> 最多 5 个去重商品卡
 -> 验证详情页
 -> 无障碍截图
 -> App cache/candidate-NN.png + manifest.json
 -> 一次全局返回并复核固定词结果页

候选卡只保留语义指纹和计数,详情截图保存在 App 内部 cache。manifest 记录匿名文件名、 截图 SHA-256、字节数和尺寸,不保存商品标题或页面原文。T-104 使用这些证据时必须通过 受控 evidence 边界读取,不能让 VLM adapter 自行遍历 cache。Android 10/API 29 及 以下不能运行当前截图探针,应在预检时明确不支持,不使用媒体投影或 shell 绕过。

CandidateEvidenceSource 只接受当前 workflow 内存中的连续 ordinal 元数据,文件名 固定为 candidate-01.png 至 candidate-05.png;每张 PNG 最多 8 MiB、全批最多 32 MiB、声明尺寸最长边不超过 10000 px,并复核规范路径、PNG/IHDR、精确字节数、 尺寸和 SHA-256。Android gateway 再解码并把最长边缩至 2048 px。当前需求快照与 搜索词必须同时匹配候选 session,否则拒绝评估;另一个关键词的结果页只允许重新进入 搜索框,不能采集候选。

评估结束后 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 业务闭环

管理员创建 PENDING 任务
 -> 采购员在 App 点击“获取任务”
 -> 后端事务内选取并置为 CLAIMED
 -> App 确认后置为 RUNNING
 -> 定期 heartbeat 续租并上报步骤
 -> 需求提取和拼多多自动化
 -> WAITING_CONFIRMATION
 -> 采购员接受/拒绝候选
 -> SUCCEEDED 或 FAILED/CANCELED
 -> 管理端展示结果

四、状态机

4.1 任务状态

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 工作流状态

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 为准。
  • claim-next 必须由 usecase 调用 repository,在一个数据库事务内完成 “选取 + 校验设备空闲 + 更新状态”。
  • 创建、领取和完成接口支持 Idempotency-Key。
  • App 对网络超时不能假定失败;必须用任务详情确认服务端最终状态。
  • 后端拒绝非法状态迁移,即使客户端 UI 隐藏了对应按钮。
  • 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 范围执行