作为B2B电商领域的技术开发者,对接1688开放平台获取商品详情是供应链系统、ERP采购模块的常见需求。本文将详细介绍如何使用Java调用1688的
item_get(或alibaba.product.get)接口获取商品全量数据。一、接口概述与前置准备
1.1 接口定位
1688商品详情API是阿里巴巴开放平台提供的核心数据接口,主要用于:
- 基础信息获取:商品ID、标题、类目、品牌、起订量(MOQ)
- 价格体系查询:批发价、阶梯价、建议零售价、分销代发价
- 库存与SKU:多规格组合、实时库存、SKU对应价格
- 多媒体数据:主图URL、详情图列表、商品描述HTML
- 商家资质:供应商名称、诚信通等级、公司信息
1.2 接入前置条件
- 注册开发者账号:访问1688开放平台完成企业实名认证
- 创建应用:在控制台"应用管理"中创建应用,获取
App Key和App Secret - 申请权限:申请
alibaba.product.get或item_get接口调用权限
二、核心技术要点
2.1 签名机制
1688 API采用MD5签名算法,核心步骤如下:
- 将所有请求参数(除
sign外)按参数名ASCII升序排序 - 拼接为
key1value1key2value2...格式(注意:无连接符) - 在字符串首尾各拼接一次
app_secret - 进行MD5加密,结果转大写
2.2 认证流程
接口采用OAuth 2.0认证,调用流程:
- 使用
app_key和app_secret获取access_token - 在业务接口请求中携带
access_token access_token有效期通常为3600秒,需实现缓存刷新机制
三、Java完整实现代码
3.1 项目依赖(Maven)
xml
<dependencies>
<!-- HTTP请求 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.43</version>
</dependency>
<!-- Lombok简化代码(可选) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency></dependencies>3.2 签名工具类
java
import java.security.MessageDigest;import java.util.Map;import java.util.TreeMap;/**
* 1688 API签名工具类
*/public class AlibabaApiSigner {
/**
* 生成MD5签名(1688官方算法)
* @param params 请求参数(不含sign)
* @param appSecret 应用密钥
* @return 大写签名字符串
*/
public static String generateSign(Map<String, String> params, String appSecret) {
// 1. 使用TreeMap按ASCII升序排序
TreeMap<String, String> sortedParams = new TreeMap<>(params);
// 2. 拼接字符串:key+value(无分隔符)
StringBuilder signStr = new StringBuilder();
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// 跳过空值和sign参数本身
if (value != null && !value.isEmpty() && !"sign".equals(key)) {
signStr.append(key).append(value);
}
}
// 3. 首尾拼接appSecret
String finalStr = appSecret + signStr.toString() + appSecret;
// 4. MD5加密并转大写
return md5(finalStr).toUpperCase();
}
/**
* MD5加密
*/
private static String md5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(str.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
sb.append("0");
}
sb.append(hex);
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException("MD5加密失败", e);
}
}}3.3 Token管理器(带缓存)
java
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpPost;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.util.EntityUtils;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.concurrent.TimeUnit;/**
* AccessToken管理器(支持自动刷新)
*/public class AccessTokenManager {
private final String appKey;
private final String appSecret;
private final String tokenUrl = "https://gw.open.1688.com/openapi/http/1/system.oauth2/getToken";
private volatile String accessToken;
private volatile long expireTime; // 过期时间戳(毫秒)
private final long refreshThreshold = 60 * 1000; // 提前60秒刷新
public AccessTokenManager(String appKey, String appSecret) {
this.appKey = appKey;
this.appSecret = appSecret;
}
/**
* 获取有效Token(线程安全)
*/
public synchronized String getValidToken() throws IOException {
long now = System.currentTimeMillis();
// 如果Token有效且未接近过期,直接返回
if (accessToken != null && now < (expireTime - refreshThreshold)) {
return accessToken;
}
// 重新获取Token
return refreshToken();
}
/**
* 刷新Token
*/
private String refreshToken() throws IOException {
Map<String, String> params = new HashMap<>();
params.put("grant_type", "client_credentials");
params.put("client_id", appKey);
params.put("client_secret", appSecret);
CloseableHttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost(tokenUrl + "?" + buildQueryString(params));
try (CloseableHttpResponse response = client.execute(post)) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
JSONObject json = JSON.parseObject(result);
if (json.containsKey("error")) {
throw new RuntimeException("获取Token失败: " + json.getString("error_description"));
}
this.accessToken = json.getString("access_token");
long expiresIn = json.getLongValue("expires_in") * 1000; // 转换为毫秒
this.expireTime = System.currentTimeMillis() + expiresIn;
System.out.println("Token刷新成功,有效期至: " + new java.util.Date(expireTime));
return accessToken;
}
}
private String buildQueryString(Map<String, String> params) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
return sb.toString();
}}3.4 商品详情API客户端
java
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpPost;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.util.EntityUtils;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.HashMap;import java.util.Map;/**
* 1688商品详情API客户端
*/public class Item1688ApiClient {
private final String appKey;
private final String appSecret;
private final AccessTokenManager tokenManager;
// 正式环境地址
private static final String API_URL = "https://gw.open.1688.com/openapi/param2/2/portals.open/api.item.get/APP_KEY";
public Item1688ApiClient(String appKey, String appSecret) {
this.appKey = appKey;
this.appSecret = appSecret;
this.tokenManager = new AccessTokenManager(appKey, appSecret);
}
/**
* 获取商品详情
* @param offerId 1688商品ID
* @param fields 指定返回字段(可选)
* @return 商品详情JSON对象
*/
public JSONObject getItemDetail(String offerId, String fields) throws Exception {
// 1. 获取有效Token
String accessToken = tokenManager.getValidToken();
// 2. 组装公共参数
Map<String, String> params = new HashMap<>();
params.put("app_key", appKey);
params.put("access_token", accessToken);
params.put("method", "portals.open.api.item.get");
params.put("format", "json");
params.put("v", "1.0");
params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
// 3. 组装业务参数
params.put("offerId", offerId);
if (fields != null && !fields.isEmpty()) {
params.put("fields", fields);
}
// 4. 生成签名
String sign = AlibabaApiSigner.generateSign(params, appSecret);
params.put("sign", sign);
// 5. 发送POST请求
String apiUrl = API_URL.replace("APP_KEY", appKey);
String response = sendPostRequest(apiUrl, params);
// 6. 解析响应
JSONObject result = JSON.parseObject(response);
if (result.containsKey("error_response")) {
JSONObject error = result.getJSONObject("error_response");
throw new RuntimeException("API调用失败: " + error.getString("msg")
+ " (错误码: " + error.getString("code") + ")");
}
return result.getJSONObject("item_get_response");
}
/**
* 发送HTTP POST请求
*/
private String sendPostRequest(String url, Map<String, String> params) throws IOException {
CloseableHttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost(url);
// 构建表单参数
StringBuilder formData = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (formData.length() > 0) formData.append("&");
formData.append(entry.getKey()).append("=")
.append(java.net.URLEncoder.encode(entry.getValue(), "UTF-8"));
}
post.setEntity(new StringEntity(formData.toString(), "application/x-www-form-urlencoded", "UTF-8"));
try (CloseableHttpResponse response = client.execute(post)) {
return EntityUtils.toString(response.getEntity(), "UTF-8");
}
}
/**
* 简化调用(返回全量字段)
*/
public JSONObject getItemDetail(String offerId) throws Exception {
return getItemDetail(offerId, null);
}}3.5 使用示例与字段解析
java
/**
* 使用示例:获取1688商品详情
*/public class Item1688Demo {
public static void main(String[] args) {
// 替换为你的应用凭证
String appKey = "your_app_key_here";
String appSecret = "your_app_secret_here";
String offerId = "610947572360"; // 1688商品ID
try {
Item1688ApiClient client = new Item1688ApiClient(appKey, appSecret);
// 指定返回字段(优化性能)
String fields = "offerId,title,price,rprice,minAmount,maxAmount,amount," +
"picUrl,detailUrl,shopName,companyName,specInfo,skuInfo";
JSONObject response = client.getItemDetail(offerId, fields);
JSONObject item = response.getJSONObject("item");
// 解析核心字段
System.out.println("=== 商品基础信息 ===");
System.out.println("商品ID: " + item.getString("offerId"));
System.out.println("商品标题: " + item.getString("title"));
System.out.println("批发价: " + item.getString("price"));
System.out.println("建议零售价: " + item.getString("rprice"));
System.out.println("\n=== 采购信息 ===");
System.out.println("起订量: " + item.getInteger("minAmount"));
System.out.println("最大采购量: " + item.getInteger("maxAmount"));
System.out.println("可售数量: " + item.getInteger("amount"));
System.out.println("\n=== 供应商信息 ===");
System.out.println("店铺名称: " + item.getString("shopName"));
System.out.println("公司名称: " + item.getString("companyName"));
System.out.println("\n=== 多媒体信息 ===");
System.out.println("主图URL: " + item.getString("picUrl"));
System.out.println("详情页链接: " + item.getString("detailUrl"));
// SKU信息解析
if (item.containsKey("skuInfo")) {
System.out.println("\n=== SKU规格信息 ===");
System.out.println(item.getString("skuInfo"));
}
} catch (Exception e) {
System.err.println("获取商品详情失败: " + e.getMessage());
e.printStackTrace();
}
}}四、关键字段映射表
| 字段名 | 说明 | 适用场景 |
|---|---|---|
offerId | 商品唯一标识 | 数据同步主键 |
title | 商品标题 | 展示、搜索 |
price | 当前批发价 | 采购成本计算 |
rprice | 建议零售价 | 分销定价参考 |
priceRange | 阶梯价区间 | B2B批量采购 |
minAmount | 最小起订量(MOQ) | 采购门槛判断 |
amount | 实时可售库存 | 库存同步 |
skuInfo | 规格组合信息 | 多SKU管理 |
picUrl | 主图URL | 图片展示 |
detailUrl | PC详情页链接 | 跳转采购 |
mixAmount | 是否支持混批 | 批发策略 |
shopName | 店铺名称 | 供应商管理 |
companyName | 注册公司名 | 资质审核 |
五、最佳实践与注意事项
5.1 性能优化建议
- Token缓存:务必实现
access_token本地缓存,避免每次请求都重新获取 - 字段裁剪:使用
fields参数按需获取字段,减少数据传输量 - 本地缓存:商品数据建议本地缓存30-60分钟,平衡实时性与API调用频次
- 连接池:生产环境使用HTTP连接池(如Apache HttpClient的PoolManager)
5.2 错误处理策略
java
// 常见错误码处理switch (errorCode) {
case "1001": // 签名错误
// 检查系统时间是否同步,重新生成签名
break;
case "110": // Token过期
// 强制刷新Token并重试
break;
case "403": // 权限不足
// 申请接口权限或检查应用状态
break;
case "500": // 服务器错误
// 指数退避重试
break;}5.3 合规与安全
- 密钥保护:
app_secret严禁前端暴露,仅服务端使用 - HTTPS强制:生产环境必须使用HTTPS协议
- 限流处理:免费版通常5次/秒,专业版50次/秒,需实现限流器
- 数据合规:遵守《1688开放平台服务协议》,不得用于爬虫或数据倒卖
六、总结
通过Java对接1688商品详情API的核心在于签名算法实现和Token生命周期管理。本文提供的代码实现了完整的签名生成、自动刷新Token、字段定制获取等功能,可直接用于供应链系统、ERP采购模块、选品工具等B2B场景的开发。
对于批发特性的业务适配,需特别关注
minAmount(起订量)、阶梯价区间、混批规则等1688特有的字段,这些是与淘宝/天猫API的主要差异点。如遇任何疑问或有进一步的需求,请随时与我私信或者评论联系。