Files
ruoyi-java/doc/腾讯文档API数据格式解析说明.md
2025-11-06 10:46:01 +08:00

12 KiB
Raw Blame History

腾讯文档 API 数据格式解析说明

问题发现

在实际调用腾讯文档 V3 API 时,发现返回的数据格式与预期完全不同。


数据格式对比

我们最初预期的格式(简单格式)

{
  "values": [
    ["单元格1", "单元格2", "单元格3"],
    ["数据1", "数据2", "数据3"]
  ]
}

实际返回的格式gridData 格式)

{
  "gridData": {
    "startRow": 0,
    "startColumn": 0,
    "rows": [
      {
        "values": [
          {
            "cellValue": {
              "text": "JY202506181808"
            },
            "cellFormat": {
              "textFormat": {
                "font": "Microsoft YaHei",
                "fontSize": 11,
                "bold": false,
                "italic": false,
                "strikethrough": false,
                "underline": false,
                "color": {
                  "red": 0,
                  "green": 0,
                  "blue": 0,
                  "alpha": 255
                }
              },
              "horizontalAlignment": "HORIZONTAL_ALIGNMENT_UNSPECIFIED",
              "verticalAlignment": "VERTICAL_ALIGNMENT_UNSPECIFIED"
            },
            "dataType": "DATA_TYPE_UNSPECIFIED"
          },
          {
            "cellValue": {
              "text": ""
            },
            ...
          }
        ]
      }
    ],
    "rowMetadata": [],
    "columnMetadata": []
  },
  "version": "0"
}

格式差异分析

数据层级

简单格式

响应
└── values (数组)
    ├── 行1 (数组)
    │   ├── "单元格1"
    │   └── "单元格2"
    └── 行2 (数组)
        ├── "数据1"
        └── "数据2"

gridData 格式

响应
└── gridData (对象)
    ├── startRow (数字)
    ├── startColumn (数字)
    ├── rows (数组)
    │   └── 行对象
    │       └── values (数组)
    │           └── 单元格对象
    │               ├── cellValue (对象)
    │               │   └── text (字符串) ← 实际文本内容在这里
    │               ├── cellFormat (对象)
    │               └── dataType (字符串)
    ├── rowMetadata (数组)
    └── columnMetadata (数组)

关键区别

项目 简单格式 gridData 格式
根字段 values gridData
行数据 直接数组 gridData.rows
单元格数据 直接字符串 cellValue.text
格式信息 cellFormat
元数据 rowMetadatacolumnMetadata

解决方案

1. 创建数据解析器

我们创建了 TencentDocDataParser 工具类来统一处理两种格式:

位置ruoyi-system/src/main/java/com/ruoyi/jarvis/util/TencentDocDataParser.java

核心功能

(1) 解析为简单数组格式

JSONArray parsedValues = TencentDocDataParser.parseToSimpleArray(apiResponse);

输入gridData 格式):

{
  "gridData": {
    "rows": [
      {
        "values": [
          {"cellValue": {"text": "单元格1"}},
          {"cellValue": {"text": "单元格2"}}
        ]
      }
    ]
  }
}

输出(简单格式):

[
  ["单元格1", "单元格2"]
]

(2) 获取指定行数据

JSONArray row = TencentDocDataParser.getRow(apiResponse, 0);  // 获取第1行

(3) 获取指定单元格文本

String cellText = TencentDocDataParser.getCellText(apiResponse, 0, 2);  // 第1行第3列

(4) 打印数据结构(调试用)

TencentDocDataParser.printDataStructure(apiResponse, 5);  // 打印前5行

2. 更新 Service 层

TencentDocServiceImpl.javareadSheetData 方法中:

// API 调用
JSONObject result = TencentDocApiUtil.readSheetData(...);

// 解析数据为统一的简单格式
JSONArray parsedValues = TencentDocDataParser.parseToSimpleArray(result);

// 返回包含简化格式的响应
JSONObject response = new JSONObject();
response.put("values", parsedValues);  // 统一格式
response.put("_原始数据", result);     // 保留原始数据供调试

