380 lines
12 KiB
Markdown
380 lines
12 KiB
Markdown
# 腾讯文档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 调用是否正常。
|
||
|