# 腾讯文档 API 读取失败诊断指南 ## 问题描述 当调用腾讯文档读取接口时,返回错误: ```json { "msg": "无法读取表头,请检查headerRow参数", "code": 500 } ``` 请求参数: ```json { "fileId": "DUW50RUprWXh2TGJK", "sheetId": "BB08J2", "headerRow": 1, "orderNoColumn": 3, "logisticsLinkColumn": 13 } ``` --- ## 已添加的调试功能 我已经在代码中添加了详细的日志记录,现在会输出以下信息: ### 1. Service 层日志 - 开始读取表格数据的参数 - 获取用户信息的响应 - Open ID 提取结果 - API 调用参数 - API 返回结果 ### 2. Controller 层日志 - 读取表头的范围 - 表头数据的完整响应 - values 数组是否为空 --- ## 诊断步骤 ### 步骤 1:查看应用日志 启用 DEBUG 级别日志: **application-dev.yml**: ```yaml logging: level: com.ruoyi.jarvis.service.impl.TencentDocServiceImpl: DEBUG com.ruoyi.jarvis.util.TencentDocApiUtil: DEBUG com.ruoyi.web.controller.jarvis.TencentDocController: DEBUG ``` 重启应用后,再次调用 API,查看日志输出。 ### 步骤 2:分析日志信息 #### 2.1 检查用户信息获取 查找日志: ``` 正在获取用户信息... 用户信息响应: {"ret":0,"msg":"Succeed","data":{...}} ``` **可能的问题**: - ❌ 如果看到 `401 Unauthorized`:Access Token 无效或过期 - ❌ 如果看到 `ret != 0`:业务逻辑错误 - ❌ 如果 `data` 为 null:响应格式不正确 **解决方案**: 1. 检查 Access Token 是否有效 2. 使用 Refresh Token 刷新 Access Token 3. 重新进行 OAuth2 授权 #### 2.2 检查 Open ID 获取 查找日志: ``` 成功获取 Open ID: bcb50c8a4b724d86bbcf6fc64c5e2b22 ``` **可能的问题**: - ❌ 如果看到 `openID 字段不存在`:响应结构解析错误 - ❌ 如果 openID 为空:用户信息不完整 **解决方案**: 1. 检查用户信息响应的完整内容 2. 确认响应格式是否为:`{"ret":0,"msg":"Succeed","data":{"openID":"xxx",...}}` 3. 注意字段名是 `openID`(大写 ID) #### 2.3 检查 API 调用 查找日志: ``` 读取表格数据 - fileId: DUW50RUprWXh2TGJK, sheetId: BB08J2, range: A1:Z1, apiUrl: https://docs.qq.com/openapi/spreadsheet/v3/files/DUW50RUprWXh2TGJK/BB08J2/A1:Z1 ``` **可能的问题**: - ❌ 如果看到 `404 Not Found`:文件 ID 或工作表 ID 错误 - ❌ 如果看到 `403 Forbidden`:没有访问权限 - ❌ 如果看到 `400 Bad Request`:请求参数格式错误 **解决方案**: 1. **验证 File ID**: - 打开腾讯文档,从 URL 中获取正确的 File ID - URL 格式:`https://docs.qq.com/sheet/DUW50RUprWXh2TGJK?tab=BB08J2` - File ID 是 `sheet/` 后面到 `?` 之前的部分 2. **验证 Sheet ID**: - Sheet ID 是 URL 中 `tab=` 后面的部分 - 例如:`BB08J2` 3. **检查文档权限**: - 确认授权用户有权限访问该文档 - 在腾讯文档中检查分享设置 #### 2.4 检查 API 响应 查找日志: ``` 表头数据响应: {"values":[["列1","列2","列3"]]} ``` 或 ``` 表头数据中values数组为空,完整响应: {...} ``` **可能的问题**: ##### 问题 A:API 返回成功但 values 为空 ```json { "values": [] } ``` 或 ```json {} ``` **原因**: 1. 指定的行数据确实为空 2. Range 格式不正确 3. 权限不足,只能看到空数据 **解决方案**: 1. 手动在腾讯文档中检查第 1 行是否有数据 2. 尝试不同的 range: - `A1:A1`(单个单元格) - `A1:E1`(前 5 列) - `A1`(从 A1 开始的所有数据) ##### 问题 B:API 返回错误 可能的错误响应: ```json { "error": "invalid_token", "error_description": "Invalid access token" } ``` **原因**: - Access Token 无效或过期 - Open ID 不正确 - Client ID(App ID)不正确 **解决方案**: 1. 刷新 Access Token 2. 重新获取 Open ID 3. 检查配置文件中的 App ID --- ## 常见问题和解决方案 ### 问题 1:Access Token 过期 **症状**: ``` getUserInfo 返回 401 Unauthorized ``` **解决方案**: ```java // 使用 Refresh Token 刷新 Access Token JSONObject newTokens = tencentDocService.refreshAccessToken(refreshToken); String newAccessToken = newTokens.getString("access_token"); String newRefreshToken = newTokens.getString("refresh_token"); // 保存新的 tokens // ... ``` ### 问题 2:文档权限不足 **症状**: ``` 调用腾讯文档API失败: HTTP 403 Forbidden ``` **解决方案**: 1. 在腾讯文档中打开该文档 2. 点击右上角"分享"按钮 3. 确认授权用户的微信/QQ 账号有访问权限 4. 如果是企业文档,需要确认企业权限设置 ### 问题 3:File ID 或 Sheet ID 错误 **症状**: ``` 调用腾讯文档API失败: HTTP 404 Not Found ``` **解决方案**: 1. 重新从浏览器地址栏复制完整 URL 2. 正确提取 File ID 和 Sheet ID: ``` URL: https://docs.qq.com/sheet/DUW50RUprWXh2TGJK?tab=BB08J2 ↑ ↑ File ID Sheet ID (18个字符) (6个字符) ``` 3. File ID 通常以 `D` 开头,长度约 18 个字符 4. Sheet ID 通常是 6 个大写字母和数字的组合 ### 问题 4:Range 格式错误 **症状**: ``` values 数组为空,但手动检查文档有数据 ``` **可能的原因**: - Range 格式不符合腾讯文档 API 规范 - 行号从 0 开始而不是从 1 开始 **测试不同的 Range 格式**: ```bash # 测试 1:单个单元格 curl "http://localhost:8080/api/test/read?fileId=XXX&sheetId=YYY&range=A1" # 测试 2:单行范围 curl "http://localhost:8080/api/test/read?fileId=XXX&sheetId=YYY&range=A1:Z1" # 测试 3:多行范围 curl "http://localhost:8080/api/test/read?fileId=XXX&sheetId=YYY&range=A1:Z10" # 测试 4:使用行号 0(如果API是从0开始) curl "http://localhost:8080/api/test/read?fileId=XXX&sheetId=YYY&range=A0:Z0" ``` ### 问题 5:鉴权头设置错误 **症状**: ``` 调用腾讯文档API失败: HTTP 401 Unauthorized ``` **检查**: 确认代码中使用了正确的鉴权方式: ```java conn.setRequestProperty("Access-Token", accessToken); conn.setRequestProperty("Client-Id", clientId); conn.setRequestProperty("Open-Id", openId); ``` 而不是: ```java conn.setRequestProperty("Authorization", "Bearer " + accessToken); // ❌ 错误 ``` --- ## 快速诊断脚本 创建一个测试接口来诊断问题: ```java @GetMapping("/test/diagnose") public Map diagnose(@RequestParam String accessToken) { Map result = new LinkedHashMap<>(); try { // 1. 测试获取用户信息 System.out.println("\n=== 步骤1:获取用户信息 ==="); JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken); result.put("1_userInfo", userInfo); System.out.println("✓ 用户信息: " + userInfo.toJSONString()); // 2. 提取 Open ID System.out.println("\n=== 步骤2:提取 Open ID ==="); JSONObject data = userInfo.getJSONObject("data"); String openID = data != null ? data.getString("openID") : null; result.put("2_openID", openID); System.out.println("✓ Open ID: " + openID); // 3. 测试读取文档 String testFileId = "DUW50RUprWXh2TGJK"; String testSheetId = "BB08J2"; String testRange = "A1:Z1"; System.out.println("\n=== 步骤3:读取表格数据 ==="); System.out.println("File ID: " + testFileId); System.out.println("Sheet ID: " + testSheetId); System.out.println("Range: " + testRange); JSONObject readResult = tencentDocService.readSheetData( accessToken, testFileId, testSheetId, testRange ); result.put("3_readResult", readResult); System.out.println("✓ 读取结果: " + readResult.toJSONString()); // 4. 检查 values 数组 System.out.println("\n=== 步骤4:检查 values 数组 ==="); JSONArray values = readResult != null ? readResult.getJSONArray("values") : null; result.put("4_values", values); result.put("4_valuesCount", values != null ? values.size() : 0); System.out.println("✓ Values 数组大小: " + (values != null ? values.size() : 0)); if (values != null && !values.isEmpty()) { System.out.println("✓ 第一行数据: " + values.getJSONArray(0).toJSONString()); } result.put("status", "success"); result.put("message", "所有测试通过"); } catch (Exception e) { result.put("status", "error"); result.put("error", e.getMessage()); result.put("stackTrace", Arrays.toString(e.getStackTrace())); System.err.println("✗ 诊断失败: " + e.getMessage()); e.printStackTrace(); } return result; } ``` **使用方法**: ```bash curl "http://localhost:8080/test/diagnose?accessToken=YOUR_ACCESS_TOKEN" ``` --- ## 检查清单 执行以下检查清单,确保所有配置正确: ### 配置检查 - [ ] `application-dev.yml` 中 `app-id` 配置正确 - [ ] `application-dev.yml` 中 `app-secret` 配置正确 - [ ] `application-dev.yml` 中 `api-base-url` 为 `https://docs.qq.com/openapi/spreadsheet/v3` - [ ] 日志级别设置为 DEBUG ### 授权检查 - [ ] Access Token 未过期(有效期3天) - [ ] 授权用户有访问该文档的权限 - [ ] 文档没有被删除或移动 ### 参数检查 - [ ] File ID 正确(从 URL 中复制) - [ ] Sheet ID 正确(从 URL 的 tab 参数中复制) - [ ] headerRow 参数正确(通常为 1) - [ ] Range 格式正确(如 `A1:Z1`) ### 代码检查 - [ ] 使用查询参数方式调用 `/oauth/v2/userinfo?access_token=xxx` - [ ] 正确解析用户信息:`userInfo.getJSONObject("data").getString("openID")` - [ ] 使用三个鉴权头:`Access-Token`, `Client-Id`, `Open-Id` --- ## 联系支持 如果以上步骤都无法解决问题,请提供以下信息: 1. **完整的日志输出**(DEBUG 级别) 2. **请求参数**: - File ID - Sheet ID - Header Row - Range 3. **腾讯文档 URL**(用于验证 ID 是否正确) 4. **错误信息**(完整的堆栈跟踪) 5. **用户信息响应**(脱敏后的 JSON) 6. **API 调用响应**(完整的 JSON) --- **诊断指南版本**:1.0 **创建时间**:2025-11-05 **适用场景**:腾讯文档 API 读取失败问题排查