11 KiB
11 KiB
写入物流链接失败 - 根本原因修复
🔴 问题描述
现象:
- ✅ 读取表头成功
- ✅ 读取数据行成功
- ✅ 数据库匹配成功(找到订单和物流链接)
- ❌ 物流链接没有写入表格
用户反馈:
"匹配成功了,物流单号没有写入表里"
🔍 根本原因
错误的 API 调用
TencentDocApiUtil.writeSheetData 方法使用了根本不存在的 API:
// ❌ 错误的实现
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
根据腾讯文档官方文档,写入数据必须使用 batchUpdate 接口:
正确的 API 规范
| 项目 | 正确值 |
|---|---|
| 路径 | /openapi/spreadsheet/v3/files/{fileId}/batchUpdate |
| 方法 | POST |
| 请求体 | { "requests": [{ "updateCells": {...} }] } |
示例请求
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 方法
修改前(错误):
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());
}
修改后(正确):
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)转换为行列索引:
/**
* 解析 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. 添加导入
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 层)
// 例如:写入 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 层)
{
"requests": [
{
"updateCells": {
"range": {
"sheetId": "BB08J2",
"startRowIndex": 2,
"endRowIndex": 3,
"startColumnIndex": 12,
"endColumnIndex": 13
},
"rows": [
{
"values": [
{
"cellValue": {
"text": "6649902864"
}
}
]
}
]
}
}
]
}
API 响应
成功响应:
{
"ret": 0,
"msg": "Succeed",
"data": {
"replies": []
}
}
错误响应(使用旧的 PUT 方法):
{
"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. 单元格位置解析测试
// 测试 parseA1Notation
int[] pos1 = parseA1Notation("A1"); // [0, 0]
int[] pos2 = parseA1Notation("M3"); // [2, 12]
int[] pos3 = parseA1Notation("Z100"); // [99, 25]
2. 完整写入测试
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
}'
预期结果:
{
"msg": "填充物流链接完成",
"code": 200,
"data": {
"filledCount": 45,
"skippedCount": 3,
"errorCount": 0,
"message": "处理完成:成功填充 45 条,跳过 3 条,错误 0 条"
}
}
3. 表格验证
打开腾讯文档表格,检查"物流单号"列(M列)是否已填入物流单号。
📚 相关官方文档
⚠️ 关键提醒
1. 腾讯文档 V3 API 没有直接的"写入"接口
❌ 错误观念:
PUT /files/{fileId}/{sheetId}/{range}- 不存在- 直接写入范围数据 - 不支持
✅ 正确做法:
- 使用
POST /files/{fileId}/batchUpdate - 通过
updateCells请求更新单元格
2. Range 格式的差异
读取数据(GET 接口):
- 使用 A1 表示法:
A3:Z52 - 直接放在 URL 路径中
写入数据(batchUpdate):
- 需要转换为索引格式
- 在请求体的
range对象中指定:{ "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 或其他错误,但被忽略或未正确处理)。
解决方案
- ✅ 使用官方标准的
batchUpdateAPI - ✅ 实现 A1 表示法到索引的转换
- ✅ 构建符合官方规范的请求体结构
- ✅ 添加详细的日志记录
关键修改
- API 路径:
/files/{fileId}/{sheetId}/{range}→/files/{fileId}/batchUpdate - HTTP 方法:
PUT→POST - 请求体:简单 values → 完整 requests 结构
文档版本:1.0
创建时间:2025-11-05
依据:腾讯文档开放平台官方 API 文档
状态:✅ 已修复