130 lines
9.6 KiB
Markdown
130 lines
9.6 KiB
Markdown
# 平台类目与属性管理 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 和全部三种批次按钮;全部同步使用红色危险按钮并二次确认。字典仍在选定属性后逐个手动同步。
|