Files
atlas-seller-kit/docs/integrations/ozon-seller-api/02-category-tree.md
T
2026-09-11 17:28:58 +08:00

277 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 类目树查询 API
> 官方文档:https://docs.ozon.ru/api/seller/zh/#operation/DescriptionCategoryAPI_GetTree
---
## 接口信息
| 项 | 值 |
|---|---|
| 方法 | POST |
| 路径 | `/v1/description-category/tree` |
| 鉴权 | 需要 `Client-Id` + `Api-Key` |
| 用途 | 获取 Ozon 商品类目树(选择类目后才能发布商品) |
---
## 请求
### 请求体
```json
{
"language": "RU"
}
```
### 参数说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| language | string | 可选 | 语言代码,可选值:`DEFAULT`(英文)、`RU`(俄文)、`EN`(英文)、`ZH_HANS`(简体中文)。默认 `DEFAULT` |
---
## 响应
### 成功响应(200)
```json
{
"result": [
{
"description_category_id": 17000001,
"category_name": "Дом и сад",
"type_id": 0,
"type_name": "",
"disabled": true,
"children": [
{
"description_category_id": 17000002,
"category_name": "Сувениры и подарки",
"type_id": 0,
"type_name": "",
"disabled": true,
"children": [
{
"description_category_id": 17032010,
"category_name": "Копилки",
"type_id": 93770,
"type_name": "Копилка",
"disabled": false,
"children": []
}
]
}
]
}
]
}
```
### 字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| description_category_id | integer | **类目 ID**(发布商品时必填) |
| category_name | string | 类目名称 |
| type_id | integer | **商品类型 ID**(发布商品时必填,与 category_id 配对) |
| type_name | string | 商品类型名称 |
| disabled | boolean | **是否可建品**。`true` = 当前节点不可用于建品;`false` = 可用于建品(通常是末级类目) |
| children | array | 子类目(递归结构) |
> **类型叶子例外**:Ozon 的最后一层可能只返回 `type_name`、`type_id`、`disabled` 和空 `children`,而不重复返回 `description_category_id`、`category_name`。此时它属于最近的父类目:缓存时应继承父类目的类目 ID,展示名称使用 `type_name`,发布时使用“父类目 `description_category_id` + 叶子 `type_id`”。
---
## 关键约束
1. **只有可发布的末级类目可建品**:选择结果必须是没有 `children` 且 `disabled=false` 的节点
2. **必须配对使用**:发布时需同时提供 `description_category_id` + `type_id`
3. **层级结构**:`children` 是递归结构,类目可有三级或更深层级;不能假定只有两层,也不能只展开一层
---
## 使用场景
### 场景 1:前端类目选择器
```
① 请求类目树(language=RU,给俄文用户看)
② 递归展开树形结构
③ 所有有子节点的目录都保持可展开;不要把目录节点映射为组件的 disabled
④ 仅允许选择没有子节点且 disabled=false 的节点
⑤ 选中后保存 description_category_id + type_id
```
### 场景 2:服务端门店缓存
```
① 门店 `Client-Id` + `Api-Key` 验证成功后拉取完整类目树(Atlas 使用 `ZH_HANS`)
② 递归保存每一层及其父子关系,按门店存入本地类目缓存;不与其他门店混用
③ 同步失败由后台任务重试,已验证的门店凭据不因此改为无效
④ 用户选类目时直接查当前目标门店缓存,不频繁调 API
```
---
## 示例代码
### Python(服务端缓存)
```python
import httpx
from typing import List, Dict, Any
async def fetch_category_tree(
client_id: str,
api_key: str,
language: str = "DEFAULT"
) -> List[Dict[str, Any]]:
"""拉取类目树并返回扁平化列表"""
headers = {
"Client-Id": client_id,
"Api-Key": api_key,
"Content-Type": "application/json"
}
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(
"https://api-seller.ozon.ru/v1/description-category/tree",
headers=headers,
json={"language": language}
)
resp.raise_for_status()
data = resp.json()
# 递归扁平化;不要限制层级数。
def flatten(nodes: List[Dict], level: int = 0, parent_id: int = 0):
flat = []
for node in nodes:
flat.append({
"description_category_id": node["description_category_id"],
"parent_id": parent_id,
"category_name": node["category_name"],
"type_id": node["type_id"],
"type_name": node["type_name"],
"disabled": node["disabled"],
"level": level,
"lang": language
})
if node.get("children"):
flat.extend(flatten(
node["children"],
level + 1,
node["description_category_id"]
))
return flat
return flatten(data.get("result", []))
```
### TypeScript(前端选择器)
```typescript
interface CategoryNode {
description_category_id: number;
category_name: string;
type_id: number;
type_name: string;
disabled: boolean;
children: CategoryNode[];
}
async function fetchCategoryTree(language = 'RU'): Promise<CategoryNode[]> {
const resp = await fetch('/api/categories/tree?lang=' + language);
const data = await resp.json();
return data.result;
}
// 转为 antd Cascader 数据结构。目录永远不能设为 disabled,
// 否则用户无法继续展开到下一级。
function toTreeData(nodes: CategoryNode[]): any[] {
return nodes.map(node => {
const hasChildren = node.children.length > 0;
return {
value: `${node.description_category_id}-${node.type_id}`,
label: node.category_name,
isLeaf: !hasChildren,
// 仅不可发布的叶子不可选择;目录节点必须可展开。
disabled: !hasChildren && node.disabled,
children: hasChildren ? toTreeData(node.children) : undefined,
data: {
description_category_id: node.description_category_id,
type_id: node.type_id
}
};
});
}
```
---
## 缓存策略
### 按门店缓存(Atlas 方案)
```python
# 类目数据的读取凭证属于门店。即使结构通常相近,也必须按 store_id 隔离缓存。
# 验证成功或缓存失效时,由 Worker 用该门店凭证拉取完整树并递归写库。
# 前端只读取该门店的缓存,绝不携带或读取 Client-Id / Api-Key。
async def get_store_category_tree(store_id: str):
cached = await load_store_category_cache(store_id)
if cached and cached.is_valid:
return cached.tree
await enqueue_store_catalog_sync(store_id)
raise CatalogCachePreparingError(store_id)
```
### 按需更新
```python
# 用户在门店管理中手动刷新时,API 同步请求 Ozon 并在成功后原子替换目标门店缓存。
# 请求或落库失败会保留旧缓存并向用户返回错误,不经 Worker 队列。
async def refresh_store_category_tree(store_id: str):
tree = await fetch_category_tree_for_store(store_id)
return await replace_store_category_cache_atomically(store_id, tree)
```
---
## 常见问题
### Q1: 类目树很大吗?
**A**: 约 **1-2 万个类目节点**,JSON 约 3-5MB。完整树在一次响应中以递归 `children` 返回,首次拉取需几秒,后续从门店缓存读取。
### Q2: 多久更新一次?
**A**: Ozon 不定期新增类目(月级别),建议 **24h TTL + 手动刷新入口**。
### Q3: 不同语言的类目树结构一样吗?
**A**: 通常 `description_category_id` / `type_id` 相同,仅 `category_name` / `type_name` 翻译不同。Atlas 统一由 Worker 为每个门店缓存 `ZH_HANS`,前端直接读取该门店缓存,不能为展示语言绕过缓存直接携带门店密钥请求 Ozon。
### Q4: `type_id=0` 是什么意思?
**A**: 目录节点通常 `type_id=0`,但前端不能据此把所有目录设为 disabled,否则它们无法展开。是否允许提交必须同时判断:它是叶子节点、`disabled=false`,并且有可用的 `type_id`。
---
## Atlas 当前集成
- **Worker**:以门店凭证调用 Ozon,递归持久化完整 `children` 树及父子关系;缓存命中时不重复调用 Ozon。
- **API**:按当前目标门店返回缓存的扁平节点(包含 `parent_id`);不会将 `Client-Id` 或 `Api-Key` 返回给浏览器。
- **前端**:根据 `parent_id` 重建任意深度的 Cascader;目录可展开,只有可发布的叶子能写入商品草稿。
- **调试**:若需要查看 Ozon 原始响应,应由受权限保护的服务端调试能力获取并脱敏展示;浏览器不得直接用门店密钥请求 Ozon。
---
## 相关文档
- [03-category-attributes.md](./03-category-attributes.md) —— 获取类目属性
- [docs/v2/database.md](../v2/database.md) §2.7 —— `category_tree` 表结构
- [docs/v2/ozon-publish.md](../v2/ozon-publish.md) §3 —— 类目字典缓存策略