Files
ruoyi-java/doc/腾讯文档API测试验证指南.md
2025-11-06 10:26:40 +08:00

18 KiB
Raw Blame History

腾讯文档 API 测试验证指南

测试目的

验证根据官方文档修复后的 API 实现是否正确工作。

前置条件

1. 获取测试凭证

访问 腾讯文档开放平台,创建应用并获取:

  • Client ID应用ID
  • Client Secret应用密钥
  • Redirect URI已配置的回调地址

2. 配置测试环境

application-dev.yml 中配置:

tencent:
  doc:
    app-id: YOUR_CLIENT_ID
    app-secret: YOUR_CLIENT_SECRET
    redirect-uri: YOUR_REDIRECT_URI
    api-base-url: https://docs.qq.com/openapi/spreadsheet/v3

3. 准备测试文档

在腾讯文档中创建一个测试表格,获取:

  • File ID从 URL 中获取)
  • Sheet ID从 URL 参数 tab 中获取)

示例 URL

https://docs.qq.com/sheet/DQXxxxxxxxxxxxxxx?tab=BB08J2
                          ↑                      ↑
                      File ID                Sheet ID

测试流程

第 1 步OAuth2 授权测试

1.1 获取授权 URL

@RestController
@RequestMapping("/test/tencent-doc")
public class TencentDocTestController {
    
    @Autowired
    private ITencentDocService tencentDocService;
    
    @GetMapping("/auth-url")
    public String getAuthUrl() {
        String authUrl = tencentDocService.getAuthUrl();
        System.out.println("授权 URL: " + authUrl);
        return authUrl;
    }
}

访问测试

GET http://localhost:8080/test/tencent-doc/auth-url

预期响应

https://docs.qq.com/oauth/v2/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=all&state=xxxxx

手动测试

  1. 复制授权 URL 到浏览器
  2. 扫码或微信授权
  3. 授权成功后会重定向到回调地址,并带上 code 参数

1.2 获取 Access Token

@GetMapping("/callback")
public JSONObject callback(@RequestParam String code) {
    System.out.println("收到授权码: " + code);
    
    JSONObject tokenResponse = tencentDocService.getAccessTokenByCode(code);
    System.out.println("Token 响应: " + tokenResponse);
    
    String accessToken = tokenResponse.getString("access_token");
    String refreshToken = tokenResponse.getString("refresh_token");
    Integer expiresIn = tokenResponse.getInteger("expires_in");
    String userId = tokenResponse.getString("user_id");
    
    System.out.println("Access Token: " + accessToken);
    System.out.println("Refresh Token: " + refreshToken);
    System.out.println("过期时间: " + expiresIn + " 秒");
    System.out.println("User ID (Open ID): " + userId);
    
    return tokenResponse;
}

预期响应(根据官方文档):

{
  "access_token": "ACCESSTOKENEXAMPLE",
  "token_type": "Bearer",
  "refresh_token": "REFRESHTOKENEXAMPLE",
  "expires_in": 259200,
  "scope": "scope.file.editable,scope.folder.creatable",
  "user_id": "bcb50c8a4b724d86bbcf6fc64c5e2b22"
}

