全部
常见问题
产品动态
精选推荐
功能建议

分析中 已回复 待规划 {{opt.name}}
分析中 已回复 待规划
Java 淘宝商品详情接口测试实战指南

管理 管理 编辑 删除

一、淘宝开放平台接入准备

1.1 权限申请流程


┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  注册开发者  │ → │  创建应用    │ → │  申请API权限 │ → │  获取授权    │
│  (淘宝联盟)  │    │  (选择类型)  │    │  (类目审核)  │    │  (OAuth2.0)  │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘



关键要素说明获取方式
App Key应用标识开放平台控制台创建应用
App Secret应用密钥与App Key配对生成
Session Key用户授权令牌OAuth2.0授权流程
API权限item.get等接口调用权按类目申请,需审核

1.2 开发环境配置


<!-- pom.xml 依赖配置 -->
<dependencies>
    <!-- 淘宝SDK(官方推荐) -->
    <dependency>
        <groupId>com.taobao.api</groupId>
        <artifactId>taobao-sdk-java-auto</artifactId>
        <version>20230824</version>
    </dependency>
    
    <!-- 或手动集成:HTTP客户端 -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.12.0</version>
    </dependency>
    
    <!-- JSON处理 -->
    <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>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.14</version>
    </dependency>
</dependencies>


二、核心实现:淘宝API客户端

2.1 基础API客户端封装


package com.taobao.api.test;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 淘宝API客户端(基于TOP协议)
 * 文档:https://open.taobao.com/doc.htm
 */
public class TaobaoApiClient {
    
    private static final String API_URL = "https://gw.api.taobao.com/router/rest";
    private static final String API_VERSION = "2.0";
    private static final String SIGN_METHOD = "hmac-sha256";  // 或 md5
    
    private final String appKey;
    private final String appSecret;
    private final OkHttpClient httpClient;
    
    public TaobaoApiClient(String appKey, String appSecret) {
        this.appKey = appKey;
        this.appSecret = appSecret;
        this.httpClient = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
            .addInterceptor(new RetryInterceptor(3))  // 重试3次
            .build();
    }
    
    /**
     * 调用淘宝API(通用方法)
     */
    public ApiResponse call(String apiName, Map<String, String> params, String sessionKey) 
            throws ApiException {
        // 1. 构建公共参数
        Map<String, String> allParams = new HashMap<>();
        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("format", "json");
        allParams.put("v", API_VERSION);
        allParams.put("sign_method", SIGN_METHOD);
        
        // 2. 业务参数
        if (params != null) {
            allParams.putAll(params);
        }
        
        // 3. 用户授权参数(需要SessionKey的接口)
        if (sessionKey != null) {
            allParams.put("session", sessionKey);
        }
        
        // 4. 生成签名
        String sign = generateSign(allParams, appSecret, SIGN_METHOD);
        allParams.put("sign", sign);
        
        // 5. 构建请求
        FormBody.Builder formBuilder = new FormBody.Builder();
        allParams.forEach(formBuilder::add);
        
        Request request = new Request.Builder()
            .url(API_URL)
            .post(formBuilder.build())
            .header("Content-Type", "application/x-www-form-urlencoded")
            .build();
        
        // 6. 执行请求
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new ApiException("HTTP错误: " + response.code());
            }
            
            String body = response.body().string();
            JSONObject json = JSON.parseObject(body);
            
            // 7. 检查API错误
            if (json.containsKey("error_response")) {
                JSONObject error = json.getJSONObject("error_response");
                throw new ApiException(
                    error.getString("msg"),
                    error.getString("sub_msg"),
                    error.getString("code")
                );
            }
            
