微店是社交电商平台的代表,分销、拼团、社群卖货场景下,获取商品详情数据是做选品分析、比价工具和分销管理的基础。本文基于 2026 年微店开放平台的最新规则,系统讲解 Java 调用微店商品详情接口 的完整链路:体系认知、签名鉴权、完整代码实现与高频避坑。
一、先认知:微店有两套 API 体系
| 对比项 | 微店开放平台(Router 网关) | 微店 Open API(REST 风格) |
|---|---|---|
| 网关 | https://api.weidian.com / openapi.weidian.com/api/router/rest | https://open.weidian.com/api/... |
| 核心接口 | weidian.item.get / weidian.item.detail.get | item.get(REST 资源式) |
| 鉴权方式 | appkey + appsecret + sign 签名 + access_token | OAuth2 Bearer Token |
| 面向 | 开发者应用(分销/选品/数据服务) | 店铺自身工具(ERP、客服系统) 官方详情接口的核心字段包含:商品标题、原价、拼团价、分销佣金比例、库存、店主信息等微店特有数据——这正是微店区别于其他平台的价值点。 建议:做分销选品、比价分析走开放平台 Router 接口;只做自己店铺的商品管理,走 Open API + OAuth2 即可。 |
二、接入准备
- 注册开发者账号:微店开放平台注册,完成个人/企业实名认证。
- 创建应用:获取 AppKey 和 AppSecret(存服务端,严禁外泄)。
- 申请权限:申请「商品详情查询」权限,2026 年起需提交使用场景说明,审核约 1–2 个工作日。
- 店铺授权:店铺级接口需通过 OAuth 2.0 授权获取 access_token(店铺主扫码授权),注意 配置 IP 白名单,否则直接返回 403。
三、签名机制(调用失败的头号原因)
微店开放平台签名规则:
- 收集所有请求参数(公共参数 + 业务参数,不含 sign 本身);
- 按参数名 ASCII 升序 排序;
- 拼接为 key=value&key=value 格式(空值参数跳过);
- 末尾拼接 appsecret,MD5 加密并转大写。
⚠️ 注意:部分 2026 年新接口已升级为 HMAC-SHA256(HmacSHA256 而非 MD5),签名前先以官方文档为准——两种实现本文都给出。
四、Maven 依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.53</version>
</dependency>
五、Java 完整实现
5.1 签名工具类(MD5 / HMAC-SHA256 双实现)
package com.example.weidian.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;
import java.util.TreeMap;
public class WeidianSignUtil {
/**
* 微店标准签名:ASCII 升序 + key=value& 拼接 + 末尾拼 appsecret + MD5 大写
*/
public static String signByMd5(Map<String, String> params, String appSecret) throws Exception {
Map<String, String> sorted = new TreeMap<>(params);
StringBuilder raw = new StringBuilder();
for (Map.Entry<String, String> entry : sorted.entrySet()) {
String value = entry.getValue();
if (value == null || value.isEmpty()) continue;
raw.append(entry.getKey()).append("=").append(value).append("&");
}
// 去掉末尾 &,再拼 appsecret
String signStr = raw.substring(0, raw.length() - 1) + appSecret;
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(signStr.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) sb.append(String.format("%02x", b));
return sb.toString().toUpperCase();
}
/**
* HMAC-SHA256 签名(部分新接口要求)
*/
public static String signByHmacSha256(Map<String, String> params, String appSecret) throws Exception {
Map<String, String> sorted = new TreeMap<>(params);
StringBuilder raw = new StringBuilder();
for (Map.Entry<String, String> entry : sorted.entrySet()) {
String value = entry.getValue();
if (value == null || value.isEmpty()) continue;
raw.append(entry.getKey()).append("=").append(value).append("&");
}
String signStr = raw.substring(0, raw.length() - 1);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] digest = mac.doFinal(signStr.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) sb.append(String.format("%02x", b));
return sb.toString();
}
}
5.2 商品详情 API 客户端
package com.example.weidian.client;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.weidian.util.WeidianSignUtil;
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.util.HashMap;
import java.util.Map;
public class WeidianItemApiClient {
private static final String API_URL = "https://openapi.weidian.com/api/router/rest";
private final String appKey;
private final String appSecret;
private final String accessToken;
private final CloseableHttpClient httpClient = HttpClients.createDefault();
public WeidianItemApiClient(String appKey, String appSecret, String accessToken) {
this.appKey = appKey;
this.appSecret = appSecret;
this.accessToken = accessToken;
}
/**
* 查询微店商品详情
* @param itemId 商品 ID(可从微店商品页 URL / 商品列表接口获取)
*/
public JSONObject getItemDetail(String itemId) throws Exception {
// 1. 公共参数(秒级时间戳)
Map<String, String> params = new HashMap<>();
params.put("method", "weidian.item.get");
params.put("appkey", appKey);
params.put("access_token", accessToken);
params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
params.put("format", "json");
params.put("version", "1.0");
// 2. 业务参数:fields 指定返回字段,减小响应体积
params.put("item_id", itemId);
params.put("fields", "item_id,title,price,final_price,stock,main_img,promotion_info");
// 3. 签名(旧接口 MD5,新接口换 signByHmacSha256)
params.put("sign", WeidianSignUtil.signByMd5(params, appSecret));
// 4. 发送 POST 请求
HttpPost post = new HttpPost(API_URL);
post.setEntity(new StringEntity(buildFormBody(params),
"application/x-www-form-urlencoded"));
try (CloseableHttpResponse response = httpClient.execute(post)) {
String body = EntityUtils.toString(response.getEntity(), "UTF-8");
JSONObject result = JSON.parseObject(body);
// 5. 统一错误处理(微店 errno=0 表示成功)
if (result.getIntValue("errno") != 0) {
throw new RuntimeException(
"调用失败: " + result.getString("errmsg")
+ " (errno=" + result.getIntValue("errno") + ")");
}
return result.getJSONObject("data");
}
}
private String buildFormBody(Map<String, String> params) {
StringBuilder sb = new StringBuilder();
params.forEach((k, v) -> sb.append(k).append("=").append(urlEncode(v)).append("&"));
return sb.substring(0, sb.length() - 1);
}
private String urlEncode(String value) {
try {
return java.net.URLEncoder.encode(value, "UTF-8");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
WeidianItemApiClient client = new WeidianItemApiClient(
"你的AppKey", "你的AppSecret", "你的AccessToken");
JSONObject item = client.getItemDetail("12345678");
// 注意:微店价格单位为“分”,需除以 100 转为元
System.out.println("商品标题: " + item.getString("title"));
System.out.println("原价: " + item.getLongValue("price") / 100.0 + " 元");
System.out.println("到手价: " + item.getLongValue("final_price") / 100.0 + " 元");
System.out.println("库存: " + item.getIntValue("stock"));
System.out.println("主图: " + item.getString("main_img"));
}
}
5.3 响应示例
JSON
{
"errno": 0,
"errmsg": "success",
"data": {
"item_id": "12345678",
"title": "旗舰智能手机 8GB+256GB",
"price": 299900,
"final_price": 279900,
"stock": 150,
"main_img": "https://...",
"promotion_info": {"desc": "满2000减200"}
}
}
六、调用频率限制与错误码
- 限流:默认 QPS = 5 次/秒;部分 REST 接口限制为 100 次/分钟,超限返回 429,建议加 1 秒/次的请求间隔。
- 常见错误:errno ≠ 0:业务错误,查看 errmsg(权限不足、商品不存在/已下架等);403:IP 白名单未配置或 access_token 无效;429:触发限流,退避重试。
七、高频避坑指南
- 价格单位是「分」:price、final_price、优惠券金额全部以分返回,展示前务必 ÷100。
- 时间戳是秒级(与京东的毫秒级不同),误差过大会导致签名/授权失败。
- AccessToken 有效期短:店铺授权令牌约 2–7 小时/天不等,必须实现本地缓存 + 自动刷新机制,不要每次调用都重新授权。
- 空值参数跳过签名:空字符串参与拼接会导致签名不一致,这是最常见的签名失败原因。
- 合规红线:拒绝爬虫抓 HTML 方案,易触发 IP 封禁与账号风控;走官方接口才能稳定获取拼团价、分销佣金等核心字段。
- 店铺级数据需店铺授权:access_token 与店铺一一绑定,查别人的店铺商品必须先拿到对应店铺的授权。
八、总结
Java 接入微店商品详情接口的核心链路:申请密钥与权限 → OAuth 店铺授权拿 access_token → 排序拼接 + MD5/HMAC-SHA256 签名 → POST 到 Router 网关 → 分转元、字段清洗。相比京东,微店的特色在于拼团价和分销佣金数据,签名算法也更轻量(MD5 为主)。把 token 自动刷新和限流退避做好,一个稳定的微店商品数据服务就上线了。
如遇任何疑问或有进一步的需求,请随时与我私信或者评论联系。

