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 签名算法
平台分配 appId 和 appSecret。appSecret 仅用于本地生成签名,不得放入请求 Header、请求体或日志中。
签名生成步骤如下:
- 获取当前 Unix 毫秒时间戳
timestamp,其值必须与X-Openapi-Timestamp完全一致。 - 按照
appId + timestamp + appSecret的顺序直接拼接签名原文,中间不添加分隔符、空格或换行。 - 使用 UTF-8 编码签名原文,并计算 MD5 摘要。
- 将摘要转换为 32 位大写十六进制字符串,作为
X-Openapi-Sign的值。
计算公式:
X-Openapi-Sign = UPPERCASE(MD5_UTF8(appId + timestamp + appSecret))
当前签名只包含 appId、timestamp 和 appSecret,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 金额规则
marketPrice和discountPrice使用 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必须属于当前供应商,不能通过修改接口变更。- 商品货号不允许通过修改接口变更。
- 请求中不得出现
marketPrice或discountPrice;价格必须通过价格更新接口修改。 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 15:58