Files
atlas-seller-kit/docs/api/collection-and-extension.md
T

134 lines
11 KiB
Markdown
Raw 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.
# 采集箱与浏览器插件 API
> 采集箱只属于工作空间:采集时没有目标平台、目标门店或店铺凭据。列表上架状态则按 ERP 页头当前平台汇总;它不是 Ozon 在线商品可见性,也不因切换到另一平台而继承。
## 1. 浏览器扩展授权
首期按毛子 ERP 风格复用 Atlas Web 登录态。用户从插件点击登录后,扩展打开固定 Atlas Web 页面;用户在该页完成登录或会话恢复后,Web 将当前短期 access token 镜像到本站 `localStorage["atlas.extension.web-access"]`。扩展每 2 秒仅从该 Atlas 标签读取这个值,保存到 Chrome extension storage,并以 Bearer token 调用下述接口。
这是维护者批准的自托管插件例外:扩展不读取 refresh cookie、Cookie、来源站 localStorage 或来源站授权信息;Web token 不得进入日志、数据库、Outbox 或采集载荷。Token 失效返回 `401` 后,插件清除自己的缓存并要求用户再次从 Atlas Web 登录同步。完整取舍见 [`../project/extension-login-alternatives.md`](../project/extension-login-alternatives.md)。
`GET /api/extension/session`
使用从 Atlas Web 同步的 Bearer token,返回最小化状态:
```json
{ "data": { "authenticated": true, "workspace": { "id": "uuid", "available": true }, "can_collect": true } }
```
不返回平台、默认店铺、Cookie、Ozon 凭据或 Web refresh token。令牌失效返回 `401`,插件清除缓存并只展示“登录 Atlas ERP”。
## 2. 插件上报采集结果
`POST /api/extension/materials`
Headers:扩展 Bearer token 与 `Idempotency-Key` 必填。请求体为已定义的 `CollectedProductPayload`,其 `source` 至少包含 `platform`、`itemKey`、`canonicalUrl`、`collectedAt`、`parserVersion`;`variants` 仅列来源有稳定证据的真实组合,媒体只传来源 URL、`mediaType`(`image` / `video`)、可选 `posterUrl`、关系和证据,不传二进制。
请求体不得包含或传入 `workspace_id`、`target_platform_id`、`platform_id`、`store_id`、`api_key`、Cookie、授权头或来源站会话字段。服务端从扩展令牌解析工作空间,并将来源 `platform` 映射为系统已启用的来源平台。
服务端在一个事务中:检查工作空间采集箱上限 → 按 `workspace + source_platform + source_item_key` 查找活动记录 → 创建记录,或以本次页面快照覆盖已有记录的来源 URL、标题、价格、原始证据、规范化载荷与候选素材。采集阶段的图片和视频只保存为 `pending_selection` 候选记录,**不下载媒体、不投递素材 Worker**;用户在编辑流程中选择媒体后,才调用按需转存接口。重复上报不会增加采集箱配额,且会保留原有 `collection_item` 与 `product` ID;旧快照中尚未完成的候选或任务会被取消,新快照使用新的来源快照版本,旧 Worker 不得回写新快照。
尚未创建任何 `product_platform_profile` 时,SKU 草稿仍是来源投影:同一 `source_variant_key` 原位更新,新 key 创建,缺失 key 软删除。**一旦进入任一平台编辑器,SKU 草稿及其平台 mapping 不再是可替换来源投影。** 此后的重复采集仍更新来源证据、规范化载荷和候选素材;同 key 复用原 SKU ID 且不覆盖用户编辑字段,新发现的来源 SKU 会为每个已有平台配置创建 `not_listed` mapping,来源未返回的 SKU 不自动删除或恢复。来源差异的人工审查 UI 另由商品编辑模块提供,采集接口不得通过删除 SKU 或级联删除映射来“覆盖”草稿。
1688 结构化采集以 `skuModel.skuInfoMap` 的真实可售记录生成 SKU;每条记录的 `price`(CNY)按两位小数四舍五入后写入对应 SKU 的 `purchase_cost_cny`,`skuProps.value` 中按规格名称匹配的图片作为带 `variantSourceKey` 的 `source_sku` 候选。尚无平台编辑草稿时,重复采集会刷新来源采购价;进入编辑器后不覆盖用户修改过的采购价。SKU 对应的来源图片可在编辑页预览,用户选择后才转存并保存为正式主图。
成功返回 `201`(新建)或 `200`(复用):
```json
{ "data": {
"collection_item": { "id": "uuid", "status": "ready_for_edit", "source_title": "商品标题", "variant_count": 3 },
"product_id": "uuid", "reused": false, "refreshed": false,
"editor_entry_path": "/products/uuid",
"materialization": { "total": 12, "pending_selection": 12, "queued": 0, "succeeded": 0, "failed": 0 }
} }
```
达到工作空间上限时返回 `422 COLLECTION_LIMIT_REACHED`,并附 `{current_count, limit}`;不能因重复点击或并发请求绕过 1000 条限制。采集阶段不会因下载来源媒体失败而失败;关键商品/SKU 解析失败才使记录进入 `failed` 并返回可展示的脱敏摘要。
## 3. Ozon Seller 网页本地观察
`POST /api/collection-items/{collection_item_id}/seller-enrichment-observations`
仅接受扩展 token。用户在浏览器已登录 `seller.ozon.ru` 标签页内,同源调用已白名单的四个网页内部接口后,将**净化结果**或不可用原因上报:
```json
{
"interface_code": "whitelisted-interface-code",
"source_variant_key": "optional-source-variant-id",
"status": "observed",
"observed_at": "2026-09-02T08:00:00Z",
"parser_version": "collector-1.0.0",
"sanitized_payload": { "only": "whitelisted business fields" }
}
```
不可用时使用 `status: "unavailable"` 并传 `unavailable_reason`;请求、响应在客户端和服务端均须过滤 Cookie、`Authorization`、店铺/个人无关信息、会话 ID 及完整 `company_id`。标准公开页面采集已成功时,本接口失败或未调用不得改变采集箱主状态、更不能阻断编辑。
服务端解析 `source_variant_key` 到当前采集商品的 SKU 后写入 `collection_seller_enrichment_observations`。接口代码不在白名单、记录不属于该工作空间或 payload 含禁用会话字段时返回 `422`;不把原始敏感 payload 写日志。
## 4. 采集箱列表与详情
`GET /api/collection-items?cursor={opaque}&limit=20&q={keyword}&source_platform_code={code}&listing_status={not_listed|partially_listed|listed}`
要求 `X-Atlas-Platform-Id`,按 `updated_at DESC, id DESC` 返回当前工作空间未删除记录。`q` 模糊匹配标题;`source_platform_code` 是**来源**筛选,不等于页头目标平台;`listing_status` 是按页头目标平台汇总的状态。
```json
{ "data": [{
"id": "uuid", "product_id": "uuid", "source": { "kind": "platform", "platform_code": "1688", "url": "https://…" },
"title": "商品标题", "thumbnail": { "asset_id": "uuid", "preview_url": "short-lived-or-null", "status": "succeeded" },
"source_price": { "min": "20.00", "max": "35.00", "currency_code": "CNY" },
"variant_summary": { "count": 3, "needs_review": false }, "note": "待确认材质",
"collection_status": "ready_for_edit",
"listing_summary": { "status": "partially_listed", "detail": "1 个 SKU 已导入;2 个等待首次上架", "last_error": null },
"created_at": "2026-09-02T08:00:00Z", "updated_at": "2026-09-02T08:05:00Z", "revision": 4
}], "meta": { "next_cursor": null, "has_more": false, "active_count": 12, "collection_limit": 1000 } }
```
`listing_summary.status` 映射 UI 的未上架/已部分上架/已上架;`collection_status` 是采集/素材处理状态,二者绝不混用。采集测试页应直接使用候选的 `source_url` 预览图片,视频则用 `preload=none` 的播放器,不等待转存、也不为预览创建存储对象;商品未命名时 `title` 返回 `null`,前端展示“未命名商品”。
`GET /api/collection-items/{collection_item_id}`
无平台 header 时仅返回采集基础信息、SKU/素材转存摘要、净化 Seller 观察结果摘要与警告;带 header 时附加 `listing_summary`。观察结果详情只对当前工作空间成员可见。
`PATCH /api/collection-items/{collection_item_id}`
Headers:`If-Match`、`Idempotency-Key` 必填。首期仅允许修改 `note`;标题、SKU 和素材在商品编辑 API 中修改。响应返回更新后的采集记录。`If-Match` 对应记录的 `revision`,包括采集 Worker 状态更新在内的每次修改都递增;不得覆盖 Worker 的状态更新。
## 5. 手动创建、按需转存、删除与素材重试
`POST /api/collection-items`
Headers:`Idempotency-Key` 必填。
```json
{ "title": "未命名手工商品", "note": "可选备注" }
```
创建 `source_kind=manual` 的采集记录、空商品草稿和默认 SKU,返回 `201`、`collection_item`、`product_id` 与 `editor_entry_path`。它仍不绑定目标平台/店铺,也不绕过之后的类目、定价和首次发布校验。
`POST /api/collection-items/{collection_item_id}/asset-materializations`
Headers:`Idempotency-Key` 必填。请求 `{ "transfer_ids": ["uuid"] }`;只能选择当前来源快照中状态为 `pending_selection` 的候选图片或视频。服务端将所选候选改为 `queued`,创建 operation,并通过 Outbox 投递 `collection.assets.materialize.requested` 给 Worker;返回 `202` 和 operation。该接口是商品编辑、加水印、AI 处理和发布流程共用的按需转存原语,不应在采集测试页自动调用。
`POST /api/collection-items/{collection_item_id}/asset-retries`
Headers:`Idempotency-Key` 必填。请求 `{ "transfer_ids": ["uuid"] }`;只能重试该采集记录中 `failed` 或 `cancelled` 的转存任务。返回 `202` 和 operation。不能用它重试来源商品解析或已成功素材。
`POST /api/collection-items/bulk-delete`
Headers:`Idempotency-Key` 必填。请求 `{ "ids": ["uuid"] }`,原子软删除其中当前工作空间的全部有效记录,返回 `202`、`deleted_count` 和延迟素材清理 operation。已上架记录同样可删除,但响应/前端确认文案必须说明:不删除、不下架任何 Ozon 商品,也不移除在线商品镜像。
`POST /api/collection-items/clear`
Headers:`Idempotency-Key` 必填。先由列表读取 `active_count` 并展示二次确认;确认请求:
```json
{ "confirm": true, "expected_active_count": 12 }
```
服务端锁定工作空间后比较当前有效数量;数量变化返回 `409 COLLECTION_COUNT_CHANGED`,要求用户重新确认。匹配时软删除全部有效记录并返回 `202`、删除数与延迟清理 operation。它不影响平台商品、发布快照或在线商品。
## 6. 与商品编辑接口的边界
`editor_entry_path` 只是 ERP 前端入口。下一批商品编辑接口会在用户进入该入口时根据页头目标平台取得/创建 `product_platform_profile`、预选默认店铺、允许切换同平台店铺,并定义 SKU 编辑、AI、定价确认、图片选择与仅未导入 SKU 的首次上架。