feat(category): 类目树、属性、属性字典维护,新增系统管理员管理类目数据

This commit is contained in:
R524809
2026-09-18 18:55:16 +08:00
parent fcddb11da0
commit 388429dbd0
58 changed files with 4347 additions and 640 deletions
@@ -29,7 +29,7 @@
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| language | string | 可选 | 语言代码,可选值:`DEFAULT`(英文)、`RU`(俄文)、`EN`(英文)、`ZH_HANS`(简体中文)。默认 `DEFAULT` |
| language | string | 可选 | 语言代码:`DEFAULT`(默认俄文)、`RU`(俄文)、`EN`(英文)、`TR`(土耳其文)、`ZH_HANS`(简体中文) |
---
@@ -105,13 +105,13 @@
⑤ 选中后保存 description_category_id + type_id
```
### 场景 2:服务端门店缓存
### 场景 2:Atlas 平台级参考缓存(目标架构,未实施)
```
① 门店 `Client-Id` + `Api-Key` 验证成功后拉取完整类目树(Atlas 使用 `ZH_HANS`)
② 递归保存每一层及其父子关系,按门店存入本地类目缓存;不与其他门店混用
③ 同步失败由后台任务重试,已验证的门店凭据不因此改为无效
④ 用户选类目时直接查当前目标门店缓存,不频繁调 API
① 按平台的定时任务用 Atlas 专用参考源凭据拉取完整类目树(Ozon 首期 `ZH_HANS`)
② 原子更新该平台共享目录;同平台门店读取同一份树,不建门店覆盖层
③ 同步失败保留上次成功版本,由后台任务重试并告警
④ 用户选类目时只查 Atlas 缓存,不频繁调 Ozon API
```
---
@@ -213,30 +213,7 @@ function toTreeData(nodes: CategoryNode[]): any[] {
## 缓存策略
### 按门店缓存(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)
```
目标方案见 [`platform-product-reference-catalog.md`](../../architecture/platform-product-reference-catalog.md):每个平台分别维护共享目录;定时自动同步与系统管理员手动触发共用异步任务、原子版本切换和失败保留旧版;前端只读 Atlas 缓存。当前运行中的门店级同步仍见下方「Atlas 当前集成」,不能把目标方案当作已经上线。
---
@@ -244,15 +221,15 @@ async def refresh_store_category_tree(store_id: str):
### Q1: 类目树很大吗?
**A**: 约 **1-2 万个类目节点**,JSON 约 3-5MB。完整树在一次响应中以递归 `children` 返回,首次拉取需几秒,后续从门店缓存读取。
**A**: 完整树在一次响应中以递归 `children` 返回;具体节点数和响应大小以实际同步记录为准,后续从 Atlas 缓存读取。
### Q2: 多久更新一次?
**A**: Ozon 不定期新增类目(月级别),建议 **24h TTL + 手动刷新入口**。
**A**: Ozon 接口未规定 Atlas 的缓存 TTL。目标架构建议由后台每日检查树,并提供管理员手动排队同步入口;间隔可按运行量调整,见[架构决策](../../architecture/platform-product-reference-catalog.md)。
### Q3: 不同语言的类目树结构一样吗?
**A**: 通常 `description_category_id` / `type_id` 相同,仅 `category_name` / `type_name` 翻译不同。Atlas 统一由 Worker 为每个门店缓存 `ZH_HANS`,前端直接读取该门店缓存,不能为展示语言绕过缓存直接携带门店密钥请求 Ozon。
**A**: 接口支持语言参数,不应未经验证就假定各语言的 ID 和翻译映射完全相同。当前 Worker 为每个门店缓存 `ZH_HANS`;目标是由 Ozon 平台参考源统一缓存该语言,同平台所有门店共用。前端不能携带门店密钥直连 Ozon。
### Q4: `type_id=0` 是什么意思?
@@ -266,6 +243,7 @@ async def refresh_store_category_tree(store_id: str):
- **API**:按当前目标门店返回缓存的扁平节点(包含 `parent_id`);不会将 `Client-Id` 或 `Api-Key` 返回给浏览器。
- **前端**:根据 `parent_id` 重建任意深度的 Cascader;目录可展开,只有可发布的叶子能写入商品草稿。
- **调试**:若需要查看 Ozon 原始响应,应由受权限保护的服务端调试能力获取并脱敏展示;浏览器不得直接用门店密钥请求 Ozon。
- **迁移目标**:平台级共享目录和统一异步管理同步尚未实现;本阶段不维护门店差异层,见[架构决策](../../architecture/platform-product-reference-catalog.md)。
---