From 228883250b499732131f8a714a342c47a2c46889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=92?= Date: Thu, 6 Nov 2025 12:40:16 +0800 Subject: [PATCH] 1 --- doc/自动识别列位置-优化说明.md | 415 ++++++++++++++++++ .../jarvis/TencentDocController.java | 100 +++-- 2 files changed, 473 insertions(+), 42 deletions(-) create mode 100644 doc/自动识别列位置-优化说明.md diff --git a/doc/自动识别列位置-优化说明.md b/doc/自动识别列位置-优化说明.md new file mode 100644 index 0000000..50e44ab --- /dev/null +++ b/doc/自动识别列位置-优化说明.md @@ -0,0 +1,415 @@ +# 自动识别列位置 - 优化说明 + +## 🎯 优化目标 + +**修改前**:列位置可以由前端传递,也可以自动识别 +**修改后**:所有列位置都由后端自动从表头识别,前端不再需要传递 + +--- + +## ✅ 优化原因 + +### 1. 降低前端复杂度 + +**修改前**,前端需要知道列的位置: +```json +{ + "accessToken": "...", + "fileId": "...", + "sheetId": "...", + "headerRow": 2, + "orderNoColumn": 2, // ❌ 前端需要传递 + "logisticsLinkColumn": 12 // ❌ 前端需要传递 +} +``` + +**修改后**,前端只需要提供基本信息: +```json +{ + "accessToken": "...", + "fileId": "...", + "sheetId": "...", + "headerRow": 2 // ✅ 只需要表头行号(可选,默认为1) +} +``` + +--- + +### 2. 增强灵活性 + +**表格结构变化时**,不需要修改前端代码: + +| 场景 | 修改前 | 修改后 | +|------|--------|--------| +| 列的顺序改变 | ❌ 需要更新前端参数 | ✅ 自动识别,无需改动 | +| 添加新列 | ❌ 需要重新计算索引 | ✅ 自动识别,无需改动 | +| 列名称不变 | ✅ 无需改动 | ✅ 无需改动 | + +--- + +### 3. 减少出错概率 + +**常见错误**: +- ❌ 前端传递的列索引不正确(数错了列) +- ❌ 前端传递的索引是从1开始,但后端期望从0开始 +- ❌ 表格结构变化后,前端忘记更新参数 + +**修改后**: +- ✅ 后端自动识别,避免手动数列 +- ✅ 统一使用从0开始的索引,前端无需关心 +- ✅ 表格结构变化后,只要列名不变,自动适配 + +--- + +## 🔧 代码修改 + +### 修改 1:删除前端参数接收 + +**修改前**: +```java +// 可选参数:指定列位置 +Integer orderNoColumn = params.get("orderNoColumn") != null ? + Integer.valueOf(params.get("orderNoColumn").toString()) : null; +Integer logisticsLinkColumn = params.get("logisticsLinkColumn") != null ? + Integer.valueOf(params.get("logisticsLinkColumn").toString()) : null; +Integer headerRow = params.get("headerRow") != null ? + Integer.valueOf(params.get("headerRow").toString()) : 1; +``` + +**修改后**: +```java +// 可选参数:表头行号 +Integer headerRow = params.get("headerRow") != null ? + Integer.valueOf(params.get("headerRow").toString()) : 1; +``` + +--- + +### 修改 2:始终自动识别列位置 + +**修改前**(条件识别): +```java +// 自动识别列位置(如果未指定) +if (orderNoColumn == null || logisticsLinkColumn == null) { + // 查找所有相关列 + ... +} +``` + +**修改后**(始终识别): +```java +// 自动识别列位置(从表头中识别) +Integer orderNoColumn = null; // "单号"列 +Integer logisticsLinkColumn = null; // "物流单号"列 +Integer arrangedColumn = null; // "是否安排"列 +Integer markColumn = null; // "标记"列 + +// 查找所有相关列 +for (int i = 0; i < headerRowData.size(); i++) { + String cellValue = headerRowData.getString(i); + if (cellValue != null) { + String cellValueTrim = cellValue.trim(); + + // 识别"单号"列 + if (orderNoColumn == null && cellValueTrim.contains("单号")) { + orderNoColumn = i; + log.info("✓ 识别到 '单号' 列:第 {} 列(索引{})", i + 1, i); + } + + // 识别"物流单号"或"物流链接"列 + if (logisticsLinkColumn == null && (cellValueTrim.contains("物流单号") || cellValueTrim.contains("物流链接"))) { + logisticsLinkColumn = i; + log.info("✓ 识别到 '物流单号' 列:第 {} 列(索引{})", i + 1, i); + } + + // 识别"是否安排"列(可选) + if (arrangedColumn == null && cellValueTrim.contains("是否安排")) { + arrangedColumn = i; + log.info("✓ 识别到 '是否安排' 列:第 {} 列(索引{})", i + 1, i); + } + + // 识别"标记"列(可选) + if (markColumn == null && cellValueTrim.contains("标记")) { + markColumn = i; + log.info("✓ 识别到 '标记' 列:第 {} 列(索引{})", i + 1, i); + } + } +} +``` + +--- + +### 修改 3:增强错误提示 + +**修改后的错误提示更加友好**: + +```java +// 检查必需的列是否都已识别 +if (orderNoColumn == null) { + return AjaxResult.error("无法找到'单号'列,请检查表头是否包含'单号'字段"); +} +if (logisticsLinkColumn == null) { + return AjaxResult.error("无法找到'物流单号'或'物流链接'列,请检查表头"); +} + +// 提示可选列的识别情况 +if (arrangedColumn == null) { + log.warn("未找到'是否安排'列,将跳过该字段的更新"); +} +if (markColumn == null) { + log.warn("未找到'标记'列,将跳过该字段的更新"); +} + +log.info("列位置识别完成 - 单号: {}, 物流单号: {}, 是否安排: {}, 标记: {}", + orderNoColumn, logisticsLinkColumn, arrangedColumn, markColumn); +``` + +--- + +## 📊 列识别规则 + +### 必需列 + +| 列名关键字 | 识别条件 | 必需 | 说明 | +|-----------|---------|------|------| +| "单号" | `cellValue.contains("单号")` | ✅ 是 | 用于匹配订单 | +| "物流单号" 或 "物流链接" | `cellValue.contains("物流单号")`
或 `cellValue.contains("物流链接")` | ✅ 是 | 写入物流链接 | + +### 可选列 + +| 列名关键字 | 识别条件 | 必需 | 说明 | +|-----------|---------|------|------| +| "是否安排" | `cellValue.contains("是否安排")` | ❌ 否 | 写入 "2" | +| "标记" | `cellValue.contains("标记")` | ❌ 否 | 写入日期(`yyMMdd`) | + +**识别规则**: +- ✅ 只要列名**包含**关键字即可(不需要完全匹配) +- ✅ 自动去除前后空格 +- ✅ 区分大小写 +- ✅ 从左到右查找,找到第一个匹配的列 + +--- + +## 🔍 日志输出示例 + +### 成功识别 + +``` +✓ 识别到 '单号' 列:第 3 列(索引2) +✓ 识别到 '物流单号' 列:第 13 列(索引12) +✓ 识别到 '是否安排' 列:第 12 列(索引11) +✓ 识别到 '标记' 列:第 15 列(索引14) +列位置识别完成 - 单号: 2, 物流单号: 12, 是否安排: 11, 标记: 14 +``` + +### 部分列缺失(可选列) + +``` +✓ 识别到 '单号' 列:第 3 列(索引2) +✓ 识别到 '物流单号' 列:第 13 列(索引12) +WARN 未找到'是否安排'列,将跳过该字段的更新 +WARN 未找到'标记'列,将跳过该字段的更新 +列位置识别完成 - 单号: 2, 物流单号: 12, 是否安排: null, 标记: null +``` + +### 必需列缺失(错误) + +``` +ERROR 无法找到'单号'列,请检查表头是否包含'单号'字段 +``` + +或 + +``` +ERROR 无法找到'物流单号'或'物流链接'列,请检查表头 +``` + +--- + +## 🧪 测试场景 + +### 场景 1:标准表格 + +**表头**: +| 日期 | 公司 | 单号 | 型号 | ... | 物流单号 | 是否安排 | 标记 | + +**结果**: +- ✅ 所有列都识别成功 +- ✅ 同时更新4个字段 + +--- + +### 场景 2:列名有变化 + +**表头**: +| 日期 | 公司 | 订单单号 | 型号 | ... | 物流链接 | 安排状态 | 备注标记 | + +**结果**: +- ✅ "订单单号" → 识别为"单号"列(包含"单号") +- ✅ "物流链接" → 识别为"物流单号"列(包含"物流链接") +- ❌ "安排状态" → 无法识别(不包含"是否安排") +- ✅ "备注标记" → 识别为"标记"列(包含"标记") + +--- + +### 场景 3:列顺序改变 + +**原表头**: +| 单号 | 公司 | 日期 | ... | 物流单号 | 是否安排 | 标记 | + +**新表头**(顺序改变): +| 日期 | 单号 | 公司 | ... | 是否安排 | 标记 | 物流单号 | + +**结果**: +- ✅ 仍然能正确识别所有列 +- ✅ 前端代码无需任何修改 + +--- + +### 场景 4:最小必需列 + +**表头**: +| 日期 | 公司 | 单号 | 型号 | ... | 物流单号 | + +**结果**: +- ✅ 必需列识别成功 +- ⚠️ "是否安排"列不存在,跳过更新 +- ⚠️ "标记"列不存在,跳过更新 +- ✅ 只更新物流单号 + +--- + +## 📋 前端调用示例 + +### 修改前(需要传递列位置) + +```javascript +// ❌ 需要手动指定列位置 +const data = { + accessToken: "...", + fileId: "DUW50RUprWXh2TGJK", + sheetId: "BB08J2", + headerRow: 2, + orderNoColumn: 2, // 需要前端知道列位置 + logisticsLinkColumn: 12 // 需要前端知道列位置 +}; + +axios.post('/jarvis/tencentDoc/fillLogisticsByOrderNo', data); +``` + +--- + +### 修改后(自动识别) + +```javascript +// ✅ 只需要基本信息 +const data = { + accessToken: "...", + fileId: "DUW50RUprWXh2TGJK", + sheetId: "BB08J2", + headerRow: 2 // 可选,默认为1 +}; + +axios.post('/jarvis/tencentDoc/fillLogisticsByOrderNo', data); +``` + +--- + +## 📝 API 参数说明 + +### 请求参数 + +| 参数名 | 类型 | 必需 | 默认值 | 说明 | +|--------|------|------|--------|------| +| `accessToken` | String | ✅ 是 | - | 腾讯文档访问令牌 | +| `fileId` | String | ✅ 是 | - | 文件ID | +| `sheetId` | String | ✅ 是 | - | 工作表ID | +| `headerRow` | Integer | ❌ 否 | 1 | 表头所在行号(从1开始) | +| `forceStart` | Boolean | ❌ 否 | false | 是否强制从指定行开始 | +| `forceStartRow` | Integer | ❌ 否 | null | 强制起始行号 | + +**已移除的参数**: +- ~~`orderNoColumn`~~ - 不再需要,自动识别 +- ~~`logisticsLinkColumn`~~ - 不再需要,自动识别 + +--- + +### 响应示例 + +```json +{ + "msg": "填充物流链接完成", + "code": 200, + "data": { + "startRow": 3, + "endRow": 52, + "filledCount": 45, + "skippedCount": 3, + "errorCount": 0, + "orderNoColumn": 2, // 自动识别的列位置 + "logisticsLinkColumn": 12, // 自动识别的列位置 + "message": "处理完成:成功填充 45 条,跳过 3 条,错误 0 条" + } +} +``` + +--- + +## ⚠️ 注意事项 + +### 1. 列名要求 + +**必需列**必须包含特定关键字: +- ✅ "单号"、"订单单号"、"第三方单号" → 都能识别 +- ❌ "编号"、"ID" → 无法识别 + +**建议**:保持列名包含明确的关键字,如"单号"、"物流单号"、"是否安排"、"标记"。 + +--- + +### 2. 列名唯一性 + +如果表格中有多个包含相同关键字的列,只会识别第一个: + +**示例**: +| 采购单号 | 销售单号 | 物流单号 | + +**识别结果**: +- "单号"列 → 第1列(采购单号) +- "物流单号"列 → 第3列 + +**建议**:如果有多个"单号"列,确保目标列是第一个出现的。 + +--- + +### 3. 向后兼容 + +虽然前端不再需要传递 `orderNoColumn` 和 `logisticsLinkColumn`,但如果传递了这些参数,后端会忽略它们,不会报错。 + +--- + +## ✅ 总结 + +### 优化效果 + +| 方面 | 优化前 | 优化后 | +|------|--------|--------| +| **前端参数** | 5个参数 | 3个参数 ✅ | +| **前端复杂度** | 需要知道列位置 | 只需要基本信息 ✅ | +| **灵活性** | 表格结构变化需要修改前端 | 自动适配 ✅ | +| **出错概率** | 容易传错列索引 | 自动识别,减少错误 ✅ | +| **可维护性** | 前后端都需要维护列信息 | 只有后端识别逻辑 ✅ | + +### 关键改进 + +1. ✅ **前端简化**:不再需要传递列位置 +2. ✅ **自动适配**:表格结构变化时自动识别 +3. ✅ **错误提示**:更友好的错误信息 +4. ✅ **日志完善**:详细的识别过程日志 + +--- + +**文档版本**: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 be2f19a..724d7ba 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 @@ -462,11 +462,7 @@ public class TencentDocController extends BaseController { String fileId = (String) params.get("fileId"); String sheetId = (String) params.get("sheetId"); - // 可选参数:指定列位置 - Integer orderNoColumn = params.get("orderNoColumn") != null ? - Integer.valueOf(params.get("orderNoColumn").toString()) : null; // 单号列索引(从0开始) - Integer logisticsLinkColumn = params.get("logisticsLinkColumn") != null ? - Integer.valueOf(params.get("logisticsLinkColumn").toString()) : null; // 物流链接列索引(从0开始) + // 可选参数:表头行号 Integer headerRow = params.get("headerRow") != null ? Integer.valueOf(params.get("headerRow").toString()) : 1; // 表头所在行(默认第1行,从1开始) @@ -536,48 +532,68 @@ public class TencentDocController extends BaseController { return AjaxResult.error("无法读取表头,请检查headerRow参数。API响应: " + headerData.toJSONString()); } - // 自动识别列位置(如果未指定) - Integer arrangedColumn = null; // "是否安排"列 - Integer markColumn = null; // "标记"列 + // 自动识别列位置(从表头中识别) + Integer orderNoColumn = null; // "单号"列 + Integer logisticsLinkColumn = null; // "物流单号"列 + Integer arrangedColumn = null; // "是否安排"列 + Integer markColumn = null; // "标记"列 - if (orderNoColumn == null || logisticsLinkColumn == null) { - JSONArray headerRowData = headerValues.getJSONArray(0); - if (headerRowData == null || headerRowData.isEmpty()) { - return AjaxResult.error("无法识别表头,请手动指定列位置"); - } - - // 查找所有相关列 - for (int i = 0; i < headerRowData.size(); i++) { - String cellValue = headerRowData.getString(i); - if (cellValue != null) { - String cellValueTrim = cellValue.trim(); - if (orderNoColumn == null && cellValueTrim.contains("单号")) { - orderNoColumn = i; - log.info("识别到 '单号' 列:第 {} 列(索引{})", i + 1, i); - } - if (logisticsLinkColumn == null && (cellValueTrim.contains("物流单号") || cellValueTrim.contains("物流链接"))) { - logisticsLinkColumn = i; - log.info("识别到 '物流单号' 列:第 {} 列(索引{})", i + 1, i); - } - if (arrangedColumn == null && cellValueTrim.contains("是否安排")) { - arrangedColumn = i; - log.info("识别到 '是否安排' 列:第 {} 列(索引{})", i + 1, i); - } - if (markColumn == null && cellValueTrim.contains("标记")) { - markColumn = i; - log.info("识别到 '标记' 列:第 {} 列(索引{})", i + 1, i); - } + JSONArray headerRowData = headerValues.getJSONArray(0); + if (headerRowData == null || headerRowData.isEmpty()) { + return AjaxResult.error("无法识别表头,表头数据为空"); + } + + // 查找所有相关列 + for (int i = 0; i < headerRowData.size(); i++) { + String cellValue = headerRowData.getString(i); + if (cellValue != null) { + String cellValueTrim = cellValue.trim(); + + // 识别"单号"列 + if (orderNoColumn == null && cellValueTrim.contains("单号")) { + orderNoColumn = i; + log.info("✓ 识别到 '单号' 列:第 {} 列(索引{})", i + 1, i); + } + + // 识别"物流单号"或"物流链接"列 + if (logisticsLinkColumn == null && (cellValueTrim.contains("物流单号") || cellValueTrim.contains("物流链接"))) { + logisticsLinkColumn = i; + log.info("✓ 识别到 '物流单号' 列:第 {} 列(索引{})", i + 1, i); + } + + // 识别"是否安排"列(可选) + if (arrangedColumn == null && cellValueTrim.contains("是否安排")) { + arrangedColumn = i; + log.info("✓ 识别到 '是否安排' 列:第 {} 列(索引{})", i + 1, i); + } + + // 识别"标记"列(可选) + if (markColumn == null && cellValueTrim.contains("标记")) { + markColumn = i; + log.info("✓ 识别到 '标记' 列:第 {} 列(索引{})", i + 1, i); } } - - if (orderNoColumn == null) { - return AjaxResult.error("无法找到单号列,请手动指定orderNoColumn参数"); - } - if (logisticsLinkColumn == null) { - return AjaxResult.error("无法找到物流链接列,请手动指定logisticsLinkColumn参数"); - } } + // 检查必需的列是否都已识别 + if (orderNoColumn == null) { + return AjaxResult.error("无法找到'单号'列,请检查表头是否包含'单号'字段"); + } + if (logisticsLinkColumn == null) { + return AjaxResult.error("无法找到'物流单号'或'物流链接'列,请检查表头"); + } + + // 提示可选列的识别情况 + if (arrangedColumn == null) { + log.warn("未找到'是否安排'列,将跳过该字段的更新"); + } + if (markColumn == null) { + log.warn("未找到'标记'列,将跳过该字段的更新"); + } + + log.info("列位置识别完成 - 单号: {}, 物流单号: {}, 是否安排: {}, 标记: {}", + orderNoColumn, logisticsLinkColumn, arrangedColumn, markColumn); + // 读取数据行 // 使用 A1 表示法(Excel格式) String range = String.format("A%d:Z%d", startRow, endRow); // 例如:A3:Z203