# 类目树查询 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`(英文)、`TR`(土耳其文)、`ZH_HANS`(简体中文) | --- ## 响应 ### 成功响应(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:Atlas 平台级参考缓存(目标架构,未实施) ``` ① 按平台的定时任务用 Atlas 专用参考源凭据拉取完整类目树(Ozon 首期 `ZH_HANS`) ② 原子更新该平台共享目录;同平台门店读取同一份树,不建门店覆盖层 ③ 同步失败保留上次成功版本,由后台任务重试并告警 ④ 用户选类目时只查 Atlas 缓存,不频繁调 Ozon 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 { 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 } }; }); } ``` --- ## 缓存策略 目标方案见 [`platform-product-reference-catalog.md`](../../architecture/platform-product-reference-catalog.md):每个平台分别维护共享目录;定时自动同步与系统管理员手动触发共用异步任务、原子版本切换和失败保留旧版;前端只读 Atlas 缓存。当前运行中的门店级同步仍见下方「Atlas 当前集成」,不能把目标方案当作已经上线。 --- ## 常见问题 ### Q1: 类目树很大吗? **A**: 完整树在一次响应中以递归 `children` 返回;具体节点数和响应大小以实际同步记录为准,后续从 Atlas 缓存读取。 ### Q2: 多久更新一次? **A**: Ozon 接口未规定 Atlas 的缓存 TTL。目标架构建议由后台每日检查树,并提供管理员手动排队同步入口;间隔可按运行量调整,见[架构决策](../../architecture/platform-product-reference-catalog.md)。 ### Q3: 不同语言的类目树结构一样吗? **A**: 接口支持语言参数,不应未经验证就假定各语言的 ID 和翻译映射完全相同。当前 Worker 为每个门店缓存 `ZH_HANS`;目标是由 Ozon 平台参考源统一缓存该语言,同平台所有门店共用。前端不能携带门店密钥直连 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。 - **迁移目标**:平台级共享目录和统一异步管理同步尚未实现;本阶段不维护门店差异层,见[架构决策](../../architecture/platform-product-reference-catalog.md)。 --- ## 相关文档 - [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 —— 类目字典缓存策略