微店作为国内知名的移动电商平台,为开发者提供了丰富的 API 接口,其中 micro.item_get 接口用于获取商品详情数据。本文将详细介绍如何使用 Java 调用该接口,包括环境准备、认证授权、签名生成、接口调用及数据解析等完整流程。
一、接口概述
micro.item_get 是微店开放平台提供的核心接口,用于获取指定商品的详细信息,包括:
- 商品基本信息(ID、标题、价格、库存)
- 商品图片与描述
- 商品规格与属性
- 销量与评价数据
- 该接口采用 HTTP GET/POST 请求方式,返回 JSON 格式数据。
二、准备工作
1. 注册开发者账号
在调用接口前,需前往微店开放平台注册开发者账号,创建应用并获取以下凭证:
| 凭证 | 说明 |
|---|---|
app_key | 应用标识 |
app_secret | 应用密钥,用于签名 |
access_token | 访问令牌,用于身份验证 |
2. 项目依赖
使用 Maven 管理依赖,在 pom.xml 中添加:
<dependencies>
<!-- HTTP 请求 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<!-- JSON 解析 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>
三、完整代码实现
1. 签名工具类
微店 API 要求对请求参数进行签名验证,以下是基于 MD5 的签名实现:
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
public class WeidianSignUtil {
/**
* 生成微店 API 签名
* 签名规则:将参数按 key 升序排列,拼接成 key1=value1&key2=value2 格式,末尾追加 app_secret,最后进行 MD5 加密
*/
public static String generateSign(Map<String, String> params, String appSecret) {
// 1. 过滤空值,按 key 升序排列
String sortedParams = params.entrySet().stream()
.filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty())
.sorted(Map.Entry.comparingByKey())
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
// 2. 拼接 app_secret
String signStr = sortedParams + appSecret;
// 3. MD5 加密
return md5(signStr).toLowerCase();
}
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 algorithm not found", e);
}
}
}
2. Access Token 获取
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.util.ArrayList;
import java.util.List;
public class WeidianAuthService {
private static final String AUTH_URL = "https://open.weidian.com/api/oauth2/token";
/**
* 使用 client_credentials 模式获取 Access Token
*/
public static String getAccessToken(String clientId, String clientSecret) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost post = new HttpPost(AUTH_URL);
List<BasicNameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("grant_type", "client_credentials"));
params.add(new BasicNameValuePair("client_id", clientId));
params.add(new BasicNameValuePair("client_secret", clientSecret));
post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
String response = EntityUtils.toString(httpClient.execute(post).getEntity(), "UTF-8");
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
if (json.has("access_token")) {
return json.get("access_token").getAsString();
} else {
throw new RuntimeException("获取 Access Token 失败: " + response);
}
} catch (Exception e) {
throw new RuntimeException("获取 Access Token 异常", e);
}
}
}
3. 商品详情服务类
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.HashMap;
import java.util.Map;
public class WeidianItemService {
private static final String API_BASE_URL = "https://api.weidian.com/v1";
private final String appKey;
private final String appSecret;
private final Gson gson = new Gson();
public WeidianItemService(String appKey, String appSecret) {
this.appKey = appKey;
this.appSecret = appSecret;
}
/**
* 获取商品详情
* @param accessToken 访问令牌
* @param itemId 商品ID
* @return 商品详情对象
*/
public ItemDetail getItemDetail(String accessToken, String itemId) {
// 构建请求参数
Map<String, String> params = new HashMap<>();
params.put("app_key", appKey);
params.put("access_token", accessToken);
params.put("item_id", itemId);
params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
// 生成签名
String sign = WeidianSignUtil.generateSign(params, appSecret);
params.put("sign", sign);
// 构建 URL
StringBuilder urlBuilder = new StringBuilder(API_BASE_URL + "/items/details?");
params.forEach((k, v) -> urlBuilder.append(k).append("=").append(v).append("&"));
String url = urlBuilder.substring(0, urlBuilder.length() - 1);
// 发送请求
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
httpGet.setHeader("Accept", "application/json");
String response = EntityUtils.toString(httpClient.execute(httpGet).getEntity(), "UTF-8");
JsonObject jsonResponse = JsonParser.parseString(response).getAsJsonObject();
// 解析响应
return parseItemDetail(jsonResponse);
} catch (Exception e) {
throw new RuntimeException("获取商品详情失败", e);
}
}
/**
* 解析商品详情 JSON 响应
*/
private ItemDetail parseItemDetail(JsonObject response) {
if (!response.has("data") || response.get("data").isJsonNull()) {
throw new RuntimeException("接口返回数据异常: " + response);
}
JsonObject data = response.getAsJsonObject("data");
ItemDetail item = new ItemDetail();
item.setItemId(data.has("item_id") ? data.get("item_id").getAsString() : "");
item.setName(data.has("item_name") ? data.get("item_name").getAsString() : "");
item.setPrice(data.has("price") ? data.get("price").getAsDouble() : 0.0);
item.setOriginalPrice(data.has("original_price") ? data.get("original_price").getAsDouble() : 0.0);
item.setStock(data.has("stock") ? data.get("stock").getAsInt() : 0);
item.setSoldCount(data.has("sold_count") ? data.get("sold_count").getAsInt() : 0);
item.setDescription(data.has("description") ? data.get("description").getAsString() : "");
item.setMainImage(data.has("main_image") ? data.get("main_image").getAsString() : "");
// 解析图片列表
if (data.has("images") && data.get("images").isJsonArray()) {
List<String> images = new ArrayList<>();
data.getAsJsonArray("images").forEach(img -> images.add(img.getAsString()));
item.setImages(images);
}
return item;
}
}
4. 商品详情实体类
import java.util.List;
public class ItemDetail {
private String itemId; // 商品ID
private String name; // 商品名称
private Double price; // 当前售价
private Double originalPrice; // 原价
private Integer stock; // 库存
private Integer soldCount; // 销量
private String description; // 商品描述
private String mainImage; // 主图
private List<String> images; // 图片列表
private List<Sku> skus; // SKU 列表
// 构造方法、Getter 和 Setter
public ItemDetail() {}
// Getters and Setters
public String getItemId() { return itemId; }
public void setItemId(String itemId) { this.itemId = itemId; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
public Double getOriginalPrice() { return originalPrice; }
public void setOriginalPrice(Double originalPrice) { this.originalPrice = originalPrice; }
public Integer getStock() { return stock; }
public void setStock(Integer stock) { this.stock = stock; }
public Integer getSoldCount() { return soldCount; }
public void setSoldCount(Integer soldCount) { this.soldCount = soldCount; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getMainImage() { return mainImage; }
public void setMainImage(String mainImage) { this.mainImage = mainImage; }
public List<String> getImages() { return images; }
public void setImages(List<String> images) { this.images = images; }
public List<Sku> getSkus() { return skus; }
public void setSkus(List<Sku> skus) { this.skus = skus; }
@Override
public String toString() {
return "ItemDetail{" +
"itemId='" + itemId + '\'' +
", name='" + name + '\'' +
", price=" + price +
", stock=" + stock +
", soldCount=" + soldCount +
'}';
}
// SKU 内部类
public static class Sku {
private String skuId;
private String properties;
private Double price;
private Integer stock;
// Getters and Setters...
}
}
5. 主程序入口
public class WeidianItemDemo {
public static void main(String[] args) {
// 配置信息(请替换为实际值)
String appKey = "your_app_key";
String appSecret = "your_app_secret";
String clientId = "your_client_id";
String clientSecret = "your_client_secret";
String itemId = "your_item_id";
try {
// 1. 获取 Access Token
System.out.println("正在获取 Access Token...");
String accessToken = WeidianAuthService.getAccessToken(clientId, clientSecret);
System.out.println("Access Token 获取成功: " + accessToken.substring(0, 10) + "...");
// 2. 调用商品详情接口
System.out.println("正在获取商品详情...");
WeidianItemService itemService = new WeidianItemService(appKey, appSecret);
ItemDetail item = itemService.getItemDetail(accessToken, itemId);
// 3. 输出结果
System.out.println("\n========== 商品详情 ==========");
System.out.println("商品ID: " + item.getItemId());
System.out.println("商品名称: " + item.getName());
System.out.println("当前售价: ¥" + item.getPrice());
System.out.println("原价: ¥" + item.getOriginalPrice());
System.out.println("库存: " + item.getStock());
System.out.println("销量: " + item.getSoldCount());
System.out.println("商品描述: " + item.getDescription());
System.out.println("主图: " + item.getMainImage());
if (item.getImages() != null) {
System.out.println("图片数量: " + item.getImages().size());
}
} catch (Exception e) {
System.err.println("程序执行异常: " + e.getMessage());
e.printStackTrace();
}
}
}
四、接口返回数据示例
调用成功后,接口返回的 JSON 数据结构如下:
{
"status": {
"status_code": 0,
"status_reason": "success"
},
"data": {
"item_id": "123456789",
"item_name": "示例商品标题",
"price": 99.00,
"original_price": 199.00,
"stock": 100,
"sold_count": 500,
"description": "<p>商品详细描述...</p>",
"main_image": "https://img.weidian.com/xxx.jpg",
"images": [
"https://img.weidian.com/xxx1.jpg",
"https://img.weidian.com/xxx2.jpg"
],
"skus": [
{
"sku_id": "sku_001",
"properties": "颜色:红色;尺码:M",
"price": 99.00,
"stock": 30
}
]
}
}
五、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
403 权限不足 | 未申请接口权限或凭证错误 | 检查 app_key 和 access_token 是否正确,确认已申请 micro.item_get 接口权限 |
签名错误 | 签名算法或参数排序有误 | 确认参数按 key 升序排列,签名字符串末尾正确追加 app_secret |
429 请求频繁 | 超出接口调用频率限制 | 增加请求间隔,使用限流策略,或申请提升调用配额 |
数据字段为空 | 商品信息未设置或权限未开启 | 检查商品是否已上架,确认店铺开启了商品信息开放权限 |
access_token 过期 | Token 有效期通常为 7200 秒 | 实现 Token 自动刷新机制,缓存 Token 并在过期前重新获取 |
六、进阶优化建议
1. Token 自动刷新与缓存
public class TokenManager {
private String accessToken;
private long expireTime;
public synchronized String getValidToken(String clientId, String clientSecret) {
if (accessToken == null || System.currentTimeMillis() > expireTime - 60000) {
// 提前 1 分钟刷新
accessToken = WeidianAuthService.getAccessToken(clientId, clientSecret);
expireTime = System.currentTimeMillis() + 7200 * 1000;
}
return accessToken;
}
}
2. 连接池配置
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
public class HttpClientPool {
private static final CloseableHttpClient httpClient;
static {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200); // 最大连接数
cm.setDefaultMaxPerRoute(50); // 每个路由最大连接数
httpClient = HttpClients.custom()
.setConnectionManager(cm)
.setConnectionManagerShared(true)
.build();
}
public static CloseableHttpClient getClient() {
return httpClient;
}
}
3. 批量获取与异步处理
对于需要批量获取商品详情的场景,建议使用线程池配合 CompletableFuture 实现异步并行调用,提升整体效率。
七、总结
本文完整介绍了使用 Java 调用微店 micro.item_get 商品详情接口的流程,包括:
- 认证授权:通过 OAuth2 获取 access_token
- 签名机制:按规范生成 MD5 签名确保请求安全
- 接口调用:使用 Apache HttpClient 发送 HTTP 请求
- 数据解析:使用 Gson 解析 JSON 响应并映射为 Java 对象
- 异常处理:针对常见错误提供解决方案
- 在实际开发中,建议根据业务需求进一步完善异常处理、日志记录和性能优化。同时,务必遵守微店开放平台的使用协议,合理控制调用频率,避免对平台造成过大压力。
如遇任何疑问或有进一步的需求,请随时与我私信或者评论联系。

