# 腾讯文档 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列 → 索引 25（A=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列 → 索引 12（A=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" 错误

