# 腾讯文档API完整修复总结

## 修复日期
2025-11-05

## 修复概述
针对腾讯文档开放平台 V3 API 集成，完成了以下全面修复：
1. 修正了 API 基础路径配置
2. 修正了 API 端点路径结构
3. 修正了鉴权方式（从 Authorization: Bearer 改为三个独立请求头）
4. 更新了所有 Service 层调用以支持新的鉴权方式

## 修复详情

### 1. API 基础路径修复

#### 修改文件
- `ruoyi-system/src/main/java/com/ruoyi/jarvis/config/TencentDocConfig.java`
- `ruoyi-admin/src/main/resources/application-dev.yml`
- `ruoyi-admin/src/main/resources/application-prod.yml`

#### 修改内容
```java
// 修改前
private String apiBaseUrl = "https://docs.qq.com/open/v1";

// 修改后
private String apiBaseUrl = "https://docs.qq.com/openapi/spreadsheet/v3";
```

#### 配置文件修改
```yaml
# application-dev.yml 和 application-prod.yml
# 修改前
api-base-url: https://docs.qq.com/open/v1

# 修改后
api-base-url: https://docs.qq.com/openapi/spreadsheet/v3
```

### 2. API 端点路径结构修复

#### 修改文件
- `ruoyi-system/src/main/java/com/ruoyi/jarvis/util/TencentDocApiUtil.java`

#### 修改的 API 端点

##### 2.1 读取表格数据 (readSheetData)
```java
// 修改前
String apiUrl = String.format("%s/spreadsheets/%s/%s/%s", apiBaseUrl, fileId, sheetId, range);

// 修改后
String apiUrl = String.format("%s/files/%s/%s/%s", apiBaseUrl, fileId, sheetId, range);

// 完整路径示例：
// https://docs.qq.com/openapi/spreadsheet/v3/files/{fileId}/{sheetId}/{range}
```

##### 2.2 写入表格数据 (writeSheetData)
```java
// 修改前
String apiUrl = String.format("%s/spreadsheets/%s/batchUpdate", apiBaseUrl, fileId);

// 修改后
String apiUrl = String.format("%s/files/%s/batchUpdate", apiBaseUrl, fileId);

// 完整路径示例：
// https://docs.qq.com/openapi/spreadsheet/v3/files/{fileId}/batchUpdate
```

##### 2.3 追加表格数据 (appendSheetData)
```java
// 修改前
String infoUrl = String.format("%s/spreadsheets/%s", apiBaseUrl, fileId);

// 修改后
String infoUrl = String.format("%s/files/%s", apiBaseUrl, fileId);

// 完整路径示例：
// https://docs.qq.com/openapi/spreadsheet/v3/files/{fileId}
```

##### 2.4 获取文件信息 (getFileInfo)
```java
// 修改前
String apiUrl = String.format("%s/spreadsheets/%s", apiBaseUrl, fileId);

// 修改后
String apiUrl = String.format("%s/files/%s", apiBaseUrl, fileId);

// 完整路径示例：
// https://docs.qq.com/openapi/spreadsheet/v3/files/{fileId}
```

##### 2.5 获取工作表列表 (getSheetList)
```java
// 修改前
String apiUrl = String.format("%s/spreadsheets/%s", apiBaseUrl, fileId);

// 修改后
String apiUrl = String.format("%s/files/%s", apiBaseUrl, fileId);

// 完整路径示例：
// https://docs.qq.com/openapi/spreadsheet/v3/files/{fileId}
```

### 3. 鉴权方式修复

#### 3.1 callApi 方法签名修改
```java
// 修改前
public static JSONObject callApi(String accessToken, String apiUrl, String method, String body)

// 修改后
public static JSONObject callApi(String accessToken, String clientId, String openId, String apiUrl, String method, String body)
```

#### 3.2 请求头修改
```java
// 修改前（错误的鉴权方式）
conn.setRequestProperty("Authorization", "Bearer " + accessToken);

// 修改后（正确的鉴权方式）
conn.setRequestProperty("Access-Token", accessToken);
conn.setRequestProperty("Client-Id", clientId);
conn.setRequestProperty("Open-Id", openId);
```

#### 3.3 新增辅助方法

##### getUserInfo 方法
用于获取用户信息（包含 Open-Id），使用传统的 Authorization: Bearer 鉴权方式。