return response;

3. 向后兼容性

解析器会自动检测数据格式:

  • 如果有 gridData 字段 → 解析为 gridData 格式
  • 如果有 values 字段 → 直接返回(简单格式)
  • 如果都没有 → 返回空数组
public static JSONArray parseToSimpleArray(JSONObject apiResponse) {
    // 方式1检查是否有 gridData 字段V3 API 新格式)
    JSONObject gridData = apiResponse.getJSONObject("gridData");
    if (gridData != null) {
        return parseGridData(gridData);
    }
    
    // 方式2检查是否有 values 字段(简单格式)
    JSONArray values = apiResponse.getJSONArray("values");
    if (values != null) {
        return values;
    }
    
    // 如果都没有,返回空数组
    return new JSONArray();
}

使用示例

示例 1读取表头

// 读取第2行索引为1作为表头
String headerRange = "A2:Z2";
JSONObject headerData = tencentDocService.readSheetData(accessToken, fileId, sheetId, headerRange);

// 获取简化后的数据
JSONArray headerValues = headerData.getJSONArray("values");
if (headerValues != null && !headerValues.isEmpty()) {
    JSONArray headerRow = headerValues.getJSONArray(0);  // 第一行数据
    
    // 遍历表头列
    for (int i = 0; i < headerRow.size(); i++) {
        String columnName = headerRow.getString(i);
        System.out.println("第 " + (i+1) + " 列: " + columnName);
    }
}

示例 2查找特定列

// 读取表头行
JSONObject headerData = tencentDocService.readSheetData(accessToken, fileId, sheetId, "A2:Z2");
JSONArray headerValues = headerData.getJSONArray("values");
JSONArray headerRow = headerValues.getJSONArray(0);

// 查找"物流单号"列的索引
int logisticsColumn = -1;
for (int i = 0; i < headerRow.size(); i++) {
    String columnName = headerRow.getString(i);
    if ("物流单号".equals(columnName)) {
        logisticsColumn = i;
        break;
    }
}

System.out.println("物流单号列在第 " + (logisticsColumn + 1) + " 列(索引: " + logisticsColumn + "");

示例 3读取数据行

// 读取数据行第3行到第100行
JSONObject sheetData = tencentDocService.readSheetData(accessToken, fileId, sheetId, "A3:Z100");
JSONArray dataValues = sheetData.getJSONArray("values");

// 遍历每一行
for (int i = 0; i < dataValues.size(); i++) {
    JSONArray row = dataValues.getJSONArray(i);
    
    // 获取订单号假设在第1列索引0
    String orderNo = row.getString(0);
    
    // 获取物流单号假设在第13列索引12
    String logisticsNo = row.getString(12);
    
    System.out.println("订单号: " + orderNo + ", 物流单号: " + logisticsNo);
}

真实数据示例

根据用户提供的截图,表格结构:

第1行合并单元格包含链接这是合并的标题行
第2行表头
  A列日期
  B列公司
  C列草号
  D列型号
  E列数量
  F列姓名
  G列电话
  H列地址
  I列价格
  J列备注
  K列打聚戳图
  L列是否安排
  M列物流单号
  N列标记

第3行及以后数据行
  A列3月10日
  B列
  C列JY20251032904
  ...
  M列物流单号可能为空

处理代码示例

// 1. 读取表头第2行
JSONObject headerData = tencentDocService.readSheetData(accessToken, fileId, sheetId, "A2:Z2");
JSONArray headerValues = headerData.getJSONArray("values");
JSONArray headerRow = headerValues.getJSONArray(0);

// 2. 查找关键列的索引
int orderNoColumn = -1;      // 订单号列(草号)
int logisticsColumn = -1;    // 物流单号列

for (int i = 0; i < headerRow.size(); i++) {
    String columnName = headerRow.getString(i);
    if (columnName != null) {
        if (columnName.contains("草号")) {
            orderNoColumn = i;
        }
        if (columnName.contains("物流单号")) {
            logisticsColumn = i;
        }
    }
}