            return new ApiResponse(json, apiName);
            
        } catch (IOException e) {
            throw new ApiException("请求失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 生成签名(HMAC-SHA256)
     */
    private String generateSign(Map<String, String> params, String secret, String method) {
        try {
            // 1. 参数排序
            List<String> keys = new ArrayList<>(params.keySet());
            Collections.sort(keys);
            
            // 2. 拼接字符串
            StringBuilder sb = new StringBuilder();
            if (method.equals("md5")) {
                sb.append(secret);
            }
            
            for (String key : keys) {
                String value = params.get(key);
                if (value != null && !value.isEmpty()) {
                    sb.append(key).append(value);
                }
            }
            
            if (method.equals("md5")) {
                sb.append(secret);
                // MD5签名
                MessageDigest md = MessageDigest.getInstance("MD5");
                byte[] digest = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
                return bytesToHex(digest).toUpperCase();
            } else {
                // HMAC-SHA256签名
                Mac mac = Mac.getInstance("HmacSHA256");
                SecretKeySpec keySpec = new SecretKeySpec(
                    (secret + "&").getBytes(StandardCharsets.UTF_8), 
                    "HmacSHA256"
                );
                mac.init(keySpec);
                byte[] digest = mac.doFinal(
                    sb.toString().getBytes(StandardCharsets.UTF_8)
                );
                return bytesToHex(digest).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();
    }
    
    /**
     * 获取商品详情(taobao.item.get)
     */
    public ItemDetail getItemDetail(Long numIid, String fields) throws ApiException {
        Map<String, String> params = new HashMap<>();
        params.put("num_iid", String.valueOf(numIid));
        params.put("fields", fields != null ? fields : 
            "num_iid,title,price,desc,pic_url,sku,props_name,item_imgs");
        
        ApiResponse response = call("taobao.item.get", params, null);
        return response.parseItemDetail();
    }
    
    /**
     * 带授权的商品查询(taobao.item.seller.get)
     */
    public ItemDetail getSellerItem(Long numIid, String sessionKey) throws ApiException {
        Map<String, String> params = new HashMap<>();
        params.put("num_iid", String.valueOf(numIid));
        params.put("fields", "num_iid,title,price,desc,sku,outer_id,quantity");
        
        ApiResponse response = call("taobao.item.seller.get", params, sessionKey);
        return response.parseItemDetail();
    }
    
    public void close() {
        httpClient.dispatcher().executorService().shutdown();
        httpClient.connectionPool().evictAll();
    }
}

2.2 响应数据模型


package com.taobao.api.test;

import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;

@Data
public class ItemDetail {
    private Long numIid;           // 商品ID
    private String title;          // 标题
    private BigDecimal price;      // 价格
    private String desc;           // 描述
    private String picUrl;         // 主图
    private List<Sku> skus;        // SKU列表
    private List<ItemImg> itemImgs; // 详情图
    private String propsName;      // 属性名
    private Long quantity;         // 库存
    
    @Data
    public static class Sku {
        private String skuId;
        private BigDecimal price;
        private Long quantity;
        private String properties;
        private String outerId;
    }
    
    @Data
    public static class ItemImg {
        private String url;
    }
}

class ApiResponse {
    private final JSONObject rawData;
    private final String apiName;
    
    public ApiResponse(JSONObject data, String apiName) {
        this.rawData = data;
        this.apiName = apiName;
    }
    
    public ItemDetail parseItemDetail() {
        // 解析淘宝响应结构:{ "item_get_response": { "item": {...} } }
        String responseKey = apiName.replace(".", "_") + "_response";
        JSONObject response = rawData.getJSONObject(responseKey);
        
        if (response == null) {
            throw new ApiException("响应格式错误,缺少" + responseKey);
        }
        
        JSONObject item = response.getJSONObject("item");
        return item.toJavaObject(ItemDetail.class);
    }
    
    public JSONObject getRawData() {
        return rawData;
    }
}

class ApiException extends RuntimeException {
    private String subMsg;
    private String errorCode;
    
    public ApiException(String message) {
        super(message);
    }
    
    public ApiException(String message, Throwable cause) {
        super(message, cause);
    }
    
    public ApiException(String message, String subMsg, String errorCode) {
        super(message + " | " + subMsg);
        this.subMsg = subMsg;
        this.errorCode = errorCode;
    }
}


三、完整的测试用例

3.1 JUnit 5 测试类


package com.taobao.api.test;

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.junit.jupiter.api.Assertions.*;

import java.math.BigDecimal;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 淘宝商品详情API测试套件
 * 运行前需配置:APP_KEY, APP_SECRET, SESSION_KEY(部分接口需要)
 */
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class TaobaoApiTest {
    
    private static final String APP_KEY = System.getenv("TB_APP_KEY");
    private static final String APP_SECRET = System.getenv("TB_APP_SECRET");
    private static final String SESSION_KEY = System.getenv("TB_SESSION_KEY");
    
    private TaobaoApiClient client;
    
    @BeforeAll
    void setUp() {
        Assumptions.assumeTrue(APP_KEY != null, "未配置TB_APP_KEY环境变量");
        client = new TaobaoApiClient(APP_KEY, APP_SECRET);
    }
    
    @AfterAll
    void tearDown() {
        if (client != null) client.close();
    }
    
    // ==================== 基础功能测试 ====================
    
    @Test
    @DisplayName("测试获取公开商品详情(taobao.item.get)")
    void testGetPublicItemDetail() {
        // 使用公开测试商品ID(如:淘宝商品ID)
        Long testItemId = 123456789L;  // 替换为实际测试商品ID
        
        assertDoesNotThrow(() -> {
            ItemDetail item = client.getItemDetail(testItemId, 
                "num_iid,title,price,pic_url");
            
            assertNotNull(item, "商品详情不应为空");
            assertEquals(testItemId, item.getNumIid(), "商品ID应匹配");
            assertNotNull(item.getTitle(), "标题不应为空");
            assertTrue(item.getPrice().compareTo(BigDecimal.ZERO) > 0, 
                "价格应大于0");
            
            System.out.println("商品标题: " + item.getTitle());
            System.out.println("商品价格: " + item.getPrice());
        });
    }
    
    @Test
    @DisplayName("测试获取私密商品详情(需授权)")
    void testGetSellerItemDetail() {
        Assumptions.assumeTrue(SESSION_KEY != null, "未配置SESSION_KEY");
        
        Long testItemId = 987654321L;  // 店铺内商品ID
        
        ItemDetail item = assertDoesNotThrow(() -> 
            client.getSellerItem(testItemId, SESSION_KEY)
        );
        
        assertNotNull(item.getQuantity(), "库存信息需要授权才能获取");
        System.out.println("库存数量: " + item.getQuantity());
    }
    
    // ==================== 异常场景测试 ====================
    
    @Test
    @DisplayName("测试无效商品ID处理")
    void testInvalidItemId() {
        Long invalidId = 999999999999L;
        
        ApiException exception = assertThrows(ApiException.class, () -> {
            client.getItemDetail(invalidId, "num_iid,title");
        });
        
        assertTrue(exception.getMessage().contains("商品不存在") || 
                   exception.getMessage().contains("isv.item-not-exist"));
    }
    
    @Test
    @DisplayName("测试无效AppKey处理")
    void testInvalidAppKey() {
        TaobaoApiClient invalidClient = new TaobaoApiClient("invalid_key", "invalid_secret");
        
        assertThrows(ApiException.class, () -> {
            invalidClient.getItemDetail(123L, "num_iid");
        });
    }
    
    @ParameterizedTest
    @ValueSource(strings = {"num_iid,title", "num_iid,title,price,desc,sku", "num_iid"})
    @DisplayName("测试不同字段组合")
    void testDifferentFields(String fields) {
        Long testId = 123456789L;
        
        assertDoesNotThrow(() -> {
            ItemDetail item = client.getItemDetail(testId, fields);
            assertNotNull(item.getNumIid());
        });
    }
    
    // ==================== 性能与并发测试 ====================
    
    @Test
    @DisplayName("测试接口响应时间(P99 < 500ms)")
    void testResponseTime() {
        int iterations = 100;
        Long testId = 123456789L;
        
        long[] latencies = new long[iterations];
        
        for (int i = 0; i < iterations; i++) {
            long start = System.nanoTime();
            client.getItemDetail(testId, "num_iid,title,price");
            long end = System.nanoTime();
            latencies[i] = (end - start) / 1_000_000; // 转换为ms
        }
        
        // 计算P99
        java.util.Arrays.sort(latencies);
        long p99 = latencies[(int)(iterations * 0.99)];
        
        System.out.println("P99 响应时间: " + p99 + "ms");
        assertTrue(p99 < 500, "P99响应时间应小于500ms,实际: " + p99 + "ms");
    }
    
    @Test
    @DisplayName("测试并发限流(淘宝限制:App级别5000次/分钟)")
    void testConcurrentRateLimit() throws InterruptedException {
        int threadCount = 10;
        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);
        
        for (int i = 0; i < threadCount; i++) {
            executor.submit(() -> {
                try {
                    for (int j = 0; j < requestsPerThread; j++) {
                        try {
                            client.getItemDetail(testId, "num_iid,title");
                            successCount.incrementAndGet();
                        } catch (ApiException e) {
                            if (e.getMessage().contains("限流") || 
                                e.getMessage().contains("access-limit")) {
                                rateLimitCount.incrementAndGet();
                                // 遇到限流,退避等待
                                Thread.sleep(1000);
                            }
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }
        
        latch.await(2, TimeUnit.MINUTES);
        executor.shutdown();
        
        int total = threadCount * requestsPerThread;
        System.out.println("总请求: " + total);
        System.out.println("成功: " + successCount.get());
        System.out.println("限流: " + rateLimitCount.get());
        
        // 断言:成功率应 > 80%(考虑限流)
        assertTrue(successCount.get() > total * 0.8, 
            "成功率过低,请检查限流策略");
    }
    
    // ==================== 数据完整性测试 ====================
    
    @Test
    @DisplayName("测试SKU数据完整性")
    void testSkuDataIntegrity() {
        Long skuItemId = 111111111L;  // 有SKU的商品
        
        ItemDetail item = client.getItemDetail(skuItemId, 
            "num_iid,title,sku,props_name");
        
        assertNotNull(item.getSkus(), "SKU列表不应为空");
        assertFalse(item.getSkus().isEmpty(), "SKU不应为空列表");
        
        for (ItemDetail.Sku sku : item.getSkus()) {
            assertNotNull(sku.getSkuId(), "SKU ID不应为空");
            assertNotNull(sku.getPrice(), "SKU价格不应为空");
            assertTrue(sku.getPrice().compareTo(BigDecimal.ZERO) > 0, 
                "SKU价格应大于0");
        }
    }
    
    @ParameterizedTest
    @CsvSource({
        "123456789, 公开商品",
        "987654321, 私密商品", 
        "111111111, SKU商品"
    })
    @DisplayName("参数化商品详情测试")
    void testMultipleItems(Long itemId, String desc) {
        System.out.println("测试[" + desc + "], ID: " + itemId);
        
        assertDoesNotThrow(() -> {
            ItemDetail item = client.getItemDetail(itemId, 
                "num_iid,title,price,pic_url");
            assertNotNull(item);
        });
    }
}


四、高级测试场景

4.1 数据驱动测试(JSON配置)


package com.taobao.api.test;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import java.io.InputStream;
import java.util.List;
import java.util.stream.Stream;

/**
 * 数据驱动测试:从JSON文件加载测试用例
 */
public class DataDrivenTest {
    
    @TestFactory
    Stream<DynamicTest> testFromJsonConfig() {
        // 加载测试数据
        InputStream is = getClass().getResourceAsStream("/test-items.json");
        List<TestCase> testCases = JSON.parseObject(is, 
            new TypeReference<List<TestCase>>() {});
        
        TaobaoApiClient client = new TaobaoApiClient(APP_KEY, APP_SECRET);
        
        return testCases.stream().map(testCase -> 
            DynamicTest.dynamicTest(testCase.getName(), () -> {
                ItemDetail item = client.getItemDetail(
                    testCase.getItemId(), 
                    testCase.getFields()
                );
                
                // 断言规则
                for (AssertionRule rule : testCase.getRules()) {
                    rule.verify(item);
                }
            })
        );
    }
    
    // test-items.json 示例:
    /*
    [
      {
        "name": "测试iPhone商品",
        "itemId": 123456789,
        "fields": "num_iid,title,price",
        "rules": [
          {"field": "title", "operator": "contains", "value": "iPhone"},
          {"field": "price", "operator": "greaterThan", "value": 5000}
        ]
      }
    ]
    */
}

4.2 契约测试(Pact)


package com.taobao.api.test;

import au.com.dius.pact.consumer.MockServer;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
import au.com.dius.pact.consumer.junit5.PactTestFor;
import au.com.dius.pact.core.model.V4Pact;
import au.com.dius.pact.core.model.annotations.Pact;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * 消费者驱动的契约测试(CDC)
 * 确保淘宝API变更时及时发现
 */
@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "taobao-api", port = "8080")
public class TaobaoApiContractTest {
    
    @Pact(consumer = "our-service")
    public V4Pact itemGetPact(PactDslWithProvider builder) {
        return builder
            .given("商品存在")
            .uponReceiving("获取商品详情请求")
            .path("/router/rest")
            .method("POST")
            .body("method=taobao.item.get&num_iid=123456&fields=num_iid,title,price")
            .willRespondWith()
            .status(200)
            .body("""
                {
                  "item_get_response": {
                    "item": {
                      "num_iid": 123456,
                      "title": "测试商品",
                      "price": "99.99"
                    }
                  }
                }
                """)
            .toPact(V4Pact.class);
    }
    
    @Test
    @PactTestFor(pactMethod = "itemGetPact")
    void testItemGetContract(MockServer mockServer) {
        // 使用Mock服务器测试消费者代码
        TaobaoApiClient mockClient = new TaobaoApiClient("test", "test") {
            @Override
            protected String getApiUrl() {
                return mockServer.getUrl();
            }
        };
        
        ItemDetail item = mockClient.getItemDetail(123456L, "num_iid,title,price");
        assertEquals("测试商品", item.getTitle());
    }
}


五、Mock测试方案(无真实调用)


package com.taobao.api.test;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import org.junit.jupiter.api.*;

/**
 * 使用WireMock进行本地Mock测试
 * 适用于CI环境或无网络权限场景
 */
public class TaobaoApiMockTest {
    
    private static WireMockServer wireMockServer;
    private TaobaoApiClient client;
    
    @BeforeAll
    static void startServer() {
        wireMockServer = new WireMockServer(9999);
        wireMockServer.start();
        WireMock.configureFor("localhost", 9999);
    }
    
    @BeforeEach
    void setUp() {
        // 配置Mock响应
        stubFor(post(urlEqualTo("/router/rest"))
            .withRequestBody(containing("method=taobao.item.get"))
            .willReturn(aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("""
                    {
                      "item_get_response": {
                        "item": {
                          "num_iid": 123456,
                          "title": "Mock商品",
                          "price": "199.99",
                          "pic_url": "http://example.com/img.jpg"
                        }
                      }
                    }
                    """)));
        
        // 创建指向Mock服务器的客户端
        client = new TaobaoApiClient("mock_key", "mock_secret") {
            @Override
            protected String getApiUrl() {
                return "http://localhost:9999/router/rest";
            }
        };
    }
    
    @Test
    void testWithMockServer() {
        ItemDetail item = client.getItemDetail(123456L, "num_iid,title,price");
        
        assertEquals("Mock商品", item.getTitle());
        assertEquals(new BigDecimal("199.99"), item.getPrice());
        
        // 验证请求
        verify(postRequestedFor(urlEqualTo("/router/rest"))
            .withRequestBody(containing("num_iid=123456")));
    }
    
    @AfterAll
    static void stopServer() {
        wireMockServer.stop();
    }
}


六、测试报告与持续集成

6.1 Maven Surefire配置


<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.1.2</version>
            <configuration>
                <environmentVariables>
                    <TB_APP_KEY>${env.TB_APP_KEY}</TB_APP_KEY>
                    <TB_APP_SECRET>${env.TB_APP_SECRET}</TB_APP_SECRET>
                </environmentVariables>
                <systemPropertyVariables>
                    <junit.jupiter.execution.parallel.enabled>true</junit.jupiter.execution.parallel.enabled>
                </systemPropertyVariables>
                <reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
            </configuration>
        </plugin>
    </plugins>
</build>

6.2 GitHub Actions CI配置


# .github/workflows/taobao-api-test.yml
name: Taobao API Tests

on:
  schedule:
    - cron: '0 2 * * *'  # 每天凌晨2点运行
  workflow_dispatch:

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'
          
      - name: Cache Maven dependencies
        uses: actions/cache@v3
        with:
          path: ~/.m2
          key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
          
      - name: Run API Tests
        env:
          TB_APP_KEY: ${{ secrets.TB_APP_KEY }}
          TB_APP_SECRET: ${{ secrets.TB_APP_SECRET }}
          TB_SESSION_KEY: ${{ secrets.TB_SESSION_KEY }}
        run: mvn test -Dtest=TaobaoApiTest
        
      - name: Generate Report
        uses: dorny/test-reporter@v1
        if: success() || failure()
        with:
          name: Taobao API Test Results
          path: target/surefire-reports/*.xml
          reporter: java-junit
          
      - name: Upload Coverage
        uses: codecov/codecov-action@v3
        with:
          files: target/site/jacoco/jacoco.xml
          


七、合规与安全检查清单


□ 权限合规
  □ 已申请淘宝开放平台正式权限(非爬虫方式)
  □ App Key已绑定服务器IP白名单
  □ Session Key通过OAuth2.0正规授权获取
  
□ 数据安全
  □ App Secret存储在环境变量/密钥管理系统(非代码)
  □ 日志中脱敏处理(不打印完整Session Key)
  □ 响应数据不缓存敏感信息(如用户隐私)
  
□ 调用规范
  □ 单App并发不超过5000次/分钟
  □ 实现退避重试(遇到限流自动降速)
  □ 非高峰期进行压力测试(避免影响生产)
  
□ 监控告警
  □ 监控API错误率(>5%触发告警)
  □ 监控响应时间(P99>1s触发告警)
  □ 监控限流触发次数(频繁限流需调整策略)
  


八、总结


测试类型工具/方法适用场景
单元测试JUnit 5 + Mockito签名算法、参数构建
集成测试真实API调用验证与淘宝API连通性
契约测试Pact防止API变更破坏消费者
Mock测试WireMockCI环境、无网络权限
性能测试JMH/自定义并发验证限流策略、容量规划


{{voteData.voteSum}} 人已参与
支持
反对
请登录后查看

123c001fa85d 最后编辑于2026-04-17 17:34:20

快捷回复
回复
回复
回复({{post_count}}) {{!is_user ? '我的回复' :'全部回复'}}
排序 默认正序 回复倒序 点赞倒序

{{item.user_info.nickname ? item.user_info.nickname : item.user_name}} LV.{{ item.user_info.bbs_level || item.bbs_level }}

作者 管理员 企业

{{item.floor}}# 同步到gitee 已同步到gitee {{item.is_suggest == 1? '取消推荐': '推荐'}}
{{item.is_suggest == 1? '取消推荐': '推荐'}} 【已收集】
{{item.floor}}# 沙发 板凳 地板 {{item.floor}}# 【已收集】
{{item.user_info.title || '暂无简介'}}
附件

{{itemf.name}}

{{item.created_at}}  {{item.ip_address}}
打赏
已打赏¥{{item.reward_price}}
{{item.like_count}}
分享
{{item.showReply ? '取消回复' : '回复'}}
删除
回复
回复

{{itemc.user_info.nickname}}

{{itemc.user_name}}

回复 {{itemc.comment_user_info.nickname}}

附件

{{itemf.name}}

{{itemc.created_at}}
打赏
已打赏¥{{itemc.reward_price}}
{{itemc.like_count}}
{{itemc.showReply ? '取消回复' : '回复'}}
删除
回复
回复
收起 展开更多
查看更多
打赏
已打赏¥{{reward_price}}
24
{{like_count}}
{{collect_count}}
添加回复 ({{post_count}})

相关推荐

快速安全登录

使用微信扫码登录
回复
回复
问题:
问题自动获取的帖子内容,不准确时需要手动修改. [获取答案]
答案:
提交
bug 需求 取 消 确 定
打赏金额
当前余额:¥{{rewardUserInfo.reward_price}}
{{item.price}}元
请输入 0.1-{{reward_max_price}} 范围内的数值
打赏成功
¥{{price}}
完成 确认打赏

微信登录/注册

切换手机号登录

{{ bind_phone ? '绑定手机' : '手机登录'}}

{{codeText}}
切换微信登录/注册
暂不绑定
CRMEB客服
CRMEB咨询热线 400-8888-794

扫码领取产品资料

功能清单
思维导图
安装教程
CRMEB开源商城下载 源码下载 CRMEB帮助文档 帮助文档
返回顶部 返回顶部
CRMEB客服