Files
goauto/docs/02-architecture-and-code-map.md

70 KiB
Raw Permalink Blame History

generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Architecture-and-Code-Map wiki_url: https://git.ilapage.cn/OPC/goauto/wiki/Architecture-and-Code-Map.- wiki_revision: 504e1ffaa2cf1989f55048ac7067d8af50fe3bf3 synchronized_at: 2026-09-22T02:56:22Z

架构与代码地图

MVP 架构

go-admin-ui
  │ REST:PDD商品、规则、任务、任务详情
  ▼
go-admin Server ───────── 数据库
  │                         ├─ pdd_product
  │ REST:注册/心跳/任务     ├─ collection_rule
  │                         ├─ collection_task(任务+结果)
  ▼                         └─ dimension/value/color_price/sku 子表
Android Portal/Agent
  ├─ 注册、心跳与设备串行锁
  ├─ 领取指定任务或空闲领取未指定任务
  ├─ 执行任务内的 URL 与规则快照
  └─ 提交结构化结果或明确错误

最小闭环

  1. 管理员添加 PDD URL,服务端规范化 URL 并提取唯一 goods_id。
  2. 管理员创建规则;规则创建后立即可用于创建任务。
  3. 管理员从一个 PDD 商品创建任务,可指定设备,也可留空等待空闲设备领取。
  4. 任务固化 URL、goods_id 和完整规则快照。
  5. Android 串行执行并把结果写回同一条任务;规格与 SKU 写入任务结果子表。
  6. 同一事务把 completed 结果全量覆盖到 PDD 商品最新档案,把 completed_partial 明确采集到的字段和规格安全合并;failed 不修改商品。
  7. 管理员在任务详情查看原始结构化结果,也可在 PDD 商品页面查看和人工覆盖最新商品资料。

关键设计

  • pdd_product.goods_id 唯一;重复时提示商品已存在,不重复新增。
  • 规则没有草稿、发布和版本流程;软删除只阻止创建新任务。
  • 已有任务不依赖规则当前状态,始终执行自身 rule_snapshot。
  • collection_task 同时保存任务生命周期和结果摘要,不建立 collection_result 主表。
  • 同一 PDD 商品最多存在一个 pending 或 running 任务。
  • 指定设备只能由该设备领取;未指定任务由在线空闲设备原子领取。
  • 重置在事务中清空原结果,保留 URL、goods_id、规则和设备快照,状态恢复为 pending。
  • Android 本地互斥与服务端原子领取共同保证单设备串行。
  • 正式采购在 Android 本地 SQLite 事务中先保存不可逆状态、稳定服务端请求 ID 和脱敏最终确认快照,再通知服务端并只允许一次创建订单点击;重启后只重放服务端标记、只读核单或上报,不再次点击。
  • 原始控件树和截图不持久化;Android Agent 端第一期不使用 OCR/VLM。服务端 SYB 登录验证码识别是唯一例外,见 #48。
  • 蝦皮规格映射独立保存在 shopee_product.specs_json:颜色只能选择关联 PDD 当前可选颜色,允许多个蝦皮颜色共用一个 PDD 颜色。Admin 商品详情的“一键匹配颜色和尺码”在服务端统一计算两个维度:保留当前仍有效的已确认映射,将唯一确定匹配及达到阈值、具备理由且候选仍有效的 AI 匹配,在重新校验规格上下文后于同一事务直接写为 confirmed,无需人工确认;低置信度或无结果保持未匹配,Provider 异常或上下文变化时不写入任何本次结果。PDD 目标规格消失后页面标记失效,映射保存和采购创建均拒绝继续使用;无需新增数据库表或 Android 能力。

最小业务数据

表 必要内容
agent_device 唯一 install_id、设备信息、状态、Token 摘要、版本化能力、最后心跳
agent_app_release / agent_app_release_setting 私有 APK 的版本、SHA-256、大小、说明、创建人,以及当前版本单例指针
pdd_product 唯一 goods_id、当前 URL、标题、店铺、数字销量/评价、三态状态和通用多维 specs_json 最新值
collection_rule id、name、content_json、创建/更新时间、deleted_at
purchase_rule / purchase_rule_setting 采购规则历史与当前规则单例指针;新任务复制规则快照,运行时无常量回退
collection_task 商品/设备外键、五态状态、URL/goods_id/规则快照、租约、结果摘要、错误和时间
collection_dimension 任务、维度键、名称、排序
collection_dimension_value 维度、值、排序
collection_color_price 任务、颜色、该颜色统一使用的整数分价格
collection_sku 任务、整数分价格、可用性、完整性
collection_sku_value SKU 与规格值的多对多关联
pdd_account 可选的账号调度引用,只保存名称和状态,不保存凭据
purchase_task 商品外键和不可变快照、执行模式、状态/租约 guard、价格边界、订单、人工支付复核、物流与回填事实
purchase_task_attempt task_id + attempt_id 幂等执行记录、阶段、规则哈希、固化规格决策和结构化错误

#241 新增:server/app/goauto/purchase/order_backfill.go 与 order_backfill_handler.go 提供 POST /api/agent/v1/purchase-tasks/order-backfill(Device Token 鉴权,逐条事务、逐条结果);server/app/goauto/models/purchase_order_guard.go 在 PurchaseTask.BeforeSave 上全局强制订单号唯一,以 purchase_rule_setting 单例行串行化订单号分配,避免新增唯一索引迁移;purchasecontract.ParseAddressSuffix 为 AddressSuffix 的反解,通过回比而非负向前瞻实现(Go RE2 不支持前瞻)。 | ai_matching_setting | 唯一单例的启用状态、OpenAI-compatible Base URL、模型、超时、内部部署明文 API Key 和更新人;仅管理员设置接口可以读取该字段 |

collection_task 的状态仅为 pending、running、completed、completed_partial、failed。设备身份和心跳表属于 Agent 领取任务的必要基础,不承载 PDD 业务数据。

设备以 JSON 数组保存最后一次注册或心跳上报的版本化能力。v1 任务兼容未上报能力的旧 Agent;v2 任务在创建指定设备任务、获取下一任务、领取和开始四个边界重复校验能力,避免旧 APK 执行未知规则。

数据库使用两个可空 guard 列表达跨数据库唯一约束:活动任务的 active_slot=1,运行中设备的 device_run_slot=1;终态记录对应列为 NULL。复合唯一索引据此保证同商品最多一个活动任务、同设备最多一个运行中任务,同时允许保留任意数量的终态历史任务。状态与 guard 列还有数据库检查约束,必须在同一条状态变更语句中更新。

采购表使用同一 guard 思路:purchase_task.active_slot 保证同一 SYB 明细最多一个活动任务,device_run_slot 保证设备串行,account_run_slot 在账号已知时保证账号串行。账号未知是合法状态。正式任务必须引用 SYB 的规则由模型钩子和后续创建服务双重校验;MySQL 8.4 不允许 syb_product_id 同时参与带参照动作的外键和跨字段 CHECK,因此不在该列添加数据库 CHECK。

配置分层

配置分三层,下层覆盖上层:

层 文件 是否进 Git 放什么
1 默认值 server/config/settings.yml 是 应用配置和非机密运维参数(extend.syb 的 baseurl / pagesize / maxmatches / ocrurl)
2 部署本地值 config.yaml(仓库根) 否(.gitignore) 数据库、端口、顺云宝凭据
3 覆盖值 GOAUTO_* 环境变量 — 服务、容器和 CI 用;优先级最高

[必须] 凭据只允许出现在第 2、3 层。 settings.yml 被 Git 跟踪,任何时候都不能往里写账号密码。

服务端自己读第 2 层(config/local.go 的 ApplyLocalConfig),查找顺序为 GOAUTO_CONFIG 指定的路径 → 当前目录 ./config.yaml → 可执行文件同级目录。找不到不是错误:容器场景只用环境变量,本来就没有这个文件。回调注册在 ApplyEnvironment 之前,环境变量因此始终有最后决定权。

[必须] 第 2 层的标量按 YAML 原始类型读取后统一转字符串。纯数字密码不加引号会被 YAML 读成整数,这里不能因此启动失败——但仍应加引号,否则前导零会丢。

开发启动脚本把 config.yaml 的路径通过 GOAUTO_CONFIG 传给服务端,不在 PowerShell 里重复解析 YAML。

前端伺服

web/.env.production 的 VUE_APP_BASE_API 为空,即生产构建发的是相对请求,前端与接口必须同源。服务端在 dist/index.html 存在时伺服构建产物并为 history 路由回退到 index.html(app/admin/router/spa.go)。

[必须] 回退只对非接口路径的 GET 生效。给写错的接口路径回 200 + HTML,客户端看到的会是 JSON 解析错误而不是 404。

[必须] dist 不存在时不注册 NoRoute。开发环境前端跑在 vite 独立端口上,装了回退会把每个真 404 变成一张 HTML 页。

已建立的工程入口

