# 腾讯文档 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<String, Object> diagnose(@RequestParam String accessToken) {
    Map<String, Object> 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 读取失败问题排查

