1. 文档说明

注意:对接商品前,首先确保商品的品牌已经在东篱商城上传并已审核通过。

1.1 文档目的

本文档定义了东篱商城与第三方供应商(如碧云天、麦克林等)之间的商品数据开放接口规范。第三方供应商通过对接本接口,可实现商品信息、价格信息等数据与东篱商城的定时或实时同步,确保商城端展示内容与供应商侧保持一致。

1.2 接口功能列表

接口支持以下操作:

批量新增商品
批量修改商品
删除原有商品
更新商品价格
商品下架
查询当前供应商的有效品牌列表
查询当前供应商的有效类目列表

1.3 商品数据模型

商品采用 SPU(标准产品单元) 与 SKU(库存量单位) 两层结构:

SPU 表示一个独立的商品,承载该商品的基本信息(如名称、品牌、类目、描述等)。
每个 SPU 下可包含多个 SKU,每个 SKU 代表该商品的一种具体规格(如颜色、尺寸、包装等),并关联各自的库存、价格、条码等属性。

通过此结构,供应商可精细化管理商品主数据与规格层级,满足多样化的上架与维护需求。

2. 接口清单

功能 方法 路径
批量新增商品 POST /openapi/products/actions/batch-create
批量修改商品 PUT /openapi/products/actions/batch-update
删除原有商品 DELETE /openapi/products/{spuId}
更新商品价格 PATCH /openapi/products/{spuId}/prices
商品下架 POST /openapi/products/{spuId}/actions/offline
获取有效品牌 GET /openapi/product-brands
获取有效类目 GET /openapi/product-categories

3. 通用约定

3.1 基础地址

{host}/openapi

实际 {host} 由不同部署环境提供。
测试环境:https://www.do.keyan.net.cn
生产环境:https://www.keyan.net.cn

3.2 请求格式

Content-Type: application/json

字符编码统一为 UTF-8,时间统一使用 Unix 毫秒时间戳。

3.3 身份认证

请求必须携带以下 Header:

Header 必填 说明
X-Openapi-App-Id 平台分配的应用 ID
X-Openapi-Timestamp 请求发起时的 Unix 毫秒时间戳
X-Openapi-Sign 请求签名

供应商身份由 X-Openapi-App-Id 对应的开放平台客户端确定,请求体中不传 supplierId

3.3.1 签名算法

平台分配 appIdappSecretappSecret 仅用于本地生成签名,不得放入请求 Header、请求体或日志中。

签名生成步骤如下:

  1. 获取当前 Unix 毫秒时间戳 timestamp,其值必须与 X-Openapi-Timestamp 完全一致。
  2. 按照 appId + timestamp + appSecret 的顺序直接拼接签名原文,中间不添加分隔符、空格或换行。
  3. 使用 UTF-8 编码签名原文,并计算 MD5 摘要。
  4. 将摘要转换为 32 位大写十六进制字符串,作为 X-Openapi-Sign 的值。

计算公式:

X-Openapi-Sign = UPPERCASE(MD5_UTF8(appId + timestamp + appSecret))

当前签名只包含 appIdtimestampappSecret,HTTP 方法、请求路径、查询参数及请求体均不参与签名。

3.3.2 签名示例

假设平台分配的认证信息为:

appId     = demo-app
appSecret = demo-secret
timestamp = 1710000000000

签名原文及结果为:

签名原文:demo-app1710000000000demo-secret
MD5:5407ad397c399c6aea299df3a5fe85f5
X-Openapi-Sign:5407AD397C399C6AEA299DF3A5FE85F5

请求 Header 示例:

X-Openapi-App-Id: demo-app
X-Openapi-Timestamp: 1710000000000
X-Openapi-Sign: 5407AD397C399C6AEA299DF3A5FE85F5

Java 签名示例:

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public static String openApiSign(String appId, long timestamp, String appSecret)
        throws NoSuchAlgorithmException {
    String raw = appId + timestamp + appSecret;
    byte[] digest = MessageDigest.getInstance("MD5")
            .digest(raw.getBytes(StandardCharsets.UTF_8));
    char[] hex = "0123456789ABCDEF".toCharArray();
    char[] sign = new char[digest.length * 2];
    for (int i = 0; i < digest.length; i++) {
        int value = digest[i] & 0xFF;
        sign[i * 2] = hex[value >>> 4];
        sign[i * 2 + 1] = hex[value & 0x0F];
    }
    return new String(sign);
}

