This commit is contained in:
2025-11-06 11:25:17 +08:00
parent a7f581bdbe
commit 0b6ba14f2f
2 changed files with 481 additions and 2 deletions

View File

@@ -0,0 +1,477 @@
# 物流链接自动填充 - 完整逻辑说明
## 📋 功能概述
自动读取腾讯文档表格中的订单信息,根据**第三方单号**thirdPartyOrderNo在数据库中查询对应的物流链接并自动填充到表格的"物流单号"列。
---
## 🔄 完整执行流程
### 第一步:读取表头(识别列位置)
```
输入参数:
- fileId: 文件ID
- sheetId: 工作表ID
- headerRow: 表头所在行号默认2
执行逻辑:
1. 构建 rangeA{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. 构建 rangeA{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. 保存到 Rediskey = "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
<select id="selectJDOrderByThirdPartyOrderNo" parameterType="string" resultMap="JDOrderResult">
SELECT * FROM jd_order
WHERE third_party_order_no = #{thirdPartyOrderNo}
LIMIT 1
</select>
```
### 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
**状态**:✅ 已完成