# V2 后端 API 设计 > 状态:方案设计(待确认) > 上游:[V2 总览](./README.md) · [V2 架构](./architecture.md) · [数据库](./database.md) 统一约定: - 前缀 `/api`;鉴权 `Authorization: Bearer `(除 `/auth/login`、`/health` 外)。 - 错误:FastAPI 语义状态码;`detail` 为可读中文;校验错误 `422`。 - 列表分页:`?page=&page_size=` 或 `?limit=&last_id=`(Ozon 风格,仅对代理 Ozon 的接口)。 - 时间一律 ISO 8601 UTC。 --- ## 1. 鉴权 `/api/auth` | 方法 | 路径 | 说明 | |---|---|---| | POST | `/auth/login` | `{token}` → `{access_token, expires_at}`。MVP:token 等于 `.env` 的 `APP_TOKEN`;升级后:用户名密码 | --- ## 2. 采集入库 `/api/materials`(插件 → 服务端) 沿用 `docs/extension/plan.md` §14 的契约,**把「文件夹」换成「商品」**。 ### `POST /api/materials` ★ 主接口 ```jsonc { "product": { // 对应「文件夹」;首次上传可空=新建 "id": "uuid-or-null" // 传了=追加到已有商品(跨平台补素材) }, "source": { "platform": "ozon", // ozon | 1688 | taobao "itemId": "123456789", "url": "https://www.ozon.ru/product/…", "collectedAt": 1710000000000 }, "texts": [ { "kind": "title", "content": "…" }, { "kind": "params", "content": "…", "pairs": [{ "key": "…", "value": "…" }] } ], "images": [ { "groupKey": "main", "groupName": "主图", "variantName": null, "url": "https://…原图…", "index": 0, "type": "img", "dedupeKey": "…" } ], "refererOrigin": "https://www.ozon.ru" // 该站图片下载需带的 Referer } ``` 响应: ```jsonc { "product": { "id": "…", "stage": "collected" }, "assetsQueued": 12 } ``` **语义**:服务端立即落库(product + texts 进 raw + assets 置 `pending`),**响应不等图片下载完成**;后台协程按 `refererOrigin` 下载源图 → 转存七牛 → 更新 asset 状态。插件无需等待。 ### 其余 | 方法 | 路径 | 说明 | |---|---|---| | POST | `/materials/bytes` | 字节兜底:`multipart/form-data`(`product_id` + `meta`(JSON) + `file`),供源图带登录态、插件在页面上下文抓字节上传 | | GET | `/products/:id/fingerprints` | 跨页去重:返回已采集 `dedupeKey` 列表 | | GET | `/collected?platform=&itemId=` | 状态回显:`{collected, count}`(页面徽标用) | --- ## 3. 商品 `/api/products` ### 列表(采集箱 / 发布列表) `GET /api/products?stage=&q=&page=&page_size=` ```jsonc { "total": 128, "items": [ { "id": "…", "stage": "collected", "name": "…", "offer_id": "", "price": null, "source_platform": "ozon", "source_url": "…", "asset_count": { "main": 6, "sku": 4, "detail": 9 }, "ozon_product_id": null, "created_at": "…", "updated_at": "…" } ] } ``` ### 详情 / 编辑 / 删除 | 方法 | 路径 | 说明 | |---|---|---| | GET | `/products/:id` | 完整商品(含 raw / pricing / copy / attributes / assets) | | PATCH | `/products/:id` | 部分更新(编辑页 autosave)。可改字段见 database.md §2.3 | | POST | `/products` | 手动新建商品(不经过插件) | | DELETE | `/products/:id` | 软删(stage→archived)或硬删(采集箱未发布项) | | POST | `/products/:id/stage` | `{stage}` 流转(editing→ready 前校验必填项) | > 编辑页的计价、文案、图片、类目/属性都是「编辑 `products` 的某几列」,统一走 `PATCH /products/:id` 或细分子资源(见 §6–§8),不新增独立存储。 --- ## 4. 汇率 `/api/fx` `GET /api/fx` → `{ "rate": 11.84, "source": "cbr", "updated_at": "…" }` 服务端抓取(FloatRates → 俄央行 → er-api 三级降级,沿用 v1 数据源但**移到服务端**),缓存 + 脏数据过滤(5~25 区间)。前端计价用它,也可在计价时快照进 `products.fx_rate`。 --- ## 5. AI 文案 `/api/ai`(沿用现有,零改动) | 方法 | 路径 | 说明 | |---|---|---| | GET | `/ai/models` | 模型列表(models.yaml) | | POST | `/ai/copy` | 中文采买信息 → 俄文标题/描述/标签 + 中文对照 | V2 编辑页的「文案」面板直接消费这两个接口,生成结果写入 `products.copy` + `products.name/description`(用户确认后)。 --- ## 6. 图片 `/api/image` | 方法 | 路径 | 说明 | |---|---|---| | POST | `/image/edit` | **智能修图**(wanx2.1-imageedit,改名,沿用现有)——单图换背景/去水印/局部重绘/加文字 | | POST | `/image/suite` | **电商套图**(集成 ecommerce-image-suite,新增)——原图 + 卖点 → 套图 | | POST | `/image/upload-token` | 获取七牛直传 token(若走前端直传;一期可省,走服务端中转) | `/image/edit` 与 `/image/suite` 的结果图由服务端**下载 → 转存七牛 → 返回七牛 URL**(改造现状 image_edit.py 已预留的扩展点)。 详见 [`image-strategy.md`](./image-strategy.md)。 --- ## 7. 店铺 `/api/shops` | 方法 | 路径 | 说明 | |---|---|---| | GET | `/shops` | 列表(脱敏:不返回 key) | | POST | `/shops` | `{name, client_id, api_key, currency_code}` 绑定(加密落库) | | PATCH | `/shops/:id` | 更新(可只更新 name/currency,或换 key) | | DELETE | `/shops/:id` | 删除(级联校验是否有进行中发布) | | POST | `/shops/:id/test` | **连通性校验**:用该店铺凭证调 Ozon `/v1/roles`,成功→`{ok:true, roles:[…]}`,失败→`status=invalid` 并返回原因 | > `test` 用 `/v1/roles`(返回该 key 的角色与方法权限),既验凭证又验权限范围,成本为零。 --- ## 8. 类目与属性 `/api/categories`(服务端代理 Ozon) | 方法 | 路径 | 说明 | |---|---|---| | GET | `/categories/tree?lang=RU` | 类目树(代理 `/v1/description-category/tree`,服务端缓存) | | GET | `/categories/:category_id/attributes?type_id=` | 属性列表(代理 `/v1/description-category/attribute`) | | GET | `/categories/attribute/:attribute_id/values?category_id=&type_id=&q=` | 属性值字典(代理 `/values` 或 `/values/search` 按需) | > 这些接口需要店铺凭证(Client-Id/Api-Key)。请求体里带 `shop_id`,服务端用对应店铺凭证调 Ozon。类目树可全局缓存(与店铺无关);属性/值按类目缓存。详见 `ozon-publish.md` §3。 --- ## 9. 发布 `/api/publish` | 方法 | 路径 | 说明 | |---|---|---| | POST | `/products/:id/publish` | `{shop_id}` 发布:组装 items[0] → `/v3/product/import` → 建 `publish_tasks` → 返回 `{task_id}` | | GET | `/publish/:taskId` | 查询发布任务状态(服务端已轮询回写,直接读库) | | GET | `/products/:id/publish-history` | 该商品历史发布记录 | **发布请求体组装**(服务端职责,见 `ozon-publish.md` §5): 1. 取 `products` 平铺字段 + `attributes`/`complex_attributes`; 2. 剥离 `_` 前缀扩展字段(DB 里已天然分层,无需剥离); 3. 校验必填(name/description/category/尺寸重量/offer_id/images); 4. `POST /v3/product/import`(头 `Client-Id`/`Api-Key`)→ 得 `task_id`; 5. 后台轮询 `POST /v1/product/import/info` → 回写 `products.stage` + `ozon_product_id`。 --- ## 10. CSV 导出 `/api/export` `GET /api/export/products.csv?stage=&ids=` → 流式返回带 BOM 的 UTF-8 CSV。 字段(默认全量,`fields=` 可指定子集): ``` offer_id, product_id, name, description_category_id, price, old_price, currency_code, vat, weight, weight_unit, depth, width, height, dimension_unit, barcode, primary_image, images(join "|"), source_platform, source_item_id, source_url, stage, published_at, created_at ``` 已发布商品含 `product_id`;未发布留空。支持按 `stage`(collected/published/全部)与 `ids`(勾选导出)筛选。CSV 字段明细与公式见 [`ozon-publish.md`](./ozon-publish.md) §6。 --- ## 11. 插件运维接口(沿用 plan.md §14) | 方法 | 路径 | 说明 | |---|---|---| | GET | `/ext/profiles` | 远程采集配置下发(选择器热更) | | POST | `/ext/logs` | 埋点批量上报(抗改版看板) | --- ## 12. 接口 → 现有代码映射(改造量) | V2 接口 | 现状 | 改造 | |---|---|---| | `/auth/*` | 无 | 新建 | | `/materials` 系列 | 无(plan 里有设计) | 新建 | | `/products` 系列 | 无 | 新建 | | `/fx` | 无(v1 前端直连第三方) | 新建(搬 v1 数据源到服务端) | | `/ai/*` | ✅ 有 | 复用 | | `/image/edit` | ✅ 有 | 复用 + 加七牛转存 | | `/image/suite` | 无 | 新建(集成 ecommerce-image-suite) | | `/shops`、`/categories`、`/publish`、`/export` | 无(`api/ozon.py` 占位) | 新建 | | `/ext/*` | 无 | 新建 |