This commit is contained in:
2025-11-06 11:05:22 +08:00
parent 763d9985fa
commit 7860df5c2e
4 changed files with 375 additions and 8 deletions

View File

@@ -0,0 +1,351 @@
# 腾讯文档 API Range 格式说明
## 问题发现
在调用腾讯文档 V3 API 时,使用 Excel 格式的 range`A3:Z203`)会返回错误:
```json
{
"code": 400001,
"message": "invalid param error: 'range' invalid"
}
```
## ✅ 正确的 Range 格式
腾讯文档 V3 API 使用**索引格式**,不是 Excel 的字母+数字格式。
### 格式规范
```
startRow,startColumn,endRow,endColumn
```
**说明**
- 所有索引**从 0 开始**
- 用逗号分隔四个值
- `startRow`:起始行索引
- `startColumn`:起始列索引
- `endRow`:结束行索引
- `endColumn`:结束列索引
---
## 📋 格式转换示例
### 示例 1读取表头第2行A到Z列
**Excel 格式**(错误):
```
A2:Z2
```
**索引格式**(正确):
```
1,0,1,25
```
**解释**
- 第2行 → 索引 1行从0开始
- A列 → 索引 0
- 第2行 → 索引 1
- Z列 → 索引 25A=0, B=1, ..., Z=25
---
### 示例 2读取数据行第3行到第203行A到Z列
**Excel 格式**(错误):
```
A3:Z203
```
**索引格式**(正确):
```
2,0,202,25
```
**解释**
- 第3行 → 索引 2
- A列 → 索引 0
- 第203行 → 索引 202
- Z列 → 索引 25
---
### 示例 3读取单个单元格M3
**Excel 格式**(错误):
```
M3
```
**索引格式**(正确):
```
2,12,2,12
```
**解释**
- 第3行 → 索引 2
- M列 → 索引 12A=0, ..., M=12
- 结束行也是第3行 → 索引 2
- 结束列也是M列 → 索引 12
---
## 🔢 行列索引对照表
### 行索引Excel行号 → 索引)
| Excel 行号 | 索引 |
|-----------|------|
| 1 | 0 |
| 2 | 1 |
| 3 | 2 |
| ... | ... |
| 100 | 99 |
| 203 | 202 |
**转换公式**`索引 = Excel行号 - 1`
### 列索引(列字母 → 索引)
| 列字母 | 索引 |
|-------|------|
| A | 0 |
| B | 1 |
| C | 2 |
| D | 3 |
| ... | ... |
| M | 12 |
| ... | ... |
| Z | 25 |
| AA | 26 |
| AB | 27 |
**转换公式**
- 单字母:`索引 = 字母 - 'A'`A=0, B=1, ...
- 多字母:`索引 = (第一字母 - 'A' + 1) * 26 + (第二字母 - 'A')`
---
## 💻 代码实现
### Java 转换方法
```java
/**
* 将Excel行号转换为API索引
* @param excelRow Excel行号从1开始
* @return API索引从0开始
*/
public static int excelRowToIndex(int excelRow) {
return excelRow - 1;
}
/**
* 将列字母转换为索引
* @param column 列字母A, B, C, ..., Z, AA, AB, ...
* @return 列索引从0开始
*/
public static int columnLetterToIndex(String column) {
column = column.toUpperCase();
int index = 0;
for (int i = 0; i < column.length(); i++) {
index = index * 26 + (column.charAt(i) - 'A' + 1);
}
return index - 1;
}
/**
* 将Excel范围转换为API range格式
* @param excelRange Excel范围如 "A3:Z203"
* @return API range格式如 "2,0,202,25"
*/
public static String excelRangeToApiRange(String excelRange) {
// 解析 A3:Z203
String[] parts = excelRange.split(":");
String start = parts[0]; // A3
String end = parts[1]; // Z203
// 提取起始列和行
int startCol = columnLetterToIndex(start.replaceAll("\\d", "")); // A -> 0
int startRow = Integer.parseInt(start.replaceAll("[A-Z]", "")) - 1; // 3 -> 2
// 提取结束列和行
int endCol = columnLetterToIndex(end.replaceAll("\\d", "")); // Z -> 25
int endRow = Integer.parseInt(end.replaceAll("[A-Z]", "")) - 1; // 203 -> 202
return String.format("%d,%d,%d,%d", startRow, startCol, endRow, endCol);
}
```
### 使用示例
```java
// 读取表头第2行
int headerRowIndex = headerRow - 1; // 2 -> 1
String headerRange = String.format("%d,0,%d,25", headerRowIndex, headerRowIndex);
// 结果:"1,0,1,25"
// 读取数据行第3行到第203行
int startRowIndex = startRow - 1; // 3 -> 2
int endRowIndex = endRow - 1; // 203 -> 202
String dataRange = String.format("%d,0,%d,25", startRowIndex, endRowIndex);
// 结果:"2,0,202,25"
// 读取单个单元格M3
int rowIndex = 2; // 第3行 -> 索引2
int colIndex = 12; // M列 -> 索引12
String cellRange = String.format("%d,%d,%d,%d", rowIndex, colIndex, rowIndex, colIndex);
// 结果:"2,12,2,12"
```
---
## 🔍 API 调用示例
### 完整的 API URL
```
https://docs.qq.com/openapi/spreadsheet/v3/files/{fileId}/{sheetId}/{range}
```
**示例**
```
# 读取表头第2行
https://docs.qq.com/openapi/spreadsheet/v3/files/DUW50RUprWXh2TGJK/BB08J2/1,0,1,25
# 读取数据行第3行到第203行
https://docs.qq.com/openapi/spreadsheet/v3/files/DUW50RUprWXh2TGJK/BB08J2/2,0,202,25
# 读取单个单元格M3
https://docs.qq.com/openapi/spreadsheet/v3/files/DUW50RUprWXh2TGJK/BB08J2/2,12,2,12
```
---
## ⚠️ 常见错误
### 错误 1使用 Excel 格式
```
❌ https://.../ files/xxx/yyy/A3:Z203
✅ https://.../files/xxx/yyy/2,0,202,25
```
### 错误 2索引从 1 开始
```
❌ 第1行 → 索引 1
✅ 第1行 → 索引 0
❌ A列 → 索引 1
✅ A列 → 索引 0
```
### 错误 3行列顺序错误
```
❌ startColumn,startRow,endColumn,endRow
✅ startRow,startColumn,endRow,endColumn
```
正确顺序:**行在前,列在后**
---
## 📊 快速参考表
| Excel 表示 | 索引格式 | 说明 |
|-----------|---------|------|
| A1:Z1 | 0,0,0,25 | 第1行A到Z列 |
| A2:Z2 | 1,0,1,25 | 第2行A到Z列表头 |
| A3:Z203 | 2,0,202,25 | 第3行到第203行A到Z列 |
| A1:A100 | 0,0,99,0 | A列前100行 |
| M3 | 2,12,2,12 | M列第3行单个单元格 |
| A1 | 0,0,0,0 | A1单元格 |
| AA1:AZ100 | 0,26,99,51 | AA到AZ列前100行 |
---
## 🔧 修改记录
### 修改文件
1.`TencentDocApiUtil.java`
- 更新 `readSheetData` 方法的注释
- 说明 range 格式为索引格式
2.`TencentDocController.java`
- 将 range 构建从 Excel 格式改为索引格式
- 添加详细的转换日志
3.`TencentDocServiceImpl.java`
- 添加 API 错误响应检查
- 当 code != 0 时抛出异常
---
## 🎯 测试验证
### 测试参数
```json
{
"accessToken": "YOUR_ACCESS_TOKEN",
"fileId": "DUW50RUprWXh2TGJK",
"sheetId": "BB08J2",
"headerRow": 2,
"orderNoColumn": 2,
"logisticsLinkColumn": 12
}
```
### 预期日志输出
```
读取表头 - Excel行号: 2, 索引行号: 1, range: 1,0,1,25
读取数据行 - Excel行号: 3 ~ 203, 索引: 2 ~ 202, range: 2,0,202,25
```
### 成功响应
```json
{
"gridData": {
"startRow": 1,
"startColumn": 0,
"rows": [
{
"values": [
{"cellValue": {"text": "日期"}},
{"cellValue": {"text": "公司"}},
{"cellValue": {"text": "草号"}},
...
]
}
]
}
}
```
---
## 📚 参考文档
根据实际API测试结果和错误提示总结的格式规范。
**关键要点**
1. ✅ Range 使用索引格式:`startRow,startColumn,endRow,endColumn`
2. ✅ 所有索引从 0 开始
3. ✅ 顺序:行在前,列在后
4. ✅ Excel行号需要减1转换为索引
---
**文档版本**1.0
**创建时间**2025-11-05
**修改原因**:修复 "invalid param error: 'range' invalid" 错误

