Files
ruoyi-java/doc/写入物流链接失败-根本原因修复.md
2025-11-06 11:33:52 +08:00

447 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 写入物流链接失败 - 根本原因修复
## 🔴 问题描述
**现象**
- ✅ 读取表头成功
- ✅ 读取数据行成功
- ✅ 数据库匹配成功(找到订单和物流链接)
-**物流链接没有写入表格**
**用户反馈**
> "匹配成功了,物流单号没有写入表里"
---
## 🔍 根本原因
### 错误的 API 调用
`TencentDocApiUtil.writeSheetData` 方法使用了**根本不存在的 API**
```java
// ❌ 错误的实现
String apiUrl = String.format("%s/files/%s/%s/%s", apiBaseUrl, fileId, sheetId, range);
// URL: https://docs.qq.com/openapi/spreadsheet/v3/files/{fileId}/{sheetId}/M3
return callApi(accessToken, appId, openId, apiUrl, "PUT", requestBody.toJSONString());
```
**问题**
- ❌ 使用 `PUT` 方法
- ❌ 路径:`/files/{fileId}/{sheetId}/{range}`
-**腾讯文档 V3 API 根本没有这个接口!**
---
## 🎯 正确的 API
根据[腾讯文档官方文档](https://docs.qq.com/open/document/app/openapi/v3/sheet/batchupdate/update.html)**写入数据必须使用 `batchUpdate` 接口**
### 正确的 API 规范
| 项目 | 正确值 |
|------|--------|
| 路径 | `/openapi/spreadsheet/v3/files/{fileId}/batchUpdate` |
| 方法 | `POST` |
| 请求体 | `{ "requests": [{ "updateCells": {...} }] }` |
### 示例请求
```http
POST https://docs.qq.com/openapi/spreadsheet/v3/files/DUW50RUprWXh2TGJK/batchUpdate
Headers:
Access-Token: {ACCESS_TOKEN}
Client-Id: {CLIENT_ID}
Open-Id: {OPEN_ID}
Content-Type: application/json
Body:
{
"requests": [
{
"updateCells": {
"range": {
"sheetId": "BB08J2",
"startRowIndex": 2, // 第3行索引从0开始
"endRowIndex": 3, // 不包含
"startColumnIndex": 12, // 第13列M列索引从0开始
"endColumnIndex": 13 // 不包含
},
"rows": [
{
"values": [
{
"cellValue": {
"text": "6649902864"
}
}
]
}
]
}
}
]
}
```
---
## ✅ 修复方案
### 1. 重写 `writeSheetData` 方法
**修改前**(错误):
```java
public static JSONObject writeSheetData(...) {
// ❌ 使用不存在的 API
String apiUrl = String.format("%s/files/%s/%s/%s", apiBaseUrl, fileId, sheetId, range);
JSONObject requestBody = new JSONObject();
requestBody.put("values", values);
return callApi(accessToken, appId, openId, apiUrl, "PUT", requestBody.toJSONString());
}
```
**修改后**(正确):
```java
public static JSONObject writeSheetData(...) {
// ✅ 使用 batchUpdate API
// 1. 解析 A1 表示法M3 -> row=2, col=12
int[] position = parseA1Notation(range);
int rowIndex = position[0];
int colIndex = position[1];
// 2. 构建 updateCells 请求
JSONObject updateCells = new JSONObject();
JSONObject rangeObj = new JSONObject();
rangeObj.put("sheetId", sheetId);
rangeObj.put("startRowIndex", rowIndex);
rangeObj.put("endRowIndex", rowIndex + 1);
rangeObj.put("startColumnIndex", colIndex);
rangeObj.put("endColumnIndex", colIndex + 1);
updateCells.put("range", rangeObj);
// 3. 构建单元格数据
JSONArray rows = new JSONArray();
JSONObject rowData = new JSONObject();
JSONArray cellValues = new JSONArray();
// 提取文本值
String text = ((JSONArray)values).getJSONArray(0).getString(0);
JSONObject cellData = new JSONObject();
JSONObject cellValue = new JSONObject();
cellValue.put("text", text);
cellData.put("cellValue", cellValue);
cellValues.add(cellData);
rowData.put("values", cellValues);
rows.add(rowData);
updateCells.put("rows", rows);
// 4. 构建 requests
JSONArray requests = new JSONArray();
JSONObject request = new JSONObject();
request.put("updateCells", updateCells);
requests.add(request);
// 5. 构建完整请求体
JSONObject requestBody = new JSONObject();
requestBody.put("requests", requests);
// 6. 调用 batchUpdate API
String apiUrl = String.format("%s/files/%s/batchUpdate", apiBaseUrl, fileId);
return callApi(accessToken, appId, openId, apiUrl, "POST", requestBody.toJSONString());
}
```
---
### 2. 新增 `parseA1Notation` 方法
用于将 A1 表示法(如 `M3`)转换为行列索引:
```java
/**
* 解析 A1 表示法为行列索引
* 例如:
* A1 -> [0, 0]
* M3 -> [2, 12]
* Z100 -> [99, 25]
*/
private static int[] parseA1Notation(String a1Notation) {
// 提取列字母和行号
StringBuilder colLetters = new StringBuilder();
StringBuilder rowNumber = new StringBuilder();
for (char c : a1Notation.toCharArray()) {
if (Character.isLetter(c)) {
colLetters.append(Character.toUpperCase(c));
} else if (Character.isDigit(c)) {
rowNumber.append(c);
}
}
// 列字母转索引A=0, B=1, ..., Z=25, AA=26, ...
int colIndex = 0;
for (int i = 0; i < colLetters.length(); i++) {
colIndex = colIndex * 26 + (colLetters.charAt(i) - 'A' + 1);
}
colIndex -= 1;
// 行号转索引1->0, 2->1, ...
int rowIndex = Integer.parseInt(rowNumber.toString()) - 1;
return new int[]{rowIndex, colIndex};
}
```
**测试用例**
| 输入 | 输出 | 说明 |
|------|------|------|
| `A1` | `[0, 0]` | 第1行A列 |
| `M3` | `[2, 12]` | 第3行M列物流单号列 |
| `Z100` | `[99, 25]` | 第100行Z列 |
| `AA1` | `[0, 26]` | 第1行AA列 |
---
### 3. 添加导入
```java
import com.alibaba.fastjson2.JSONArray; // ✅ 新增
```
---
## 📊 API 对比表
| 对比项 | 错误实现 | 正确实现 |
|--------|----------|----------|
| **API 路径** | `/files/{fileId}/{sheetId}/{range}` | `/files/{fileId}/batchUpdate` ✅ |
| **HTTP 方法** | `PUT` | `POST` ✅ |
| **请求体格式** | `{ "values": [...] }` | `{ "requests": [{ "updateCells": {...} }] }` ✅ |
| **Range 格式** | 直接使用 A1 表示法 | 转换为索引startRowIndex, endRowIndex, ... ✅ |
| **官方文档支持** | ❌ 不存在 | ✅ 官方标准接口 |
---
## 🔄 完整请求流程
### 原始调用Controller 层)
```java
// 例如:写入 M3 单元格
String columnLetter = "M"; // 物流单号列
int row = 3; // Excel 行号
String cellRange = "M3";
JSONArray writeValues = new JSONArray();
JSONArray writeRow = new JSONArray();
writeRow.add("6649902864"); // 物流单号
writeValues.add(writeRow);
tencentDocService.writeSheetData(accessToken, fileId, sheetId, cellRange, writeValues);
```
### 转换后的请求API 层)
```json
{
"requests": [
{
"updateCells": {
"range": {
"sheetId": "BB08J2",
"startRowIndex": 2,
"endRowIndex": 3,
"startColumnIndex": 12,
"endColumnIndex": 13
},
"rows": [
{
"values": [
{
"cellValue": {
"text": "6649902864"
}
}
]
}
]
}
}
]
}
```
### API 响应
**成功响应**
```json
{
"ret": 0,
"msg": "Succeed",
"data": {
"replies": []
}
}
```
**错误响应**(使用旧的 PUT 方法):
```json
{
"code": 404,
"message": "Not Found"
}
```
---
## 📝 修改文件清单
| 文件 | 修改内容 | 状态 |
|------|----------|------|
| `TencentDocApiUtil.java` | 重写 `writeSheetData` 方法,使用 batchUpdate API | ✅ |
| `TencentDocApiUtil.java` | 新增 `parseA1Notation` 方法,解析 A1 表示法 | ✅ |
| `TencentDocApiUtil.java` | 添加 `JSONArray` 导入 | ✅ |
---
## 🎯 预期效果
### 修复前
```
找到订单物流链接 - 单号: JY202506181808, 物流链接: https://..., 行号: 3
写入物流链接失败 - 行: 3, 错误: 404 Not Found
```
### 修复后
```
找到订单物流链接 - 单号: JY202506181808, 物流链接: https://..., 行号: 3
写入表格数据batchUpdate- fileId: DUW50RUprWXh2TGJK, sheetId: BB08J2, range: M3, rowIndex: 2, colIndex: 12
成功写入物流链接 - 单元格: M3, 单号: JY202506181808, 物流链接: https://...
```
---
## 🧪 测试验证
### 1. 单元格位置解析测试
```java
// 测试 parseA1Notation
int[] pos1 = parseA1Notation("A1"); // [0, 0]
int[] pos2 = parseA1Notation("M3"); // [2, 12]
int[] pos3 = parseA1Notation("Z100"); // [99, 25]
```
### 2. 完整写入测试
```bash
curl -X POST 'http://localhost:30313/jarvis/tencentDoc/fillLogisticsByOrderNo' \
-H 'Content-Type: application/json' \
-d '{
"accessToken": "YOUR_ACCESS_TOKEN",
"fileId": "DUW50RUprWXh2TGJK",
"sheetId": "BB08J2",
"headerRow": 2
}'
```
**预期结果**
```json
{
"msg": "填充物流链接完成",
"code": 200,
"data": {
"filledCount": 45,
"skippedCount": 3,
"errorCount": 0,
"message": "处理完成:成功填充 45 条,跳过 3 条,错误 0 条"
}
}
```
### 3. 表格验证
打开腾讯文档表格,检查"物流单号"列M列是否已填入物流单号。
---
## 📚 相关官方文档
- [批量更新接口batchUpdate](https://docs.qq.com/open/document/app/openapi/v3/sheet/batchupdate/update.html) ⭐⭐⭐
- [UpdateCellsRequest 参数说明](https://docs.qq.com/open/document/app/openapi/v3/sheet/batchupdate/update.html#updatecellsrequest)
- [在线表格资源描述](https://docs.qq.com/open/document/app/openapi/v3/sheet/model/spreadsheet.html)
---
## ⚠️ 关键提醒
### 1. 腾讯文档 V3 API 没有直接的"写入"接口
**错误观念**
- `PUT /files/{fileId}/{sheetId}/{range}` - 不存在
- 直接写入范围数据 - 不支持
**正确做法**
- 使用 `POST /files/{fileId}/batchUpdate`
- 通过 `updateCells` 请求更新单元格
### 2. Range 格式的差异
**读取数据**GET 接口):
- 使用 A1 表示法:`A3:Z52`
- 直接放在 URL 路径中
**写入数据**batchUpdate
- 需要转换为索引格式
- 在请求体的 `range` 对象中指定:
```json
{
"startRowIndex": 2,
"endRowIndex": 3,
"startColumnIndex": 12,
"endColumnIndex": 13
}
```
### 3. 索引从 0 开始
| Excel 概念 | API 索引 |
|-----------|----------|
| 第 1 行 | rowIndex = 0 |
| 第 3 行 | rowIndex = 2 |
| A 列 | columnIndex = 0 |
| M 列 | columnIndex = 12 |
---
## ✅ 总结
### 问题本质
之前的代码使用了**根本不存在的 API 接口**,导致所有写入操作都静默失败(可能返回 404 或其他错误,但被忽略或未正确处理)。
### 解决方案
1. ✅ 使用官方标准的 `batchUpdate` API
2. ✅ 实现 A1 表示法到索引的转换
3. ✅ 构建符合官方规范的请求体结构
4. ✅ 添加详细的日志记录
### 关键修改
- **API 路径**`/files/{fileId}/{sheetId}/{range}` → `/files/{fileId}/batchUpdate`
- **HTTP 方法**`PUT` → `POST`
- **请求体**:简单 values → 完整 requests 结构
---
**文档版本**1.0
**创建时间**2025-11-05
**依据**:腾讯文档开放平台官方 API 文档
**状态**:✅ 已修复