验证要点

  • 响应包含 access_token
  • 响应包含 refresh_token
  • 响应包含 user_id(即 Open ID
  • expires_in 为 2592003天

第 2 步:获取用户信息测试(关键修复点)

@GetMapping("/user-info")
public JSONObject getUserInfo(@RequestParam String accessToken) {
    System.out.println("测试获取用户信息Access Token: " + accessToken);
    
    JSONObject result = TencentDocApiUtil.getUserInfo(accessToken);
    System.out.println("完整响应: " + result.toJSONString());
    
    // 验证响应结构
    Integer ret = result.getInteger("ret");
    String msg = result.getString("msg");
    JSONObject data = result.getJSONObject("data");
    
    System.out.println("业务返回码 (ret): " + ret);
    System.out.println("业务返回信息 (msg): " + msg);
    
    if (ret == 0 && data != null) {
        String openID = data.getString("openID");
        String nick = data.getString("nick");
        String avatar = data.getString("avatar");
        String source = data.getString("source");
        String unionID = data.getString("unionID");
        
        System.out.println("✓ Open ID: " + openID);
        System.out.println("✓ 昵称: " + nick);
        System.out.println("✓ 头像: " + avatar);
        System.out.println("✓ 来源: " + source);
        System.out.println("✓ Union ID: " + unionID);
    } else {
        System.err.println("✗ 获取用户信息失败: " + msg);
    }
    
    return result;
}

访问测试

GET http://localhost:8080/test/tencent-doc/user-info?accessToken=YOUR_ACCESS_TOKEN

预期响应(根据官方文档):

{
  "ret": 0,
  "msg": "Succeed",
  "data": {
    "openID": "bcb50c8a4b724d86bbcf6fc64c5e2b22",
    "nick": "用户昵称",
    "avatar": "https://thirdwx.qlogo.cn/mmopen/xxx",
    "source": "wx",
    "unionID": "xxxxxx"
  }
}

验证要点(关键!):

  • HTTP 状态码为 200
  • ret 字段为 0表示成功
  • msg 字段为 "Succeed"
  • data 对象存在且包含 openID 字段(注意大写 ID
  • openID 字段不为空
  • 其他字段nick、avatar 等)正常返回

常见错误

  1. 如果返回 401Access Token 无效或过期
  2. 如果返回 ret != 0:业务逻辑错误,查看 msg 信息
  3. 如果 data 为 null响应解析错误

第 3 步:获取文件信息测试

@GetMapping("/file-info")
public JSONObject getFileInfo(
    @RequestParam String accessToken,
    @RequestParam String fileId
) {
    System.out.println("测试获取文件信息");
    System.out.println("File ID: " + fileId);
    
    JSONObject result = tencentDocService.getFileInfo(accessToken, fileId);
    System.out.println("文件信息: " + result.toJSONString());
    
    // 解析文件信息
    String fileIdResp = result.getString("fileId");
    JSONObject metadata = result.getJSONObject("metadata");
    JSONArray sheets = result.getJSONArray("sheets");
    
    System.out.println("✓ 文件 ID: " + fileIdResp);
    System.out.println("✓ 元数据: " + metadata);
    System.out.println("✓ 工作表数量: " + (sheets != null ? sheets.size() : 0));
    
    if (sheets != null) {
        for (int i = 0; i < sheets.size(); i++) {
            JSONObject sheet = sheets.getJSONObject(i);
            JSONObject properties = sheet.getJSONObject("properties");
            if (properties != null) {
                String sheetId = properties.getString("sheetId");
                String title = properties.getString("title");
                Integer rowCount = properties.getInteger("rowCount");
                Integer columnCount = properties.getInteger("columnCount");
                
                System.out.println("  工作表 " + (i + 1) + ": " + title);
                System.out.println("    - Sheet ID: " + sheetId);
                System.out.println("    - 行数: " + rowCount);
                System.out.println("    - 列数: " + columnCount);
            }
        }
    }
    
    return result;
}

访问测试

GET http://localhost:8080/test/tencent-doc/file-info?accessToken=YOUR_ACCESS_TOKEN&fileId=YOUR_FILE_ID

验证要点

  • HTTP 状态码为 200
  • 返回文件 ID
  • 返回工作表列表
  • 每个工作表包含 properties 信息

第 4 步:读取表格数据测试

@GetMapping("/read-data")
public JSONObject readData(
    @RequestParam String accessToken,
    @RequestParam String fileId,
    @RequestParam String sheetId,
    @RequestParam(defaultValue = "A1:Z10") String range
) {
    System.out.println("测试读取表格数据");
    System.out.println("File ID: " + fileId);
    System.out.println("Sheet ID: " + sheetId);
    System.out.println("Range: " + range);
    
    JSONObject result = tencentDocService.readSheetData(
        accessToken, fileId, sheetId, range
    );
    System.out.println("读取结果: " + result.toJSONString());
    
    // 解析数据
    JSONArray values = result.getJSONArray("values");
    if (values != null && values.size() > 0) {
        System.out.println("✓ 读取到 " + values.size() + " 行数据");
        
        // 打印前 5 行
        for (int i = 0; i < Math.min(5, values.size()); i++) {
            JSONArray row = values.getJSONArray(i);
            System.out.println("  行 " + (i + 1) + ": " + row.toJSONString());
        }
        
        if (values.size() > 5) {
            System.out.println("  ... 还有 " + (values.size() - 5) + " 行");
        }
    } else {
        System.out.println("✓ 指定范围内没有数据");
    }
    
    return result;
}

访问测试

GET http://localhost:8080/test/tencent-doc/read-data?accessToken=YOUR_ACCESS_TOKEN&fileId=YOUR_FILE_ID&sheetId=YOUR_SHEET_ID&range=A1:Z10

预期响应

{
  "values": [
    ["列1", "列2", "列3"],
    ["数据1", "数据2", "数据3"],
    ["数据4", "数据5", "数据6"]
  ]
}

验证要点

  • HTTP 状态码为 200
  • 返回 values 数组
  • 数据结构为二维数组
  • 数据内容正确

第 5 步:写入表格数据测试

@PostMapping("/write-data")
public JSONObject writeData(
    @RequestParam String accessToken,
    @RequestParam String fileId,
    @RequestParam String sheetId,
    @RequestParam(defaultValue = "A1") String range
) {
    System.out.println("测试写入表格数据");
    
    // 构造测试数据
    Object[][] values = {
        {"测试标题1", "测试标题2", "测试标题3"},
        {"测试数据1", "测试数据2", "测试数据3"},
        {"测试数据4", "测试数据5", "测试数据6"}
    };
    
    System.out.println("写入数据到 " + range);
    
    JSONObject result = tencentDocService.writeSheetData(
        accessToken, fileId, sheetId, range, values
    );
    System.out.println("写入结果: " + result.toJSONString());
    
    return result;
}

访问测试

POST http://localhost:8080/test/tencent-doc/write-data?accessToken=YOUR_ACCESS_TOKEN&fileId=YOUR_FILE_ID&sheetId=YOUR_SHEET_ID&range=A1

验证要点

  • HTTP 状态码为 200
  • 写入成功
  • 在腾讯文档中手动验证数据已写入

第 6 步:追加数据测试

@PostMapping("/append-data")
public JSONObject appendData(
    @RequestParam String accessToken,
    @RequestParam String fileId,
    @RequestParam String sheetId
) {
    System.out.println("测试追加表格数据");
    
    // 构造测试数据
    Object[][] values = {
        {"追加行1-列1", "追加行1-列2", "追加行1-列3"},
        {"追加行2-列1", "追加行2-列2", "追加行2-列3"}
    };
    
    System.out.println("追加 " + values.length + " 行数据");
    
    // 注意appendSheetData 内部会自动查找最后一行并追加
    // 这里需要使用 TencentDocApiUtil 直接调用
    JSONObject result = TencentDocApiUtil.appendSheetData(
        accessToken,
        tencentDocConfig.getAppId(),
        getOpenID(accessToken),  // 辅助方法
        fileId,
        sheetId,
        values,
        tencentDocConfig.getApiBaseUrl()
    );
    
    System.out.println("追加结果: " + result.toJSONString());
    
    return result;
}

// 辅助方法:获取 Open ID
private String getOpenID(String accessToken) {
    JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
    JSONObject data = userInfo.getJSONObject("data");
    return data.getString("openID");
}

验证要点

  • HTTP 状态码为 200
  • 数据追加到表格末尾
  • 在腾讯文档中手动验证数据位置正确

完整测试流程示例

@RestController
@RequestMapping("/test/tencent-doc")
public class TencentDocTestController {
    
    @Autowired
    private ITencentDocService tencentDocService;
    
    @Autowired
    private TencentDocConfig tencentDocConfig;
    
    /**
     * 完整流程测试
     */
    @GetMapping("/full-test")
    public Map<String, Object> fullTest(
        @RequestParam String accessToken,
        @RequestParam String fileId,
        @RequestParam String sheetId
    ) {
        Map<String, Object> results = new LinkedHashMap<>();
        
        try {
            // 1. 获取用户信息
            System.out.println("\n=== 第1步获取用户信息 ===");
            JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
            JSONObject data = userInfo.getJSONObject("data");
            String openID = data.getString("openID");
            
            results.put("1_userInfo", Map.of(
                "status", "success",
                "openID", openID,
                "nick", data.getString("nick")
            ));
            System.out.println("✓ 用户信息获取成功Open ID: " + openID);
            
            // 2. 获取文件信息
            System.out.println("\n=== 第2步获取文件信息 ===");
            JSONObject fileInfo = tencentDocService.getFileInfo(accessToken, fileId);
            
            results.put("2_fileInfo", Map.of(
                "status", "success",
                "fileId", fileInfo.getString("fileId"),
                "sheetCount", fileInfo.getJSONArray("sheets").size()
            ));
            System.out.println("✓ 文件信息获取成功");
            
            // 3. 读取表格数据
            System.out.println("\n=== 第3步读取表格数据 ===");
            JSONObject readResult = tencentDocService.readSheetData(
                accessToken, fileId, sheetId, "A1:Z10"
            );
            
            JSONArray values = readResult.getJSONArray("values");
            results.put("3_readData", Map.of(
                "status", "success",
                "rowCount", values != null ? values.size() : 0
            ));
            System.out.println("✓ 读取数据成功,共 " + (values != null ? values.size() : 0) + " 行");
            
            // 4. 写入测试数据
            System.out.println("\n=== 第4步写入测试数据 ===");
            Object[][] testData = {
                {"测试时间", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())},
                {"测试状态", "成功"}
            };
            
            JSONObject writeResult = tencentDocService.writeSheetData(
                accessToken, fileId, sheetId, "A100", testData
            );
            
            results.put("4_writeData", Map.of(
                "status", "success"
            ));
            System.out.println("✓ 写入数据成功");
            
            // 5. 总结
            results.put("summary", Map.of(
                "totalTests", 4,
                "passedTests", 4,
                "failedTests", 0,
                "status", "✓ 所有测试通过"
            ));
            
            System.out.println("\n=== 测试完成 ===");
            System.out.println("✓ 所有测试通过!");
            
        } catch (Exception e) {
            results.put("error", Map.of(
                "status", "failed",
                "message", e.getMessage(),
                "type", e.getClass().getName()
            ));
            
            System.err.println("\n=== 测试失败 ===");
            System.err.println("✗ 错误: " + e.getMessage());
            e.printStackTrace();
        }
        
        return results;
    }
}

