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

10 KiB
Raw Permalink Blame History

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. 请求示例

{
  "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. 错误格式

{
  "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。从仓库根目录安装固定依赖并先预检:

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 窗口设置环境变量并运行:

$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 会指数退避;校验、 鉴权或业务冲突会立即停止,不会跳过失败批次继续写后续数据。

需要只导入指定文件时,把路径作为位置参数传入;要改变更新策略必须显式指定:

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 将确定的待补规格转换为正式规格

三个工作簿没有颜色、尺码的维度名称,不能固定认为第一列是颜色、第二列是尺码。 需要补齐已经导入的待补规格时,使用独立的确定性转换模式:

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:

$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 和数据库凭据不得 写入命令文件、日志、工单或任务归档。