42 KiB
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: 0b9d4e6e0e97f495d19a53e1aa0ecea3a66ba495 synchronized_at: 2026-09-07T09:27:49Z
架构与代码地图
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 与规则快照
└─ 提交结构化结果或明确错误
最小闭环
- 管理员添加 PDD URL,服务端规范化 URL 并提取唯一
goods_id。 - 管理员创建规则;规则创建后立即可用于创建任务。
- 管理员从一个 PDD 商品创建任务,可指定设备,也可留空等待空闲设备领取。
- 任务固化 URL、goods_id 和完整规则快照。
- Android 串行执行并把结果写回同一条任务;规格与 SKU 写入任务结果子表。
- 同一事务把
completed结果全量覆盖到 PDD 商品最新档案,把completed_partial明确采集到的字段和规格安全合并;failed不修改商品。 - 管理员在任务详情查看原始结构化结果,也可在 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 幂等执行记录、阶段、规则哈希、固化规格决策和结构化错误 |
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 校验任务归属,并复用 AdminBatchRetry的当前档案、不可逆边界、设备能力/忙碌和幂等创建逻辑;旧失败任务不修改,新任务固定原设备。 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/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。