访问测试

GET http://localhost:8080/test/tencent-doc/full-test?accessToken=YOUR_ACCESS_TOKEN&fileId=YOUR_FILE_ID&sheetId=YOUR_SHEET_ID

常见问题排查

问题 1获取用户信息返回 401

原因Access Token 无效或过期

解决方案

  1. 检查 Access Token 是否正确
  2. 使用 Refresh Token 刷新 Access Token
  3. 重新进行 OAuth2 授权

问题 2获取用户信息返回 ret != 0

原因:业务逻辑错误

解决方案

  1. 查看 msg 字段的具体错误信息
  2. 确认 Access Token 是否有效
  3. 检查网络连接

问题 3无法获取 Open ID返回 null

原因:响应解析错误

解决方案

  1. 打印完整响应内容,检查结构
  2. 确认使用 data.getString("openID")(大写 ID
  3. 确认响应中包含 data 对象

问题 4表格操作返回 404

原因File ID 或 Sheet ID 错误

解决方案

  1. 从浏览器地址栏重新获取 File ID 和 Sheet ID
  2. 确认用户有权限访问该文档
  3. 检查 API 路径是否正确

问题 5表格操作返回 403

原因:权限不足

解决方案

  1. 确认授权时选择了正确的权限范围
  2. 在腾讯文档中检查文档的分享设置
  3. 确认 Access Token 对应的用户有编辑权限

测试检查清单

OAuth2 授权

  • 成功生成授权 URL
  • 用户可以扫码或微信授权
  • 成功获取 Access Token
  • 成功获取 Refresh Token
  • Access Token 有效期正确3天

用户信息 API (关键修复点)

  • HTTP 请求使用查询参数 access_token
  • 响应包含 retmsgdata 字段
  • ret 为 0 表示成功
  • data.openID 字段存在且不为空(注意大写 ID
  • 其他用户信息nick、avatar 等)正常

文件操作 API

  • 成功获取文件信息
  • 成功获取工作表列表
  • 工作表信息完整sheetId、title、rowCount 等)

