Files
cmautobuy/docs/admin/10-商品目录接入接口.md

201 lines
10 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.
# 10 商品目录批量接入接口
第三方脚本先把不同 Excel 归一化,再用本接口一次提交蝦皮商品、蝦皮 SKU、PDD
商品及商品关联。接口只做 upsert,本批次没出现的数据不会被删除。
## 1. 地址与鉴权
- `POST /api/v1/integrations/catalog/batches`
- 请求头:`Authorization: Bearer <专用 Token>`
- 请求体上限 5 MB;每批商品总数最多 500,蝦皮 SKU 最多 5000。
Token 通过 `CMAUTOBUY_CATALOG_TOKEN` 或未提交的 `config.yaml` 配置。未配置时接口
返回 503;凭据错误返回 401。不要把 Token 放进 URL、日志或导入文件。
## 2. 请求示例
```json
{
"schema_version": 1,
"batch_id": "source-file-20260811-001",
"observed_at": "2026-08-11T10:00:00+08:00",
"update_policy": "fill_missing",
"dry_run": false,
"shopee_products": [
{"goods_id": "S-1", "title": "蝦皮上衣", "status": "NORMAL", "main_sku_code": "A01", "image_url": "https://img.example.com/S-1.jpg", "shop_name": "蝦皮示例店铺"}
],
"shopee_skus": [
{"sku_id": null, "goods_id": "S-1", "spec_raw": "黑色,M", "color": "黑色", "size": "M", "parse_ok": true, "sku_code": "A01-B-M"}
],
"pdd_products": [
{
"goods_id": "P-1",
"url": "https://mobile.yangkeduo.com/goods.html?goods_id=P-1",
"title": "PDD 上衣",
"shop_name": "示例店铺",
"dimensions": [{"key": "color", "name": "颜色"}, {"key": "size", "name": "尺码"}],
"skus": [{"options": {"color": "黑色", "size": "M"}, "price_cent": 1299, "list_price_cent": 1599, "available": true}]
}
],
"associations": [{"shopee_goods_id": "S-1", "pdd_goods_id": "P-1"}]
}
```
金额单位固定为人民币分,不接受小数金额。`spec_raw` 必须原样提交。PDD 规格必须用
结构化 `dimensions` 和 `skus`,第三方不能提交数据库内部的 `skus_json`。
`sku_id` 是可选的真实蝦皮 SKU ID;来源文件没有该值时省略、传 `null` 或空字符串,
不得自行生成。系统用内部主键和 `goods_id + spec_key` 保持记录身份,后续取得真实 ID
会补到原记录,不会重复新增。
`image_url` 和 `shop_name` 可选。图片只接受不含用户信息和明显凭据参数的完整
HTTP/HTTPS URL,最长 2048 字节;店铺名最长 500 个字符。Admin 只保存地址并由浏览器
懒加载,不会在服务端下载或代理图片。
## 3. 写入与幂等规则
- 整个批次在一个事务中写入:实体、SKU、关联任一步失败就全部回滚。
- `(source, batch_id)` 是幂等键。请求字节相同则重放首次结果;同键不同内容返回 409。
- `update_policy` 可省略,默认 `fill_missing`:
- `insert_only`:只新增,已有规格不修改;
- `fill_missing`:只补颜色、尺码、建议、SKU 编码等空字段;
- `overwrite_same_source`:只有来源相同且 `observed_at` 更新时才覆盖来源字段。
- 较旧的 `observed_at` 不覆盖较新的接口数据,任何策略都不能覆盖 `is_manual=1`。
- `spec_raw` 和由它生成的规格身份不被更新;同商品同规格出现不同真实 SKU ID 返回 409。
- 蝦皮主图和店铺名也遵循本批次策略,但各自独立记录来源、观测时间和人工保护;
空值永不清除现有值。顺运宝观测图片不参与蝦皮主图更新。
- 蝦皮 upsert 不覆盖采购员维护的 PDD 链接;SKU 的 `is_manual` 不被接口改写。
- 空关联可以建立、相同关联不重复写;已有不同 PDD 关联返回 409,绝不静默替换。
- 批次缺少某条记录不表示删除,接口没有“全量覆盖”语义。
### 3.1 数据库预检
请求中的 `dry_run` 设为 `true` 时,接口只在只读事务中检查当前数据库,不登记
`catalog_import_runs`,也不写入商品、SKU、PDD 或关联。响应 `status` 为
`previewed`,`counts` 用于显示预计新增或现有记录数量;`conflicts` 列出同一蝦皮商品
已有不同 PDD 关联的业务 ID。正式导入仍会重新执行相同的非覆盖和关联冲突判断。
第三方规范 CSV 导入固定使用 `fill_missing`:已有蝦皮商品、SKU 和 PDD 商品的非空字段
不被覆盖;PDD 已采集的标题、店铺、规格、价格和状态不被第三方 CSV 修改。相同关联
幂等跳过,不同关联返回冲突,绝不替换。
成功响应包含批次状态以及 SKU 新增、补空、同来源更新、人工跳过、旧数据跳过等计数。相同请求重放时
`replayed=true`。
## 4. 错误格式
```json
{
"error": {
"code": "ASSOCIATION_CONFLICT",
"message": "蝦皮商品已经关联其他 PDD 商品",
"retryable": false,
"request_id": "调用方可选请求号",
"details": {}
}
}
```
调用方应按 `code` 处理,不要解析中文 `message`。校验错误通常是 400/422,业务冲突
是 409,临时服务错误是 500/503。响应和导入记录都不会回显 Token 或完整请求体。
## 5. schema v8~v10 发布与回退
发布前先备份生产库,并在 MySQL 8.4、库名以 `_test` 结尾的测试库演练 v7→v8→v9→v10。
迁移只增加列和唯一索引,既有 `sku_id` 继续作为内部主键,因此历史引用不变;若检测
到同商品重复 `spec_key`,迁移会停止且不记录 v8,必须人工确认,不能自动合并。
若数据库已经记录 v8 或 v9,但缺少 `field_sources`、`field_observed_at` 等 v8 结构,
或缺少图片、店铺人工标记约束等 v9 结构,v10 会幂等重放两版迁移并只回填空值。
不要通过删除迁移版本、清库或手工伪造结构来绕过自检;修复完成且 v8/v9 形状均
正确后,程序才会记录 v10。
旧版 Admin 不理解无真实 SKU ID 的新记录。应用回退前应暂停目录接口并确认没有此类
记录;不要删除 v8 列或把内部主键复制成伪造的外部 SKU ID。
## 6. 三个已处理工作簿的分批导入脚本
仓库根目录的 `tools/import_catalog_xlsx.py` 用于导入以下三个本地商业样本,文件本身
不提交 Git:
- `raw_data/shopee_chanpin_1_已处理.xlsx`
- `raw_data/shopee_chanpin_2_已处理.xlsx`
- `raw_data/shopee_chanpin_3_已处理.xlsx`
脚本需要 Python 3.10+ 和 `openpyxl`。从仓库根目录安装固定依赖并先预检:
```powershell
python -m pip install -r tools/requirements-catalog-import.txt
python tools/import_catalog_xlsx.py --dry-run
```
预检会读取全部行、校验固定字段和关联、生成批次并检查 500 商品、5000 SKU、5 MB
三个接口限制,但不会读取 Token,也不会发送请求。当前三个样本的基准结果是 2075 个
蝦皮商品、33049 条 SKU、11 个批次;样本变化后以脚本实际输出为准。
真实提交前先备份生产数据库,然后在当前 PowerShell 窗口设置环境变量并运行:
```powershell
$env:CMAUTOBUY_CATALOG_BASE_URL = "https://buy.833729.com"
$env:CMAUTOBUY_CATALOG_TOKEN = Read-Host "请输入商品目录专用 Token"
python tools/import_catalog_xlsx.py
Remove-Item Env:CMAUTOBUY_CATALOG_TOKEN
```
脚本默认使用 `fill_missing`,不会用空字段清除现有数据,也不会覆盖人工字段。文件没有
真实蝦皮 SKU ID和可靠的颜色/尺码维度名称,因此脚本只按两个变种列的原顺序生成
`spec_raw`,不猜测 `sku_id`、颜色或尺码;PDD 只提交确定存在的商品 ID、规范化 URL、
店铺名及关联,不伪造 PDD 标题、价格或规格。
每个批次 ID 由转换器版本、文件内容哈希、批量大小、更新策略和批次序号确定,
`observed_at` 取批内 Excel 最大更新时间。因此同一文件和参数重新运行会提交完全相同的请求:已成功批次返回
`replayed=true`,可用于断点续传。临时网络错误、429 和可重试 5xx 会指数退避;校验、
鉴权或业务冲突会立即停止,不会跳过失败批次继续写后续数据。
需要只导入指定文件时,把路径作为位置参数传入;要改变更新策略必须显式指定:
```powershell
python tools/import_catalog_xlsx.py raw_data/shopee_chanpin_3_已处理.xlsx
python tools/import_catalog_xlsx.py --update-policy overwrite_same_source --dry-run
```
### 6.1 将确定的待补规格转换为正式规格
三个工作簿没有颜色、尺码的维度名称,不能固定认为第一列是颜色、第二列是尺码。
需要补齐已经导入的待补规格时,使用独立的确定性转换模式:
```powershell
python tools/import_catalog_xlsx.py --formalize-specs --dry-run
```
该模式按商品整体判断两列,不逐行改变列含义。只有整列取值都包含明确尺码证据时,
才把它作为尺码列;另一列作为颜色/款式。单维商品只有在全部取值都能明确识别为
尺码或颜色时才转换。证据冲突、列数不一致或含义不明的商品不会进入写请求,继续
显示“待补”。
当前三个样本的预检基准如下:
| 结果 | 商品数 | SKU 数 |
|---|---:|---:|
| 可以确定并正式化 | 1920 | 31178 |
| 含义不确定,继续待补 | 155 | 1871 |
| 源文件合计 | 2075 | 33049 |
正式化模式强制使用 `fill_missing`:只补数据库中的空颜色/尺码,不覆盖人工记录或已有
正式字段;`spec_raw`、`spec_key` 和真实 SKU ID 均不改变。它使用独立的
`xlsx-spec-v1` 批次版本,相同文件和参数可以幂等重跑。
生产执行前必须重新备份数据库,并先在 MySQL 8.4、库名以 `_test` 结尾的测试库或
生产备份副本演练。确认预检计数和前后不变量后,才在当前 PowerShell 进程设置 Token:
```powershell
$env:CMAUTOBUY_CATALOG_BASE_URL = "https://buy.833729.com"
$env:CMAUTOBUY_CATALOG_TOKEN = Read-Host "请输入商品目录专用 Token"
python tools/import_catalog_xlsx.py --formalize-specs
Remove-Item Env:CMAUTOBUY_CATALOG_TOKEN
```
执行后核对商品总数仍为 2075、SKU 总数仍为 33049,`spec_raw/spec_key` 不变,正式
SKU 增量与预检一致,歧义 SKU 仍为待补,且没有失败批次。Token 和数据库凭据不得
写入命令文件、日志、工单或任务归档。