3.3.3 时间戳与验签要求

  • X-Openapi-Timestamp 必须是十进制 Unix 毫秒时间戳,不是秒级时间戳。
  • 服务端默认只接受与当前时间相差不超过 5 分钟的请求;实际有效期以部署环境配置为准。
  • 三个认证 Header 缺失、应用不存在或已停用、时间戳不合法或过期、签名不一致时,服务端均返回 401 Unauthorized
  • 调用方应确保服务器时钟准确,并为每次请求使用当前时间戳重新计算签名。
  • 服务端按大写十六进制字符串进行精确比较,调用方不得发送小写签名。

3.4 幂等规则

  • 商品以“当前供应商 + 商品货号”作为业务唯一键。
  • 重复提交相同商品货号不会创建第二个商品。
  • 修改、删除、价格更新和下架接口重复调用时,应返回当前最终结果,不产生重复数据。

3.5 批量规则

  • 批量新增和批量修改接口每次最多提交 500 条商品。
  • 请求中的商品货号不能重复。
  • 每条商品独立处理,一条商品失败不回滚同一批次中已经成功的其他商品。
  • 响应返回接收数量、成功数量、失败数量以及逐条失败数据。
  • HTTP 200 OK 表示批次已处理完成,不表示批次内所有商品都成功。
  • 请求体结构无效或商品数量超过 500 条时,整个请求返回 400 Bad Request

3.6 金额规则

  • marketPricediscountPrice 使用 JSON 数字类型。
  • 单位为人民币元,最多保留两位小数。
  • 金额必须大于或等于 0.01
  • 优惠价不得高于市场价。

4. 数据模型

4.1 商品基本信息

字段 类型 必填 说明
spuId string 修改时是 平台 SPU ID;新增成功后由平台返回
name string 商品名称,最多 200 个字符
productCode string 供应商商品货号;同一供应商下唯一,创建后不可修改
brandId string 平台品牌 ID,必须是当前供应商的有效品牌,参考10. 获取当前供应商有效品牌
categoryId string 平台类目 ID,必须是当前供应商可用的有效末级类目,参考11. 获取当前供应商有效类目
deliveryCycle string 货期,例如 3天现货
mainImageUrl string 商品主图地址,必须是可访问的 HTTPS 地址
casNumber string CAS 号
description string 商品详情,可为文本或平台允许的富文本内容,可以是第三方的商品详情地址
officialWebsite string 供应商官网商品地址,必须是 HTTP 或 HTTPS 地址
skus array SKU 列表;新增商品时至少包含一个 SKU

4.2 新增商品 SKU

字段 类型 必填 说明
specification string 规格,例如 500ml/瓶
marketPrice number 市场价
discountPrice number 优惠价,即商城实际销售价

4.3 修改商品 SKU

修改商品接口不允许修改价格。

字段 类型 必填 说明
originalSpecification string 修改前的 SKU 规格,用于定位原有 SKU
specification string 修改后的 SKU 规格

如果不修改 SKU 规格,批量修改商品时可以不传 skus。新增 SKU 不通过修改商品接口处理。

5. 批量新增商品

一次创建 1 至 500 个商品,每个商品可以包含多个 SKU。

5.1 请求

POST /openapi/products/actions/batch-create
{
  "products": [
    {
      "name": "无水乙醇",
      "productCode": "P-100001",
      "brandId": "brand-1001",
      "categoryId": "category-2001",
      "deliveryCycle": "3天",
      "mainImageUrl": "https://supplier.example.com/images/P-100001.jpg",
      "casNumber": "64-17-5",
      "description": "分析纯无水乙醇",
      "officialWebsite": "https://supplier.example.com/products/P-100001",
      "skus": [
        {
          "specification": "500ml/瓶",
          "marketPrice": 40.00,
          "discountPrice": 32.50
        },
        {
          "specification": "2.5L/瓶",
          "marketPrice": 150.00,
          "discountPrice": 120.00
        }
      ]
    },
    {
      "name": "甲醇",
      "productCode": "P-100002",
      "brandId": "brand-1001",
      "categoryId": "category-2001",
      "deliveryCycle": "现货",
      "mainImageUrl": "https://supplier.example.com/images/P-100002.jpg",
      "skus": [
        {
          "specification": "500ml/瓶",
          "marketPrice": 35.00,
          "discountPrice": 29.90
        }
      ]
    }
  ]
}