功能 当前目录
服务端基线 server/(go-admin v2.3.0)
最小闭环数据模型 server/app/goauto/models/
数据迁移与约束测试 server/app/goauto/migrations/
go-admin 迁移注册 server/cmd/migrate/migration/version-local/1786700000000_goauto_schema.go
设备注册、认证、停用与吊销 server/app/goauto/device/
PDD 商品与规则 server/app/goauto/product/、server/app/goauto/rule/
PDD 商品档案增量迁移 server/cmd/migrate/migration/version-local/1786700500000_pdd_product_archive.go
虾皮商品档案与规格映射 server/app/goauto/shopeeproduct/、web/src/views/goauto/shopee-products/;Admin 按确认原型提供颜色下拉人工映射、确定性尺码批量预览、失效提示和单一保存入口,SYB 快捷入口可突出目标颜色;不修改两端原始规格
虾皮商品档案增量迁移 server/cmd/migrate/migration/version-local/1786700600000_shopee_product_archive.go
SYB 商品明细导入、解析与虾皮档案合并 server/app/goauto/sybimport/(解析、幂等落库、同步编排、导入端点、管理端 API 和 Admin 页面均已实现)
顺云宝(SYB)ERP HTTP 客户端与登录会话 server/app/goauto/sybclient/(登录、OCR 验证码、会话缓存、列表与明细读取;见 SYB-ERP-Interface-Contract)
SYB 商品明细增量迁移 server/cmd/migrate/migration/version-local/1786700700000_syb_product_import.go
SYB 店铺准入、发现与过滤 server/app/goauto/sybshop/、server/app/goauto/sybimport/;迁移 server/cmd/migrate/migration/version-local/1786700900000_syb_shop.go
SYB 后台导入任务、进度、单任务互斥与启动恢复 server/app/goauto/sybimport/sync_run.go、sync_run_handler.go;表 syb_sync_run,迁移 server/cmd/migrate/migration/version-local/1786701000000_syb_sync_run.go
SYB 每小时自动同步 server/app/goauto/sybimport/import_handler.go 提供手动/定时共用的 StartImport 服务;scheduled_job.go 通过 go-admin 定时任务调度,固定按 Asia/Shanghai 取今天和昨天;任务注册迁移为 server/cmd/migrate/migration/version-local/1786701600000_syb_hourly_sync_job.go
采购任务数据与类型化规则契约 server/app/goauto/models/purchase.go、server/app/goauto/purchasecontract/;迁移 server/cmd/migrate/migration/version-local/1786701100000_purchase_contract.go
采购任务单条/批量预检与创建、租约、attempt 幂等、Admin 只读查询和人工处置状态机 server/app/goauto/purchase/;batch-preview 通过批量预加载 SYB、蝦皮、PDD 与最新任务执行快速只读预检,不访问 AI;batch-create 仍按最新数据逐项完整复核并可在必要时调用 AI;既有追加迁移为 server/cmd/migrate/migration/version-local/1786701200000_purchase_state_machine.go
采购规格标准化匹配与 AI Provider 设置 server/app/goauto/aimatching/;服务端先做繁简、空白/全半角/大小写和公斤/斤的唯一确定性匹配,再按需调用单一 OpenAI-compatible Provider;1786701300000_ai_matching_setting.go 创建设置表,1786701400000_ai_matching_setting_plain_api_key.go 将原加密列迁移为内部明文 api_key,仅管理员读取
任务领取、结果、同任务 attempt 重采与删除 server/app/goauto/task/;collection_task.attempt_number 保存当前次数,collection_task_attempt 归档旧终态执行,增量迁移为 server/cmd/migrate/migration/version-local/1787983500000_collection_task_attempt.go
管理端基线 web/(go-admin-ui v3.0.0)
管理端闭环页面 web/src/views/goauto/
备货采购服务端路径 purchase_task.task_type 与迁移 1787885300000_stock_purchase.go;POST /api/admin/v1/purchase-tasks/stock 由 server/app/goauto/purchase/service.go 校验 PDD 当前可选规格并固化 direct_select,复用既有任务状态机和设备/账号互斥;重试、替换和 SYB 回填显式排除 stock
Admin 采购任务列表、详情与人工处理 web/src/views/goauto/purchase-tasks/、web/src/api/goauto/purchase-tasks.js;创建入口不在本模块
Admin AI 规格匹配设置 web/src/views/goauto/ai-matching-settings/、web/src/api/goauto/ai-matching-settings.js;管理员可查看、维护和测试 Provider(包括内部明文 API Key);采购员菜单硬排除,仍只可通过受控 API 查看启用状态
GoAuto 系统菜单与采购员权限基线 server/app/goauto/access/ 统一声明 12 个模块、路由元数据和 Admin API 权限矩阵;迁移 1787885400000_goauto_menus.go 幂等维护 sys_menu、sys_menu_api_rule、采购员默认菜单绑定和 Casbin 固定白名单。web/src/router/index.js 只保留公共路由,web/src/store/modules/permission.js 在登录时根据 GET /api/v1/menurole 返回结果注册业务路由与侧栏;退出、凭据异常和切换角色会清除旧动态路由,未授权深链接回退工作台。菜单可见性与 API 授权彼此独立
Admin 失败采购任务批量重试 POST /api/admin/v1/purchase-tasks/batch-retry;服务端 server/app/goauto/purchase/retry.go 负责资格判定、逐项幂等创建与部分成功结果,Admin 页面只允许选择服务端标记可重试的行;不修改 Android Agent
SYB 店铺管理页面与接口封装 web/src/views/goauto/syb-shops/、web/src/api/goauto/syb-shops.js;确认原型快照 prototypes/49/v1/index.html
SYB 异步导入、商品筛选、当前页采购选择/确认/逐条结果与同步记录页面 web/src/views/goauto/syb-products/、web/src/views/goauto/syb-sync-runs/、web/src/api/goauto/syb-products.js、web/src/api/goauto/purchase-tasks.js;商品列表按店铺名称包含匹配,并支持最多 100 个多行订单号精确筛选;确认原型见 #44 设计证据,导入原型快照为 prototypes/50/v2/index.html
Android Agent 基线 android/app/src/main/java/cn/ilapage/goauto/agent/
Android 无障碍规则执行 android/app/src/main/java/cn/ilapage/goauto/agent/automation/
PDD 安全规格入口与评论页误触恢复 automation/PddProductDetailCollector.kt:明确选择语义、底部购买文字、评论上下文排除、一次返回重试和结构化错误;不保存控件树或截图
Android 采购规则解释器与正式地址/订单动作 android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseRuleContract.kt、PurchaseRehearsalExecutor.kt、PurchaseLiveAutomation.kt
Android 采购任务、attempt 与 Outbox 持久化 android/app/src/main/java/cn/ilapage/goauto/agent/persistence/;调度入口 service/AgentForegroundService.kt
三端统一验证 scripts/verify.ps1

以上入口均已落地;新增 API、表或执行动作时必须同步更新本代码地图与共享契约。

cmautobuy 商品离线导入

server/cmd/import-cmautobuy-products/ 是 #70 的独立单向导入入口,转换逻辑位于 server/app/goauto/cmautobuyimport/。它不注册 HTTP 路由、不参与服务启动,也不是数据库 schema migration。

导入器从 cmautobuy MySQL 只读一致性快照读取有效 pdd_products、shopee_products 和 shopee_skus,再写入 GoAuto 的 pdd_product、shopee_product。旧 PDD 组合 SKU 聚合为 GoAuto 通用维度;同一颜色出现多个不同价格时不猜测价格。旧组合 spec_mappings、SYB、任务、订单和其他域数据不进入导入范围。

默认模式是 dry-run;显式 --apply 才会在目标 MySQL 单事务覆盖同业务键商品。来源与目标配置都来自未跟踪 YAML,连接串和密码不输出。来源蝦皮的字符串 pdd_goods_id 必须通过目标 PDD goods_id 换算为数字 pdd_product_id,提交前重新校验 JSON、唯一键和关联完整性。

