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
@@ -0,0 +1,46 @@
# 平台级商品参考目录的归属与维护决策
> 决策日期:2026-09-18。状态:平台参考目录、系统管理员维护 API/页面及定时同步链路已经实现;商品编辑器的门店级引用尚未迁移。当前商品编辑契约仍以 [`product-editor-and-publishing.md`](../api/product-editor-and-publishing.md) 为准。本决策取代 2026-09-10「每个门店完整保存一份类目、属性和字典」的目标设计,不抹去其实现历史。
## 1. 依据与边界
Ozon 的[类目树接口](https://docs.ozon.ru/api/seller/zh/#operation/DescriptionCategoryAPI_GetTree)请求体仅以 `language` 选择返回语言;[属性接口](https://docs.ozon.ru/api/seller/zh/#operation/DescriptionCategoryAPI_GetAttributes)以 `description_category_id + type_id + language` 查询属性;[字典值接口](https://docs.ozon.ru/api/seller/zh/#operation/DescriptionCategoryAPI_GetAttributeValues)还要求 `attribute_id`,并用 `last_value_id` 分页。属性响应的 `category_dependent` 指示字典值是否随类目变化。三个接口都需要卖家 `Client-Id + Api-Key`。
Atlas 本阶段明确采用**平台级参考数据**:同一 `platform_id` 下的所有门店共用一套类目/商品类型树、类型属性定义和字典候选值,不再按门店复制,也**不建立或维护门店差异层**。Ozon 文档未保证所有卖家账号返回完全一致;“同平台门店共用”是本阶段产品假设,而不是 Ozon 官方保证。若实际发现账号差异,再评审下文保留的扩展方案,不提前实现。
## 2. 数据归属
| 数据 | 目标归属与键 | 约束 |
|---|---|---|
| 类目/商品类型树 | `platform_id` 下的一份平台目录,名称按语言保存;Ozon 可发布叶子使用 `description_category_id + type_id` 标识 | 同平台门店读取相同的目录节点、父子关系与 `disabled`;不以名称或单独 `type_id` 判等,也不复制到各门店 |
| 类型属性定义 | `platform_id + 平台类目/类型键 + 属性键 + 语言/版本` | Ozon 的 `is_required`、`is_aspect`、`dictionary_id`、类型和数量限制属于其平台适配数据;其他平台由各自适配器定义外部标识和规则 |
| 字典候选值 | `platform_id + 平台类目/类型键 + 属性键 + 字典键 + 语言 + 值键`;记录完整/部分同步状态 | Ozon 的 `category_dependent=false` 可供后续去重,但不能仅以 `dictionary_id + value_id` 跨类目或跨平台合并 |
| 门店连接与商品/SKU 已填属性 | 原工作空间、门店、商品、profile、SKU | 凭据、上架结果、用户填报值、确认状态和发布快照仍是业务数据,按工作空间隔离;它们不是平台参考目录 |
平台参考表以 `platform_id` 隔离 Ozon、未来的 Wildberries 等平台,只保存对应平台可复用的参考元数据;不存客户凭据、商品、价格或店铺专有数据,也不使用 `store_id` 作为目录分区键。所有门店连接和商品引用仍须满足 [`development-collaboration-contract.md`](../project/development-collaboration-contract.md) 的工作空间外键与 RLS 约束。读 API 依页头平台上下文过滤,不能跨平台混用 ID,也不能据此枚举其他店铺信息。
目标交互上,切换到**同一平台**的另一个门店不应清空已选类目和属性;切换平台则必须重新选择该平台的类目并按新平台 schema 校验。新门店凭据验证只处理该门店连接,不为其复制或重新同步整套平台目录;若平台目录尚未准备好,由平台级任务补齐。当前门店级外键尚未迁移前仍维持现有切换行为。
## 3. 同步方案:自动为主、人工为辅
统一的后台同步服务按 `platform_id` 负责定时任务、首次缺失时的补齐和管理员手动请求。三种触发方式都创建有权限校验、幂等去重的 `platform_catalog_sync_runs + platform_catalog_outbox_events`;这是不带 `workspace_id` 的全局任务事实,不能借用租户 `operations/outbox_events`。Worker 通过平台适配器读取服务端安全存储的参考源凭据并调用对应平台。浏览器、日志、run 和 outbox 载荷不包含 API Key。管理员手动同步不得在 HTTP 请求内直接访问平台或绕过同一套限流、审计与事务发布流程。
1. **同步来源**:每个平台分别配置 Atlas 专用的参考源凭据及适配器;Ozon 首期使用专用参考门店凭据作为技术采集身份,而非普通客户工作空间门店。设置最小授权、密钥轮换、失效告警与备用源,不能未经授权借用客户门店密钥。若暂无专用凭据,保持现有门店级链路,待来源配置完成后迁移。
2. **基线调度**:当前实现为类目树和类型属性成功后 24 小时内不重复、字典成功后 7 天内不重复;调度器每 60 秒扫描,每个平台最多 6 个活动任务。BullMQ 最多尝试 5 次并从 5 秒开始指数退避。当前固定按树、属性、字典排序,在数千类型下会造成字典饥饿;后续必须改为属性/字典配额轮转,并增加近期访问优先级。具体间隔是 Atlas 运营参数,不是 Ozon 文档承诺的 TTL。
3. **管理员页面**:按平台展示参考源状态、树、属性、字典候选值、最后成功时间、失败详情、排队任务和「同步类目树 / 指定类目属性 / 指定字典」操作。手动动作必须受系统管理员权限控制、记录操作者,并返回异步任务;页面不展示密钥明文。当前实现位于 `/system/platform-catalog`,全局系统管理员授权独立于工作空间角色。
4. **读路径**:正常用户只读 Atlas 缓存。首次缺失时可排队补齐并显示准备中;已有数据过期时优先提供上次成功快照并后台更新。不能在每次打开编辑器或下拉框时同步请求 Ozon。大字典搜索按规范化关键词及上述字典作用域共享缓存,并去重并发补齐。
5. **原子性与监控**:P0 按平台及树、类型属性、字典作用域在单事务内 upsert,提交前读取方继续看到旧数据;失败保留上次成功缓存。仅在确认完整分页完成后才处理消失的字典值,部分同步记录 `is_complete=false`。当前尚未实现不可变版本快照、内容哈希、`next_refresh_at`、连续失败告警和异常删减保护,这些属于上线前可靠性加固,不能在文档中误记为已完成。
Ozon 字典分页每次最多 2000 个值;[搜索接口](https://docs.ozon.ru/api/seller/zh/#operation/DescriptionCategoryAPI_SearchAttributeValues)要求至少两个字符、每次最多 100 个值。搜索命中只是局部候选,不能证明字典已完整同步。当前平台任务单次最多读取 5 页(10000 项);超过上限会正确保留为部分缓存,但续传游标尚未实现。
## 4. 门店差异层的未来预案(本阶段不实施)
本阶段按同平台所有门店使用相同目录、属性定义和候选值实施:**不创建门店差异表,不按门店同步参考目录,不在读取或发布校验时叠加门店覆盖规则**。门店凭据状态和某次发布的失败原因仍按门店记录,但不据此自动改写平台共享目录。
如果联调或线上证据表明确有账号专属的可售状态、属性或字典差异,再单独提出迁移:可按 `workspace_id + store_id` 建门店覆盖层,或按市场/卖家分组收窄共享范围,并制定差异检测与回退策略。该预案只保留扩展方向,不是当前开发任务或上线前置条件。
## 5. 从现有实现迁移
当前 `022_store_catalog_cache.sql` 的 `store_product_categories`、属性和字典表,以及 profile 的 `store_product_category_id` 外键都是已上线的门店级事实,**本次文档决策不改变它们**。旧 `001_foundation.sql` / `021_product_category_attribute_schema.sql` 虽已有平台级表,但原字典唯一键不足以表达按类目/类型变化的字典;不能直接当成最终系统目录复用。
迁移 `024_platform_catalog_management.sql` 已补齐以 `platform_id` 为作用域的参考来源、候选值和同步任务表,**没有建立门店差异表**;API、Worker 和管理页面已经按该作用域工作。下一步仍需以 Ozon 外部 ID 对照旧门店记录、回填平台目录。草稿/接口迁移应先提供兼容读写与可回滚映射,逐步改为平台目录引用,最后经校验和维护者批准再清理冗余门店缓存。现有商品编辑 HTTP 路径、UUID 含义和门店同步 operation 尚未改变;任何契约变更须先更新 `docs/api/`、共享类型和测试。Wildberries 等新平台应新增平台适配器及自己的外部 ID 映射,不复用 Ozon 的 `description_category_id + type_id` 语义。