5.2 成功响应

HTTP 状态码:200 OK

{
  "code": "SUCCESS",
  "message": "批量新增处理完成",
  "data": {
    "totalCount": 2,
    "successCount": 1,
    "failureCount": 1,
    "successes": [
      {
        "index": 0,
        "productCode": "P-100001",
        "spuId": "spu-100001"
      }
    ],
    "failures": [
      {
        "index": 1,
        "productCode": "P-100002",
        "code": "BRAND_NOT_FOUND",
        "message": "品牌不存在或不是当前供应商的有效品牌",
        "fields": ["brandId"],
        "sourceData": {
          "name": "甲醇",
          "productCode": "P-100002",
          "brandId": "brand-invalid",
          "categoryId": "category-2001"
        }
      }
    ]
  }
}

5.3 业务规则

  • products 必填,数量为 1 至 500。
  • 每个商品至少包含一个 SKU。
  • 同一个商品下的 SKU 规格不能重复。
  • 商品货号在当前供应商下必须唯一。
  • 品牌和类目必须处于有效状态,并且当前供应商有权使用。
  • 新增成功后,商品默认进入上架状态。
  • 同一条商品内任意字段或 SKU 校验失败时,该条商品整体失败。

6. 批量修改商品

一次修改 1 至 500 个已有商品。该接口只能修改商品基本信息和已有 SKU 的规格,不能修改市场价或优惠价。

6.1 请求

PUT /openapi/products/actions/batch-update
{
  "products": [
    {
      "spuId": "spu-100001",
      "name": "无水乙醇 AR",
      "brandId": "brand-1001",
      "categoryId": "category-2001",
      "deliveryCycle": "2天",
      "mainImageUrl": "https://supplier.example.com/images/P-100001-v2.jpg",
      "casNumber": "64-17-5",
      "description": "更新后的商品详情",
      "officialWebsite": "https://supplier.example.com/products/P-100001",
      "skus": [
        {
          "originalSpecification": "500ml/瓶",
          "specification": "500mL/瓶"
        }
      ]
    },
    {
      "spuId": "spu-100002",
      "name": "甲醇 AR",
      "brandId": "brand-1001",
      "categoryId": "category-2001",
      "deliveryCycle": "3天",
      "mainImageUrl": "https://supplier.example.com/images/P-100002-v2.jpg"
    }
  ]
}

6.2 成功响应

HTTP 状态码:200 OK

{
  "code": "SUCCESS",
  "message": "批量修改处理完成",
  "data": {
    "totalCount": 2,
    "successCount": 1,
    "failureCount": 1,
    "successes": [
      {
        "index": 0,
        "spuId": "spu-100001",
        "productCode": "P-100001"
      }
    ],
    "failures": [
      {
        "index": 1,
        "spuId": "spu-100002",
        "code": "PRODUCT_NOT_FOUND",
        "message": "商品不存在或不属于当前供应商",
        "fields": ["spuId"],
        "sourceData": {
          "spuId": "spu-100002",
          "name": "甲醇 AR",
          "brandId": "brand-1001"
        }
      }
    ]
  }
}

6.3 业务规则

  • products 必填,数量为 1 至 500。
  • 每条商品必须传 spuId,平台使用“当前供应商 + spuId”定位商品。
  • spuId 必须属于当前供应商,不能通过修改接口变更。
  • 商品货号不允许通过修改接口变更。
  • 请求中不得出现 marketPricediscountPrice;价格必须通过价格更新接口修改。
  • skus 只用于修改已有 SKU 的规格,不用于新增或删除 SKU。
  • 使用 originalSpecification 定位已有 SKU。
  • 请求中没有出现的原有 SKU 保持不变。
  • 修改商品名称、品牌、类目、主图、CAS 号、详情或者 SKU 规格后,商品直接上架。
  • 同一条商品修改失败时,该条商品不保存部分修改。

7. 删除原有商品

删除供应商不再维护的原有商品。

7.1 请求

DELETE /openapi/products/{spuId}

请求无 Body。

7.2 成功响应

HTTP 状态码:204 No Content

