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

149 lines
9.0 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 网页 localStorage,也不用 Web 端 refresh cookie。每个发布扩展在服务端登记 `client_id` 和允许的 `chrome-extension://{extension-id}/…` 回调地址,扩展使用 PKCE 授权码流程。
1. 扩展打开:`GET /api/extension/authorize?client_id={id}&redirect_uri={uri}&state={random}&code_challenge={S256}&code_challenge_method=S256`。
2. 用户在 Atlas Web 完成登录;首期不额外展示工作空间选择/授权确认页。
3. 服务端只向已登记的 `redirect_uri` 回跳 `?code={one-time}&state={original}`。授权码 60 秒有效、只能交换一次,且绑定 client、回调地址和 PKCE challenge。
4. 扩展调用 `POST /api/extension/auth/exchange`:
```json
{
"client_id": "atlas-collector",
"code": "one-time-code",
"redirect_uri": "chrome-extension://extension-id/callback",
"code_verifier": "original-pkce-verifier"
}
```
成功返回扩展专用短期 access token 与可轮换 refresh token;令牌只存 Chrome 扩展专属存储:
```json
{ "data": {
"access_token": "eyJ…", "expires_in": 900, "refresh_token": "opaque", "refresh_token_expires_in": 2592000,
"token_type": "Bearer"
} }
```
`POST /api/extension/auth/refresh` 接收 `{ "refresh_token": "opaque" }`,轮换并返回同形状令牌;`POST /api/extension/auth/revoke` 撤销当前扩展 refresh token,返回 `204`。令牌续期、撤销和错误响应不得泄露 Web 会话或其他工作空间信息。
`GET /api/extension/session`
使用扩展 access 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/关系和证据,不传图片二进制。
请求体不得包含或传入 `workspace_id`、`target_platform_id`、`platform_id`、`store_id`、`api_key`、Cookie、授权头或来源站会话字段。服务端从扩展令牌解析工作空间,并将来源 `platform` 映射为系统已启用的来源平台。
服务端在一个事务中:检查工作空间采集箱上限 → 按 `workspace + source_platform + source_item_key` 查找活动记录 → 创建或复用 `collection_item`、`product`、来源确认的 `product_variants` 和持久化 `collection_asset_transfers` → 再经 outbox 投递素材 Worker。重复上报不会增加采集箱配额,也不覆盖用户已编辑草稿。
成功返回 `201`(新建)或 `200`(复用):
```json
{ "data": {
"collection_item": { "id": "uuid", "status": "materializing", "source_title": "商品标题", "variant_count": 3 },
"product_id": "uuid", "reused": false,
"editor_entry_path": "/collection-items/uuid/edit",
"materialization": { "total": 12, "queued": 12, "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` 是采集/素材处理状态,二者绝不混用。首张素材转存未完成时 thumbnail 为占位状态;商品未命名时 `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-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 的首次上架。