Android Agent 0.3 设备端外壳(#88)

  • Android 入口仍为单 Activity,但从动态单页升级为 AndroidX Fragment + Material BottomNavigationView 的四 Tab 外壳:状态、采集、采购、设置;首次启动默认进入状态页,并持久保存最近一次有效 Tab。
  • android/app/src/main/java/cn/ilapage/goauto/agent/ui/ 保存状态页、设置页、任务占位页、无障碍真实就绪检测和共享 UI 组件;采集/采购历史列表与详情由 #90 接入。
  • 状态页同时读取系统启用的 AccessibilityService 组件和 GoAutoAccessibilityService.instance 绑定事实,区分“未开启”“已开启等待连接”“已开启并就绪”,并显示服务连接、当前任务和最近心跳。
  • 设置页维护服务器地址和设备名称,测试连接只访问 GET /api/v1/health;保存后沿用当前 installId / Device Token 重新连接。执行任务或测试连接期间表单与按钮禁用。
  • 设置页只显示 Token 是否配置,不返回或显示明文;不包含 PDD 账号、采购写操作或支付入口。
  • AgentStateStore 保存界面所需的当前任务类型/编号摘要;任务结束后由执行服务清除。它不保存控件树、截图、完整规则或凭据。

Android Agent 任务结束冷却与返回(#89)

  • AgentForegroundService 在采集结果已提交,或采购结果与 Outbox 已在同一事务安全保存后,建立 5 秒任务结束冷却;不新增第二套队列轮询器。
  • 冷却到期仍由现有任务轮询按“采购优先、再采集”依次确认两个队列都为空。任何新任务、网络/服务端异常或本地未解除的不可逆采购边界都会取消返回。
  • IdleReturnCoordinator 只保存冷却时间、自动化前台包名与无障碍前台切换序号,不保存控件树或截图。用户切换到其他 App 后,即使又返回 PDD,也不会被强制拉回 Agent。
  • 返回请求只允许从 Agent 本次自动化停留的 PDD 或受支持浏览器页面发起,并由无障碍服务打开 MainActivity:现有 Activity 保持当前 Tab,重建时恢复最近一次有效 Tab;Android 后台 Activity 启动限制导致失败时只提示手动打开,不重复强拉。
  • 任务执行和冷却分别使用带超时的亮屏锁;MainActivity 仅在运行态标记要求时设置 FLAG_KEEP_SCREEN_ON。任务、新任务接管、取消、返回或服务销毁后统一释放,不绕过设备 PIN、图案或密码。

Agent 任务历史(#90)

  • Android 的采集任务与采购任务 Tab 由 TaskHistoryFragment 承载,复用搜索、状态筛选、分页、加载/空/失败状态和只读详情能力。
  • AgentApiClient 调用服务端当前设备历史接口;任务编号规范化由客户端先处理,服务端再次校验。
  • 服务端采集与采购模块各自使用 agent_history.go 负责 Device Token 设备隔离、最近 30 天过滤、分页和最小化 DTO。
  • purchase_task.actual_unit_price_cent 是可空字段,只记录 Agent 实际观察到的采购单价;旧任务不回填。

Agent 受控重新采集(#91、#155)

  • 服务端 task/lifecycle_service.go 的同一 Reset 事务同时服务管理端和设备端;设备端入口使用显式 Device Token 模式,不能用空 Token 退化为管理端路径。
  • Reset 复用原 collection_task.id:事务先归档旧终态到 collection_task_attempt,递增 attempt_number,按来源刷新最新规则快照,再清理主表当前结果/错误/租约并恢复 pending。归档以 task + attempt 唯一,reset requestId 也唯一,阻止并发或重放重复递增。
  • Admin 来源读取原规则当前存活内容;Agent 当前页来源读取当前手动默认规则。当前页未识别任务可以在下一 attempt 首次绑定,已识别任务仍由 IdentifyCurrentPage 的 goods_id 冲突保护限制为原商品。
  • POST /api/agent/v1/collection-tasks/{taskId}/reset 返回 taskId、attemptNumber、状态和重放标记;设备归属/在线/忙碌、规则兼容、商品冲突和采购占用在事务内检查。
  • Android TaskHistoryFragment 对所有失败来源显示“重新采集”;当前页来源复用无障碍、近期 PDD 前台、本地互斥和冷却检查,成功后调用 AgentForegroundService.start,执行仍由 next/claim/start 完成。Admin 详情抽屉与 Agent 详情都展示 attempt 历史摘要;Agent 接口不返回历史规则快照。

Agent 受控采购重试(#95)

  • 服务端 server/app/goauto/purchase/retry.go 的 AgentRetry 使用 Device Token 校验任务归属,并复用 Admin BatchRetry 的当前档案、不可逆边界、设备能力/忙碌和幂等创建逻辑;旧失败任务不修改,新任务固定原设备。
  • GET /api/agent/v1/purchase-tasks 与详情通过 retryable / retryDisabledReason 返回服务端资格结论;POST /api/agent/v1/purchase-tasks/{taskId}/retry 只接受 requestId,不接受客户端指定设备、规格或规则。
  • Android TaskHistoryFragment 在采购失败列表和详情按服务端资格显示单任务“重试采购”,二次确认后调用新接口;成功后唤醒既有 AgentForegroundService 队列,实际执行仍只走 next → claim → start。Agent 不实现批量重试、取消订单、修改既有订单或支付。

Android 系统辅助功能按钮(#97)

  • Android 8.0 及以上的 GoAutoAccessibilityService 请求系统 flagRequestAccessibilityButton,服务连接后向 AccessibilityButtonController 注册单一回调,销毁时注销;服务重连不会叠加回调。
  • 单击由系统分配给 GoAuto 的辅助功能按钮时,只调用 openAgentPreservingTab(),以前台 MainActivity 打开 Agent 并保持最近一次有效 Tab;不领取、创建、重置或重试任务,不打开 PDD,也不改变 5 秒空闲返回规则。
  • 不支持或未分配系统辅助功能按钮的 ROM 保持安全降级;项目不创建自定义悬浮窗。

Android Agent 状态页手动检查(#98)

  • AgentStatusFragment 使用 Android 原生下拉刷新容器;只有状态页顶部下拉会请求一次立即检查,采集/采购历史页的下拉只刷新记录,两者语义不同。
  • AgentForegroundService.checkNow 只向既有单线程同步与调度入口提交一个可合并请求;不建立第二套轮询器,也不改变 15 秒自动轮询、采购优先、设备互斥或服务端租约。
  • 手动请求在已有同步运行时保留一个待处理标记,当前同步结束后再执行一次;重复手势不会并发请求。
  • 结果通过仅限当前应用包的广播返回状态页,区分无任务、采集任务、采购任务、设备忙、配置/认证和网络错误。状态页无障碍信息只读,唯一系统设置入口保留在设置 Tab。
  • 手动检查不创建、重置或重试任务,不绕过无障碍与身份校验,不打开 PDD,不修改地址、不创建订单、不支付。

Agent 任务记录刷新与摘要缓存(#99)

  • Android 的采集/采购历史页使用单个 SwipeRefreshLayout 包裹既有滚动页,只在列表顶部下拉刷新当前查询;请求中合并重复手势,加载失败页仍保留可见重试入口。
  • 设置页保存 1/3/7/15/30 天记录范围(默认 7 天),用当前 Device Token 分页读取采集与采购摘要。两类读取独立结算,支持完整、空、部分和失败反馈。
  • TaskHistoryCache 使用独立 SharedPreferences 保存当前同步范围的列表摘要,网络读取失败时可显示最近同步摘要;Token 继续只保存在 Android Keystore 保护的设备身份存储中,任务详情不离线镜像。
  • 服务端两个 Agent 历史列表查询增加 days=1..30,并继续在数据库查询中强制设备隔离、分页上限和 30 天最大窗口;详情读取仍保持原 30 天边界。
  • 该链路只读,不接入任务领取、执行、重置、重试、PDD 导航、地址修改、创建订单或支付。

Agent 当前页面临时采集(#101)

  • 服务端入口位于 server/app/goauto/task/current_page.go:CreateCurrentPage 原子完成设备校验、任务创建、规则快照和租约;IdentifyCurrentPage 对 Agent 上报的白名单链接重新校验并最终裁决 goods_id,负责 PDD 商品复用/创建和任务身份绑定;服务端保留受限正文解析作为短链兜底。路由为 /api/agent/v1/current-page-collection-tasks 及其 /{taskId}/identify。
  • 默认规则设置位于 server/app/goauto/rule/agent_manual_setting.go,使用单行表 agent_manual_collection_setting。迁移 1787790000000_agent_current_page_collection.go 增加任务来源、识别幂等字段、可空 PDD 外键和默认规则表,并对旧任务回填 admin。
  • collection_task.source 目前只允许 admin / agent_current_page。当前页面任务创建时由设备运行槽防并发,识别商品后再参与 PDD 商品活动槽;结构化结果仍写入既有任务及规格子表,不新增临时任务表。失败后通过 #155 的同任务 attempt 机制重新进入队列,不创建第二条临时任务。
  • Android 入口仍由 TaskHistoryFragment 承载;点击确认时先启动 AgentForegroundService,不先把 Agent 退到后台。服务先用 TaskExecutionMutex 预占本地串行槽,服务端创建成功后把预占转为真实 taskId;前台已是 PDD 时不执行返回,前台仍是 Agent 且近期见过 PDD 时只尝试一次返回,否则使用不清理任务栈、不带深链参数的 PDD 启动 Intent 恢复现有任务,并在有界等待确认 PDD 前台后才识别身份和调用既有 PddProductDetailCollector。
  • CurrentPageIdentityRunner 负责页面证据、唯一分享/复制入口和面板清理;ClipboardRelayActivity 使用独立、不可导出的短生命周期任务在前台读取新鲜剪贴板,完成后移除中转任务并露出原 PDD 页面。PddShareLinkExpander 仅在手机侧用无 Cookie、无项目凭据的移动端 GET 有界展开白名单短链,逐跳校验并最多读取 64KB 正文;Agent 不裁决 goods_id。原始剪贴板、链接与响应正文不进入日志或缓存。
  • v2 规则新增可选 currentPageIdentity(分享/复制别名和三个有界超时),旧 v2 规则由 Android 使用安全默认值;新设备能力为 collector.pdd.current-page-share.v1。
  • 该路径复用既有结果/失败接口、PDD 最新档案写回、任务结束返回和采集间隔,不调用浏览器导航,不进入任何采购、地址、创建订单或支付代码。

SYB 档口入库码(#119~#122)

  • 数据模型与迁移位于 server/app/goauto/models/syb_inner_code.go、server/app/goauto/migrations/ 和 server/cmd/migrate/migration/version-local/1786701500000_syb_inner_code.go;Excel 业务记录、逐件入库码、匹配计划、回写批次、检查点、租约和幂等请求分表保存。
  • server/app/goauto/sybinnercode/ 负责 Excel 解析、物理删除、异步只读匹配、回写预览、全局串行回写、逐件回读确认、结果不明确后的只读复核和启动恢复。
  • server/app/goauto/sybclient/ 提供 SYB 读取与三个入库码写接口;匹配器只依赖只读接口,不能取得写能力。真实写入只由用户确认后的回写批次触发。
  • Admin API 注册在 server/app/goauto/sybinnercode/router.go,页面位于 web/src/views/goauto/syb-inner-codes/,菜单入口为“档口入库码”。
  • 服务启动时,未开始的 queued 记录释放回可回写,已经进入远端动作的 applying 记录转为 needs_check,不自动重放写请求。

PDD 商品替换数据模型(#129)

  • server/app/goauto/models/replacement.go 定义审计主表 pdd_product_replacement 与分项表 pdd_product_replacement_item;版本迁移为 1787885000000_pdd_product_replacement.go,只新增表,不修改既有表和数据。
  • 主表用 origin_type + origin_task_id 区分失败采集任务和失败采购任务来源,以 target_collection_task_id 保存替代商品采集证据,以 created_by_device_id 保存发起设备。当前设备模型没有操作人绑定,因此该字段只能追溯到设备,不能追溯到采购员账号。
  • 主表的可空 active_slot 与 source_product_id 组成唯一索引,使同一源商品跨 SQLite、MySQL 和 PostgreSQL 同时最多存在一条 active 替换记录;历史记录使用 superseded,不软删除、不提供删除接口。
  • 分项表按 replacement_id + shopee_product_id 唯一保存本次实际影响集合,并独立记录 matching / matched / manual_required、匹配来源、置信度和持久 worker 的尝试/错误信息。某条采购任务能否续做只能读取对应虾皮商品的分项状态,不能读取主表总体进度。
  • server/app/goauto/replacement/ 提供内部登记、幂等冲突校验、来源/采集证据校验、目标有效性、活动记录唯一性和环检测,以及只读审计查询;管理端只读路由为 GET /api/admin/v1/pdd-product-replacements 与 GET /api/admin/v1/pdd-product-replacements/{replacementId}。Agent 没有直接写入该领域的 HTTP 权限。

PDD 商品替换生效与规格匹配(#131)

  • replacement.Service.RegisterAndActivate 在同一数据库事务内锁定受影响采购任务与虾皮商品:虾皮商品改指替代 PDD、清除旧规格映射,待执行/待探测采购任务取消并释放租约与运行守卫;执行中和终态任务的 PDD 外键及全部快照保持原样,旧 PDD 仅置为 disabled。
  • pdd_product_replacement_item 是持久匹配工作项;pdd_product_replacement_worker_lease 为多实例全局租约。事务提交后异步唤醒,API 服务启动时恢复遗留 matching,最多有限重试,失败转 manual_required。
  • 匹配复用 aimatching.Service.Resolve。确定性 exact_match 可直接确认且不要求置信度;ai_match 只有在开关启用、置信度达到 ai_matching_setting.auto_confirm_min_confidence(默认 0.9)、候选/角色/原因/输入版本全部通过校验时才确认。
  • 人工维护虾皮规格映射后,同一事务同步分项状态与主表 completed / completed_partial 聚合;worker 只更新仍为 matching 且商品关联、规格 JSON 未变化的行,不覆盖人工结果。
  • 纠错使用 CorrectAndActivate:原记录转 superseded,新建原始失效商品到新目标的记录,只迁移原 replacement_item 冻结的虾皮商品集合,不影响共享中间目标的其他商品。
  • 历史采购执行身份继续以 PDDGoodsIDSnapshot / PDDURLSnapshot 为准;当前采购查询未发现按可变 pdd_product_id 聚合历史数据的实现,因此本工单无需改写统计 SQL。

创建采购任务异步规格匹配(#148)

  • purchase_spec_match_work_item 是采购独立持久工作项;模型在 server/app/goauto/models/purchase.go,迁移由 migrations.MigratedModels 统一创建。
  • server/app/goauto/purchase/match_worker.go 负责原子领取、租约、30 秒/2 分钟/10 分钟退避、启动恢复、严格自动确认和输入指纹重校验;只写采购任务快照,不写虾皮长期映射。
  • service.go 在需要 provider 时把 pending 任务与工作项同事务创建并立即返回;lifecycle.go 在 Next、Claim、Start 三个入口检查工作项,防止未完成匹配的任务执行。
  • match_admin.go、admin_query.go 和 /purchase-tasks/{taskId}/matching* 提供状态、重新入队和人工候选选择;Admin 页面在列表和详情展示匹配状态与恢复操作。

PDD 颜色图片采集与存储(#133)

PddProductDetailCollector
  -> 颜色卡片子树内锁定同 content-desc ImageView bounds
  -> GoAutoAccessibilityService.takeScreenshot(仅内存)
  -> 按 bounds 裁剪并压缩 JPEG
  -> 先 POST /tasks/{taskId}/result
  -> 再逐张 POST /tasks/{taskId}/color-images
  -> task.UploadColorImage 校验任务/设备/颜色/JPEG/上限
  -> pdd_product_color_image(PDD 商品 + 颜色唯一,保留最新)
  -> static/uploadfile/goauto-color/*.jpg
  -> product.Service.Detail 返回 colorImages
  -> Admin PDD 商品详情 64×64 缩略图、无图占位与点击预览
  • 整屏 Bitmap 不进入文件系统、网络、数据库或诊断;截图回调完成后在内存中裁剪,用后回收。
  • pdd_product_color_image 保存图片路径、类型、字节数、宽高、来源任务与设备;来源任务继续关联其 rule_snapshot。同商品同颜色唯一,更新成功后删除被替换的受控目录旧文件。
  • 上传处理沿用既有 /static/uploadfile 静态能力,但使用独立 Agent 受控接口和 goauto-color 子目录,不复用公共上传入口。
  • 图片旁路独立于结构化结果状态机。Android 先安全提交结果,再上传图片;任何图片异常只写本地脱敏诊断,不触发失败提交或任务状态回滚。

Admin GoAuto 分组导航与权限(#142)

  • server/app/goauto/access/modules.go 是 12 个 GoAuto 页面模块及两个一级菜单组的代码事实来源;本地迁移 1787885600000_goauto_menu_groups.go 创建结构性父菜单,并把既有页面菜单直接迁入父组。
  • 「采集采购」依次包含:SYB 商品、SYB 同步记录、档口入库码、虾皮商品、PDD 商品、采集任务、采购管理;「采采管理」依次包含:SYB 店铺、采集规则、采购规则、设备列表、AI 规格匹配。
  • 迁移保留页面菜单 ID、路由、组件和 API 关联,移除旧的 12 个一级模块根菜单;导航固定为“分组 → 页面”两级,不增加第三级。
  • Admin 角色继续通过角色菜单查询取得全部 GoAuto 页面;采购员获得两个父组和除采购规则、AI 规格匹配外的 10 个页面,AI 页面及入口均不可见。
  • Web 继续通过 /api/v1/menurole 动态生成路由;直接访问组内页面时展开对应父组并高亮当前页面。

GoAuto 采购员 API 权限启动对账(#156)

  • server/app/goauto/access/purchaser.go 的 AdminAPIs 是 GoAuto 管理接口与采购员授权矩阵的唯一代码事实源;PurchaserAPIs() 只筛选其中明确标记为采购员可用的条目。
  • API 服务在注册路由和监听端口之前调用 access.ReconcilePurchaserPermissions:补齐 sys_api 缺失项,并在单一事务内只删除、重建 casbin_rule 中 ptype=p, v0=purchaser 的策略。其他角色、自建策略和菜单绑定不在对账范围。
  • 对账每次服务启动执行且幂等;代码新增采购员接口后无需补丁迁移,重启即可补齐;代码减权后旧采购员策略会被清除。
  • 任一数据库对账失败时,API 启动直接返回错误,不注册路由、不监听端口,避免权限矩阵未对齐时继续提供服务。既有版本化迁移保留其历史语义,但不再是运行时权限同步的唯一入口。

采购规则与 Agent 发布组件

  • server/app/goauto/purchaserule/ 提供管理员采购规则 CRUD、当前规则切换和运行时加载;迁移 1787983600000_purchase_rules.go 新增两张表、原字节播种默认规则,并把“采购规则”放入“采采管理”。
  • server/app/goauto/purchasecontract/ 使用万分比定点值校验和计算 priceGuard,默认 0.2 / 1.5 与旧整数公式一致。
  • server/app/goauto/apprelease/ 负责私有 APK 上传、Manifest 解析、哈希、当前版本和双认证下载;迁移 1787983700000_agent_app_release.go 仅追加版本表与单例设置表。存储根可由 GOAUTO_AGENT_RELEASE_DIR 指定,缺省为服务端工作目录下 var/goauto-agent-releases。
  • Android update/ 组件负责启动静默检查、手动检查、私有下载、完整性校验、任务忙碌门禁、FileProvider 和系统安装确认;不参与任务轮询、心跳或前台服务生命周期。

PDD 商品反向关联与订单继续采购(#161)

  • server/app/goauto/product/related.go 通过 shopee_product.pdd_product_id 与 syb_product.shopee_product_id 批量反查;PDD 详情只返回虾皮商品和规格映射摘要,不嵌入订单行。
  • GET /api/admin/v1/pdd-products/{productId}/related-syb-products 返回统一扁平分页,参数为 page、pageSize(默认 20、最大 100)、scope=actionable|all(默认 actionable)和可选 shopeeProductId。管理员和采购员可读,其他角色由 Casbin 拒绝。
  • purchase.Service.ProcessStages 是 SYB 页和 PDD 关联订单共用的只读阶段入口;批量加载、不调用 AI。当前采购规则缺失或无效时仍返回基础关联、采集和已有任务事实,依赖规则才能创建的行明确标记不可采购。
  • Web 的 PDD 商品详情把“创建备货采购”保留在顶部,把“关联订单继续采购”放在关联商品区块;订单行使用统一分页,并复用既有 batch-preview / batch 创建路径。

蝦皮规格自动匹配批处理(#195)

  • 服务端在 server/app/goauto/shopeeproduct/auto_match_batch.go 复用单商品“一键匹配颜色和尺码”原子服务,定时任务与 Admin 手动执行共用同一批处理入口。
  • shopee_spec_auto_match_run 保存触发来源、稳定 requestId、运行摘要和可空唯一 active_slot;活动槽与租约保证多实例、定时和手动同时触发时全局最多一个运行批次。
  • shopee_spec_auto_match_work_item 按蝦皮商品唯一保存输入指纹、尝试次数、下次尝试时间和逐商品租约。已完成或低置信度/无结果的相同输入不重复调用 AI;规格、关联或 AI 设置更新时间变化后才允许重新处理。
  • 迁移 1788290000000_shopee_spec_auto_match.go 幂等创建两张表并写入调用目标 GoAutoShopeeSpecAutoMatch。系统任务默认关闭,默认 Cron 为每小时第 15 分钟、每批最多 20 个商品。
  • Admin 蝦皮商品列表通过异步手动接口启动同一批次并轮询最近运行摘要;该入口仅管理员可用,不创建采购任务、订单,不调用 Android Agent,也不执行付款。

SYB 异常规格 AI 定时解析(#198)

  • server/app/goauto/sybimport/ai_parse_batch.go 以 SYB 明细为单位执行“确定性重解析 → 封闭候选 AI 解析”,与 #195 的“蝦皮规格 → PDD 规格”任务保持独立。
  • syb_spec_ai_parse_run 保存全局批次、活动槽、租约与结构化计数;syb_spec_ai_parse_work_item 按 syb_product_id 唯一保存输入指纹、尝试次数、冷却和逐行租约。
  • syb_product 的 ai_confirmed、置信度、限长理由、确认时间和隐藏输入指纹记录 AI 确认事实;parse_status 仍只记录确定性解析器结果,manually_confirmed 仍只代表人工决定。
  • Provider 只接收单条 productSpec 和关联蝦皮商品的颜色/尺码候选;返回值必须逐字属于对应候选。采购可信门禁接受仍有效的 AI 确认,但候选消失后立即 fail-closed。

定时任务持久化执行历史(#199)

  • server/app/jobs/execution_log.go 为 Exec 与 HTTP 两类调度入口统一记录一次执行生命周期;server/app/jobs/models/sys_job_execution_log.go 对应表 sys_job_execution_log,保存执行标识、任务/调用目标快照、定时触发类型、开始/结束时间、耗时、running / succeeded / failed / interrupted 状态及脱敏错误码和摘要。
  • 服务启动时按当前“每个数据库一个调度器实例”的拓扑,把上次进程遗留的 running 记录安全收敛为 interrupted;任务删除后历史仍保留并可只读查询。
  • GET /api/v1/sysjob/:id/execution-logs 由 server/app/jobs/service/execution_log.go、apis/execution_log.go 和 router/sys_job.go 提供任务级分页、状态与开始时间过滤;沿用隐藏菜单 JobLog 的角色菜单绑定和精确 GET 权限。Web 入口为 web/src/views/schedule/index.vue 的单选“日志”按钮,详情页为 web/src/views/schedule/log.vue。
  • 执行历史明确不保存任务参数、AI Provider 地址或密钥、第三方原始响应和业务原始载荷;当前不提供删除、保留期限自动化、WebSocket 实时流或立即执行动作。
  • 追加迁移为 server/cmd/migrate/migration/version-local/1788357000000_sys_job_execution_log.go:创建执行历史表、登记只读 API、关联 JobLog 菜单,并只给迁移前已绑定该菜单的角色补充精确 Casbin 权限。

客户端密钥访问架构(#237)

代码基线 71f7751(含用户确认的默认 HTTP/HTTPS 兼容),2026-09-07 完成 Server/Web 代码与隔离测试;本机迁移、管理员菜单写入与 Server/Web 启动已于 2026-09-07 授权完成,真实 HTTP 管理列表和模块目录加载通过;线上仍未部署。

  • server/app/goauto/clientkey 管理独立密钥、授权及审计;clientapi/routes.go 的显式 Inventory 将 /api/client/v1 映射到既有业务处理器,绝不转发任意 Admin 路由。
  • clientapi/gateway.go 默认接受 HTTP 与 HTTPS,无协议开关或转发协议头门禁,按 Bearer 密钥、模块及能力顺序校验,每次请求读取数据库,无授权缓存。common/clientprincipal 传递独立客户端身份,不生成或伪装管理员 JWT。
  • client_api_key 保存名称、随机 256 位密钥的 SHA-256 摘要、前缀、授权 JSON、启用状态、版本、创建/修改人和最后使用时间;完整密钥仅创建响应一次返回。client_api_key_audit 保存管理变更和客户端请求元数据,不保存请求正文、查询参数、响应正文或凭据。
  • 编辑与停用采用启用状态和 version 条件更新,并与变更审计同事务提交;冲突返回 409。业务执行前先持久化请求审计意图,失败则不执行业务。完成后更新状态;更新失败或进程中断可能留下 status=0,表示结果待核对,不能据此自动重放。
  • 部分既有业务操作人字段使用该密钥最近授权管理员的 ID 兼容现有外键;实际调用方以独立审计的 key_id 为准,不能把业务字段当成人工操作证据。
  • Admin 页面 web/src/views/goauto/client-keys/index.vue 复用创建/编辑授权弹窗;菜单位于“采采管理”,仅管理员可见。新追加迁移 1788798000000_client_api_key.go 创建两表及管理员菜单,不改 Android。

采购规格面板预滑动兼容(#238)

代码基线 58a6c1c,Android 0.9.60 / versionCode 73(构建完成不等同于已安装/发布)。PurchaseRehearsalExecutor.applyPostAction 对 openSpecPanel.swipeAfter 只兼容解析、不执行机械预滑动,waitAfterMs 保留;首趟继续原 probeSpecs 遍历,第二趟继续原 selectSpec 精确查找与容器内有界滚动。其他动作的后置滑动仍沿用既有执行语义,失败不会被统一忽略。

GoAutoAccessibilityService.swipePurchase 的失败分类由 PurchaseSwipeFailureReason 枚举提供;共享 swipeNode 仅增加可选分类回调,不改变手势目标、轨迹、1500ms 回调等待或其他调用者行为。GoAutoPurchasePanel 日志经 AgentForegroundService 关联 task、attempt、device 与规则快照哈希,新增预滑动跳过/必需滑动失败标量;不记录节点文字、坐标、原始控件树、截图或凭据。

Server/Web、数据库和任务快照不变;旧 APK 仍有预滑动行为,必须更新 Agent 才生效。相关验证在 PurchaseRehearsalExecutorTest,Android 全量测试与 APK 构建入口不变。

SYB 逐页保存与部分成功(#239)

实现绑定 c6a962d;代码已实现不代表当前线上已部署。每页完整明细在外部请求结束后按页事务保存;页回滚不累计明细/新增/覆盖数,已提交页保留。日期局部读取失败继续下一日期,全局数据库/进度/会话/取消故障停止。当天漂移不在一次运行内重扫;后续运行重新扫描并幂等补齐。

同步状态增加 partial_success(部分成功,15 字符,复用现有 varchar(16),无需迁移)。有错误且 created+updated>0 为部分成功;有错误无已提交明细为 failed;完整且无错误为 succeeded(包括无符合店铺的数据)。中断仍为 interrupted,不把中断追认为成功。所有终态沿用活动槽释放规则。

orderCount 是已验证页的原始列表读取数量;detailCount、created、updated 为已提交明细及其新增/覆盖数量;daysProcessed 是完整通过的日期数,不是已尝试日期数。失败日期/页码/阶段写入现有脱敏限长 errorMessage。部分成功不刷新店铺的完整同步统计。

Web 唯一展示位置为“采集采购 → SYB 同步记录”:列表状态、状态筛选及详情支持部分成功,详情保留已保存数量、错误原因与重新同步补齐提示。定时任务日志只表示异步任务受理,不等于最终业务同步成功。

入口为 SyncWithShopSnapshot → loadDailyList → importSyncPage;server/app/goauto/sybimport/sync_page.go 封装页事务和提交后计数;import_handler.go 判定终态,sync_run.go 保存及筛选,Web 复用 web/src/views/goauto/syb-sync-runs/index.vue。

Android 连续临时采集收尾(#250)

当前实现绑定 37e8b56(Agent 0.9.72 / 85),用户确认 v5 按次数返回后切回 Agent。仅 Android,无 Admin/API/权限/数据库迁移;取代 v1~v4 首页导航。

  • AgentSettingsFragment 在采集间隔后复用卡片和 SwitchMaterial;仅开启显示“返回次数”按钮,Material 单选框选择 1~5 次(默认 3),立即保存或取消。成功无常驻反馈,错误/忙碌显示原因。任务忙碌禁用,弹窗选定时再检查。
  • AgentSettingsStore 在私有 goauto_agent_settings 保存 continuous_collection(false)与 continuous_collection_return_count(3);次数越界或读取错误回落 3,写入失败恢复旧值,无网络加载。
  • requestCurrentPageCollection 保留原范围判断;executeTask 在采集开始固定次数。executeTaskWhileAwake 提交结果、结束图片上传后返回 resultStatus;ContinuousCollectionPolicy.finish 仅对完整确认成功调用新 PddCollectionReturnNavigator。失败/部分成功不导航。冷却间隔不变,连续模式不启动旧 idle-return。
  • 删除 PddCollectionHomeNavigator 和专属首页/搜索层判据。新导航器仅使用 capture/back、当前包名、openAgentPreservingTab;1~5 次 Back,每次后 500ms,前台离开 PDD 终止剩余 Back;保留风险场景停止。最后打开 Agent 并等待 500ms 验证包名,不验证 PDD 首页。仍在原任务互斥与唤醒锁范围内完成,结束后原 finally 释放。
  • GoAutoCollector 日志关联 taskId、attemptNumber、deviceId、规则快照 SHA-256;记录 continuousReturn、configuredBacks、固定 reason 和 actions。原因含 count_completed、left_pdd、back_failed、unsafe_page、agent_launch_failed、agent_not_confirmed、navigation_exception、invalid_count;actions 为 Back 尝试数,不代表最终页面。无节点文字、树、截图或个人数据落盘;不扩大采购诊断存储。
  • ContinuousCollectionTest 覆盖范围/终态、1~5 次、无变化仍按次数、前台变化、风险、动作/启动失败、包名确认和异常;ContinuousCollectionSettingsTest 覆盖开关与次数持久化、默认/越界/写入失败。:app:testDebugUnitTest :app:assembleDebug 376 项通过。旧首页专属测试随旧行为替换,新版未安装,UI 大字体/横屏、多设备和完整采集真机链路尚未验证。

SYB 存疑规格候选解析(#251)

实现绑定 64b4205,仅 Server app/goauto/sybimport;无 API 字段、数据库迁移、权限、并发模式或 UI 变化。

  • parse_candidates.go: ParseWithCandidates 先运行原 Parse,仅 uncertain 时解码并校验已存 SpecsJSON;复用 closedShopeeCandidates 提取颜色/尺码封闭候选,拒绝未知有值角色/重复角色。完整候选组合与原文归一比较唯一时确认,其他情况返回原 ParseResult。
  • ApplyDetail 在加载既有蝦皮商品后、写入规格前调用;Reparse 在保留人工/AI 确认保护后加载关联候选调用。新增 ParseResult 私有 matchedCandidates 标记,使 mergeParsedSpec 对已经存在的候选直接返回,避免按维度名重复合并或改变映射。
  • #198 ProcessSpecAIParseRun → Reparse 自然接入确定性优化,仍需 AI 的记录沿用原后续步骤,不新增异步任务/Provider 调用/接口。
  • 验证 go test ./app/goauto/sybimport ./app/goauto/aimatching ./app/goauto/shopeeproduct ./app/goauto/purchase 和 go build ./... 通过;新增候选正反/格式/组合色/单维度、歧义/空值、同步重解析一致、人工保护及无需调用 AI 测试。AI 原有置信度/失败重试测试改用仍需语义理解的合成输入,继续验证原分支。
  • 本轮只使用本地测试数据与 httptest AI,未触发真实 SYB 同步、外部 AI、采购、数据库迁移或服务重启;线上及历史数据效果尚待授权验证。

蝦皮详情匹配等待与回读(#254)

实现绑定 9088e6b(2026-09-10,分支 fix/254-match-loading);已通过合成数据测试,尚未合并 main 或发布线上。

  • Server shopeeproduct.Service.DetailWithAutoMatchBudget 为详情 GET 附加派生的整次匹配预算,不返回 Provider、Key 等配置。AutoMatchMappings 读取 AI 设置 T,使用 2T+10 秒的单个父 context;颜色、尺码、保存及结果回读共用剩余时间。初始配置读取也受有界 context 约束,读取耗时从后续预算扣除。
  • 批量定时匹配调用内部 autoMatchMappings 并继续使用原 MaxProviderTimeout=600 秒,不随本次详情入口调整;候选建议与其他 AI 功能不改。
  • Web ShopeeProductDetailDrawer 在提交前以普通 GET 刷新商品与预算,仅 auto-match POST 使用 (2T+20) 秒超时。准备失败/缺少预算时不发 POST;旧 Server 必须先更新,不能只发布新版 Web。
  • 加载状态同时绑定商品加载代次和操作序号。关闭/切换立即结束该页面等待状态;同商品重开读取详情,旧响应/旧 finally 不得污染新操作。匹配期间禁止在当前抽屉编辑映射,原人工草稿放弃确认保留。
  • POST 未正常返回时只回读详情及关联 PDD,不自动重发 POST;回读失败结束转圈并保留刷新入口。GET 沿用单请求10秒超时,完整结果回读最多两次串行 GET,不是无限等待。
  • 验证:Go shopeeproduct/aimatching/clientapi 测试与 go build;Web node --test tests/unit/shopee-match-lifecycle.test.cjs,Playwright shopee-match-lifecycle 与 shopee-pdd-picker,受影响 ESLint 与 build:prod。

SYB 行内一键关联最近采集(#253)

实现绑定 b1594dc,2026-09-10;分支 feat/253-one-click-link,包含 #254 依赖。已完成合成数据测试及构建,未合并 main、未部署,不代表当前线上已具备此能力。

  • Web syb-products/index.vue 在“去关联”旁增加“一键关联+下拉箭头”,共享 quick-link.js 页面级状态。quick-link-device-preference.js 按当前浏览器来源、API环境、用户ID保存设备ID,独立于批量采购偏好;不存密钥或商品数据。首次主动作选机继续原行,箭头选机只保存偏好。
  • Server shopeeproduct/latest_collection.go 复用 LinkPDD 的可选 Admin 模式。短事务核对 SYB→Shopee、未关联/规格版本、设备状态,选择并锁定最新 completed/completed_partial 的 agent_current_page 任务及其 PDD;仅完整采集、active且有可用颜色或尺码时更新,WHERE pdd_product_id IS NULL 不覆盖其他操作者的关联。响应保留该事务产生的规格版本及来源task/device/PDD,不能用后续GET替换冻结上下文再自动匹配。
  • 不新增表、迁移、路由或权限对账。原手动关联/替换请求不变;共享 Handler 明确拒绝 Client API 使用此跨模块模式,不能借 Shopee-only write grant 读取 SYB/设备上下文。来源任务原规则快照不改,taskId 可回溯既有采集证据。
  • 关联后读取现有采购准备;需要人工确认或不具备AI资格时停止自动链路并保留关联,其他情况调用原 AutoMatchMappings,沿用 #254 动态预算及原事务保存/上下文冲突保护。最后刷新实际处理阶段,不调用创建采购/订单接口。
  • 运行期间冻结行、商品、手机;前端重复主动作禁用,切换偏好仍只影响未来操作。卸载或 keep-alive deactivated 令旧操作失效,禁止旧回调继续下一步;不声称已经取消服务端在途写入。异常仅有限GET回读和刷新,不自动重发POST。
  • 验证命令:Server go test -p 1 ./...、go build ./...;Web node --test tests/unit/syb-quick-link.test.cjs tests/unit/shopee-match-lifecycle.test.cjs、pnpm exec playwright test tests/e2e/syb-quick-link.spec.ts tests/e2e/shopee-match-lifecycle.spec.ts --workers=1、受影响文件 ESLint、pnpm build:prod。SQLite 单连接并发测试不代替生产 MySQL 行锁验收;真实AI、生产数据、跨设备人工闭环待验证。

SYB 行内一键替换(#258)

实现绑定 2b01974;部署与验收见 #258 发布证据。本节补充 #253,不改变原未关联入口。

  • syb-products/quick-link.js 复用分体按钮、设备偏好和匹配/回读流程;已关联且允许处理阶段时同位置显示一键替换。先只读预览,固定任务、目标商品及规格版本;不同商品一次确认,同商品不重绑。
  • shopeeproduct/latest_collection.go 在原 latestCollection 增加可选 replacement 模式。预览和提交均短事务锁定 SYB、Shopee、原PDD、设备、来源采集与目标PDD,核对旧关联及上下文。提交按预览 taskId 读取,不重新挑选后来采集的商品。
  • Admin 路由注入 purchase.ValidateQuickReplacement,在事务内复用采购准备数据集及处理阶段计算,不调用 AI。缺少校验函数拒绝替换;客户端密钥不注入且仍拒绝 latestCollection。无新路由、权限、表或迁移。
  • 关联完成后才执行现有 AI 匹配,不在数据库事务中等待 Provider;同一 SYB 正式采购创建与替换共用 SYB 行锁。历史采购快照不变。SQLite 合成测试覆盖冲突与串行并发请求,不能替代生产 MySQL 多连接锁验收。

辅助信息缺失的采集关联(#259)

实现绑定 e467706,仅 Server;部署/验收见 #259。替代 #253/#258 中“部分完成一律拒绝”的限制,不改变 completed 原有路径。

  • completed_partial 的 missing 必须是非空合法列表且仅包含 salesText、shopName、reviewCount,才进入辅助信息兼容检查;title、规格/价格缺失、遍历截断、SKU数量上限及未知原因仍拒绝。
  • shopeeproduct/partial_collection.go 核对本次 task 的维度和值、可售SKU的完整标记/规格引用/非负价格、本次颜色价格,以及当前档案可选值由本次有效SKU覆盖,不能借旧档案合并残留的规格或价格通过。真实单维度允许,不虚构另一维度;本次明确报告缺少规格仍拒绝。
  • 一键关联、替换预览及确认提交共用验证。失败沿用409 LATEST_COLLECTION_UNAVAILABLE及现有提示,不回退更早采集、不换手机。状态 completed_partial 和 missing 原样保留;辅助字段缺失仍可在任务详情查看。
  • 无接口字段、数据库迁移、Android/Web或权限变更;普通人工关联与采购/AI资格不变。不将“允许关联”视为“已匹配或已采购”。

采购单标题面板与地址就绪等待(#260、#261)

实现绑定 #260 3c1ffa4(执行器回归测试 7de4318)、#261 5ea07f1,Android 0.9.74 / versionCode87,基于0.9.73。安装及真实采购验收以工单证据为准;不代表 main 的 Android 已整合这些分支。

  • 采购仅在安全规格入口点击返回SUCCESS后,记录本次页面包名/Activity作为内存上下文;开始新执行或页面身份改变时清除。PddScreenParser 的新增 purchaseEntryContext 默认关闭,仅该采购执行器传入;普通解析和采集原判定保留。
  • 当前仍为同一PDD Activity、商品ID非空、没有页面风险、没有选中摘要,且唯一有标题规格滚动区、一个维度有值,同时具有关闭、数量、支付展示区域与下单操作证据时,单标题可识别为NORMAL_SCROLLABLE。只是允许进入既有精确规格探测/选择,不执行下单或付款,不全局认可UNKNOWN,不增加入口重复点击或滑动。
  • 地址入口点击原有1000ms保留;地址列表等待增加唯一修改目标要求。点击修改和保存由500ms改为1000ms,其他通用动作间隔不变,无新配置项。
  • 修改后不再只见“详细地址”文字就立即写入。最多50次、每次间隔200ms读取(不含读取耗时),检查PDD页面风险;唯一可见启用有内容的详细地址EditText,连续两次路径、边界、内容及Activity相同才继续原输入流程。始终零个/多个或不稳定时失败,不取第一个候选。
  • 输入内容生成、输入后回读、保存后退出及返回规格面板、最终复核和单次下单边界不变。新增本机脱敏诊断 addressEditorCandidates / addressEditorPolls 两个整数,与task/attempt/device/ruleHash关联;不记录地址内容、原始树或整屏截图。
  • 仅Android;无Server/Web、共享API字段、数据库迁移或权限变化。真实采购、地址写入及订单创建不作为自动安装验证,不执行付款。

v2 商品入口补点与地址返回等待(#260、#261)

2026-09-10 实现绑定 #260 713111d、#261 d0a82f3,Android 0.9.75 / versionCode88;真实采购效果以工单验收为准,非 main 合并声明。

  • 入口恢复不再把全页面支付方式展示文案单独视为支付页。当前包名/Activity、商品标题和安全入口必须与点击前一致,入口唯一且可用;已打开面板、关闭控件、数量、选中摘要、订单操作、风险/评价页面仍拒绝恢复。支付 Activity 或明确支付、订单、地址文本继续阻止补点。仅允许既有一次重新定位后的规格入口中心手势,随后仍验证面板,不循环、不扩展到地址保存或下单。
  • 保存地址后的确认读取、必要时一次返回后的确认读取,增加最多50次、间隔200ms的有界就绪检查,页面捕获耗时另计。连续两次同一Activity满足原地址回读与确认面板条件后继续;完整信息不要求滚动区域。已明确到地址列表时保留原返回路径,不等待列表出现下单按钮;返回后增加1000ms缓冲。
  • 就绪检查不修改原地址回读和结构下单入口规则;超时后只对既有识别的面板、唯一可用滚动区域执行原有有限滚动。零/多个区域仍失败,不凭地址入口或未知底部节点授权下单。最终精确规格、数量、价格、地址与单次下单边界不变。
  • 私有采购诊断新增布尔 savedAddressMatched、savedPanelMatched;整数 savedLegacySubmitCandidates、savedAddressCards、savedAddressTargets、savedScrollCandidates、savedConfirmationPolls。只记录标量并绑定task/attempt/device/ruleHash,不保存地址、原始树或整屏图。
  • CG109 v1现场读取未达到空闲状态,不能确认原失败具体缺哪项证据;v2覆盖迟到确认节点及可观测性,不将合成测试通过冒充真机修复验收。无Server/Web/API/迁移/权限变化,不需要服务器重启。

探测到正式采购的单次面板交接(#262)

2026-09-10 实现绑定 2ea32fd,Android0.9.76/code89;基于#260/#261,未声明合并main或真实采购验收通过。

  • AgentForegroundService通过PurchaseProbeHandoff只在成功spec_probe且结束后新快照仍识别PDD规格面板时保留一次性内存交接。仅taskId/goodsId/ruleHash/deviceId、Activity、前台revision和单调时钟时间,不保留控件节点、地址或选中状态,不落盘;有效期180秒。
  • 正式阶段消费时检查同任务/商品/规则/设备、前台revision及时间,消费后即清除。失配/超时/无任务或采集任务调度/结果异常/进程重启均不能沿用;普通手动再试不会继承已消费交接。现有服务端租约、互斥和匹配等待策略不变。
  • 有交接时,执行器恢复限定的purchaseEntryContext,最多10次、间隔200ms新读取(捕获耗时另计);连续两次相同PDD Activity且具备既有商品/面板证据才跳过openProduct。空树可有限等待,其他应用/Activity改变/页面风险立即拒绝复用。没有交接时保留原一次判断路径;不把UNKNOWN当成功,不跳过正式精确规格/数量/价格/地址或下单验证。
  • 私有诊断新增handoffPresent、productReused布尔值,reusePolls整数及reuseReason固定枚举legacy_evidence/page_problem/page_changed/handoff_ready/evidence_timeout。仍绑定task/attempt/device/ruleHash,无敏感文本。
  • 无Server/Web、共享API或数据库变化,不需重启服务器。只有合成测试验证;原CG107/108日志证实两阶段,但未记录旧版重开判断,不把上下文丢失假设宣称为唯一现场根因。

人工订单回填金额扩展(#306)

实现绑定 8c01329(2026-09-18),分支 feat/306-order-paid-amount;待验收、未部署。复用 #241/#242 链路,不改普通采购提交和扫描器导航。

  • Android automation/OrderBackfillPolicy.kt:BackfillDetailReader 在订单内收集唯一“实付”金额,使用 BigDecimal 精确转换为人民币分;BackfillItem 增加可选 pddOrderAmountCent。
  • Android network/OrderBackfillUpload.kt 与 persistence/TaskHistoryCache.kt:上传可选金额,读取并缓存 Server 确认金额和独立警告;不把成功警告改为任务失败。
  • Server models/purchase.go:purchase_task.pdd_order_amount_cent 为 nullable BIGINT、非负;与 actual_unit_price_cent 独立。追加迁移 1789800100000_purchase_order_amount.go 使用版本内的局部表模型,仅扩展 purchase_task;历史值 NULL,不回填猜测金额。
  • Server purchase/order_backfill.go:原有逐条事务/设备与订单号保护内补齐空金额;相同值幂等,异值保留并警告;已 order_created 同号任务也能补写。purchase/admin_query.go 输出金额。
  • Web views/goauto/purchase-tasks/index.vue:复用现有订单 state-card 显示 CNY 实付价格或“尚未获取”;列表结构不变。
  • 本次不修改 SYB 客户端或 #305,金额不进入 SYB 请求。

验证覆盖 Android 解析/载荷/回执缓存、Server 幂等/冲突/旧请求和局部 SQLite 迁移、Web 金额/空值展示;真机端到端、实际 MySQL 迁移和发布须另行授权,不能以本地测试替代。

SYB 单号回填持久队列(#305)

实现绑定 e89de1a(2026-09-18,未部署),仅 Server/Web;Android 复用现有普通结果与人工订单回填。

  • purchase/lifecycle.go、purchase/order_backfill.go → ensureOrderWriteback:与订单事实同事务建立 pending,purchase_task_id 唯一;外部失败不撤销已提交的订单事实。
  • models/purchase_order_writeback.go:purchase_order_writeback 保存不可变 stock/detail/单号快照、状态、attempt_count、write_started、租约、脱敏错误和完成时间;purchase_order_writeback_command 保存 requestId/输入摘要/逐项接受结果;purchase_order_writeback_lease 保存全局单例租约。追加迁移 1789800200000,不改旧物流字段,不批量排入历史订单。
  • purchase/order_writeback.go:批量命令事务/幂等/条件校验和 Admin 状态投影;order_writeback_handler.go/router.go 暴露 Admin 补偿;access/purchaser.go 纳入原采购权限启动对账目录。
  • purchase/order_writeback_worker.go:由 cmd/api/server.go 每数据库启动一个3秒轮询 worker;数据库2分钟单例租约使跨进程及不同任务的相同SYB明细写入串行。写前核对当前任务/关联快照,持久化写入起始标记,并验证租约所有者/剩余时间;不持有数据库事务跨网络请求。失联running恢复只回读,unknown不自动调度;其他同目标未确认写入阻止新写。
  • sybclient/order_number.go:一次 POST updateDetailPurchaseCode,不跟随重定向、不使用读接口重试策略。会话仅从现有 SessionStore 恢复,不在 worker 自动登录/OCR;外部原始错误不写入任务原因。
  • Web purchase-tasks API/页面:共享勾选、按动作筛选、独立状态列/详情及逐项接受结果;付款和物流流程保持原样。
  • 验证入口:go test ./app/goauto/purchase ./app/goauto/sybclient ./app/goauto/access ./app/goauto/migrations ./cmd/migrate/migration/version-local;Web tests/e2e/purchase-order-writeback.spec.ts。测试只使用隔离SQLite和fake/httptest,不代表MySQL多实例或真实SYB验收。

会话类失败自动重试(#330)

  • purchase/order_writeback_worker.go:restoreOrderWritebackClient 在 ImportCookiesJSON 后调用 sybclient.CheckSession,UserID<=0 显式判不可用;finishSessionUnavailable 复用 lease_expires_at 作为退避到期时间(maxSessionRetryAttempts=6,sessionRetryBackoff 5/10/15/30/30m),领取条件增加 failed+SYB_SESSION_UNAVAILABLE+到期+未达上限。无迁移。
  • purchase/order_writeback.go:会话类失败的 CanSubmit 不受退避租约限制;手工重新提交 attempt_count 置 0。
  • 验证:go test ./app/goauto/purchase(含 httptest 模拟 /am/user/get 与断言 syb_session 未删除)。

Chrome PDD 订单回填扩展(#316)

  • chrome-extension/ 是独立 Manifest V3 交付单元:popup 只负责配置、启动/停止、状态轮询和逐项结果;content script 只在 mobile.yangkeduo.com 的隔离世界中按可见 DOM 串行读取;service worker 负责持久运行状态、聚合冲突和分批提交。
  • 列表遍历限定虚拟列表订单卡片的商品行,排除店铺头和支付、确认收货、退款、售后、取消、更多等动作区;详情只展开收货/订单信息。未看到明确列表末尾证据时必须标记未完整扫描,不得宣称全量。
  • 客户端密钥与冻结的 Admin Origin 只保存在扩展可信上下文;content script 无权读取。请求禁用 Cookie 与重定向,批次最多 50 条,稳定 UUID 可在 service worker 重启或超时后重放;已确认批次不重复计数。
  • 服务端 POST /api/client/v1/purchase-tasks/order-backfill 复用 Agent 回填事务校验,但使用独立 client principal,不伪造 Device Token 或 deviceId。Agent 路径仍严格保持设备归属校验及失败后按设备范围回读。

下单结果读取及应付总额链路(#325)

分支 fix/325-order-result,2026-09-19,尚未发布及真实下单验收。PurchaseLiveAutomation.readOrderResult 采用25次支付页返回后采样、首次待付款页独立30次采样/最多4次按需滑动;原过渡60次与总90次采样上限保留有界终止。parsePayableAmount只从本次只读标签集合提取明确应付总额。

数据链路:PurchaseOrderEvidence.pddOrderAmountCent → PurchaseExecutionOutcome → service/PurchaseResultPayload.kt → purchase.ResultRequest → lifecycle.go 的 order_created 分支 → 既有 purchase_task.pdd_order_amount_cent。重启只读核单也沿用同一可选字段。复用已有结果Outbox、幂等哈希和订单号唯一性;不新增表、配置、任务或SYB金额字段。未修改我的订单回填、Chrome扩展和Web,不能将旧入口的实付含义描述为本次已改成应付。

Admin 订单资料补全与一致回填资格(#326)

实现3a2472d(2026-09-19),Server/Web,未合并发布;无迁移、无Android/插件改动。

  • purchase/types.go ManualRequest 增加可选 pddOrderAmountCent;manual.go ResolveUnknown 复用既有行锁事务和模型单号保护,扩展为未知结果确认或已创建订单补缺。仅此操作启用新增重放内容校验和事务内 ensureOrderWriteback,其他人工付款/取消/授权流程不新增入队副作用。
  • order_writeback.go 的任务资格去掉 order_created 限制;validOrderWritebackTarget 统一有效 stock/detail 边界。OrderWritebackViews 一次批量读取关联而非逐行查询,RequestOrderWriteback 和 worker 执行前再次检查。队列结构、租约、外部写入协议不变。
  • web/src/views/goauto/purchase-tasks/index.vue 沿用原详情/弹窗/resolve-unknown API,新增缺字段补录入口、整数分转换、完整性展示及失败保留输入,不增加数据库状态或新的权限资源。旧未知结果请求不带金额继续兼容。
  • 回归:server go test -p 1 ./app/goauto/purchase ./app/goauto/clientapi ./app/goauto/sybclient;Web 生产构建及 tests/e2e/purchase-order-writeback.spec.ts 的6项隔离浏览器测试。真实SYB接口用fake/httptest替代,未执行付款、采购或线上写入。

规格面板识别回退与选项去重(#331/#332)

  • automation/PddProductDetailCollector.kt:SpecPanelType.REQUIRED_EVIDENCE 回退分支(地址入口/支付入口/唯一数量输入框 + 同一面板容器 + ≥1 辅助证据);ParsedPddScreen 增加 hasAddressEntry、hasPaymentEntry、hasQuantityInput、hasQuantityAdjustControls、quantityInputCount 与 requiredEvidenceSummary();SpecPanelStabilityPolicy(等待上限 5000ms,地址返回采样 200ms);规格值按选项块合并(optionBlock)。
  • automation/PurchaseRehearsalExecutor.kt:waitForSpecPanel 对回退类型要求连续两次结构一致;selectionFailureEvidence 选中失败诊断。
  • automation/PurchaseLiveAutomation.kt:waitForSettledPanelAfterAddressSave 保存地址后稳定等待(不按返回键);finalSubmitTargets 拒绝零尺寸文字/容器;新增构造参数 panelDiagnostic(仅布尔/计数)。
  • 测试:SpecPanelRecognitionTest、SpecOptionDedupTest、SpecPanelFixtures(脱敏构造样本)。