From 07c9aac9e6be5c8a5fb01647e71b325267622e73 Mon Sep 17 00:00:00 2001 From: van Date: Sat, 4 Apr 2026 15:45:45 +0800 Subject: [PATCH] 1 --- .../jarvis/TencentDocController.java | 226 +++++++++++++++++- .../service/impl/TencentDocServiceImpl.java | 14 +- .../jarvis/util/TencentDocDataParser.java | 17 ++ 3 files changed, 241 insertions(+), 16 deletions(-) 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 41988d6..b99fe67 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 @@ -9,6 +9,7 @@ import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.jarvis.domain.JDOrder; import com.ruoyi.jarvis.service.ITencentDocService; import com.ruoyi.jarvis.service.IJDOrderService; +import com.ruoyi.jarvis.util.TencentDocDataParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -895,6 +896,40 @@ public class TencentDocController extends BaseController { return null; } + /** + * 合并同一行的腾讯文档填充任务(物流更新与京东下单订单号补充可能分两条加入队列) + */ + private void mergeTencentDocRowFillUpdate(JSONObject target, JSONObject extra) { + if (extra.containsKey("logisticsLink")) { + target.put("logisticsLink", extra.getString("logisticsLink")); + target.remove("isJdOrderIdOnlyUpdate"); + } + if (Boolean.TRUE.equals(extra.getBoolean("isLinkUpdated"))) { + target.put("isLinkUpdated", true); + if (extra.containsKey("oldLogisticsLink")) { + target.put("oldLogisticsLink", extra.getString("oldLogisticsLink")); + } + if (extra.containsKey("newLogisticsLink")) { + target.put("newLogisticsLink", extra.getString("newLogisticsLink")); + } + } + if (extra.containsKey("phone")) { + target.put("phone", extra.getString("phone")); + } + if (extra.containsKey("phoneColumn")) { + target.put("phoneColumn", extra.getInteger("phoneColumn")); + } + if (extra.containsKey("jdOrderId")) { + target.put("jdOrderId", extra.getString("jdOrderId")); + } + if (extra.containsKey("jdOrderIdColumn")) { + target.put("jdOrderIdColumn", extra.getInteger("jdOrderIdColumn")); + } + if (Boolean.TRUE.equals(extra.getBoolean("isJdOrderIdOnlyUpdate")) && !target.containsKey("logisticsLink")) { + target.put("isJdOrderIdOnlyUpdate", true); + } + } + /** * 批量同步物流链接 - 读取表格数据,根据单号查询订单系统中的物流链接,并填充到表格 * 优化:记录上次处理的最大行数,每次从最大行数-100开始读取,避免重复处理历史数据 @@ -1007,7 +1042,8 @@ public class TencentDocController extends BaseController { } // 自动识别列位置(从表头中识别) - Integer orderNoColumn = null; // "单号"列 + Integer orderNoColumn = null; // 第三方「单号」列(排除京东下单订单号、物流单号) + Integer jdPlaceOrderNoColumn = null; // 「京东下单订单号」列,对应本地 orderId Integer logisticsLinkColumn = null; // "物流单号"列 Integer remarkColumn = null; // "备注"列 Integer arrangedColumn = null; // "是否安排"列 @@ -1027,8 +1063,15 @@ public class TencentDocController extends BaseController { String cellValueTrim = cellValue.trim(); log.debug("列 {} 内容: [{}]", i, cellValueTrim); + if (jdPlaceOrderNoColumn == null && TencentDocDataParser.isJdPlaceOrderNoHeader(cellValueTrim)) { + jdPlaceOrderNoColumn = i; + log.info("✓ 识别到 '京东下单订单号' 列:第 {} 列(索引{})", i + 1, i); + } + // 识别"单号"列 - if (orderNoColumn == null && cellValueTrim.contains("单号")) { + if (orderNoColumn == null && cellValueTrim.contains("单号") + && !TencentDocDataParser.isJdPlaceOrderNoHeader(cellValueTrim) + && !cellValueTrim.contains("物流")) { orderNoColumn = i; log.info("✓ 识别到 '单号' 列:第 {} 列(索引{})", i + 1, i); } @@ -1087,9 +1130,12 @@ public class TencentDocController extends BaseController { if (phoneColumn == null) { log.warn("未找到'下单电话'列,将跳过该字段的更新"); } + if (jdPlaceOrderNoColumn == null) { + log.warn("未找到'京东下单订单号'列,将跳过该字段的同步(请在表头增加该列以写入京东 orderId)"); + } - log.info("列位置识别完成 - 单号: {}, 物流单号: {}, 备注: {}, 是否安排: {}, 标记: {}, 下单电话: {}", - orderNoColumn, logisticsLinkColumn, remarkColumn, arrangedColumn, markColumn, phoneColumn); + log.info("列位置识别完成 - 单号: {}, 京东下单订单号: {}, 物流单号: {}, 备注: {}, 是否安排: {}, 标记: {}, 下单电话: {}", + orderNoColumn, jdPlaceOrderNoColumn, logisticsLinkColumn, remarkColumn, arrangedColumn, markColumn, phoneColumn); // 读取数据行:接口实际只能读 200 行,严格限制单次行数,失败时逐步缩小范围重试 // 腾讯文档 get_range 的 range 为「结束行不包含」:要读到 endRow 含最后一行,须传 endRow+1 @@ -1229,7 +1275,11 @@ public class TencentDocController extends BaseController { errorCount++; continue; } - if (row == null || row.size() <= Math.max(orderNoColumn, logisticsLinkColumn)) { + int minDataColIdx = Math.max(orderNoColumn, logisticsLinkColumn); + if (jdPlaceOrderNoColumn != null) { + minDataColIdx = Math.max(minDataColIdx, jdPlaceOrderNoColumn); + } + if (row == null || row.size() <= minDataColIdx) { continue; // 跳过空行或列数不足的行 } @@ -1263,7 +1313,19 @@ public class TencentDocController extends BaseController { if (existingOrder != null) { String dbLogisticsLink = existingOrder.getLogisticsLink(); String trimmedDbLink = (dbLogisticsLink != null) ? dbLogisticsLink.trim() : ""; - + String dbJdOrderId = existingOrder.getOrderId() != null ? existingOrder.getOrderId().trim() : ""; + String docJdOrderId = ""; + if (jdPlaceOrderNoColumn != null && row.size() > jdPlaceOrderNoColumn) { + try { + String cj = row.getString(jdPlaceOrderNoColumn); + docJdOrderId = cj != null ? cj.trim() : ""; + } catch (Exception ex) { + log.debug("读取京东下单订单号列异常 行 {}: {}", excelRow, ex.getMessage()); + } + } + boolean needJdOrderIdUpdate = jdPlaceOrderNoColumn != null && !dbJdOrderId.isEmpty() + && (docJdOrderId.isEmpty() || !docJdOrderId.equals(dbJdOrderId)); + // 比对物流链接,如果不一致则需要更新 if (!trimmedDbLink.isEmpty() && !trimmedExistingLink.equals(trimmedDbLink)) { // 物流链接不一致,需要更新文档中的链接 @@ -1279,6 +1341,10 @@ public class TencentDocController extends BaseController { update.put("isLinkUpdated", true); // 标记为物流链接更新 update.put("oldLogisticsLink", trimmedExistingLink); // 旧链接 update.put("newLogisticsLink", trimmedDbLink); // 新链接 + if (jdPlaceOrderNoColumn != null && !dbJdOrderId.isEmpty()) { + update.put("jdOrderId", dbJdOrderId); + update.put("jdOrderIdColumn", jdPlaceOrderNoColumn); + } updates.add(update); filledCount++; // 统计为填充数量 @@ -1293,6 +1359,17 @@ public class TencentDocController extends BaseController { log.info("========== 物流链接不一致,已加入更新队列 - 单号: {}, 行号: {}, 旧链接: {}, 新链接: {} ==========", orderNo, excelRow, trimmedExistingLink, trimmedDbLink); + } else if (needJdOrderIdUpdate) { + JSONObject update = new JSONObject(); + update.put("row", excelRow); + update.put("orderNo", orderNo); + update.put("isJdOrderIdOnlyUpdate", true); + update.put("jdOrderId", dbJdOrderId); + update.put("jdOrderIdColumn", jdPlaceOrderNoColumn); + updates.add(update); + filledCount++; + log.info("========== 需补充/更新京东下单订单号 - 第三方单号: {}, 行: {}, 文档: [{}], 系统: [{}] ==========", + orderNo, excelRow, docJdOrderId, dbJdOrderId); } else { // 物流链接一致或数据库中没有链接,只需同步订单状态 if (existingOrder.getTencentDocPushed() == null || existingOrder.getTencentDocPushed() == 0) { @@ -1382,6 +1459,11 @@ public class TencentDocController extends BaseController { update.put("phoneColumn", phoneColumn); } + if (jdPlaceOrderNoColumn != null && order.getOrderId() != null && !order.getOrderId().trim().isEmpty()) { + update.put("jdOrderId", order.getOrderId().trim()); + update.put("jdOrderIdColumn", jdPlaceOrderNoColumn); + } + // 注意:不再保存order对象,写入成功后会重新查询以确保数据最新 updates.add(update); @@ -1420,12 +1502,17 @@ public class TencentDocController extends BaseController { // 获取今天的日期,格式:yyMMdd(如:251105) String today = new java.text.SimpleDateFormat("yyMMdd").format(new java.util.Date()); - // 将更新按行分组,批量写入 - Map rowUpdates = new java.util.HashMap<>(); + // 将更新按行分组,批量写入(同一行可能同时有物流与京东单号任务,需合并) + Map rowUpdates = new java.util.LinkedHashMap<>(); for (int i = 0; i < updates.size(); i++) { JSONObject update = updates.getJSONObject(i); int row = update.getIntValue("row"); - rowUpdates.put(row, update); + JSONObject existing = rowUpdates.get(row); + if (existing == null) { + rowUpdates.put(row, update); + } else { + mergeTencentDocRowFillUpdate(existing, update); + } } // 批量写入(每行单独写入,同时更新多个字段) @@ -1437,9 +1524,92 @@ public class TencentDocController extends BaseController { try { int row = entry.getKey(); JSONObject update = entry.getValue(); - String logisticsLink = update.getString("logisticsLink"); String expectedOrderNo = update.getString("orderNo"); + // 仅补充/更新「京东下单订单号」列(文档已有物流且链接未变) + if (Boolean.TRUE.equals(update.getBoolean("isJdOrderIdOnlyUpdate"))) { + Integer jdCol = update.getInteger("jdOrderIdColumn"); + String jdId = update.getString("jdOrderId"); + if (jdCol == null || jdId == null || jdId.trim().isEmpty()) { + log.warn("京东下单订单号任务参数不全 - 行 {}", row); + errorCount++; + continue; + } + jdId = jdId.trim(); + String jdVerifyRange = String.format("A%d:Z%d", row, row); + JSONObject jdVerifyData = tencentDocService.readSheetData(accessToken, fileId, sheetId, jdVerifyRange); + if (jdVerifyData == null || !jdVerifyData.containsKey("values")) { + errorCount++; + Map errorLog = new java.util.HashMap<>(); + errorLog.put("orderNo", expectedOrderNo); + errorLog.put("row", row); + errorLog.put("errorType", "验证失败"); + errorLog.put("errorMessage", "无法读取行数据(京东单号)"); + errorLogs.add(errorLog); + continue; + } + JSONArray jdVerifyRows = jdVerifyData.getJSONArray("values"); + if (jdVerifyRows == null || jdVerifyRows.isEmpty()) { + errorCount++; + continue; + } + JSONArray jdVerifyRow = jdVerifyRows.getJSONArray(0); + int jdNeedIdx = Math.max(orderNoColumn, jdCol); + if (jdVerifyRow == null || jdVerifyRow.size() <= jdNeedIdx) { + errorCount++; + continue; + } + String jdCurOrderNo = jdVerifyRow.getString(orderNoColumn); + if (jdCurOrderNo == null || !jdCurOrderNo.trim().equals(expectedOrderNo)) { + errorCount++; + continue; + } + String docJd = ""; + if (jdVerifyRow.size() > jdCol) { + try { + String sj = jdVerifyRow.getString(jdCol); + docJd = sj != null ? sj.trim() : ""; + } catch (Exception ignore) { + // ignore + } + } + if (docJd.equals(jdId)) { + skippedCount++; + continue; + } + JSONArray jdRequests = new JSONArray(); + jdRequests.add(buildUpdateCellRequest(sheetId, row - 1, jdCol, jdId, false)); + JSONObject jdBatch = new JSONObject(); + jdBatch.put("requests", jdRequests); + tencentDocService.batchUpdate(accessToken, fileId, jdBatch); + currentBatchSuccessUpdates++; + if (row > currentBatchMaxSuccessRow) { + currentBatchMaxSuccessRow = row; + } + log.info("✓ 已更新京东下单订单号 - 第三方单号: {}, 行: {}, 值: {}", expectedOrderNo, row, jdId); + logOperation(batchId, fileId, sheetId, "BATCH_SYNC", expectedOrderNo, row, jdId, + "SUCCESS", "更新京东下单订单号"); + Map jdSuccessLog = new java.util.HashMap<>(); + jdSuccessLog.put("orderNo", expectedOrderNo); + jdSuccessLog.put("row", row); + jdSuccessLog.put("jdOrderId", jdId); + jdSuccessLog.put("updateType", "JD_ORDER_ID"); + successLogs.add(jdSuccessLog); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + continue; + } + + String logisticsLink = update.getString("logisticsLink"); + if (logisticsLink == null || logisticsLink.trim().isEmpty()) { + log.warn("跳过写入 - 行 {} 缺少物流链接", row); + skippedCount++; + continue; + } + // 重新读取该行数据,验证单号和物流链接列 String verifyRange = String.format("A%d:Z%d", row, row); JSONObject verifyData = tencentDocService.readSheetData(accessToken, fileId, sheetId, verifyRange); @@ -1476,7 +1646,11 @@ public class TencentDocController extends BaseController { } JSONArray verifyRow = verifyRows.getJSONArray(0); - if (verifyRow == null || verifyRow.size() <= Math.max(orderNoColumn, logisticsLinkColumn)) { + int verifyMinColIdx = Math.max(orderNoColumn, logisticsLinkColumn); + if (jdPlaceOrderNoColumn != null) { + verifyMinColIdx = Math.max(verifyMinColIdx, jdPlaceOrderNoColumn); + } + if (verifyRow == null || verifyRow.size() <= verifyMinColIdx) { log.warn("验证失败 - 行 {} 列数不足", row); errorCount++; @@ -1567,6 +1741,26 @@ public class TencentDocController extends BaseController { log.info("✓ 物流链接更新,保留原标记日期 - 单号: {}, 行: {}", expectedOrderNo, row); } + Integer jdColUpd = update.getInteger("jdOrderIdColumn"); + String jdIdUpd = update.getString("jdOrderId"); + if (jdColUpd != null && jdIdUpd != null && !jdIdUpd.trim().isEmpty()) { + jdIdUpd = jdIdUpd.trim(); + String docJdVal = ""; + if (verifyRow.size() > jdColUpd) { + try { + String sj = verifyRow.getString(jdColUpd); + docJdVal = sj != null ? sj.trim() : ""; + } catch (Exception ignore) { + // ignore + } + } + boolean syncJd = Boolean.TRUE.equals(isLinkUpdated) || docJdVal.isEmpty() || !docJdVal.equals(jdIdUpd); + if (syncJd) { + requests.add(buildUpdateCellRequest(sheetId, row - 1, jdColUpd, jdIdUpd, false)); + log.info("✓ 准备写入京东下单订单号 - 第三方单号: {}, 行: {}, 值: {}", expectedOrderNo, row, jdIdUpd); + } + } + // 构建完整的 batchUpdate 请求体 JSONObject batchUpdateBody = new JSONObject(); batchUpdateBody.put("requests", requests); @@ -1622,6 +1816,9 @@ public class TencentDocController extends BaseController { if (orderToUpdate != null && orderToUpdate.getModelNumber() != null) { successLog.put("modelNumber", orderToUpdate.getModelNumber()); } + if (jdIdUpd != null && !jdIdUpd.trim().isEmpty()) { + successLog.put("jdOrderId", jdIdUpd.trim()); + } // 检查是否为物流链接更新(复用之前的变量) if (Boolean.TRUE.equals(isLinkUpdated)) { @@ -1694,6 +1891,7 @@ public class TencentDocController extends BaseController { result.put("skippedCount", skippedCount); result.put("errorCount", errorCount); result.put("orderNoColumn", orderNoColumn); + result.put("jdPlaceOrderNoColumn", jdPlaceOrderNoColumn); result.put("logisticsLinkColumn", logisticsLinkColumn); result.put("skipPushedOrders", skipPushedOrders); // 是否跳过已推送订单 @@ -2035,7 +2233,8 @@ public class TencentDocController extends BaseController { if (cellValue != null) { String cellValueTrim = cellValue.trim(); - if (orderNoColumn == null && cellValueTrim.contains("单号") && !cellValueTrim.contains("物流")) { + if (orderNoColumn == null && cellValueTrim.contains("单号") && !cellValueTrim.contains("物流") + && !TencentDocDataParser.isJdPlaceOrderNoHeader(cellValueTrim)) { orderNoColumn = i; log.info("✓ 识别到 '单号' 列:第 {} 列(索引{})", i + 1, i); } @@ -2333,7 +2532,8 @@ public class TencentDocController extends BaseController { if (cellValue != null) { String cellValueTrim = cellValue.trim(); - if (orderNoColumn == null && cellValueTrim.contains("单号") && !cellValueTrim.contains("物流")) { + if (orderNoColumn == null && cellValueTrim.contains("单号") && !cellValueTrim.contains("物流") + && !TencentDocDataParser.isJdPlaceOrderNoHeader(cellValueTrim)) { orderNoColumn = i; log.info("✓ 识别到 '单号' 列:第 {} 列(索引{})", i + 1, i); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocServiceImpl.java index d3705e6..fee96e3 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocServiceImpl.java @@ -204,6 +204,7 @@ public class TencentDocServiceImpl implements ITencentDocService { // 2. 识别列位置(根据表头) Integer dateColumn = null; Integer companyColumn = null; + Integer jdPlaceOrderNoColumn = null; Integer orderNoColumn = null; Integer modelColumn = null; Integer quantityColumn = null; @@ -218,9 +219,13 @@ public class TencentDocServiceImpl implements ITencentDocService { for (int i = 0; i < headerCells.size(); i++) { String cellText = headerCells.getString(i); if (cellText != null) { - if (cellText.contains("日期")) dateColumn = i; + if (TencentDocDataParser.isJdPlaceOrderNoHeader(cellText)) { + if (jdPlaceOrderNoColumn == null) { + jdPlaceOrderNoColumn = i; + } + } else if (cellText.contains("日期")) dateColumn = i; else if (cellText.contains("公司")) companyColumn = i; - else if (cellText.contains("单号")) orderNoColumn = i; + else if (cellText.contains("单号") && !cellText.contains("物流")) orderNoColumn = i; else if (cellText.contains("型号")) modelColumn = i; else if (cellText.contains("数量")) quantityColumn = i; else if (cellText.contains("姓名")) nameColumn = i; @@ -237,7 +242,7 @@ public class TencentDocServiceImpl implements ITencentDocService { throw new RuntimeException("未找到'单号'列,请检查表头配置"); } - log.info("表头识别完成 - 单号列: {}, 物流列: {}", orderNoColumn, logisticsColumn); + log.info("表头识别完成 - 单号列: {}, 京东下单订单号列: {}, 物流列: {}", orderNoColumn, jdPlaceOrderNoColumn, logisticsColumn); // 3. 读取数据区域,查找第一个空行(单号列为空) String dataRange = String.format("A%d:Z%d", startRow, startRow + 999); @@ -293,6 +298,9 @@ public class TencentDocServiceImpl implements ITencentDocService { if (orderNoColumn != null && order.getThirdPartyOrderNo() != null) { requests.add(buildUpdateRequest(sheetId, rowIndex, orderNoColumn, order.getThirdPartyOrderNo(), false)); } + if (jdPlaceOrderNoColumn != null && order.getOrderId() != null && !order.getOrderId().trim().isEmpty()) { + requests.add(buildUpdateRequest(sheetId, rowIndex, jdPlaceOrderNoColumn, order.getOrderId().trim(), false)); + } if (modelColumn != null && order.getModelNumber() != null) { requests.add(buildUpdateRequest(sheetId, rowIndex, modelColumn, order.getModelNumber(), false)); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/util/TencentDocDataParser.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/util/TencentDocDataParser.java index d90aa51..dcf53f5 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/util/TencentDocDataParser.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/util/TencentDocDataParser.java @@ -213,5 +213,22 @@ public class TencentDocDataParser { } } } + + /** + * 表头是否为「京东下单订单号」列(与第三方「单号」等列区分) + */ + public static boolean isJdPlaceOrderNoHeader(String cellValueTrim) { + if (cellValueTrim == null) { + return false; + } + String t = cellValueTrim.trim(); + if (t.isEmpty()) { + return false; + } + if (t.contains("京东下单订单号")) { + return true; + } + return t.contains("京东") && t.contains("下单") && t.contains("订单号"); + } }