14 KiB
14 KiB
物流链接自动填充 - 完整逻辑说明
📋 功能概述
自动读取腾讯文档表格中的订单信息,根据第三方单号(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 请求示例
读取表头:
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}
读取数据行:
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}
写入物流链接(单个单元格):
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 │
└─────────────────────────────────────────────────────────┘
⚙️ 配置参数
接口请求参数
{
"accessToken": "用户访问令牌(必填)",
"fileId": "文件ID(必填)",
"sheetId": "工作表ID(必填)",
"headerRow": 2, // 表头行号(可选,默认2)
"orderNoColumn": 2, // 单号列位置(可选,自动识别)
"logisticsLinkColumn": 12 // 物流列位置(可选,自动识别)
}
自动识别列位置
如果不传 orderNoColumn 和 logisticsLinkColumn,系统会自动识别:
// 在表头行中查找匹配的列名
for (int i = 0; i < headerRow.size(); i++) {
String cellValue = headerRow.getString(i);
if ("单号".equals(cellValue)) {
orderNoColumn = i; // 找到"单号"列
}
if ("物流单号".equals(cellValue)) {
logisticsLinkColumn = i; // 找到"物流单号"列
}
}
📈 返回结果示例
成功响应
{
"msg": "物流链接填充成功",
"code": 200,
"data": {
"startRow": 3,
"endRow": 52,
"lastMaxRow": 52,
"filledCount": 45, // 成功填充45个
"skippedCount": 3, // 跳过3个(已有物流信息)
"errorCount": 2, // 失败2个(未找到订单)
"message": "成功填充45个物流链接,跳过3个,失败2个"
}
}
没有更多数据
{
"msg": "没有需要处理的数据",
"code": 200,
"data": {
"startRow": 103,
"endRow": 152,
"lastMaxRow": 100, // 上次处理到第100行
"filledCount": 0,
"skippedCount": 0,
"errorCount": 0,
"message": "指定范围内没有数据"
}
}
错误响应
{
"msg": "读取数据行失败: invalid param error: 'range' invalid",
"code": 500
}
🔧 关键代码片段
1. 根据第三方单号查询订单
// 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. 写入物流链接到指定单元格
// 构建更新请求
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()会小于预期列数 - 需要做好空值判断和边界检查
🧪 测试验证
测试请求
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
📚 相关文档
文档版本:1.0
创建时间:2025-11-05
状态:✅ 已完成