```java
/**
 * 获取用户信息（包含Open-Id）
 * 
 * @param accessToken 访问令牌
 * @return 用户信息（包含 openId 字段）
 */
public static JSONObject getUserInfo(String accessToken) {
    // 腾讯文档用户信息接口：https://docs.qq.com/open/document/app/oauth2/userinfo.html
    // 注意：此接口使用不同的鉴权方式（Authorization: Bearer）
    String apiUrl = "https://docs.qq.com/oauth/v2/userinfo";
    return callApiLegacy(accessToken, apiUrl, "GET", null);
}
```

##### callApiLegacy 方法
用于支持旧版 OAuth2 用户信息接口的 Authorization: Bearer 鉴权方式。

```java
/**
 * 调用腾讯文档API（使用传统的 Authorization: Bearer 鉴权方式）
 * 仅用于 OAuth2 用户信息接口
 */
private static JSONObject callApiLegacy(String accessToken, String apiUrl, String method, String body) {
    try {
        // ... 连接设置 ...
        conn.setRequestMethod(method);
        conn.setRequestProperty("Authorization", "Bearer " + accessToken);
        conn.setRequestProperty("Content-Type", "application/json");
        // ... 处理请求和响应 ...
    } catch (Exception e) {
        // ... 错误处理 ...
    }
}
```

### 4. Service 层更新

#### 修改文件
- `ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocServiceImpl.java`

#### 修改的方法
所有与腾讯文档 API 交互的方法都进行了更新，在调用 API 前先获取 Open-Id：

##### 4.1 uploadLogisticsToSheet 方法
```java
@Override
public JSONObject uploadLogisticsToSheet(String accessToken, String fileId, String sheetId, List<JDOrder> orders) {
    try {
        // ... 参数验证 ...
        
        // 获取用户信息（包含Open-Id）
        JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
        String openId = userInfo.getString("openId");
        if (openId == null || openId.isEmpty()) {
            throw new RuntimeException("无法获取Open-Id，请检查Access Token是否有效");
        }
        
        // ... 构建数据 ...
        
        // 追加数据到表格
        return TencentDocApiUtil.appendSheetData(
            accessToken, 
            tencentDocConfig.getAppId(), 
            openId, 
            fileId, 
            sheetId, 
            values, 
            tencentDocConfig.getApiBaseUrl()
        );
    } catch (Exception e) {
        // ... 错误处理 ...
    }
}
```

##### 4.2 appendLogisticsToSheet 方法
类似的修改模式：先获取 openId，然后传递给 API 调用。

##### 4.3 readSheetData 方法
```java
@Override
public JSONObject readSheetData(String accessToken, String fileId, String sheetId, String range) {
    try {
        // 获取用户信息（包含Open-Id）
        JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
        String openId = userInfo.getString("openId");
        if (openId == null || openId.isEmpty()) {
            throw new RuntimeException("无法获取Open-Id，请检查Access Token是否有效");
        }
        
        return TencentDocApiUtil.readSheetData(
            accessToken, 
            tencentDocConfig.getAppId(), 
            openId, 
            fileId, 
            sheetId, 
            range, 
            tencentDocConfig.getApiBaseUrl()
        );
    } catch (Exception e) {
        // ... 错误处理 ...
    }
}
```

##### 4.4 writeSheetData 方法
同样的模式。

##### 4.5 getFileInfo 方法
同样的模式。

##### 4.6 getSheetList 方法
同样的模式。

## 完整的修复清单

### 配置文件（3个）
1. ✅ `TencentDocConfig.java` - 修正 API 基础路径
2. ✅ `application-dev.yml` - 修正 API 基础路径
3. ✅ `application-prod.yml` - 修正 API 基础路径

### Util 工具类（1个）
4. ✅ `TencentDocApiUtil.java`
   - 修正 `callApi` 方法签名（添加 clientId, openId 参数）
   - 修正请求头设置（改用 Access-Token, Client-Id, Open-Id）
   - 新增 `getUserInfo` 方法（获取用户信息和 Open-Id）
   - 新增 `callApiLegacy` 方法（支持旧版 OAuth2 接口）
   - 修正 `readSheetData` 方法（更新 API 路径和参数）
   - 修正 `writeSheetData` 方法（更新 API 路径和参数）
   - 修正 `appendSheetData` 方法（更新 API 路径和参数）
   - 修正 `getFileInfo` 方法（更新 API 路径和参数）
   - 修正 `getSheetList` 方法（更新 API 路径和参数）

