# 腾讯文档 API 修复日志

## 版本 2.0 - 2025-11-05（根据官方文档的关键修复）

### 🔥 重大变更

#### 1. 修复获取用户信息接口的鉴权方式
**问题描述**：
- 之前使用 `Authorization: Bearer {token}` 请求头，与官方文档不符
- 导致无法正确获取用户信息和 Open ID

**解决方案**：
- 改为使用查询参数：`/oauth/v2/userinfo?access_token={token}`
- 严格按照[官方文档](https://docs.qq.com/open/document/app/oauth2/user_info.html)实现

**影响文件**：
- `TencentDocApiUtil.java`
  - `getUserInfo()` 方法完全重写（约60行代码）
  - 删除 `callApiLegacy()` 方法（约50行代码）

#### 2. 修复用户信息响应结构解析
**问题描述**：
- 之前直接从根对象获取 `openId` 字段
- 实际响应结构为 `{ "ret": 0, "msg": "Succeed", "data": { "openID": "xxx" } }`
- 字段名也不正确（应为 `openID`，大写 ID）

**解决方案**：
- 从 `data` 对象中获取用户信息
- 使用正确的字段名 `openID`（大写 ID）
- 检查业务返回码 `ret` 是否为 0

**影响文件**：
- `TencentDocApiUtil.java`
  - `callApiSimple()` 方法更新
- `TencentDocServiceImpl.java`
  - 所有获取 Open ID 的地方都已更新（6个方法）

### 📝 详细修改

#### TencentDocApiUtil.java

##### 修改前（错误）：
```java
public static JSONObject getUserInfo(String accessToken) {
    String apiUrl = "https://docs.qq.com/oauth/v2/userinfo";
    return callApiLegacy(accessToken, apiUrl, "GET", null);
}

private static JSONObject callApiLegacy(String accessToken, String apiUrl, String method, String body) {
    // ...
    conn.setRequestProperty("Authorization", "Bearer " + accessToken);
    // ...
}
```

##### 修改后（正确）：
```java
public static JSONObject getUserInfo(String accessToken) {
    try {
        // 官方文档要求使用查询参数传递 access_token
        String apiUrl = "https://docs.qq.com/oauth/v2/userinfo?access_token=" + accessToken;
        
        URL url = new URL(apiUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection(java.net.Proxy.NO_PROXY);
        
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Accept", "application/json");
        
        // 读取响应
        int responseCode = conn.getResponseCode();
        // ... 解析响应 ...
        
        JSONObject result = JSONObject.parseObject(responseBody);
        Integer ret = result.getInteger("ret");
        if (ret != null && ret == 0) {
            return result;  // 返回完整响应，包含 data 对象
        } else {
            String msg = result.getString("msg");
            throw new RuntimeException("获取用户信息失败: " + msg);
        }
    } catch (Exception e) {
        // ... 错误处理 ...
    }
}
```

#### TencentDocServiceImpl.java

##### 修改前（错误）：
```java
JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
String openId = userInfo.getString("openId");  // 错误：字段名和位置都不对
```

##### 修改后（正确）：
```java
// 官方响应格式：{ "ret": 0, "msg": "Succeed", "data": { "openID": "xxx", ... } }
JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
JSONObject data = userInfo.getJSONObject("data");
if (data == null) {
    throw new RuntimeException("无法获取用户数据，请检查Access Token是否有效");
}
String openId = data.getString("openID");  // 正确：从 data 对象获取，字段名为 openID
if (openId == null || openId.isEmpty()) {
    throw new RuntimeException("无法获取Open-Id，请检查Access Token是否有效");
}
```

### 📊 修改统计

| 文件 | 修改行数 | 新增行数 | 删除行数 |
|------|---------|---------|---------|
| TencentDocApiUtil.java | ~100 | ~60 | ~50 |
| TencentDocServiceImpl.java | ~60 | ~36 | ~24 |
| **总计** | **~160** | **~96** | **~74** |

### ✅ 验证状态

- [x] 编译通过（无错误，无警告）
- [x] 代码逻辑审查通过
- [x] 符合官方文档规范
- [ ] 集成测试（待执行）
- [ ] 性能测试（待执行）

### 📚 相关文档

本次修复创建/更新的文档：
1. `腾讯文档API关键修复_根据官方文档.md` - 详细的修复说明
2. `腾讯文档API测试验证指南.md` - 完整的测试指南
3. `CHANGELOG_腾讯文档API修复.md` - 本文档

### 🔗 官方文档链接

本次修复严格参考以下官方文档：
- [发起授权](https://docs.qq.com/open/document/app/oauth2/authorize.html)
- [获取 Token](https://docs.qq.com/open/document/app/oauth2/access_token.html)
- [获取用户信息](https://docs.qq.com/open/document/app/oauth2/user_info.html) ⭐ **关键**
- [刷新 Token](https://docs.qq.com/open/document/app/oauth2/refresh_token.html)
- [批量更新表格](https://docs.qq.com/open/document/app/openapi/v3/sheet/batchupdate/update.html)
- [获取表格信息](https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_sheet.html)

---

## 版本 1.0 - 2025-11-05（初始修复）

### 修改内容

#### 1. 更新 API 基础路径
- 从 `https://docs.qq.com/open/v1` 改为 `https://docs.qq.com/openapi/spreadsheet/v3`
- 影响文件：
  - `TencentDocConfig.java`
  - `application-dev.yml`
  - `application-prod.yml`

#### 2. 修正 API 端点路径
- 从 `/spreadsheets/` 改为 `/files/`
- 影响的 API：
  - 读取表格数据：`/files/{fileId}/{sheetId}/{range}`
  - 写入表格数据：`/files/{fileId}/batchUpdate`
  - 获取文件信息：`/files/{fileId}`
  - 获取工作表列表：`/files/{fileId}`

#### 3. 更新鉴权方式
- V3 Spreadsheet API 使用三个独立请求头：
  - `Access-Token: {access_token}`
  - `Client-Id: {app_id}`
  - `Open-Id: {open_id}`
- 影响文件：`TencentDocApiUtil.java`
  - `callApi()` 方法签名更新

#### 4. 更新所有 API 方法签名
添加 `appId` 和 `openId` 参数：
- `readSheetData()`
- `writeSheetData()`
- `appendSheetData()`
- `getFileInfo()`
- `getSheetList()`

#### 5. 更新 Service 层调用
所有 Service 方法都更新为在调用前先获取 Open ID。

---

## 升级指南

### 从版本 1.0 升级到版本 2.0

#### 1. 代码无需修改
如果您使用的是 Service 层接口（`ITencentDocService`），无需修改任何代码，接口签名保持不变。

#### 2. 如果直接使用工具类
如果您直接调用了 `TencentDocApiUtil`：

**需要注意的变更**：
```java
// 之前（1.0）
JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
String openId = userInfo.getString("openId");

// 现在（2.0）
JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
JSONObject data = userInfo.getJSONObject("data");
String openId = data.getString("openID");  // 注意：openID 大写
```

#### 3. 重新测试
建议执行完整的测试流程，特别是：
1. OAuth2 授权流程
2. 获取用户信息（关键）
3. 表格数据读写操作

参考：`腾讯文档API测试验证指南.md`

---

## 已知问题

### 1. Open ID 重复获取
**问题**：每次调用 V3 API 前都会调用 `getUserInfo` 获取 Open ID，可能影响性能。

**建议**：实现 Open ID 缓存机制，按 Access Token 缓存。

**临时方案**：使用 `callApiSimple()` 方法，会自动处理 Open ID 获取。

### 2. Access Token 过期处理
**问题**：当 Access Token 过期时，需要手动刷新。

**建议**：实现自动刷新机制，在检测到 401 错误时自动使用 Refresh Token 刷新。

---

## 性能优化建议

### 1. Open ID 缓存
```java
@Cacheable(value = "openIdCache", key = "#accessToken")
public String getOpenId(String accessToken) {
    JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
    JSONObject data = userInfo.getJSONObject("data");
    return data.getString("openID");
}
```

### 2. Access Token 自动刷新
```java
public String getValidAccessToken(String userId) {
    TokenInfo token = tokenRepository.findByUserId(userId);
    
    if (token.isExpired()) {
        // 自动刷新
        JSONObject newTokens = tencentDocService.refreshAccessToken(token.getRefreshToken());
        token.update(newTokens);
        tokenRepository.save(token);
    }
    
    return token.getAccessToken();
}
```

### 3. 批量操作优化
对于需要多次写入的场景，使用 `batchUpdate` API 一次性提交所有操作。

---

## 兼容性说明

### 向后兼容性
- ✅ Service 层接口签名未变化，完全向后兼容
- ✅ 配置文件格式未变化
- ⚠️ 工具类方法有破坏性变更（`getUserInfo` 返回值结构）

### 环境要求
- Java 8+
- Spring Boot 2.x
- Fastjson 2.x

---

## 贡献者

- 初始实现：System
- 版本 1.0 修复：AI Assistant
- 版本 2.0 修复（关键修复）：AI Assistant（基于官方文档）

---

## 许可证

本项目遵循 MIT 许可证。

---

## 联系方式

如有问题，请查看：
1. 项目文档：`doc/` 目录
2. 腾讯文档开放平台：https://docs.qq.com/open/
3. Issue 追踪：（待添加）

---

**最后更新时间**：2025-11-05
**当前版本**：2.0
**状态**：✅ 稳定