7.3 业务规则

  • 使用“当前供应商 + spuId”定位商品。
  • 已产生订单、报价、合同或其他业务引用的商品不得物理删除。
  • 对已有业务引用的商品执行逻辑删除,并保留商品、SKU 及历史业务数据。
  • 删除商品前,平台自动将商品及其全部 SKU 下架。
  • 已删除商品不能再次修改、更新价格或上架。
  • 重复删除同一个商品按成功处理。
  • 删除表示供应商不再维护该商品;下架表示暂时停止销售,后续仍可按平台流程恢复上架。

8. 商品价格更新

按 SKU 规格更新一个商品下一个或多个 SKU 的市场价和优惠价。

8.1 请求

PATCH /openapi/products/{spuId}/prices
{
  "skus": [
    {
      "specification": "500mL/瓶",
      "marketPrice": 45.00,
      "discountPrice": 35.50
    },
    {
      "specification": "2.5L/瓶",
      "marketPrice": 155.00,
      "discountPrice": 125.00
    }
  ]
}

8.2 成功响应

HTTP 状态码:200 OK

{
  "code": "SUCCESS",
  "message": "商品价格更新成功",
  "data": {
    "spuId": "spu-100001",
    "productCode": "P-100001",
    "updatedCount": 2,
    "skus": [
      {
        "specification": "500mL/瓶",
        "marketPrice": 45.00,
        "discountPrice": 35.50
      },
      {
        "specification": "2.5L/瓶",
        "marketPrice": 155.00,
        "discountPrice": 125.00
      }
    ]
  }
}

8.3 业务规则

  • 使用“当前供应商 + spuId”定位商品。
  • 使用商品下的 specification 精确定位 SKU。
  • 规格在同一个商品下必须唯一。
  • 请求内的 SKU 规格不能重复。
  • 优惠价不得高于市场价。
  • 任意规格不存在或价格不合法时,整个价格更新请求失败,不执行部分更新。
  • 价格变低,直接上架,价格变高,自动下架,等待平台审核审核通过后自动上架。

9. 商品下架

将商品及其全部 SKU 设置为不可销售状态。

9.1 请求

POST /openapi/products/{spuId}/actions/offline
{
  "reason": "供应商停止销售"
}
字段 类型 必填 说明
reason string 下架原因,最多 500 个字符

9.2 成功响应

HTTP 状态码:200 OK

{
  "code": "SUCCESS",
  "message": "商品已下架",
  "data": {
    "spuId": "spu-100001",
    "productCode": "P-100001",
    "offlineTime": "2026-08-26T10:30:00+08:00"
  }
}

9.3 业务规则

  • 使用“当前供应商 + spuId”定位商品。
  • 商品下架后,其全部 SKU 同时变为不可销售。
  • 下架操作应同步更新商品目录、搜索结果和购物车中的商品可售状态。
  • 下架不会删除商品、SKU 或历史订单数据。
  • 已下架商品重复调用该接口按成功处理。
  • 本期不开放第三方供应商直接上架;商品重新上架仍需遵循平台审核流程。

10. 获取当前供应商有效品牌

获取当前 OpenAPI 客户端所绑定供应商可以使用的有效品牌。

10.1 请求

GET /openapi/product-brands

10.2 成功响应

{
  "code": "SUCCESS",
  "message": "查询成功",
  "data": [
    {
      "brandId": "brand-1001",
      "brandName": "Macklin/麦克林"
    },
    {
      "brandId": "brand-1002",
      "brandName": "阿拉丁"
    }
  ]
}

10.3 业务规则

  • 只返回当前供应商已关联且状态有效的品牌。
  • 一次返回全部有效品牌,不支持搜索和分页。
  • 返回的 brandId 用于新增和修改商品接口。
  • 已停用或已撤销供应商关联的品牌不返回。

11. 获取当前供应商有效类目

获取当前 OpenAPI 客户端所绑定供应商可以使用的有效商品类目。

11.1 请求

GET /openapi/product-categories

11.2 成功响应

{
  "code": "SUCCESS",
  "message": "查询成功",
  "data": [
    {
      "categoryId": "category-1000",
      "categoryName": "化学试剂",
      "parentId": null,
      "level": 1,
      "leaf": false,
      "children": [
        {
          "categoryId": "category-2001",
          "categoryName": "醇类试剂",
          "parentId": "category-1000",
          "level": 2,
          "leaf": true,
          "children": []
        }
      ]
    }
  ]
}