表格数据操作 API

  • 成功读取表格数据
  • 数据格式为二维数组
  • 成功写入表格数据
  • 写入的数据在腾讯文档中可见
  • 成功追加表格数据
  • 追加的数据位置正确(在最后一行之后)

性能测试建议

1. 并发测试

测试多个用户同时调用 API 的性能表现。

2. 大数据量测试

测试读取和写入大量数据(如 1000 行)的性能。

3. API 限流测试

测试 API 调用频率限制,避免被限流。


测试指南版本1.0 最后更新2025-11-05 适用修复版本:根据官方文档的关键修复


快速开始

如果您想快速验证修复是否成功,执行以下最小测试:

# 1. 启动应用
cd ruoyi-java
mvn spring-boot:run

# 2. 获取授权(浏览器访问)
http://localhost:8080/test/tencent-doc/auth-url

# 3. 扫码授权后,从回调 URL 中获取 code

# 4. 测试用户信息接口(最关键)
curl "http://localhost:8080/test/tencent-doc/user-info?accessToken=YOUR_ACCESS_TOKEN"

# 预期看到:
# {
#   "ret": 0,
#   "msg": "Succeed",
#   "data": {
#     "openID": "xxx...",
#     "nick": "用户昵称",
#     ...
#   }
# }

如果看到正确的响应结构,说明关键修复已生效!