From 0b6ba14f2f31675968f3a791d712ffb235aae527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=92?= Date: Thu, 6 Nov 2025 11:25:17 +0800 Subject: [PATCH] 1 --- doc/物流链接自动填充-完整逻辑说明.md | 477 ++++++++++++++++++ .../jarvis/TencentDocController.java | 6 +- 2 files changed, 481 insertions(+), 2 deletions(-) create mode 100644 doc/物流链接自动填充-完整逻辑说明.md diff --git a/doc/物流链接自动填充-完整逻辑说明.md b/doc/物流链接自动填充-完整逻辑说明.md new file mode 100644 index 0000000..570e755 --- /dev/null +++ b/doc/物流链接自动填充-完整逻辑说明.md @@ -0,0 +1,477 @@ +# 物流链接自动填充 - 完整逻辑说明 + +## 📋 功能概述 + +自动读取腾讯文档表格中的订单信息,根据**第三方单号**(thirdPartyOrderNo)在数据库中查询对应的物流链接,并自动填充到表格的"物流单号"列。 + +--- + +## 🔄 完整执行流程 + +### 第一步:读取表头(识别列位置) + +``` +输入参数: +- fileId: 文件ID +- sheetId: 工作表ID +- headerRow: 表头所在行号(默认:2) + +执行逻辑: +1. 构建 range:A{headerRow}:Z{headerRow}(例如:A2:Z2) +2. 调用 API 读取表头数据 +3. 解析表头,识别关键列的位置: + - "单号" 列(orderNoColumn)→ 这是第三方单号列 + - "物流单号" 列(logisticsLinkColumn)→ 要写入物流信息的列 +``` + +**示例日志**: +``` +读取表头 - 行号: 2, range: A2:Z2 +解析后的数据行数: 1 +第 1 行(26列): ["日期","公司","单号","型号","数量",...,"物流单号","","标记",...] +识别到关键列: + - "单号" 列位置: 第 3 列(索引2) + - "物流单号" 列位置: 第 13 列(索引12) +``` + +--- + +### 第二步:读取数据行(批量处理) + +``` +输入参数: +- startRow: 起始行号(表头行 + 1,默认:3) +- endRow: 结束行号(startRow + 49,每次读取50行) + +执行逻辑: +1. 构建 range:A{startRow}:Z{endRow}(例如:A3:Z52) +2. 调用 API 读取数据行 +3. 如果读取失败(range超出实际范围),返回"没有更多数据" +4. 如果成功,继续第三步 +``` + +**示例日志**: +``` +开始读取数据行 - 行号: 3 ~ 52, range: A3:Z52 +解析后的数据行数: 50 +``` + +**⚠️ 重要限制**: +- A1 表示法的 range **不能超出表格实际数据区域** +- 如果表格只有 100 行,`A3:Z200` 会报错:`invalid param error: 'range' invalid` +- 因此每次只读取 **50 行**,避免超出范围 + +--- + +### 第三步:逐行处理(匹配+填充) + +``` +对于每一行数据: + +1. 提取第三方单号 + - 从 orderNoColumn("单号"列)取值 + - 例如:row[2] = "JY202506181808" + +2. 数据库查询 + - 调用:jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNo) + - 查询条件:third_party_order_no = "JY202506181808" + - 返回:JDOrder 对象(包含 logisticsLink 字段) + +3. 检查物流链接 + - 如果 order == null:跳过,errorCount++ + - 如果 logisticsLink 为空:跳过,errorCount++ + - 如果 logisticsLink 不为空:继续第4步 + +4. 写入物流链接 + - 目标单元格:第 {当前行号} 行,第 {logisticsLinkColumn} 列 + - 例如:第 3 行,第 13 列(M3) + - 调用 batchUpdate API 写入物流链接 + - 成功:filledCount++ + - 失败:errorCount++ +``` + +**示例日志**: +``` +正在处理订单 - 单号: JY202506181808, 行号: 3, 列: 3 +查询到订单物流链接: https://m.kuaidi100.com/result.jsp?nu=6649902864 +准备写入 - 目标单元格: 第 3 行, 第 13 列(索引12) +写入成功 - 单号: JY202506181808 +``` + +--- + +### 第四步:更新进度(Redis缓存) + +``` +执行逻辑: +1. 记录本次处理的最大行号:lastMaxRow = endRow +2. 保存到 Redis:key = "tencent:doc:lastMaxRow:{fileId}:{sheetId}" +3. 下次执行时,从 lastMaxRow 开始继续处理 +``` + +**进度管理**: +- 首次执行:startRow = 3, endRow = 52 +- 第二次执行:startRow = 52, endRow = 101 +- 第三次执行:startRow = 101, endRow = 150 +- ...以此类推,直到所有数据处理完毕 + +--- + +## 📊 关键数据结构 + +### 1. 表格结构 + +| 列号 | 列名 | 数据示例 | 用途 | +|------|------|----------|------| +| A (0) | 日期 | 3月10日 | - | +| B (1) | 公司 | XX公司 | - | +| **C (2)** | **单号** | **JY202506181808** | **用于查询数据库** | +| D (3) | 型号 | iPhone 14 | - | +| E (4) | 数量 | 1 | - | +| ... | ... | ... | - | +| **M (12)** | **物流单号** | **6649902864** | **要填充的目标列** | + +### 2. 数据库表结构(jd_order) + +| 字段 | 类型 | 说明 | 示例 | +|------|------|------|------| +| **third_party_order_no** | varchar | **第三方单号(唯一索引)** | **JY202506181808** | +| **logistics_link** | varchar | **物流链接** | **https://m.kuaidi100.com/...** | +| order_no | varchar | 京东单号 | JD123456789 | +| order_status | int | 订单状态 | 1 | +| ... | ... | ... | ... | + +### 3. API 请求示例 + +**读取表头**: +```http +GET https://docs.qq.com/openapi/spreadsheet/v3/files/DUW50RUprWXh2TGJK/BB08J2/A2:Z2 +Headers: + Access-Token: {ACCESS_TOKEN} + Client-Id: {CLIENT_ID} + Open-Id: {OPEN_ID} +``` + +**读取数据行**: +```http +GET https://docs.qq.com/openapi/spreadsheet/v3/files/DUW50RUprWXh2TGJK/BB08J2/A3:Z52 +Headers: + Access-Token: {ACCESS_TOKEN} + Client-Id: {CLIENT_ID} + Open-Id: {OPEN_ID} +``` + +**写入物流链接**(单个单元格): +```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行(索引2) + "endRowIndex": 3, // 不包含 + "startColumnIndex": 12, // 第13列(索引12) + "endColumnIndex": 13 // 不包含 + }, + "rows": [ + { + "values": [ + { + "cellValue": { + "text": "6649902864" + } + } + ] + } + ] + } + } + ] +} +``` + +--- + +## 🎯 核心逻辑图 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 1. 读取表头(A2:Z2) │ +│ → 识别"单号"列位置(orderNoColumn) │ +│ → 识别"物流单号"列位置(logisticsLinkColumn) │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ 2. 读取数据行(A3:Z52,每次50行) │ +│ → 解析为二维数组:[[row1], [row2], ...] │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ 3. 逐行处理 │ +│ FOR EACH row IN dataRows: │ +│ a. 提取单号:orderNo = row[orderNoColumn] │ +│ b. 查询数据库: │ +│ order = DB.query(third_party_order_no=orderNo) │ +│ c. 检查物流链接: │ +│ IF order != null AND logistics_link != null: │ +│ → 写入表格: │ +│ cell[rowIndex][logisticsLinkColumn] │ +│ = order.logistics_link │ +│ → filledCount++ │ +│ ELSE: │ +│ → errorCount++ │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ 4. 更新进度 │ +│ → Redis.set("lastMaxRow", endRow) │ +│ → 返回统计信息:filledCount, errorCount, skippedCount │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## ⚙️ 配置参数 + +### 接口请求参数 + +```json +{ + "accessToken": "用户访问令牌(必填)", + "fileId": "文件ID(必填)", + "sheetId": "工作表ID(必填)", + "headerRow": 2, // 表头行号(可选,默认2) + "orderNoColumn": 2, // 单号列位置(可选,自动识别) + "logisticsLinkColumn": 12 // 物流列位置(可选,自动识别) +} +``` + +### 自动识别列位置 + +如果不传 `orderNoColumn` 和 `logisticsLinkColumn`,系统会自动识别: + +```java +// 在表头行中查找匹配的列名 +for (int i = 0; i < headerRow.size(); i++) { + String cellValue = headerRow.getString(i); + if ("单号".equals(cellValue)) { + orderNoColumn = i; // 找到"单号"列 + } + if ("物流单号".equals(cellValue)) { + logisticsLinkColumn = i; // 找到"物流单号"列 + } +} +``` + +--- + +## 📈 返回结果示例 + +### 成功响应 + +```json +{ + "msg": "物流链接填充成功", + "code": 200, + "data": { + "startRow": 3, + "endRow": 52, + "lastMaxRow": 52, + "filledCount": 45, // 成功填充45个 + "skippedCount": 3, // 跳过3个(已有物流信息) + "errorCount": 2, // 失败2个(未找到订单) + "message": "成功填充45个物流链接,跳过3个,失败2个" + } +} +``` + +### 没有更多数据 + +```json +{ + "msg": "没有需要处理的数据", + "code": 200, + "data": { + "startRow": 103, + "endRow": 152, + "lastMaxRow": 100, // 上次处理到第100行 + "filledCount": 0, + "skippedCount": 0, + "errorCount": 0, + "message": "指定范围内没有数据" + } +} +``` + +### 错误响应 + +```json +{ + "msg": "读取数据行失败: invalid param error: 'range' invalid", + "code": 500 +} +``` + +--- + +## 🔧 关键代码片段 + +### 1. 根据第三方单号查询订单 + +```java +// Controller层 +String orderNo = row.getString(orderNoColumn); // 从"单号"列取值 +JDOrder order = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNo); + +// Service层 +@Override +public JDOrder selectJDOrderByThirdPartyOrderNo(String thirdPartyOrderNo) { + return jdOrderMapper.selectJDOrderByThirdPartyOrderNo(thirdPartyOrderNo); +} + +// Mapper.xml + +``` + +### 2. 写入物流链接到指定单元格 + +```java +// 构建更新请求 +JSONObject updateRequest = new JSONObject(); +JSONObject updateCells = new JSONObject(); + +// 指定目标单元格范围(第excelRow行,第logisticsLinkColumn列) +JSONObject range = new JSONObject(); +range.put("sheetId", sheetId); +range.put("startRowIndex", excelRow - 1); // Excel行号转索引 +range.put("endRowIndex", excelRow); // 不包含 +range.put("startColumnIndex", logisticsLinkColumn); // 列索引 +range.put("endColumnIndex", logisticsLinkColumn + 1); // 不包含 + +// 设置单元格值 +JSONArray rows = new JSONArray(); +JSONObject rowData = new JSONObject(); +JSONArray values = new JSONArray(); +JSONObject cellData = new JSONObject(); +JSONObject cellValue = new JSONObject(); +cellValue.put("text", logisticsLink); // 物流链接文本 +cellData.put("cellValue", cellValue); +values.add(cellData); +rowData.put("values", values); +rows.add(rowData); + +updateCells.put("range", range); +updateCells.put("rows", rows); +updateRequest.put("updateCells", updateCells); + +// 调用 batchUpdate API +tencentDocService.batchUpdate(accessToken, fileId, updateRequest); +``` + +--- + +## ⚠️ 注意事项 + +### 1. Range 格式限制 + +✅ **正确格式**(A1 表示法): +- `A2:Z2` - 表头行 +- `A3:Z52` - 数据行(50行) +- `M3` - 单个单元格 + +❌ **错误格式**: +- `1,0,1,25` - 索引格式(已废弃) +- `A3:Z203` - 超出实际数据范围(会报错) + +### 2. API 限制 + +根据官方文档: +- 查询范围行数 ≤ 1000 +- 查询范围列数 ≤ 200 +- 范围内总单元格数 ≤ 10000 + +### 3. 批处理策略 + +- **每次读取 50 行**:避免超出表格实际范围 +- **分批处理**:通过 Redis 记录进度,支持多次执行 +- **错误重试**:如果某一行写入失败,记录错误但继续处理下一行 + +### 4. 数据库字段映射 + +⚠️ **关键对应关系**: +- 表格中的"单号" → 数据库中的 `third_party_order_no` +- **不是** `order_no`(京东单号) +- **不是** `remark`(备注) + +### 5. 空值处理 + +- 如果单元格为空,`row.getString(index)` 返回空字符串 `""` +- 如果行不存在,`values.size()` 会小于预期列数 +- 需要做好空值判断和边界检查 + +--- + +## 🧪 测试验证 + +### 测试请求 + +```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 + }' +``` + +### 预期日志 + +``` +读取表头 - 行号: 2, range: A2:Z2 +解析后的数据行数: 1 +第 1 行(26列): ["日期","公司","单号",...,"物流单号","","标记",...] + +开始读取数据行 - 行号: 3 ~ 52, range: A3:Z52 +解析后的数据行数: 50 + +正在处理订单 - 单号: JY202506181808, 行号: 3 +查询到订单物流链接: https://m.kuaidi100.com/result.jsp?nu=6649902864 +写入成功 - 单号: JY202506181808 + +正在处理订单 - 单号: JY202506181809, 行号: 4 +未找到订单 - 单号: JY202506181809 + +... + +填充完成 - 成功: 45, 跳过: 3, 失败: 2 +``` + +--- + +## 📚 相关文档 + +- [腾讯文档官方 API 文档](https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_range.html) +- [A1 表示法说明](https://docs.qq.com/open/document/app/openapi/v3/sheet/model/a1_notation.html) +- [批量更新接口](https://docs.qq.com/open/document/app/openapi/v3/sheet/batchupdate/update.html) + +--- + +**文档版本**:1.0 +**创建时间**:2025-11-05 +**状态**:✅ 已完成 + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocController.java index 6d9d1eb..0f034da 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocController.java @@ -502,8 +502,10 @@ public class TencentDocController extends BaseController { startRow = headerRow + 1; // 默认从表头下一行开始 } - // 计算读取范围:从起始行开始,读取足够多的行(假设每次最多处理200行) - int endRow = startRow + 200; // 每次最多读取200行 + // 计算读取范围:从起始行开始,读取足够多的行 + // 根据官方文档限制:查询范围行数≤1000,列数≤200,总单元格≤10000 + // 为了避免超出表格实际范围(A1表示法的range不能超出实际数据区域),每次只读取50行 + int endRow = startRow + 49; // 每次最多读取50行(包含起始行) log.info("开始填充物流链接 - 文件ID: {}, 工作表ID: {}, 起始行: {}, 结束行: {}, 上次最大行: {}", fileId, sheetId, startRow, endRow, lastMaxRow);