18 KiB
18 KiB
腾讯文档 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
手动测试:
- 复制授权 URL 到浏览器
- 扫码或微信授权
- 授权成功后会重定向到回调地址,并带上
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为 259200(3天)
第 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 等)正常返回
常见错误:
- 如果返回 401:Access Token 无效或过期
- 如果返回
ret != 0:业务逻辑错误,查看msg信息 - 如果
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 无效或过期
解决方案:
- 检查 Access Token 是否正确
- 使用 Refresh Token 刷新 Access Token
- 重新进行 OAuth2 授权
问题 2:获取用户信息返回 ret != 0
原因:业务逻辑错误
解决方案:
- 查看
msg字段的具体错误信息 - 确认 Access Token 是否有效
- 检查网络连接
问题 3:无法获取 Open ID(返回 null)
原因:响应解析错误
解决方案:
- 打印完整响应内容,检查结构
- 确认使用
data.getString("openID")(大写 ID) - 确认响应中包含
data对象
问题 4:表格操作返回 404
原因:File ID 或 Sheet ID 错误
解决方案:
- 从浏览器地址栏重新获取 File ID 和 Sheet ID
- 确认用户有权限访问该文档
- 检查 API 路径是否正确
问题 5:表格操作返回 403
原因:权限不足
解决方案:
- 确认授权时选择了正确的权限范围
- 在腾讯文档中检查文档的分享设置
- 确认 Access Token 对应的用户有编辑权限
测试检查清单
OAuth2 授权 ✅
- 成功生成授权 URL
- 用户可以扫码或微信授权
- 成功获取 Access Token
- 成功获取 Refresh Token
- Access Token 有效期正确(3天)
用户信息 API ✅ (关键修复点)
- HTTP 请求使用查询参数
access_token - 响应包含
ret、msg、data字段 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": "用户昵称",
# ...
# }
# }
如果看到正确的响应结构,说明关键修复已生效! ✅