Files
atlas-seller-kit/docs/architecture/platform-product-reference-catalog.md
T
2026-09-20 15:23:44 +08:00

8.6 KiB
Raw Blame History

平台级商品参考目录的归属与维护决策

决策日期:2026-09-18。状态:平台参考目录、系统管理员维护 API/页面、同步链路、商品编辑器平台级读取及首次发布预检已实现;真正的 Ozon 发布 job 尚未开发。当前商品编辑契约以 product-editor-and-publishing.md 为准。本决策取代 2026-09-10「每个门店完整保存一份类目、属性和字典」的目标设计,不抹去其实现历史。

1. 依据与边界

Ozon 的类目树接口请求体仅以 language 选择返回语言;属性接口以 description_category_id + type_id + language 查询属性;字典值接口还要求 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 的工作空间外键与 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 首期使用专用参考门店凭据作为技术采集身份,而非普通客户工作空间门店。设置最小授权、密钥轮换、失效告警与备用源,不能未经授权借用客户门店密钥。若暂无专用凭据,平台同步任务保持不可执行,读路径按 stale/expired/missing 规则返回;不得回退到客户门店级目录或临时借用某个用户的密钥。
  2. 基线调度(当前代码事实):类目树最近成功时间达到 7 天后由调度器创建低优先级周任务;管理员仍可手动创建高优先级树任务。类型属性和字典已经退出周期自动候选,类型属性只从管理员显式创建的三种批次中逐批领取,字典只保留单项手动入口。普通商品读路径已按 14/21 天执行 fresh/stale/expired/missing 状态机,并创建或复用 user_access 平台任务。
  3. 管理员页面:按平台展示参考源状态、树、属性、字典候选值、最后成功时间、失败详情和排队任务;提供类目树手动同步、三种范围的类型属性批次和单字典同步。缓存新鲜度设置由操作按钮打开弹窗,最近任务手动刷新有 loading 与成功/失败提示,5 秒自动轮询保持静默。所有手动动作受系统管理员权限控制、记录操作者并返回异步任务;页面不展示密钥明文。当前实现位于 /system/platform-catalog,全局系统管理员授权独立于工作空间角色。
  4. 读路径:正常用户只读 Atlas 缓存。首次缺失时可排队补齐并显示准备中;已有数据过期时优先提供上次成功快照并后台更新。不能在每次打开编辑器或下拉框时同步请求 Ozon。大字典搜索按规范化关键词及上述字典作用域共享缓存,并去重并发补齐。
  5. 原子性与监控:P0 按平台及树、类型属性、字典作用域在单事务内 upsert,提交前读取方继续看到旧数据;失败保留上次成功缓存。仅在确认完整分页完成后才处理消失的字典值,部分同步记录 is_complete=false。当前尚未实现不可变版本快照、内容哈希、next_refresh_at、连续失败告警和异常删减保护,这些属于上线前可靠性加固,不能在文档中误记为已完成。

Ozon 字典分页每次最多 2000 个值;搜索接口要求至少两个字符、每次最多 100 个值。搜索命中只是局部候选,不能证明字典已完整同步。当前平台任务单次最多读取 5 页(10000 项);超过上限会正确保留为部分缓存,但续传游标尚未实现。

4. 门店差异层的未来预案(本阶段不实施)

本阶段按同平台所有门店使用相同目录、属性定义和候选值实施:不创建门店差异表,不按门店同步参考目录,不在读取或发布校验时叠加门店覆盖规则。门店凭据状态和某次发布的失败原因仍按门店记录,但不据此自动改写平台共享目录。

如果联调或线上证据表明确有账号专属的可售状态、属性或字典差异,再单独提出迁移:可按 workspace_id + store_id 建门店覆盖层,或按市场/卖家分组收窄共享范围,并制定差异检测与回退策略。该预案只保留扩展方向,不是当前开发任务或上线前置条件。

5. 从现有实现迁移

022_store_catalog_cache.sql 的门店级表和 profile 的 store_product_category_id 仍保留为兼容/回滚数据,但商品编辑器不再读取它们。当前事实来源是平台级类目、属性和 platform_catalog_dictionary_values。

迁移 024~026 建立平台参考来源、候选值、同步任务、策略和资源状态;027_product_editor_platform_catalog.sql 以 Ozon 外部类目/类型 ID 回填商品的 platform_product_category_id,并为部分字典增加关键词搜索任务作用域。旧门店列和表暂不删除,新保存会尽量反写可映射的门店类目 UUID,供回滚使用。清理旧表必须等真正的 Ozon 发布 job 复用平台预检、完成稳定性验证并取得维护者批准后另行执行。Wildberries 等新平台应新增平台适配器及自己的外部 ID 映射,不复用 Ozon 的 description_category_id + type_id 语义。