System.out.println("订单号列索引: " + orderNoColumn);        // 预期: 2C列
System.out.println("物流单号列索引: " + logisticsColumn);    // 预期: 12M列

// 3. 读取数据行从第3行开始
JSONObject sheetData = tencentDocService.readSheetData(accessToken, fileId, sheetId, "A3:Z100");
JSONArray dataValues = sheetData.getJSONArray("values");

// 4. 处理每一行数据
for (int i = 0; i < dataValues.size(); i++) {
    JSONArray row = dataValues.getJSONArray(i);
    
    // 获取订单号
    String orderNo = orderNoColumn >= 0 && orderNoColumn < row.size() 
        ? row.getString(orderNoColumn) 
        : null;
    
    // 获取物流单号
    String logisticsNo = logisticsColumn >= 0 && logisticsColumn < row.size() 
        ? row.getString(logisticsColumn) 
        : null;
    
    if (orderNo != null && !orderNo.isEmpty()) {
        System.out.println("第 " + (i+3) + " 行 - 订单号: " + orderNo + ", 物流单号: " + logisticsNo);
        
        // 如果物流单号为空,可以填充
        if (logisticsNo == null || logisticsNo.isEmpty()) {
            System.out.println("  → 需要填充物流单号");
        }
    }
}

写入数据注意事项

写入接口的数据格式

根据腾讯文档 API 规范,写入数据时仍然使用简单格式:

// 写入数据(简单格式)
Object[][] values = {
    {"数据1", "数据2", "数据3"}
};

tencentDocService.writeSheetData(accessToken, fileId, sheetId, "A10", values);

不需要转换为 gridData 格式API 会自动处理。


调试技巧

1. 启用详细日志

logging:
  level:
    com.ruoyi.jarvis.util.TencentDocDataParser: DEBUG
    com.ruoyi.jarvis.service.impl.TencentDocServiceImpl: DEBUG

2. 查看数据结构

当调用 readSheetData会自动打印前3行数据结构

数据结构(共 98 行,显示前 3 行):
  第 1 行15列: ["日期","公司","草号",...,"物流单号","标记"]
  第 2 行15列: ["3月10日","","JY20251032904",...,"",""]
  第 3 行15列: ["3月10日","","JY20250309184",...,"6649902864",""]

3. 检查原始响应

Service 返回的数据中包含 _原始数据 字段,可以查看 API 的原始响应:

JSONObject result = tencentDocService.readSheetData(...);
JSONObject originalData = result.getJSONObject("_原始数据");
System.out.println("原始响应: " + originalData.toJSONString());

常见问题

Q1为什么会有两种数据格式

A:腾讯文档 V3 API 使用 gridData 格式以支持更丰富的格式信息(字体、颜色、对齐方式等)。但对于简单的数据读写,我们只需要文本内容,因此解析器会提取纯文本数据。

Q2读取的数据是否包含格式信息

AgridData 格式包含完整的格式信息(字体、颜色等),但我们的解析器只提取文本内容。如果需要格式信息,可以从 _原始数据 字段中获取。

Q3解析器会影响性能吗

A:解析器只是简单的 JSON 遍历和文本提取,性能影响很小。对于大数据量(数千行),建议分批读取。

Q4是否兼容旧代码

A:完全兼容。解析后的数据格式与旧代码期望的格式一致({"values": [[]]}),无需修改现有代码。


总结

关键要点

  1. 腾讯文档 V3 API 使用 gridData 格式
  2. 创建了 TencentDocDataParser 统一处理
  3. Service 层自动解析为简单格式
  4. 完全向后兼容,无需修改上层代码
  5. 保留原始数据供调试使用

文件清单

  • TencentDocDataParser.java - 数据解析器(新增)
  • TencentDocServiceImpl.java - Service 层(已更新)
  • 腾讯文档API数据格式解析说明.md - 本文档(新增)

文档版本1.0
创建时间2025-11-05
适用场景:腾讯文档 V3 API 数据格式解析