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

11 KiB
Raw Blame History

写入物流链接失败 - 根本原因修复

🔴 问题描述

现象

  • 读取表头成功
  • 读取数据行成功
  • 数据库匹配成功(找到订单和物流链接)
  • 物流链接没有写入表格

用户反馈

"匹配成功了,物流单号没有写入表里"


🔍 根本原因

错误的 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 或其他错误,但被忽略或未正确处理)。

解决方案

  1. 使用官方标准的 batchUpdate API
  2. 实现 A1 表示法到索引的转换
  3. 构建符合官方规范的请求体结构
  4. 添加详细的日志记录

关键修改

  • API 路径/files/{fileId}/{sheetId}/{range}/files/{fileId}/batchUpdate
  • HTTP 方法PUTPOST
  • 请求体:简单 values → 完整 requests 结构

文档版本1.0
创建时间2025-11-05
依据:腾讯文档开放平台官方 API 文档
状态 已修复