From 1ec895b8e666fb6c445a8c50ae86fb7d332ff8e4 Mon Sep 17 00:00:00 2001 From: QiuSW <105186638@qq.com> Date: Sat, 29 Aug 2026 21:49:19 +0800 Subject: [PATCH] docs(goauto): document rules and agent releases (#127 #143 #144 #146) --- docs/02-architecture-and-code-map.md | 23 ++++++++++----- docs/03-business-rules-and-glossary.md | 23 +++++++++------ docs/04-local-development-and-verification.md | 12 ++++++-- docs/08-agent-api-contract.md | 28 +++++++++++++++++-- 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/docs/02-architecture-and-code-map.md b/docs/02-architecture-and-code-map.md index 9786b29..839172a 100644 --- a/docs/02-architecture-and-code-map.md +++ b/docs/02-architecture-and-code-map.md @@ -2,8 +2,8 @@ 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: d0222d4337992bedcf2b973bc3bb9d43922c0ea3 -synchronized_at: 2026-08-29T08:29:05Z +wiki_revision: d94641e4574e064395edb9da4b52b1a268925923 +synchronized_at: 2026-08-29T13:50:07Z @@ -63,8 +63,10 @@ Android Portal/Agent | 表 | 必要内容 | |---|---| | `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` | 维度、值、排序 | @@ -138,7 +140,7 @@ Android Portal/Agent | 备货采购服务端路径 | `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/` 统一声明 11 个模块、路由元数据和 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 授权彼此独立 | +| 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` | @@ -289,10 +291,10 @@ PddProductDetailCollector ## Admin GoAuto 分组导航与权限(#142) -- `server/app/goauto/access/modules.go` 是 11 个 GoAuto 页面模块及两个一级菜单组的代码事实来源;本地迁移 `1787885600000_goauto_menu_groups.go` 创建结构性父菜单,并把既有页面菜单直接迁入父组。 -- 「采集采购」依次包含:SYB 商品、SYB 同步记录、档口入库码、虾皮商品、PDD 商品、采集任务、采购管理;「采采管理」依次包含:SYB 店铺、采集规则、设备列表、AI 规格匹配。 -- 迁移保留页面菜单 ID、路由、组件和 API 关联,移除旧的 11 个一级模块根菜单;导航固定为“分组 → 页面”两级,不增加第三级。 -- Admin 角色继续通过角色菜单查询取得全部 GoAuto 页面;采购员获得两个父组和除 AI 规格匹配外的 10 个页面,AI 页面及入口均不可见。 +- `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` 动态生成路由;直接访问组内页面时展开对应父组并高亮当前页面。 @@ -302,3 +304,10 @@ PddProductDetailCollector - 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 和系统安装确认;不参与任务轮询、心跳或前台服务生命周期。 diff --git a/docs/03-business-rules-and-glossary.md b/docs/03-business-rules-and-glossary.md index 48da7b8..198d7be 100644 --- a/docs/03-business-rules-and-glossary.md +++ b/docs/03-business-rules-and-glossary.md @@ -2,8 +2,8 @@ generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Business-Rules-and-Glossary wiki_url: https://git.ilapage.cn/OPC/goauto/wiki/Business-Rules-and-Glossary.- -wiki_revision: 2e474dd5f804c3e754ecc8e8722c9469e424acf0 -synchronized_at: 2026-08-29T09:02:23Z +wiki_revision: 96b59298692b897eb64f06a1d9c7ab03282fa7ad +synchronized_at: 2026-08-29T13:50:19Z @@ -144,8 +144,8 @@ synchronized_at: 2026-08-29T01:39:44Z - 价格保护保存参考单价、最低单价、最高单价和币种,执行时以 PDD App 实际单价判断;价格越界明确失败,不考虑优惠券。 - Admin 从 SYB 当前页选择商品,可在“创建采购”和“重新解析”两种批量用途间切换;采购模式只允许选择预检合格行,表头全选不跨页。批量预检和正式创建均最多 100 条并逐条重新校验,每条 SYB 明细只创建一个独立任务;部分失败不回滚其他成功项。 - SYB 商品列表在列表数据返回后立即展示,采购准备状态独立异步加载;只读批量预检只执行批量数据库读取、已确认映射和本地确定性匹配,禁止调用 AI Provider。正式创建仍按最新数据完整复核,只有创建阶段无法唯一确定规格时才允许调用 AI。 -- 批量创建使用服务端内置并经契约校验的正式采购规则,每条任务保存不可变快照;设备默认人工指定,也可以留空由符合能力的空闲设备领取。 -- 批量创建的价格保护来自 PDD 商品档案而不是 SYB/Shopee 的 TWD 售价:已确认颜色映射时按该颜色人民币价格计算;待规格探测时用可用颜色最低价的 0.2 倍到最高价的 1.5 倍,参考价取最高价。没有可用颜色价格时不能创建。 +- 批量创建读取管理员选定的当前采购规则并重新执行契约校验,每条任务保存不可变快照;当前规则缺失或无效时明确阻断,不回退到代码常量。设备默认人工指定,也可以留空由符合能力的空闲设备领取。 +- 批量创建的价格保护来自 PDD 商品档案而不是 SYB/Shopee 的 TWD 售价:当前采购规则可用 `priceGuard.minRatio`(0.1~1.0)和 `maxRatio`(1.0~3.0)配置比例,缺省仍为 0.2 / 1.5;最低价向下取整、最高价向上取整到人民币分。已确认颜色映射时按该颜色计算,待规格探测时以全部可用颜色的最低/最高价计算,参考价取最高价;没有可用颜色价格时不能创建。 - 地址后缀为 `_cg{purchase_task.id}`。选好规格和数量后,Agent 仍停留在当前规格/下单面板;若收货地址被面板裁切,只允许在唯一、可见且占据主要宽度的纵向滚动容器内有限向下拉动来显示地址,禁止按页面最大滚动区域盲目滑动。Agent 只在唯一地址入口、唯一修改按钮和唯一详细地址输入框均成立时修改;脱敏手机号文字本身不可点击时,仍以该唯一文字节点的中心坐标执行一次精确手势点击,不沿用可能覆盖整个下单面板的可点击祖先。“修改”“保存”和“提交订单”等文字节点即使依赖可点击父节点,也必须保留原始文字节点作为每次重新定位的锚点,父节点只用于验证存在可点击路径。近乎完全重叠的无障碍重复节点按一个目标处理,仍有多个独立目标或页面切换超时则明确失败。按首个 `-` 或 `_` 截取地址主体后追加当前后缀,保存后必须回读完整新地址。修改或回读失败时禁止创建订单,地址全文和控件树不落库。 - 地址编辑页可以同时存在收货人、手机号和详细地址等多个输入框;Agent 只选择与“详细地址”标签纵向重叠且位于其右侧的唯一输入框,不能用页面输入框总数或顺序猜测。 - 点击创建订单前,Agent 必须先在本地事务保存 `order_submit_started`、不可逆时间、稳定请求 ID 和不含地址全文的最终确认快照,再用同一请求 ID通知服务端;两侧成功后才允许精确点击唯一创建订单按钮一次。 @@ -162,7 +162,7 @@ synchronized_at: 2026-08-29T01:39:44Z - 已创建订单默认禁止再次采购;管理员或采购员可以做一次性重新采购授权,新任务创建成功时在同一事务消耗授权,旧任务和旧订单保留。已标记为已支付的订单不能授权或创建重新采购任务。 - 人工回填候选只允许从已支付订单选择;同一 SYB 明细后来选择的订单覆盖旧候选,但不删除旧订单事实。 - Admin 采购管理只查看和处理已有任务,不提供创建入口或支付按钮;单条和批量采购任务都从 SYB 商品列表发起。订单结果未知时必须先人工核对并解除;处于该状态时页面不提供重新采购授权。 -- 采购失败任务可在采购管理当前页批量勾选重试,最多 100 条。重试不修改旧任务,而是用当前 SYB/PDD 档案、当前规格映射、当前价格保护和最新内置采购规则创建新的 `pending` 任务,并生成新任务编号与地址后缀;来源蝦皮订单号沿用失败任务的不可变快照。 +- 采购失败任务可在采购管理当前页批量勾选重试,最多 100 条。重试不修改旧任务,而是用当前 SYB/PDD 档案、当前规格映射、当前价格保护和当前采购规则创建新的 `pending` 任务,并生成新任务编号与地址后缀;来源蝦皮订单号沿用失败任务的不可变快照。 - 只有正式采购、未进入不可逆边界、没有订单号或下单时间、且仍是同一 SYB 最新记录的 `failed` 任务可重试。原设备离线、停用、忙碌或能力不足时该项失败且不自动换机;未指定设备时仍由空闲设备领取。 - 批量重试逐项处理并允许部分成功;同一请求幂等重放不会重复创建。失败任务不再使用一次性重新采购授权,该授权只保留给已经创建过订单且满足条件的任务。 @@ -374,6 +374,13 @@ synchronized_at: 2026-08-29T01:39:44Z ## Admin 采集采购与采采管理导航(#142) - 「采集采购」固定顺序为:SYB 商品、SYB 同步记录、档口入库码、虾皮商品、PDD 商品、采集任务、采购管理。 -- 「采采管理」固定顺序为:SYB 店铺、采集规则、设备列表、AI 规格匹配。 -- Admin 可见两个分组及全部 11 个页面;采购员可见两个分组和除 AI 规格匹配外的 10 个页面。父组只承担导航结构,不新增业务权限。 -- 分组迁移保留既有页面菜单 ID、URL、组件和 API 权限关联,删除旧的 11 个一级模块根菜单;菜单只保留“分组 → 页面”两级。 +- 「采采管理」固定顺序为:SYB 店铺、采集规则、采购规则、设备列表、AI 规格匹配。 +- Admin 可见两个分组及全部 12 个页面;采购员可见两个分组和除采购规则、AI 规格匹配外的 10 个页面。父组只承担导航结构,不新增业务权限。 +- 分组迁移保留既有页面菜单 ID、URL、组件和 API 权限关联,删除旧的 12 个一级模块根菜单;菜单只保留“分组 → 页面”两级。 + +## 采购规则与 Agent 版本管理 + +- `purchase_rule` 保存管理员维护的采购规则;单例 `purchase_rule_setting` 指向当前规则。迁移以现有默认正式规则原字节播种首条记录。新建、批量预检、批量创建和安全重试读取当前规则;已有采购任务始终执行自身快照。 +- “采购规则”是“采采管理”下与“采集规则”同级的管理员菜单,采购员角色没有菜单和 API 权限。 +- Agent APK 版本由管理员在设备页抽屉中上传、核对并显式设为当前。服务端从 APK Manifest 读取整数 `versionCode` 和 `versionName`,保存 SHA-256、大小、说明和创建人;APK 位于非公开目录,Admin 与设备下载均需认证。 +- Agent 只以整数 `versionCode` 判断更新。启动时静默检查一次,设置页可手动检查、显示下载进度并取消;下载完成必须校验大小和 SHA-256。设备有活动任务时禁止检查、下载和安装;安装始终交给 Android 系统确认,项目不绕过未知来源权限。 diff --git a/docs/04-local-development-and-verification.md b/docs/04-local-development-and-verification.md index 3ae7649..93bd4a4 100644 --- a/docs/04-local-development-and-verification.md +++ b/docs/04-local-development-and-verification.md @@ -2,8 +2,8 @@ generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Local-Development-and-Verification wiki_url: https://git.ilapage.cn/OPC/goauto/wiki/Local-Development-and-Verification.- -wiki_revision: 652e61e73cbd8b78cb7d2d7b011c4790693ab9d9 -synchronized_at: 2026-08-28T06:51:06Z +wiki_revision: 88cbef39a8744753021b82f2c97879db29e320d4 +synchronized_at: 2026-08-29T13:42:00Z # 本地开发与验证 @@ -301,3 +301,11 @@ adb shell am start -n cn.ilapage.goauto.agent/.MainActivity - 分别验证含 goods_id 的直链,以及白名单内无 goods_id 的 `p.pinduoduo.com` 短链和 `mobile.yangkeduo.com/goods2.html?ps=...`:直链不应触发 Agent 额外网络展开;无 goods_id 的白名单链接应优先由手机侧在 4 跳、每次连接/读取 5 秒、64KB 正文上限内展开,并覆盖 302/307。正文只含 `refer_goods_id` 时不得误判为商品身份。另覆盖链接后紧跟中文、多个链接冲突、白名单外跳转、超时和服务端兜底;诊断、日志和数据库不得出现链接原文、goods_id、剪贴板或响应正文。成功/部分成功应显示来源“Agent 当前页面”,相同 goods_id 不产生重复商品,任务详情与 PDD 最新档案一致。 - 断开网络、离开详情页、制造重复分享入口或剪贴板不可用时,应得到普通人可理解的失败原因并释放设备槽;原始分享文案、剪贴板、控件树和截图不得出现在数据库或日志。 - 验证期间不得自动搜索或选择相似商品,不得修改虾皮关联,不创建采购任务、不修改地址、不创建订单,永久禁止支付。 + +### Android Agent 0.9.28 应用内更新检查(#143/#144) + +- 自动验证:`cd server && go test ./app/goauto/apprelease ./app/goauto/access ./app/goauto/migrations`;`cd android && .\gradlew.bat testDebugUnitTest assembleDebug`。服务端 APK 解析测试在 debug APK 已构建时读取真实 Manifest,当前期望 `versionCode=41`、`versionName=0.9.28`。 +- 新增迁移 `1787983700000_agent_app_release.go` 只创建 `agent_app_release` 和 `agent_app_release_setting`。未获得明确本机/正式库迁移授权时只做测试库验证。 +- APK 文件目录使用 `GOAUTO_AGENT_RELEASE_DIR`;缺省为服务端工作目录下 `var/goauto-agent-releases`,不得映射成公开静态目录。部署需让 Admin/API 进程对该目录具有创建、写入、读取和删除临时失败文件的权限。 +- 真机发布验证前确认设备空闲,再由管理员上传一个签名一致、versionCode 更高的 APK并显式设为当前。依次验证启动静默提示、设置页手动检查、下载进度/取消、断网、哈希不一致删除、未知来源引导和系统确认安装。任务执行中全部更新动作必须被阻止,心跳与前台服务继续运行。 +- 上传或设为当前属于发布动作,安装会改变设备应用版本;没有用户独立授权时不得执行。安装后系统可能关闭无障碍服务,只能由用户在系统设置重新开启。 diff --git a/docs/08-agent-api-contract.md b/docs/08-agent-api-contract.md index bd90d51..cb3032e 100644 --- a/docs/08-agent-api-contract.md +++ b/docs/08-agent-api-contract.md @@ -2,8 +2,8 @@ generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Android-Agent-API-Contract wiki_url: https://git.ilapage.cn/OPC/goauto/wiki/Android-Agent-API-Contract.- -wiki_revision: cbbcc9accc4b1de3bd2b87cb816a204d7acf6e99 -synchronized_at: 2026-08-29T08:48:19Z +wiki_revision: 811176b77b923111b5ee972b421716ea224a3a96 +synchronized_at: 2026-08-29T13:43:00Z @@ -506,7 +506,7 @@ POST /api/agent/v1/tasks/{taskId}/fail Admin 列表与详情由 #35 实现;#67 增加 `shopeeOrderNoSnapshot` 的列表/详情返回和 `shopeeOrderNo` 筛选,空快照返回空字符串并由页面显示“—”,不与 `pddOrderNo` 混用;#44 在 SYB 商品列表提供单条/当前页批量创建入口。页面预检只负责提前解释,`batch` 提交时仍逐条执行现有单任务事务和活动任务、订单、支付、设备能力门禁;返回项包含任务号,或失败原因与下一步建议。只有管理员和采购员可以调用。 -创建请求的价格保护使用整数分:`referenceUnitPriceCent`、`minUnitPriceCent`、`maxUnitPriceCent` 和 `currency`。执行时以 PDD App 实际单价校验;低于最小值或高于最大值均返回普通人可理解的价格越界错误,不考虑优惠券,不以订单总价替代单价判断。 #44 批量创建不接受浏览器提交价格:服务端从 PDD 颜色规格价格生成 CNY 快照,已映射颜色按该颜色的 0.2~1.5 倍,待规格探测时按全部可用颜色最低价的 0.2 倍到最高价的 1.5 倍;没有可用颜色价格时逐条拒绝。SYB/Shopee 的 TWD 售价不参与 PDD 采购价格保护。 +创建请求的价格保护使用整数分:`referenceUnitPriceCent`、`minUnitPriceCent`、`maxUnitPriceCent` 和 `currency`。执行时以 PDD App 实际单价校验;低于最小值或高于最大值均返回普通人可理解的价格越界错误,不考虑优惠券,不以订单总价替代单价判断。批量创建不接受浏览器提交价格:服务端读取当前采购规则的可选 `priceGuard`,`minRatio` 允许 0.1~1.0,`maxRatio` 允许 1.0~3.0 且前者不得大于后者;字段缺省时使用 0.2 / 1.5。最低边界按“最低颜色价 × minRatio”向下取整,最高边界按“最高颜色价 × maxRatio”向上取整,均使用定点整数计算以保持旧默认逐分一致;没有可用颜色价格时逐条拒绝。SYB/Shopee 的 TWD 售价不参与 PDD 采购价格保护。 ### Android 接口(由 #34 实现) @@ -783,3 +783,25 @@ file= - 同一 PDD 商品和颜色重复上传以最新图片为准。响应路径供 Admin 商品详情展示,不返回整屏截图、控件树、XML 或个人数据。 - 接口独立于结果提交。任一上传失败都不得修改任务状态、`missing`、规格、颜色价格或 SKU;Agent 不进行会拖长任务的重试。 - 典型错误:`COLOR_IMAGE_TOO_LARGE`(HTTP 413)、`COLOR_IMAGE_UNSUPPORTED`(HTTP 415)、`COLOR_IMAGE_INVALID`(HTTP 422)、任务/设备/状态冲突(HTTP 409)。 + +## 采购规则管理接口 + +| 方法 | 路径 | 说明 | +|---|---|---| +| `GET` / `POST` | `/api/admin/v1/purchase-rules` | 管理员分页查看或创建严格校验的采购规则 | +| `PATCH` / `DELETE` | `/api/admin/v1/purchase-rules/{ruleId}` | 管理员修改或删除非当前规则;当前规则不能删除 | +| `GET` / `PUT` | `/api/admin/v1/purchase-rules/current` | 查看或按 `requestId + ruleId` 幂等切换当前规则 | + +新采购任务、批量预检、批量创建、批量重试和安全的同任务重试读取当前规则;当前设置缺失、规则已删除或内容不通过正式采购契约时返回明确错误,不回退到内置常量。既有任务的 `ruleSnapshot` 不变。所有接口仅管理员可用。 + +## Agent 应用版本接口 + +| 方法 | 路径 | 认证与说明 | +|---|---|---| +| `GET` / `POST` | `/api/admin/v1/agent-app-releases` | 仅管理员;列表或上传不超过 200 MB 的 APK,服务端解析版本并计算 SHA-256 | +| `PUT` | `/api/admin/v1/agent-app-releases/current` | 仅管理员;按 `requestId + releaseId` 幂等设置当前版本 | +| `GET` | `/api/admin/v1/agent-app-releases/{releaseId}/download` | Admin JWT 鉴权下载,不提供公开静态地址 | +| `GET` | `/api/agent/v1/app/latest` | Device Token;返回当前版本元数据或 `data=null` | +| `GET` | `/api/agent/v1/app/releases/{releaseId}/download` | Device Token;私有 APK 下载 | + +Agent 只比较整数 `versionCode`。设备有活动任务时禁止检查、下载和安装;下载到应用私有缓存并校验响应大小与 SHA-256,失败立即删除。安装使用 FileProvider 和 Android 系统安装确认页;未知来源权限必须由用户在系统设置授权,不静默安装。