# 平台类目与属性管理 API > 2026-09-18 新增契约,2026-09-20 增加平台刷新策略和类型属性批次。平台级目录供系统管理员维护,普通商品编辑器和首次发布预检也已切换到同一平台缓存;旧门店目录仅保留兼容/回滚。平台参考目录的归属见 [`platform-product-reference-catalog.md`](../architecture/platform-product-reference-catalog.md),新鲜度与优先级规则见 [`platform-catalog-cache-refresh-plan.md`](../architecture/platform-catalog-cache-refresh-plan.md)。 ## 权限与安全 - 本模块接口都要求有效登录会话。除 `GET /api/system/access` 外,必须具有数据库授予且未撤销的 `system_admin` 全局权限;工作空间 `owner/admin` 不等于系统管理员。未授权返回 `403 SYSTEM_ADMIN_REQUIRED`。 - 系统管理员授权只能由受控数据库运维操作授予或撤销,不提供自助提权 HTTP 接口。权限变更在下一次请求时生效;前端菜单只是展示控制。 - 目录、同步来源和任务均按 `platform_id` 分区,不使用客户工作空间的 `workspace_id` 承载平台任务。Ozon `Client-Id` / `Api-Key` 只经 TLS 提交至 API,Key 加密落库,不写日志、Outbox 或响应。 - Wildberries 等其他平台可展示目录空状态;未实现适配器时发起同步返回 `422 PLATFORM_CATALOG_UNSUPPORTED`。 ### 首位系统管理员初始化 系统管理员不能通过产品界面自助获得权限。目标用户先完成正常注册,再由数据库运维账号执行以下 SQL;应用运行账号 `atlas_app` 已被禁止修改该表: ```sql INSERT INTO atlas.system_admin_grants (user_id) SELECT id FROM atlas.users WHERE lower(email) = lower('管理员邮箱') ON CONFLICT (user_id) DO UPDATE SET revoked_at = NULL, granted_at = now(); ``` 撤销权限时设置 `revoked_at = now()`,不要删除用户或改变其工作空间角色。授权和撤销均在下一次请求时生效。 ## 接口 `GET /api/system/access` 返回 `{ "data": { "is_system_admin": boolean } }`,供已登录 Web 决定是否展示系统管理入口;服务端权限判断仍以数据库授权为准。 `GET /api/system/platform-catalog/platforms` 返回 `{ "data": [{ "id", "code", "name", "source": { "configured", "client_id_hint", "updated_at" }, "summary": { "category_count", "last_tree_sync_at", "next_tree_sync_at", "last_error" } }] }`。`client_id_hint` 仅为脱敏末尾字符,不返回 API Key、密文或指纹。`next_tree_sync_at` 固定为最近成功时间后 7 天;从未成功时为 `null` 并由调度器尽快创建首次任务。 `PUT /api/system/platform-catalog/platforms/{platform_id}/source` Body:`{ "client_id": "...", "api_key": "..." }`。仅 Ozon 适配器接受当前配置;重复提交同一凭据不产生新版本或重复任务。首次配置或更换凭据时返回脱敏来源状态,并为该平台排队一次类目树同步;更换后旧版本的未完成任务不得用新密钥执行。 `GET /api/system/platform-catalog/platforms/{platform_id}/categories` 返回当前平台的全部树节点 `{ id, parent_id, external_category_id, external_type_id, name, path, is_active, synced_at, attribute_last_success_at, attribute_cache_status, attribute_last_error, attribute_last_error_at }`;按父子关系在客户端构建树,目录节点可展开,仅可发布类型叶子用于查看属性。纯目录节点的属性状态为 `null`;可发布类型状态为 `fresh | stale | expired | missing`。 `GET /api/system/platform-catalog/platforms/{platform_id}/refresh-policy` 返回平台目录刷新策略: ```json { "data": { "attribute_fresh_days": 14, "attribute_expire_days": 21, "dictionary_fresh_days": 14, "dictionary_expire_days": 21, "updated_at": "2026-09-20T00:00:00Z" } } ``` 尚未显式配置的平台返回相同默认值。类目树固定每 7 天同步,不属于可配置策略。 `PUT /api/system/platform-catalog/platforms/{platform_id}/refresh-policy` Body 必须完整提交 `{ "attribute_fresh_days", "attribute_expire_days", "dictionary_fresh_days", "dictionary_expire_days" }`。四项均为 `1..365` 的整数,同类资源的 `expire_days` 必须严格大于 `fresh_days`。成功保存后立即影响后续状态判断,但不得因此主动扫描或创建属性/字典任务;服务端记录修改人和时间。 `GET /api/system/platform-catalog/platforms/{platform_id}/categories/{category_id}/attributes` 返回该类型的属性定义 `{ id, platform_attribute_id, complex_id, name, description, group_name, value_type, is_required, is_aspect, is_collection, max_value_count, dictionary_id, is_active, synced_at, last_success_at, cache_status, last_error_message, last_error_at, dictionary_last_success_at, dictionary_cache_status, dictionary_is_complete, dictionary_last_error_message, dictionary_last_error_at, dictionary_value_count }`。类型级 `last_success_at/cache_status` 会在该类型的每个属性行重复,便于管理页展示;没有任何属性行时应结合属性汇总或资源状态判断 missing。`category_id` 不属于所选平台时返回 404。 `GET /api/system/platform-catalog/platforms/{platform_id}/attributes/{attribute_id}/values?q={keyword}&page={number}` 仅字典属性可查;`page` 从 1 开始、每页最多 100,返回 `{ "data": [{ "id", "external_value_id", "value", "info", "picture" }], "meta": { "page", "page_size", "total", "is_complete" } }`。搜索只查 Atlas 缓存;`is_complete=false` 表示已缓存部分候选值,不表示 Ozon 字典只有这些值。 `POST /api/system/platform-catalog/platforms/{platform_id}/syncs` Headers:`Idempotency-Key`(UUID)必填。Body:`{ "scope": "tree" | "attributes" | "dictionary", "category_id"?: "uuid", "attribute_id"?: "uuid" }`。树同步不带其他 ID;兼容的单类型属性同步需要当前平台的可发布 `category_id`;字典同步需要该类型下带字典的 `attribute_id`。同作用域已有 queued/running 任务时返回已有任务,并将本次幂等键持久映射到该任务;同一用户重试不会在原任务完成后意外创建第二个任务。成功排队返回 `202` 与 `{ "data": { "id", "platform_id", "scope", "status", "trigger", "priority", "created_at" } }`。同步由 Worker 异步执行,失败保留上次成功缓存。树任务优先级独立决定;属性和字典根据资源年龄选择 `low` 或 `high`,missing/严格超过过期阈值为 `high`。 `GET /api/system/platform-catalog/platforms/{platform_id}/attribute-sync-summary` 返回当前策略下活动可发布类型的聚合数量: ```json { "data": { "total": 7365, "fresh": 100, "stale": 200, "expired": 300, "missing": 6765, "active_batch_id": null, "evaluated_at": "2026-09-20T00:00:00Z" } } ``` 状态边界严格按平台策略和最近成功时间计算:`age < fresh_days` 为 fresh,`fresh_days ≤ age ≤ expire_days` 为 stale,`age > expire_days` 为 expired,从未成功为 missing。 `POST /api/system/platform-catalog/platforms/{platform_id}/attribute-sync-batches` Headers:`Idempotency-Key`(UUID)必填。Body: ```json { "mode": "missing_expired" | "missing_stale_expired" | "all" } ``` - `missing_expired` 固定 missing、expired 类型快照; - `missing_stale_expired` 固定 missing、stale、expired 类型快照; - `all` 固定全部活动可发布类型快照,包含 fresh。 没有命中类型时返回 `422 CATALOG_ATTRIBUTE_BATCH_EMPTY`。同一平台已有 queued/running 批次时返回 `409 CATALOG_ATTRIBUTE_BATCH_ACTIVE` 和现有批次 ID,避免两个全量扫描互相竞争。成功返回 `202` 与批次 `{ id, platform_id, mode, status, total_count, pending_count, running_count, succeeded_count, failed_count, cancelled_count, created_at, started_at, completed_at }`。Worker 每分钟限量领取批次项;missing/expired 子任务为高优先级,fresh/stale 子任务为低优先级。批次创建后新增的类型不加入本批次。 `GET /api/system/platform-catalog/platforms/{platform_id}/attribute-sync-batches?limit={number}` 返回最近批次及实时聚合进度,最多 50 条。批次允许部分失败:任一子任务失败时最终状态为 `failed`,但成功类型继续保留且不会回滚。 `GET /api/system/platform-catalog/platforms/{platform_id}/attribute-sync-batches/{batch_id}` 返回单个批次的相同进度字段;批次不属于当前平台时返回 404。 `GET /api/system/platform-catalog/platforms/{platform_id}/syncs?limit={number}` 返回该平台最近的同步任务(最多 50 条),包括作用域、触发方式、状态、开始/完成时间、数量摘要及脱敏错误。系统管理员可据此查看手动和定时更新结果。 类目树调度固定为最近一次成功后 7 天。类型属性和属性字典没有周期性自动扫描;它们只由管理员操作、普通用户访问和发布预检创建任务。 ## 管理页面交互 - 类目搜索框固定在树卡片顶部;搜索结果会展开到命中节点,普通目录节点可展开,只有活动的可发布类型可以选中查看属性。 - “缓存新鲜度设置”以操作区按钮打开弹窗,不在主页面常驻表单。保存成功后关闭弹窗并刷新策略、类目和当前属性状态;失败时保留弹窗及用户输入。 - “最近同步任务”的手动刷新按钮在请求期间显示 loading;成功显示 message,失败显示统一 API 错误。页面每 5 秒的自动轮询保持静默,不重复打扰管理员。 - 类型属性提供 missing+expired、missing+stale+expired 和全部三种批次按钮;全部同步使用红色危险按钮并二次确认。字典仍在选定属性后逐个手动同步。