107 lines
6.1 KiB
Markdown
107 lines
6.1 KiB
Markdown
# 在线商品镜像与同步 API
|
||
|
||
> 在线商品是平台 + 门店独有的外部事实镜像,一行对应一个 Ozon `product_id` / `offer_id` SKU。它包含 Atlas 发布商品和外部商品;不能跨店铺、跨平台复用,也不能当作采集箱 SPU。首期接口只读镜像和触发同步,不支持在此编辑、删除、下架、改价或设置库存。
|
||
|
||
## 1. 当前门店与镜像边界
|
||
|
||
所有接口要求 `X-Atlas-Platform-Id`。`store_id` 未传时服务端取当前工作空间该平台默认店铺;没有默认店铺返回 `422 DEFAULT_STORE_REQUIRED`。显式传入的 `store_id` 必须属于当前工作空间和 header 平台。
|
||
|
||
- 采集箱的“已上架”表示 Atlas 首次导入 SKU 的成功事实;它不由本模块的 `visible`、库存或审核状态推导。
|
||
- `platform_listings.visible` 是 Ozon 同步返回的实际可见性;`null` 表示尚未获取,不能当作 `false`。
|
||
- Atlas 发布 SKU 以 `store_id + offer_id` 和回填的 Ozon `product_id` 映射。外部同步商品 `source=external_sync`,没有 Atlas 草稿关联也必须显示。
|
||
- 同步失败始终保留上一轮成功镜像;不把失败、超时或尚未见到的条目直接解释为已下架。
|
||
|
||
## 2. 在线商品列表与详情
|
||
|
||
`GET /api/platform-listings?store_id={uuid}&cursor={opaque}&limit=20&q={keyword}&visibility={all|visible|invisible|unknown}&source={atlas_published|external_sync}&moderation_status={value}&include_archived=false`
|
||
|
||
默认按 `updated_at DESC, id DESC` 返回当前店铺镜像。`q` 匹配 `name`、`offer_id`、`remote_product_id`、`remote_sku`;`visibility=unknown` 只匹配 `visible=null`;`include_archived=false` 排除同步后标记已归档的历史镜像。
|
||
|
||
```json
|
||
{ "data": [{
|
||
"id": "uuid", "store_id": "uuid", "platform_id": "uuid",
|
||
"remote_product_id": "123456", "offer_id": "atlas-abc", "remote_sku": "987654",
|
||
"name": "商品标题", "currency_code": "RUB", "selling_price": "999.00", "list_price": "1110.00",
|
||
"visible": true, "remote_state": "processed", "moderation_status": "approved", "validation_status": "success",
|
||
"stock_summary": { "total": 0, "has_data": true },
|
||
"image": { "url": "https://platform-image-or-null", "width_px": null, "height_px": null },
|
||
"errors": [], "visibility_details": {},
|
||
"source": "atlas_published", "atlas_origin": { "collection_item_id": "uuid", "product_id": "uuid", "variant_id": "uuid" },
|
||
"last_synced_at": "2026-09-02T08:00:00Z", "missing_since": null, "archived_at": null
|
||
}], "meta": {
|
||
"next_cursor": null, "has_more": false,
|
||
"store": { "id": "uuid", "name": "Ozon 主店" },
|
||
"last_successful_sync_at": "2026-09-02T08:00:00Z", "last_sync_status": "succeeded"
|
||
} }
|
||
```
|
||
|
||
`image.url` 是 Ozon 返回的图片引用或 `null`,不是 Atlas `media_assets` 的永久地址;没有图片不意味着 Atlas 素材丢失。`errors` 仅返回页面可展示的结构化摘要,隐藏 Ozon 原始响应中可能存在的无关敏感字段。
|
||
|
||
`GET /api/platform-listings/{listing_id}`
|
||
|
||
返回单个 SKU 镜像的完整业务摘要、可见性详情、库存摘要、审核/失败原因、Atlas 来源链接和最近同步信息。它不返回 `raw_payload`;需要平台原始响应排障时仅由受控运营工具读取。
|
||
|
||
## 3. 手动触发同步
|
||
|
||
`POST /api/stores/{store_id}/platform-product-sync-runs`
|
||
|
||
Headers:`Idempotency-Key` 必填。Body 可省略,或为:
|
||
|
||
```json
|
||
{ "reason": "manual_refresh" }
|
||
```
|
||
|
||
服务端验证店铺连接处于可同步状态,然后创建一次**全量**同步 run 并投递 Worker:
|
||
|
||
```text
|
||
/v3/product/list(分页,最多每页 1000)
|
||
→ /v3/product/info/list(每批最多 100)
|
||
→ 按 store + remote_product_id / offer_id upsert 镜像
|
||
→ 完整成功后将本轮未见记录标记 missing_since / archived,不物理删除
|
||
```
|
||
|
||
返回 `202 Accepted`:
|
||
|
||
```json
|
||
{ "data": { "sync_run": {
|
||
"id": "uuid", "store_id": "uuid", "status": "queued", "trigger_source": "manual",
|
||
"listed_count": 0, "upserted_count": 0, "failed_count": 0, "created_at": "2026-09-02T08:00:00Z"
|
||
} } }
|
||
```
|
||
|
||
同一工作空间同一店铺只允许一个 `queued` / `running` 全量同步;正在同步时返回 `409 SYNC_ALREADY_RUNNING` 和现有 `sync_run_id`。无有效 Seller API 凭据时返回 `422 STORE_CREDENTIAL_UNAVAILABLE`,不尝试使用浏览器 Cookie。
|
||
|
||
## 4. 同步状态与历史
|
||
|
||
`GET /api/platform-product-sync-runs/{sync_run_id}`
|
||
|
||
返回当前/历史同步 run:
|
||
|
||
```json
|
||
{ "data": {
|
||
"id": "uuid", "store_id": "uuid", "status": "running", "trigger_source": "manual",
|
||
"cursor_started": null, "cursor_finished": null,
|
||
"listed_count": 1000, "upserted_count": 100, "failed_count": 0,
|
||
"started_at": "2026-09-02T08:00:10Z", "completed_at": null,
|
||
"error": null
|
||
} }
|
||
```
|
||
|
||
`GET /api/stores/{store_id}/platform-product-sync-runs?cursor={opaque}&limit=20`
|
||
|
||
按创建时间倒序返回该店铺同步历史。失败 run 的 `error` 是可展示摘要;前端应保留并标注上一轮成功同步时间,而不是清空在线列表。
|
||
|
||
Worker 只在完成所有分页和详情批次后把 run 标记 `succeeded`。部分请求失败时 run 为 `failed`,已成功 upsert 的记录仍保留,但不执行“未见记录归档”步骤,避免将网络错误放大为商品消失。
|
||
|
||
归档规则(2026-09-26 确认):首次成功全量同步未见某 SKU,仅记录 `missing_since`;第二次成功全量同步仍未见,或 `missing_since` 已超过 7 天,写入 `archived_at`。Worker 定时处理超过 7 天的记录,活跃全量同步期间暂缓归档。失败、部分同步或取消本身不改变这两个字段;再次出现时清空并恢复展示。列表默认排除已归档商品。
|
||
|
||
## 5. 明确不在首期接口中的操作
|
||
|
||
- 从在线商品编辑 Ozon 商品、重新发布或更新已导入 SKU;
|
||
- 设置库存、仓库、可见性或下架;
|
||
- 认领/导入外部同步商品到采集箱;
|
||
- 自动同步的频率、手动同步冷却时间和多店并发策略;
|
||
- 运营人员查看完整 Ozon 原始 payload。
|
||
|
||
这些操作均需要单独定义 Ozon 可修改字段、审核/重试边界和审计规则,不能借用采集箱“首次上架”接口。
|