Files
ruoyi-java/doc/物流链接自动填充-完整逻辑说明.md
2025-11-06 11:25:17 +08:00

478 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 物流链接自动填充 - 完整逻辑说明
## 📋 功能概述
自动读取腾讯文档表格中的订单信息,根据**第三方单号**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
**状态**:✅ 已完成