11.3 业务规则

  • 只返回当前供应商有权经营且状态有效的类目。
  • 一次返回全部有效类目,不支持搜索和分页。
  • 返回树形类目结构。
  • 新增和修改商品只能使用 leaf: true 的末级类目 ID。
  • 已停用或不在供应商经营范围内的类目不返回。

12. 通用响应格式

12.1 成功响应

{
  "code": "SUCCESS",
  "message": "操作成功",
  "data": {}
}

204 No Content 响应不包含响应体。

12.2 失败响应

{
  "code": "PRODUCT_NOT_FOUND",
  "message": "商品不存在或不属于当前供应商",
  "errors": [
    {
      "field": "productCode",
      "message": "商品不存在或不属于当前供应商"
    }
  ]
}

13. HTTP 状态码

HTTP 状态码 说明
200 OK 请求处理成功;批量接口的具体结果以响应统计为准
204 No Content 删除成功且无响应体
400 Bad Request JSON 格式错误、缺少必填字段、批量为空或超过 500 条
401 Unauthorized OpenAPI 认证信息缺失或签名错误
403 Forbidden 当前应用无商品接口权限
404 Not Found 商品、SKU、品牌或类目不存在,或资源不属于当前供应商
409 Conflict 商品货号重复、商品状态冲突或重复请求内容不一致
422 Unprocessable Entity 请求格式正确,但不满足商品业务规则
429 Too Many Requests 请求频率超过限制
500 Internal Server Error 平台内部异常

14. 业务错误码

错误码 说明
INVALID_REQUEST 请求参数不合法
BATCH_SIZE_EXCEEDED 批量商品数量超过 500 条
PRODUCT_NOT_FOUND 商品不存在或不属于当前供应商
PRODUCT_CODE_DUPLICATED 当前供应商下商品货号重复
PRODUCT_CODE_IMMUTABLE 商品货号不允许修改
BRAND_NOT_FOUND 品牌不存在或不是当前供应商的有效品牌
CATEGORY_NOT_FOUND 类目不存在或不是当前供应商的有效类目
CATEGORY_NOT_LEAF 商品类目不是末级类目
SKU_NOT_FOUND 指定规格的 SKU 不存在
SKU_SPECIFICATION_DUPLICATED 同一商品下 SKU 规格重复
PRICE_UPDATE_NOT_ALLOWED 修改商品接口包含价格字段
INVALID_PRICE 市场价或优惠价格式不正确
DISCOUNT_PRICE_GREATER_THAN_MARKET_PRICE 优惠价高于市场价
PRODUCT_REVIEW_IN_PROGRESS 商品正在审核,当前操作不允许执行
PRODUCT_ALREADY_DELETED 商品已经删除
PRODUCT_DELETE_NOT_ALLOWED 当前商品状态不允许删除
OPENAPI_SIGNATURE_INVALID OpenAPI 请求签名错误
OPENAPI_REQUEST_EXPIRED OpenAPI 请求已过期
RATE_LIMIT_EXCEEDED 请求超过限流阈值

15. 字段校验汇总

字段 校验规则
products 批量新增、修改必填,数量为 1 至 500
spuId 批量修改以及价格更新、删除、下架时必填,必须属于当前供应商
name 必填,去除首尾空格后不能为空,最多 200 个字符
productCode 必填,同供应商下唯一,创建后不可修改,最多 100 个字符
brandId 必填,必须为当前供应商的有效品牌
categoryId 必填,必须为当前供应商可用的有效末级类目
deliveryCycle 必填,最多 100 个字符
mainImageUrl 必填,必须为 HTTPS URL,最多 2,000 个字符
casNumber 非必填,最多 100 个字符
description 非必填,最多 50,000 个字符
officialWebsite 非必填,必须为 HTTP 或 HTTPS URL,最多 2,000 个字符
skus 新增时必填且至少一个;单个商品最多 20 个 SKU
specification 必填,同一商品下不能重复,最多 200 个字符
originalSpecification 修改 SKU 规格时必填,必须匹配一个已有 SKU
marketPrice 新增和价格更新时必填,大于或等于 0.01,最多两位小数
discountPrice 新增和价格更新时必填,大于或等于 0.01,最多两位小数,不能高于市场价
作者:向海林  创建时间:2026-08-26 14:23
最后编辑:南山大侠  更新时间:2026-08-26 15:58