View File

@@ -504,16 +504,16 @@ public class TencentDocController extends BaseController {
// 计算读取范围从起始行开始读取足够多的行假设每次最多处理200行
int endRow = startRow + 200; // 每次最多读取200行
String startColumn = "A";
String endColumn = "Z";
String range = String.format("%s%d:%s%d", startColumn, startRow, endColumn, endRow);
log.info("开始填充物流链接 - 文件ID: {}, 工作表ID: {}, 起始行: {}, 结束行: {}, 上次最大行: {}",
fileId, sheetId, startRow, endRow, lastMaxRow);
// 读取表格数据(先读取表头行用于识别列位置)
String headerRange = String.format("%s%d:%s%d", startColumn, headerRow, endColumn, headerRow);
log.info("读取表头 - 范围: {}", headerRange);
// 腾讯文档V3 API的range格式startRow,startColumn,endRow,endColumn从0开始的索引
// 例如第2行A列到Z列 = "1,0,1,25"行索引从0开始所以第2行是索引1
int headerRowIndex = headerRow - 1; // Excel行号转索引第2行 = 索引1
String headerRange = String.format("%d,0,%d,25", headerRowIndex, headerRowIndex); // 读取A到Z列0-25
log.info("读取表头 - Excel行号: {}, 索引行号: {}, range: {}", headerRow, headerRowIndex, headerRange);
JSONObject headerData = null;
try {
@@ -566,7 +566,12 @@ public class TencentDocController extends BaseController {
}
// 读取数据行
log.info("开始读取数据行 - 范围: {}", range);
// 构建数据行的range从startRow到endRowA列到Z列
int startRowIndex = startRow - 1; // Excel行号转索引
int endRowIndex = endRow - 1; // Excel行号转索引
String range = String.format("%d,0,%d,25", startRowIndex, endRowIndex); // 读取A到Z列0-25
log.info("开始读取数据行 - Excel行号: {} ~ {}, 索引: {} ~ {}, range: {}", startRow, endRow, startRowIndex, endRowIndex, range);
JSONObject sheetData = null;
try {
sheetData = tencentDocService.readSheetData(accessToken, fileId, sheetId, range);

View File

@@ -263,6 +263,16 @@ public class TencentDocServiceImpl implements ITencentDocService {
log.info("API调用成功原始返回结果: {}", result != null ? result.toJSONString() : "null");
// 检查API响应中的错误码
if (result != null && result.containsKey("code")) {
Integer code = result.getInteger("code");
if (code != null && code != 0) {
String message = result.getString("message");
log.error("腾讯文档API返回错误 - code: {}, message: {}", code, message);
throw new RuntimeException("腾讯文档API错误: " + message + " (code: " + code + ")");
}
}
// 解析数据为统一的简单格式
JSONArray parsedValues = TencentDocDataParser.parseToSimpleArray(result);
log.info("解析后的数据行数: {}", parsedValues != null ? parsedValues.size() : 0);

View File

@@ -336,12 +336,13 @@ public class TencentDocApiUtil {
* @param openId 开放平台用户ID
* @param fileId 文件ID在线表格的唯一标识
* @param sheetId 工作表ID可从表格链接中获取如 ?tab=BB08J2 中的 BB08J2
* @param range 范围,例如 "A1:Z100"行列从0开始遵循左闭右开原则
* @param range 范围,格式startRow,startColumn,endRow,endColumn从0开始例如"0,0,10,26"表示A1Z11
* @param apiBaseUrl API基础地址默认https://docs.qq.com/openapi/spreadsheet/v3
* @return 表格数据JSON格式包含values数组
* @return 表格数据JSON格式包含gridData
*/
public static JSONObject readSheetData(String accessToken, String appId, String openId, String fileId, String sheetId, String range, String apiBaseUrl) {
// V3版本API路径格式/openapi/spreadsheet/v3/files/{fileId}/{sheetId}/{range}
// range格式startRow,startColumn,endRow,endColumn从0开始的索引
String apiUrl = String.format("%s/files/%s/%s/%s", apiBaseUrl, fileId, sheetId, range);
log.info("读取表格数据 - fileId: {}, sheetId: {}, range: {}, apiUrl: {}", fileId, sheetId, range, apiUrl);
return callApi(accessToken, appId, openId, apiUrl, "GET", null);