Files

176 lines
8.9 KiB
Markdown
Raw Permalink 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.
# V2 Ozon 发布集成
> 状态:方案设计(待确认)
> 上游:[V2 总览](./README.md) · [V2 架构](./architecture.md) · [数据库](./database.md) · [API](./api.md)
> 官方文档:[Ozon Seller API(中文)](https://docs.ozon.ru/api/seller/zh/)
---
## 1. 鉴权与店铺凭证
Ozon Seller API 用 **两个请求头** 鉴权(不是 OAuth):
```
Client-Id: <你的 Client ID>
Api-Key: <你的 API Key>
```
- 获取:Ozon 卖家后台 → 设置 → Seller API → 生成 Key(可选权限级别)。
- **凭证归属店铺**:V2 里每店铺一条 `shops` 记录,`client_id`/`api_key` 加密落库,调用时解密拼头。
- **连通性校验**`POST /api/shops/:id/test` 调 [`/v1/roles`](https://docs.ozon.ru/api/seller/zh/#operation/AccessAPI_RolesByToken)(返回该 key 的角色与可用方法),既验证凭证又看权限范围,零成本。
服务端封装 `services/ozon_client.py`:统一 base URL`https://api-seller.ozon.ru`)、拼头、超时、错误映射(400/403/409/500 → 语义化 detail)、限流退避。
---
## 2. 核心接口(本项目用到)
| 用途 | 方法 | 说明 |
|---|---|---|
| 类目树 | `POST /v1/description-category/tree` | 返回 `description_category_id / type_id / category_name / type_name / disabled / children`**只有末级类目可建品** |
| 类目属性 | `POST /v1/description-category/attribute` | 入参 `description_category_id + type_id`;返回属性含 `is_required / is_aspect / is_collection / dictionary_id / type / max_value_count` |
| 属性值字典 | `POST /v1/description-category/attribute/values` | 入参 `attribute_id + category_id + type_id + limit(≤2000) + last_value_id`(分页) |
| 属性值搜索 | `POST /v1/description-category/attribute/values/search` | 按 `value` 模糊匹配参考值(≥2 字符,limit≤100) |
| **发布/更新商品** | `POST /v3/product/import` | 一次 ≤100 个 item;返回 `task_id` |
| **发布状态** | `POST /v1/product/import/info` | 入参 `task_id`;返回 `items[{offer_id, product_id, status, errors[]}]` |
| 商品列表/回填 | `POST /v3/product/list` | 用 `offer_id/product_id` 过滤取 `product_id`,或分页拉全部 |
| 跟卖复制 PDP | `POST /v1/product/import-by-sku` | 入参 `sku + name + offer_id + price...`;返回 `task_id + unmatched_sku_list` |
| 图片更新 | `POST /v1/product/pictures/import` | 按 `product_id` 覆盖 `images/images360/color_image` |
| 商品详情(含图片/审核错误) | `POST /v3/product/info/list` | 回读已发布商品的图片/状态/错误 |
---
## 3. 类目与属性字典(采集属性 → Ozon 属性的关键)
### 3.1 数据流
```
类目树(全局缓存)
└─ 用户选类目 → 得 description_category_id + type_id
└─ 拉该类目属性(按 category+type 缓存)
└─ 对每个「有字典」的属性,按需拉值(/values 或 /values/search
```
- **类目树全局缓存**:与店铺无关(虽然接口要凭证),服务端拉一次存 `category_tree` 表 + 内存 LRUTTL 24h。
- **属性按类目缓存**`category_attributes` 表,按 `(category_id, type_id)` 缓存。
- **属性值按需拉取**:值目录可能非常大,只在用户映射到某个属性时才拉,且用 `/values/search`(按关键词搜)而非全量拉。
### 3.2 属性映射(采集的 `raw.params` → Ozon `attributes[]`
这是发布链路**最重的工作**。流程:
```
采集 raw.params: [{key:"Материал", value:"Нержавеющая сталь"}, …]
│ ① 自动匹配:key 与属性 name 模糊匹配(归一化 + 词干)
│ ② 有字典的属性:value 去 /values/search 找 dictionary_value_id
属性映射 UI:自动匹配结果 + 人工确认未匹配项 + 必填项高亮
products.attributes = [{complex_id:0, id, values:[{dictionary_value_id, value}]}]
```
**必填项校验**:服务端在「ready 校验」和「发布前」两次校验:`category_attributes``is_required=true` 的属性必须已映射,否则阻断发布并指出缺哪些。
---
## 4. 发布请求体组装(对齐 ImportProductsV3
`items[0]` 字段(已核对官方示例):
| 字段 | 来源 | 说明 |
|---|---|---|
| offer_id | `products.offer_id` | **自己的货号**,跟卖不能用竞品的 |
| name / description | `products.name/description`(俄文) | |
| description_category_id / type_id | `products.*` | 从类目树选 |
| price / old_price / currency_code / vat | `products.*` | currency 须与店铺设置一致(默认 RUB) |
| depth/width/height/dimension_unit/weight/weight_unit | `products.*` | **必填且不能为 0**(官方硬约束) |
| barcode | `products.barcode` | 可选 |
| images | `products.images`(七牛 URL,≤15) | 顺序即展示顺序;首张为主图。**必须 https 直链**(实测 Ozon 不接受 http,见 §7 |
| primary_image | `products.primary_image` | 用 primary_image 则 images ≤14 |
| images360 / color_image | `products.*` | 可选 |
| attributes | `products.attributes` | 映射结果 |
| complex_attributes | `products.complex_attributes` | 视频/尺码表等 |
| pdf_list / promotions | 可选 | 一般留空 |
**跟卖场景可选优化**:若竞品允许复制 PDP,走 `/v1/product/import-by-sku`(只需 sku + 基本信息),更快且继承竞品详情——但受「卖家是否允许复制」限制,且不能更新,故作为**可选快捷路径**,主路径仍是 `import`
---
## 5. 发布状态机与轮询
`/v3/product/import` 是异步的,返回 `task_id`。流程:
```
POST /v3/product/import → { task_id }
│ 建 publish_tasks(status=pending, ozon_task_id)
后台协程轮询 POST /v1/product/import/info { task_id }
│ items[0].status ∈ imported | moderation | failed(+errors[])
imported → products.stage=published, ozon_product_id=items[0].product_id
moderation → products.stage=publishing(继续轮询,通常 <1 天)
failed → products.stage=failed, publish_tasks.errors=items[0].errors
```
- 轮询间隔:先 5s,退避到 30s`moderation` 状态降低频率到分钟级。
-`/v3/product/list`filter by offer_id)回读 `product_id` 兜底(轮询遗漏时)。
- **上架还需设置库存**`import` 成功后商品进入后台但不自动上架(`architecture.md` 与官方文档均明确「只有设置库存后才开售」)。V2 一期发布到「已创建/审核」即可,库存设置(`/v2/products/stocks`)作为二期可选,或提示用户去后台补库存。
---
## 6. CSV 导出字段
服务端 `GET /api/export/products.csv`,带 BOM 的 UTF-8,Excel 直接打开不乱码。字段:
```
offer_id, product_id, name, description_category_id, type_id,
price, old_price, currency_code, vat,
weight, weight_unit, depth, width, height, dimension_unit,
barcode, primary_image, images, source_platform, source_item_id, source_url,
stage, published_at, created_at, updated_at
```
- `images``|` 拼接七牛 URL。
- 未发布商品 `product_id` 为空。
- 支持筛选 `stage`collected/ready/published/failed/全部)与 `ids`(勾选导出)。
> 对齐 V1:v1 登记表导出的 `sku + 卢布预留价` 组合码,V2 里 `offer_id + price`(卢布)即等价物;若要完全兼容 v1 组合码,可加一列 `combo``offer_id + 卢布预留价`)。
---
## 7. 错误处理与限流
| Ozon 错误 | 含义 | 处理 |
|---|---|---|
| 400 Invalid parameter | 参数错误 | 把 detail 透传前端,定位字段 |
| 403 Access denied | 权限不足 | 提示检查 Api-Key 权限级别 |
| 409 Request conflict | 冲突(如 offer_id 重复) | 提示改 offer_id 或走更新 |
| 429 / 限流 | 频率超限 | 指数退避重试 |
| `item_limit_exceeded` | 超过当日建/更新商品限额 | 提示限额,可查 `/v4/product/info/limit` |
`publish_tasks.errors` 完整保存 Ozon 返回的 errors 数组,前端发布结果页展示中文解读。
---
## 8. 店铺绑定交互
1. 店铺管理页「新增店铺」:填 `名称 + Client ID + API Key + 结算币种`
2. 点「测试连接」→ `/api/shops/:id/test` → 调 `/v1/roles` → 显示 `ok` 与角色列表,或失败原因(凭证错/权限不足/网络)。
3. 保存后 `client_id` 只显示尾号打码(如 `…1234`),key 永不回显。
4. 发布时从店铺下拉选择目标店铺。
---
## 9. 一期范围 vs 二期
| 能力 | 一期 | 二期 |
|---|---|---|
| 店铺绑定 + 连通性校验 | ✅ | |
| 类目树 + 属性 + 值字典(缓存) | ✅ | |
| 属性映射 UI(自动 + 人工) | ✅ 基础版 | 智能匹配优化 |
| `/v3/product/import` 发布 + 轮询回填 | ✅ | |
| `/v1/product/import-by-sku` 跟卖复制 | 🟡 可选 | |
| 库存设置(上架) | ❌(提示去后台) | `/v2/products/stocks` |
| 价格/库存批量更新 | ❌ | `/v1/product/import/prices``/v2/products/stocks` |
| 图片更新(换图) | ❌ | `/v1/product/pictures/import` |