### Service 服务类（1个）
5. ✅ `TencentDocServiceImpl.java`
   - 修正 `uploadLogisticsToSheet` 方法（添加 Open-Id 获取逻辑）
   - 修正 `appendLogisticsToSheet` 方法（添加 Open-Id 获取逻辑）
   - 修正 `readSheetData` 方法（添加 Open-Id 获取逻辑）
   - 修正 `writeSheetData` 方法（添加 Open-Id 获取逻辑）
   - 修正 `getFileInfo` 方法（添加 Open-Id 获取逻辑）
   - 修正 `getSheetList` 方法（添加 Open-Id 获取逻辑）

## 官方文档参考

### API 路径规范
```
基础URL：https://docs.qq.com/openapi/spreadsheet/v3

API 端点：
- 批量更新：POST /files/{fileId}/batchUpdate
- 获取文件信息：GET /files/{fileId}
- 读取表格数据：GET /files/{fileId}/{sheetId}/{range}
```

### 鉴权方式规范
根据官方文档（https://docs.qq.com/open/document/app/openapi/v3/sheet/batchUpdate.html），
所有 V3 API 请求必须包含以下三个请求头：

```http
Access-Token: ACCESS_TOKEN
Client-Id: CLIENT_ID
Open-Id: OPEN_ID
```

### Open-Id 获取
通过 OAuth2 用户信息接口获取：
```
GET https://docs.qq.com/oauth/v2/userinfo
Authorization: Bearer ACCESS_TOKEN
```

响应示例：
```json
{
  "openId": "用户的开放平台ID",
  "unionId": "用户的联合ID",
  "nickname": "用户昵称",
  ...
}
```

## 测试建议

### 1. 配置验证
```bash
# 检查配置文件中的 API 基础地址是否正确
grep "api-base-url" ruoyi-admin/src/main/resources/application-*.yml
```

### 2. 编译验证
```bash
cd ruoyi-java
mvn clean compile
```

### 3. 功能测试步骤
1. 启动应用
2. 进行 OAuth2 授权，获取 Access Token
3. 调用 `getUserInfo` API，验证是否能正确获取 Open-Id
4. 调用 `getFileInfo` API，验证是否能正确访问文档
5. 调用 `readSheetData` API，验证是否能正确读取数据
6. 调用 `writeSheetData` API，验证是否能正确写入数据
7. 调用 `appendSheetData` API，验证是否能正确追加数据

### 4. 错误排查
如果仍然出现 404 错误：
- 检查 fileId 是否正确
- 检查 sheetId 是否正确
- 检查 Access Token 是否有效
- 检查 Open-Id 是否成功获取
- 检查网络连接和代理设置

如果出现 401 错误：
- 检查 Access Token 是否过期
- 检查 Client-Id (AppId) 是否正确
- 检查 Open-Id 是否正确
- 检查用户是否有权限访问该文档

## 注意事项

1. **代理设置**：代码中已添加 `Proxy.NO_PROXY` 设置，确保直接连接腾讯文档 API，避免代理干扰。

2. **Open-Id 获取**：每次调用 V3 API 前都会先调用 getUserInfo 获取 Open-Id。如果频繁调用可能影响性能，建议后续优化为缓存机制。

3. **错误处理**：所有 API 调用都包含完善的错误处理和日志记录，便于问题排查。

4. **API 版本**：确保使用 V3 版本的 API，V1 和 V2 版本可能已经废弃或行为不同。

5. **鉴权方式差异**：
   - V3 Spreadsheet API：使用 `Access-Token`, `Client-Id`, `Open-Id` 三个请求头
   - OAuth2 用户信息 API：使用 `Authorization: Bearer {token}` 请求头

## 总结

本次修复完全基于腾讯文档开放平台官方 V3 API 文档，修正了以下核心问题：

1. ✅ API 基础路径从 `/open/v1` 修正为 `/openapi/spreadsheet/v3`
2. ✅ API 端点路径从 `/spreadsheets/` 修正为 `/files/`
3. ✅ 鉴权方式从 `Authorization: Bearer` 修正为 `Access-Token`, `Client-Id`, `Open-Id` 三个独立请求头
4. ✅ Service 层所有调用都已更新以支持新的鉴权方式
5. ✅ 新增 `getUserInfo` 方法自动获取 Open-Id

所有修改已通过代码编译检查，无 lint 错误。接下来需要进行实际的集成测试以验证 API 调用是否正常。

