# 自动识别列位置 - 优化说明

## 🎯 优化目标

**修改前**：列位置可以由前端传递，也可以自动识别  
**修改后**：所有列位置都由后端自动从表头识别，前端不再需要传递

---

## ✅ 优化原因

### 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("物流单号")` <br> 或 `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  
**状态**：✅ 已完成

