一、1688开放平台概述
1.1 平台特点
表格
| 特性 | 说明 |
|---|
| 平台定位 | B2B批发采购平台 |
| API体系 | 阿里开放平台统一架构(TOP协议) |
| 认证方式 | OAuth 2.0 + AppKey签名 |
| 数据范围 | 商品、订单、物流、会员、店铺等 |
| 调用限制 | 应用级别流量控制,需申请权限包 |
1.2 核心API分类
┌─────────────────────────────────────────────────────────┐
│ 1688 API体系 │
├─────────────────────────────────────────────────────────┤
│ 商品API │ 1688.product.get / 1688.product.search │
│ 订单API │ 1688.trade.get / 1688.trade.list │
│ 物流API │ 1688.logistics.trace.get │
│ 会员API │ 1688.member.get / 1688.member.basicInfo │
│ 店铺API │ 1688.shop.get / 1688.shop.category.get │
│ 分销API │ 1688.distribution.product.get │
└─────────────────────────────────────────────────────────┘
二、环境准备与SDK集成
2.1 Maven依赖配置
<dependencies>
<dependency>
<groupId>com.taobao.api</groupId>
<artifactId>taobao-sdk-java-auto</artifactId>
<version>20230824</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
</dependencies>
2.2 配置文件
alibaba:
openplatform:
app-key: ${ALIBABA_APP_KEY}
app-secret: ${ALIBABA_APP_SECRET}
server-url: https://gw.open.1688.com/openapi
sandbox-url: https://gw.api.tbsandbox.com/router/rest
connect-timeout: 10000
read-timeout: 30000
max-retries: 3
retry-interval: 1000
三、核心客户端实现
3.1 1688 API客户端封装
package com.alibaba1688.api;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class Alibaba1688Client {
private static final Logger log = LoggerFactory.getLogger(Alibaba1688Client.class);
private static final String DEFAULT_SERVER_URL = "https://gw.open.1688.com/openapi";
private static final String SIGN_METHOD_HMAC = "hmac-sha256";
private static final String API_VERSION = "1";
private final String appKey;
private final String appSecret;
private final String serverUrl;
private final OkHttpClient httpClient;
public Alibaba1688Client(String appKey, String appSecret) {
this(appKey, appSecret, DEFAULT_SERVER_URL);
}
public Alibaba1688Client(String appKey, String appSecret, String serverUrl) {
this.appKey = appKey;
this.appSecret = appSecret;
this.serverUrl = serverUrl;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(20, 5, TimeUnit.MINUTES))
.addInterceptor(new LoggingInterceptor())
.addInterceptor(new RetryInterceptor(3, 1000))
.build();
}
public ApiResponse execute(String apiName, Map<String, String> params, String accessToken)
throws ApiException {
Map<String, String> allParams = new TreeMap<>();
allParams.put("method", apiName);
allParams.put("app_key", appKey);
allParams.put("timestamp", LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
allParams.put("v", API_VERSION);
allParams.put("sign_method", SIGN_METHOD_HMAC);
allParams.put("format", "json");
if (params != null) {
allParams.putAll(params);
}
if (accessToken != null) {
allParams.put("access_token", accessToken);
}
String sign = generateHmacSign(allParams, appSecret);
allParams.put("sign", sign);
FormBody.Builder formBuilder = new FormBody.Builder();
allParams.forEach((k, v) -> {
if (v != null) {
formBuilder.add(k, v);
}
});
Request request = new Request.Builder()
.url(serverUrl + "/param2/1/" + apiName + "/" + appKey)
.post(formBuilder.build())
.header("User-Agent", "Alibaba1688-Java-SDK/1.0")
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new ApiException("HTTP_ERROR", "HTTP " + response.code());
}
String body = response.body().string();
log.debug("API响应: {}", body);
JSONObject json = JSON.parseObject(body);
if (json.containsKey("error_response")) {
JSONObject error = json.getJSONObject("error_response");
throw new ApiException(
error.getString("code"),
error.getString("msg"),
error.getString("sub_msg")
);
}
return new ApiResponse(json, apiName);
} catch (IOException e) {
throw new ApiException("NETWORK_ERROR", "请求失败: " + e.getMessage(), e);
}
}
private String generateHmacSign(Map<String, String> params, String secret) {
try {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (value != null && !value.isEmpty()) {
sb.append(key).append(value);
}
}
String stringToSign = sb.toString();
log.debug("待签名字符串: {}", stringToSign);
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(
(secret + "&").getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
mac.init(keySpec);
byte[] bytes = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return bytesToHex(bytes).toUpperCase();
} catch (Exception e) {
throw new RuntimeException("签名生成失败", e);
}
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public ProductDetail getProduct(Long productId, String accessToken) throws ApiException {
Map<String, String> params = new HashMap<>();
params.put("productID", String.valueOf(productId));
params.put("webSite", "1688");
ApiResponse response = execute("1688.product.get", params, accessToken);
return response.parse(ProductDetail.class, "productInfo");
}
public ProductSearchResult searchProducts(String keyword, int page, int pageSize,
String accessToken) throws ApiException {
Map<String, String> params = new HashMap<>();
params.put("q", keyword);
params.put("page", String.valueOf(page));
params.put("pageSize", String.valueOf(pageSize));
params.put("webSite", "1688");
ApiResponse response = execute("1688.product.search", params, accessToken);
return response.parse(ProductSearchResult.class, "result");
}
public TradeDetail getTrade(Long orderId, String accessToken) throws ApiException {
Map<String, String> params = new HashMap<>();
params.put("orderId", String.valueOf(orderId));
params.put("webSite", "1688");
params.put("includeFields", "orderBaseInfo,orderEntryInfo");
ApiResponse response = execute("1688.trade.get", params, accessToken);
return response.parse(TradeDetail.class, "orderInfo");
}
public void close() {
httpClient.dispatcher().executorService().shutdown();
httpClient.connectionPool().evictAll();
}
}
3.2 数据模型定义
package com.alibaba1688.api.model;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class ProductDetail {
@JSONField(name = "productID")
private Long productId;
@JSONField(name = "productTitle")
private String title;
@JSONField(name = "productPrice")
private PriceInfo price;
@JSONField(name = "productImage")
private ImageInfo image;
@JSONField(name = "productSKU")
private List<SkuInfo> skus;
@JSONField(name = "saleInfo")
private SaleInfo saleInfo;
@JSONField(name = "deliveryInfo")
private DeliveryInfo deliveryInfo;
@Data
public static class PriceInfo {
@JSONField(name = "price")
private BigDecimal price;
@JSONField(name = "priceRange")
private List<PriceRange> ranges;
}
@Data
public static class PriceRange {
@JSONField(name = "startQuantity")
private Integer startQuantity;
@JSONField(name = "price")
private BigDecimal price;
}
@Data
public static class SkuInfo {
@JSONField(name = "skuId")
private Long skuId;
@JSONField(name = "specId")
private String specId;
@JSONField(name = "price")
private BigDecimal price;
@JSONField(name = "amountOnSale")
private Integer stock;
@JSONField(name = "attributes")
private List<Attribute> attributes;
}
@Data
public static class Attribute {
@JSONField(name = "attributeName")
private String name;
@JSONField(name = "attributeValue")
private String value;
}
}
@Data
public class TradeDetail {
@JSONField(name = "orderId")
private Long orderId;
@JSONField(name = "buyerID")
private String buyerId;
@JSONField(name = "sellerID")
private String sellerId;
@JSONField(name = "orderStatus")
private String status;
@JSONField(name = "totalAmount")
private BigDecimal totalAmount;
@JSONField(name = "createTime")
private LocalDateTime createTime;
@JSONField(name = "payTime")
private LocalDateTime payTime;
@JSONField(name = "orderEntryList")
private List<OrderEntry> entries;
@Data
public static class OrderEntry {
@JSONField(name = "productID")
private Long productId;
@JSONField(name = "skuID")
private Long skuId;
@JSONField(name = "quantity")
private Integer quantity;
@JSONField(name = "price")
private BigDecimal price;
}
}
四、完整测试套件
4.1 基础功能测试
package com.alibaba1688.api.test;
import com.alibaba1688.api.Alibaba1688Client;
import com.alibaba1688.api.ApiException;
import com.alibaba1688.api.model.ProductDetail;
import com.alibaba1688.api.model.TradeDetail;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.*;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class Alibaba1688ApiTest {
private static final String APP_KEY = System.getenv("ALIBABA_APP_KEY");
private static final String APP_SECRET = System.getenv("ALIBABA_APP_SECRET");
private static final String ACCESS_TOKEN = System.getenv("ALIBABA_ACCESS_TOKEN");
private Alibaba1688Client client;
@BeforeAll
void setUp() {
Assumptions.assumeTrue(APP_KEY != null, "未配置ALIBABA_APP_KEY环境变量");
client = new Alibaba1688Client(APP_KEY, APP_SECRET);
}
@AfterAll
void tearDown() {
if (client != null) client.close();
}
@Test
@DisplayName("测试获取商品详情 - 基础信息")
void testGetProductBasicInfo() {
Long testProductId = 123456789L;
assertThatNoException().isThrownBy(() -> {
ProductDetail product = client.getProduct(testProductId, ACCESS_TOKEN);
assertThat(product).isNotNull();
assertThat(product.getProductId()).isEqualTo(testProductId);
assertThat(product.getTitle()).isNotEmpty();
assertThat(product.getPrice()).isNotNull();
System.out.println("商品标题: " + product.getTitle());
System.out.println("商品价格: " + product.getPrice().getPrice());
});
}
@Test
@DisplayName("测试获取商品详情 - SKU信息完整性")
void testGetProductSkuInfo() {
Long skuProductId = 987654321L;
ProductDetail product = client.getProduct(skuProductId, ACCESS_TOKEN);
assertThat(product.getSkus()).isNotEmpty();
for (ProductDetail.SkuInfo sku : product.getSkus()) {
assertThat(sku.getSkuId()).isNotNull();
assertThat(sku.getPrice()).isGreaterThan(BigDecimal.ZERO);
assertThat(sku.getStock()).isGreaterThanOrEqualTo(0);
}
}
@Test
@DisplayName("测试获取商品详情 - 阶梯价格")
void testGetProductPriceRange() {
ProductDetail product = client.getProduct(111111111L, ACCESS_TOKEN);
assertThat(product.getPrice().getRanges()).isNotEmpty();
List<ProductDetail.PriceRange> ranges = product.getPrice().getRanges();
for (int i = 1; i < ranges.size(); i++) {
assertThat(ranges.get(i).getStartQuantity())
.isGreaterThan(ranges.get(i-1).getStartQuantity());
}
}
@ParameterizedTest
@CsvSource({
"手机配件, 1, 20",
"工业设备, 1, 10",
"服装批发, 2, 50"
})
@DisplayName("测试商品搜索 - 多关键词")
void testSearchProducts(String keyword, int page, int pageSize) {
var result = client.searchProducts(keyword, page, pageSize, ACCESS_TOKEN);
assertThat(result.getTotal()).isGreaterThanOrEqualTo(0);
assertThat(result.getProducts()).hasSizeLessThanOrEqualTo(pageSize);
System.out.printf("关键词[%s]找到%d个商品%n", keyword, result.getTotal());
}
@Test
@DisplayName("测试获取订单详情")
void testGetTradeDetail() {
Assumptions.assumeTrue(ACCESS_TOKEN != null, "需要ACCESS_TOKEN");
Long testOrderId = 555666777L;
TradeDetail trade = client.getTrade(testOrderId, ACCESS_TOKEN);
assertThat(trade.getOrderId()).isEqualTo(testOrderId);
assertThat(trade.getStatus()).isIn(
"waitbuyerpay", "waitsellersend", "waitbuyerreceive",
"success", "cancel", "terminated"
);
assertThat(trade.getTotalAmount()).isGreaterThan(BigDecimal.ZERO);
}
@Test
@DisplayName("测试无效商品ID - 应抛出异常")
void testInvalidProductId() {
Long invalidId = 999999999999L;
ApiException exception = catchThrowableOfType(
() -> client.getProduct(invalidId, ACCESS_TOKEN),
ApiException.class
);
assertThat(exception.getErrorCode())
.containsAnyOf("isv.product-not-exist", "PRODUCT_NOT_FOUND");
}
@Test
@DisplayName("测试无权限访问 - 应抛出异常")
void testNoPermission() {
String invalidToken = "invalid_token_123";
assertThatThrownBy(() ->
client.getProduct(123456L, invalidToken)
).isInstanceOf(ApiException.class)
.satisfies(e -> {
ApiException ae = (ApiException) e;
assertThat(ae.getErrorCode()).contains("TOKEN");
});
}
@Test
@DisplayName("测试参数缺失 - 应抛出异常")
void testMissingRequiredParam() {
assertThatThrownBy(() ->
client.execute("1688.product.get", new HashMap<>(), ACCESS_TOKEN)
).isInstanceOf(ApiException.class);
}
}
4.2 性能与并发测试
package com.alibaba1688.api.test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class Alibaba1688PerformanceTest {
private static final int WARM_UP_ITERATIONS = 10;
private static final int TEST_ITERATIONS = 100;
@Test
@DisplayName("单接口基准测试 - P99延迟")
void testSingleApiLatency() {
Alibaba1688Client client = createClient();
Long testId = 123456789L;
for (int i = 0; i < WARM_UP_ITERATIONS; i++) {
client.getProduct(testId, ACCESS_TOKEN);
}
long[] latencies = new long[TEST_ITERATIONS];
for (int i = 0; i < TEST_ITERATIONS; i++) {
long start = System.nanoTime();
client.getProduct(testId, ACCESS_TOKEN);
long end = System.nanoTime();
latencies[i] = (end - start) / 1_000_000;
}
java.util.Arrays.sort(latencies);
long p50 = latencies[TEST_ITERATIONS / 2];
long p95 = latencies[(int)(TEST_ITERATIONS * 0.95)];
long p99 = latencies[(int)(TEST_ITERATIONS * 0.99)];
double avg = java.util.Arrays.stream(latencies).average().orElse(0);
System.out.println("=== 延迟统计 (ms) ===");
System.out.printf("平均: %.2f%n", avg);
System.out.printf("P50: %d%n", p50);
System.out.printf("P95: %d%n", p95);
System.out.printf("P99: %d%n", p99);
assertThat(p99).isLessThan(1000);
}
@Test
@DisplayName("并发测试 - 限流行为验证")
void testConcurrentRateLimiting() throws InterruptedException {
Alibaba1688Client client = createClient();
int threadCount = 5;
int requestsPerThread = 20;
Long testId = 123456789L;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
AtomicInteger successCount = new AtomicInteger(0);
AtomicInteger rateLimitCount = new AtomicInteger(0);
AtomicInteger errorCount = new AtomicInteger(0);
AtomicLong totalLatency = new AtomicLong(0);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
try {
for (int j = 0; j < requestsPerThread; j++) {
long start = System.currentTimeMillis();
try {
client.getProduct(testId, ACCESS_TOKEN);
successCount.incrementAndGet();
} catch (ApiException e) {
if (e.getErrorCode().contains("LIMIT") ||
e.getMessage().contains("限流")) {
rateLimitCount.incrementAndGet();
} else {
errorCount.incrementAndGet();
}
} finally {
totalLatency.addAndGet(System.currentTimeMillis() - start);
}
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
});
}
latch.await(2, TimeUnit.MINUTES);
executor.shutdown();
int total = threadCount * requestsPerThread;
System.out.println("=== 并发测试结果 ===");
System.out.printf("总请求: %d%n", total);
System.out.printf("成功: %d (%.1f%%)%n", successCount.get(),
100.0 * successCount.get() / total);
System.out.printf("限流: %d (%.1f%%)%n", rateLimitCount.get(),
100.0 * rateLimitCount.get() / total);
System.out.printf("错误: %d%n", errorCount.get());
System.out.printf("平均延迟: %d ms%n", totalLatency.get() / total);
assertThat(successCount.get()).isGreaterThan((int)(total * 0.9));
}
@Test
@DisplayName("压力测试 - 阶梯加压")
void testStepLoad() throws InterruptedException {
int[] vusLevels = {1, 5, 10, 20};
int durationPerLevel = 30;
for (int vus : vusLevels) {
System.out.printf("%n=== 加压至 %d 并发 ===%n", vus);
ExecutorService executor = Executors.newFixedThreadPool(vus);
CountDownLatch levelLatch = new CountDownLatch(vus);
AtomicInteger levelRequests = new AtomicInteger(0);
AtomicInteger levelErrors = new AtomicInteger(0);
long levelStart = System.currentTimeMillis();
for (int i = 0; i < vus; i++) {
executor.submit(() -> {
while (System.currentTimeMillis() - levelStart < durationPerLevel * 1000) {
try {
client.getProduct(123456L, ACCESS_TOKEN);
levelRequests.incrementAndGet();
Thread.sleep(200);
} catch (Exception e) {
levelErrors.incrementAndGet();
}
}
levelLatch.countDown();
});
}
levelLatch.await();
executor.shutdown();
long actualDuration = (System.currentTimeMillis() - levelStart) / 1000;
double rps = (double) levelRequests.get() / actualDuration;
System.out.printf("持续时间: %d秒, 总请求: %d, 错误: %d, RPS: %.1f%n",
actualDuration, levelRequests.get(), levelErrors.get(), rps);
}
}
}
4.3 数据一致性测试
package com.alibaba1688.api.test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Alibaba1688ConsistencyTest {
@Test
@DisplayName("测试库存数据一致性 - 并发读取")
void testStockConsistency() throws InterruptedException {
Long testProductId = 555666777L;
int concurrentReaders = 10;
int readsPerThread = 10;
ExecutorService executor = Executors.newFixedThreadPool(concurrentReaders);
List<ProductDetail> results = new CopyOnWriteArrayList<>();
for (int i = 0; i < concurrentReaders; i++) {
executor.submit(() -> {
for (int j = 0; j < readsPerThread; j++) {
ProductDetail product = client.getProduct(testProductId, ACCESS_TOKEN);
results.add(product);
assertThat(product.getProductId()).isEqualTo(testProductId);
assertThat(product.getSkus()).isNotNull();
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
for (ProductDetail product : results) {
for (ProductDetail.SkuInfo sku : product.getSkus()) {
assertThat(sku.getSkuId()).isNotNull();
}
}
System.out.println("总读取次数: " + results.size());
System.out.println("数据一致性验证通过");
}
@Test
@DisplayName("测试分页数据一致性")
void testPaginationConsistency() {
String keyword = "电子元器件";
int totalPages = 3;
List<Long> allProductIds = new ArrayList<>();
for (int page = 1; page <= totalPages; page++) {
var result = client.searchProducts(keyword, page, 20, ACCESS_TOKEN);
for (ProductSummary summary : result.getProducts()) {
assertThat(allProductIds)
.withFailMessage("第%d页发现重复商品ID: %d", page, summary.getProductId())
.doesNotContain(summary.getProductId());
allProductIds.add(summary.getProductId());
}
}
System.out.printf("共读取%d页,获取%d个唯一商品%n", totalPages, allProductIds.size());
}
}
五、Mock测试方案
package com.alibaba1688.api.test;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.junit.jupiter.api.*;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.assertj.core.api.Assertions.*;
public class Alibaba1688MockTest {
private static WireMockServer wireMockServer;
private Alibaba1688Client client;
@BeforeAll
static void startServer() {
wireMockServer = new WireMockServer(9999);
wireMockServer.start();
WireMock.configureFor("localhost", 9999);
}
@BeforeEach
void setUp() {
stubFor(post(urlPathMatching("/openapi/param2/1/1688\.product\.get/.*"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("""
{
"productInfo": {
"productID": 123456,
"productTitle": "Mock工业设备",
"productPrice": {
"price": 999.99,
"priceRange": [
{"startQuantity": 1, "price": 999.99},
{"startQuantity": 100, "price": 899.99}
]
},
"productSKU": [
{
"skuId": 111,
"specId": "color:red",
"price": 999.99,
"amountOnSale": 1000
}
]
}
}
""")));
stubFor(post(urlPathMatching("/openapi/param2/1/1688\.trade\.get/.*"))
.willReturn(aResponse()
.withStatus(200)
.withBody("""
{
"error_response": {
"code": "LIMIT_REQUEST",
"msg": "请求过于频繁",
"sub_msg": "请稍后再试"
}
}
""")));
client = new Alibaba1688Client("mock_key", "mock_secret",
"http://localhost:9999/openapi");
}
@Test
@DisplayName("Mock测试 - 正常商品详情")
void testMockProductDetail() {
ProductDetail product = client.getProduct(123456L, "mock_token");
assertThat(product.getTitle()).isEqualTo("Mock工业设备");
assertThat(product.getPrice().getPrice()).isEqualByComparingTo(new BigDecimal("999.99"));
assertThat(product.getSkus()).hasSize(1);
}
@Test
@DisplayName("Mock测试 - 限流异常处理")
void testMockRateLimit() {
ApiException exception = catchThrowableOfType(
() -> client.getTrade(123L, "mock_token"),
ApiException.class
);
assertThat(exception.getErrorCode()).isEqualTo("LIMIT_REQUEST");
}
@AfterAll
static void stopServer() {
wireMockServer.stop();
}
}
六、CI/CD集成
name: 1688 API Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 3 * * *'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
- name: Run Unit Tests (Mock)
run: mvn test -Dtest=Alibaba1688MockTest
- name: Run Integration Tests
env:
ALIBABA_APP_KEY: ${{ secrets.ALIBABA_APP_KEY }}
ALIBABA_APP_SECRET: ${{ secrets.ALIBABA_APP_SECRET }}
ALIBABA_ACCESS_TOKEN: ${{ secrets.ALIBABA_ACCESS_TOKEN }}
run: mvn test -Dtest=Alibaba1688ApiTest
- name: Generate Test Report
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: 1688 API Test Report
path: target/surefire-reports/*.xml
reporter: java-junit
七、最佳实践总结
| 实践项 | 建议 | 原因 |
|---|
| 环境隔离 | 使用沙箱环境进行测试 | 避免影响生产数据 |
| 限流保护 | 单App 5000次/分钟,单IP 1000次/分钟 | 防止触发平台风控 |
| Token管理 | 使用短期Token,定期刷新 | 降低安全风险 |
| 数据脱敏 | 日志中隐藏敏感字段 | 保护商业数据 |
| 重试策略 | 指数退避,最多3次 | 提高成功率,避免雪崩 |
| 监控告警 | 监控错误率、延迟、限流次数 | 及时发现问题 |