diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocConfigController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocConfigController.java index 2f57b28..cae330b 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocConfigController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/TencentDocConfigController.java @@ -69,6 +69,7 @@ public class TencentDocConfigController extends BaseController { // 2. 从Redis获取文档配置 String fileId = redisCache.getCacheObject(REDIS_KEY_PREFIX + "fileId"); String sheetId = redisCache.getCacheObject(REDIS_KEY_PREFIX + "sheetId"); + Integer headerRow = redisCache.getCacheObject(REDIS_KEY_PREFIX + "headerRow"); Integer startRow = redisCache.getCacheObject(REDIS_KEY_PREFIX + "startRow"); // 如果Redis中没有,则使用配置文件中的默认值 @@ -78,6 +79,9 @@ public class TencentDocConfigController extends BaseController { if (sheetId == null || sheetId.isEmpty()) { sheetId = tencentDocConfig.getSheetId(); } + if (headerRow == null) { + headerRow = tencentDocConfig.getHeaderRow(); + } if (startRow == null) { startRow = tencentDocConfig.getStartRow(); } @@ -86,6 +90,7 @@ public class TencentDocConfigController extends BaseController { config.put("accessTokenStatus", accessTokenStatus); config.put("fileId", fileId); config.put("sheetId", sheetId); + config.put("headerRow", headerRow); config.put("startRow", startRow); config.put("appId", tencentDocConfig.getAppId()); config.put("apiBaseUrl", tencentDocConfig.getApiBaseUrl()); @@ -162,6 +167,7 @@ public class TencentDocConfigController extends BaseController { try { String fileId = params.getString("fileId"); String sheetId = params.getString("sheetId"); + Integer headerRow = params.getInteger("headerRow"); Integer startRow = params.getInteger("startRow"); // 验证必填字段 @@ -172,6 +178,11 @@ public class TencentDocConfigController extends BaseController { return AjaxResult.error("工作表ID不能为空"); } + // headerRow默认值为2 + if (headerRow == null || headerRow < 1) { + headerRow = 2; + } + // startRow默认值为3 if (startRow == null || startRow < 1) { startRow = 3; @@ -193,6 +204,7 @@ public class TencentDocConfigController extends BaseController { // 保存到Redis(180天有效期) redisCache.setCacheObject(REDIS_KEY_PREFIX + "fileId", fileId.trim(), 180, TimeUnit.DAYS); redisCache.setCacheObject(REDIS_KEY_PREFIX + "sheetId", sheetId.trim(), 180, TimeUnit.DAYS); + redisCache.setCacheObject(REDIS_KEY_PREFIX + "headerRow", headerRow, 180, TimeUnit.DAYS); redisCache.setCacheObject(REDIS_KEY_PREFIX + "startRow", startRow, 180, TimeUnit.DAYS); // 清除该文档的同步进度(配置更新时重置进度,从新的startRow重新开始) @@ -205,15 +217,17 @@ public class TencentDocConfigController extends BaseController { // 同时更新TencentDocConfig对象(内存中) tencentDocConfig.setFileId(fileId.trim()); tencentDocConfig.setSheetId(sheetId.trim()); + tencentDocConfig.setHeaderRow(headerRow); tencentDocConfig.setStartRow(startRow); - log.info("H-TF订单自动写入配置已更新 - fileId: {}, sheetId: {}, startRow: {}", - fileId.trim(), sheetId.trim(), startRow); + log.info("H-TF订单自动写入配置已更新 - fileId: {}, sheetId: {}, headerRow: {}, startRow: {}", + fileId.trim(), sheetId.trim(), headerRow, startRow); JSONObject result = new JSONObject(); result.put("message", "配置更新成功,已保存到Redis(180天有效期)"); result.put("fileId", fileId.trim()); result.put("sheetId", sheetId.trim()); + result.put("headerRow", headerRow); result.put("startRow", startRow); result.put("hint", "现在录入分销标识为 H-TF 的订单时,将自动追加到此腾讯文档(从第" + startRow + "行开始匹配)"); 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 bb8d043..c861ffd 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 @@ -445,7 +445,164 @@ public class TencentDocController extends BaseController { } /** - * 根据单号填充物流链接 - 读取表格数据,根据单号查询订单系统中的物流链接,并填充到表格 + * 直接填充单个订单的物流链接 + * + * @param params 包含 thirdPartyOrderNo(第三方单号)和 logisticsLink(物流链接) + * @return 填充结果 + */ + @PostMapping("/fillSingleLogistics") + public AjaxResult fillSingleLogistics(@RequestBody Map params) { + try { + // 1. 获取参数 + String thirdPartyOrderNo = (String) params.get("thirdPartyOrderNo"); + String logisticsLink = (String) params.get("logisticsLink"); + + if (thirdPartyOrderNo == null || thirdPartyOrderNo.isEmpty()) { + return AjaxResult.error("第三方单号不能为空"); + } + if (logisticsLink == null || logisticsLink.isEmpty()) { + return AjaxResult.error("物流链接不能为空"); + } + + // 2. 获取访问令牌 + String accessToken; + try { + accessToken = tencentDocTokenService.getValidAccessToken(); + } catch (Exception e) { + return AjaxResult.error("访问令牌无效,请先完成授权"); + } + + // 3. 从配置中读取文档信息 + final String CONFIG_KEY_PREFIX = "tencent:doc:auto:config:"; + String fileId = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "fileId"); + String sheetId = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "sheetId"); + Integer headerRow = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "headerRow"); + Integer configStartRow = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "startRow"); + + if (fileId == null || fileId.isEmpty()) { + fileId = tencentDocConfig.getFileId(); + } + if (sheetId == null || sheetId.isEmpty()) { + sheetId = tencentDocConfig.getSheetId(); + } + if (headerRow == null) { + headerRow = tencentDocConfig.getHeaderRow(); + } + if (configStartRow == null) { + configStartRow = tencentDocConfig.getStartRow(); + } + + if (fileId == null || fileId.isEmpty() || sheetId == null || sheetId.isEmpty()) { + return AjaxResult.error("文档配置不完整,请先完成配置"); + } + + // 4. 读取表头(从配置的 headerRow 读取,与 startRow 独立) + String headerRange = String.format("A%d:Z%d", headerRow, headerRow); + + JSONObject headerData = tencentDocService.readSheetData(accessToken, fileId, sheetId, headerRange); + if (headerData == null || !headerData.containsKey("gridData")) { + return AjaxResult.error("无法读取表头数据"); + } + + // 5. 解析表头,找到"单号"和"物流单号"列 + JSONObject gridData = headerData.getJSONObject("gridData"); + JSONArray rows = gridData.getJSONArray("rows"); + if (rows == null || rows.isEmpty()) { + return AjaxResult.error("表头数据为空"); + } + + JSONObject headerRowData = rows.getJSONObject(0); + JSONArray headerCells = headerRowData.getJSONArray("values"); + + int orderNoColumn = -1; + int logisticsColumn = -1; + + for (int i = 0; i < headerCells.size(); i++) { + JSONObject cell = headerCells.getJSONObject(i); + if (cell.containsKey("cellValue")) { + String cellText = cell.getJSONObject("cellValue").getString("text"); + if (cellText != null) { + if (cellText.contains("单号")) { + orderNoColumn = i; + } else if (cellText.contains("物流")) { + logisticsColumn = i; + } + } + } + } + + if (orderNoColumn == -1 || logisticsColumn == -1) { + return AjaxResult.error("未找到'单号'或'物流'列,请检查表头配置"); + } + + log.info("表头解析完成 - 单号列: {}, 物流列: {}", orderNoColumn, logisticsColumn); + + // 6. 读取数据区域,查找指定单号 + String dataRange = String.format("A%d:Z%d", configStartRow, configStartRow + 999); + JSONObject data = tencentDocService.readSheetData(accessToken, fileId, sheetId, dataRange); + + if (data == null || !data.containsKey("gridData")) { + return AjaxResult.error("无法读取数据区域"); + } + + JSONObject dataGridData = data.getJSONObject("gridData"); + JSONArray dataRows = dataGridData.getJSONArray("rows"); + + if (dataRows == null || dataRows.isEmpty()) { + return AjaxResult.error("数据区域为空"); + } + + // 7. 查找匹配的单号 + int targetRow = -1; + for (int i = 0; i < dataRows.size(); i++) { + JSONObject row = dataRows.getJSONObject(i); + JSONArray cells = row.getJSONArray("values"); + + if (cells != null && cells.size() > orderNoColumn) { + JSONObject orderNoCell = cells.getJSONObject(orderNoColumn); + if (orderNoCell.containsKey("cellValue")) { + String cellText = orderNoCell.getJSONObject("cellValue").getString("text"); + if (thirdPartyOrderNo.equals(cellText)) { + targetRow = configStartRow + i; + break; + } + } + } + } + + if (targetRow == -1) { + return AjaxResult.error("未找到单号: " + thirdPartyOrderNo); + } + + log.info("找到单号 {} 在第 {} 行", thirdPartyOrderNo, targetRow); + + // 8. 写入物流链接 + com.ruoyi.jarvis.util.TencentDocApiUtil.writeSheetData( + accessToken, + fileId, + sheetId, + String.format("%s%d", getColumnLetter(logisticsColumn), targetRow), + logisticsLink, + tencentDocConfig.getAppId(), + tencentDocConfig.getApiBaseUrl() + ); + + JSONObject result = new JSONObject(); + result.put("thirdPartyOrderNo", thirdPartyOrderNo); + result.put("logisticsLink", logisticsLink); + result.put("row", targetRow); + result.put("column", logisticsColumn); + + return AjaxResult.success("物流链接填充成功", result); + + } catch (Exception e) { + log.error("填充物流链接失败", e); + return AjaxResult.error("填充物流链接失败: " + e.getMessage()); + } + } + + /** + * 批量同步物流链接 - 读取表格数据,根据单号查询订单系统中的物流链接,并填充到表格 * 优化:记录上次处理的最大行数,每次从最大行数-100开始读取,避免重复处理历史数据 * 自动获取和管理访问令牌,点击同步时自动刷新token */ @@ -486,14 +643,16 @@ public class TencentDocController extends BaseController { } } - // 从配置中读取startRow(数据起始行号) + // 从配置中读取表头行和数据起始行 + Integer headerRow = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "headerRow"); Integer configStartRow = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "startRow"); + + if (headerRow == null) { + headerRow = tencentDocConfig.getHeaderRow(); + } if (configStartRow == null) { configStartRow = tencentDocConfig.getStartRow(); } - - // 表头行号 = 数据起始行 - 1(例如:数据从第3行开始,表头在第2行) - Integer headerRow = configStartRow != null ? configStartRow - 1 : 2; // 可选参数:是否强制从指定行开始(如果为true,则忽略Redis记录的最大行数) Boolean forceStart = params.get("forceStart") != null ? diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/TencentDocConfig.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/TencentDocConfig.java index b6f3a25..10e02b6 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/TencentDocConfig.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/TencentDocConfig.java @@ -50,7 +50,10 @@ public class TencentDocConfig { /** 工作表ID(H-TF订单的目标工作表ID) */ private String sheetId; - /** 起始行号(从第几行开始搜索匹配单号,默认为3,即第3行开始为数据行) */ + /** 表头行号(表头所在的行,默认为2) */ + private Integer headerRow = 2; + + /** 起始行号(数据开始的行,从第几行开始搜索匹配单号,默认为3) */ private Integer startRow = 3; /** @@ -154,6 +157,14 @@ public class TencentDocConfig { this.sheetId = sheetId; } + public Integer getHeaderRow() { + return headerRow; + } + + public void setHeaderRow(Integer headerRow) { + this.headerRow = headerRow; + } + public Integer getStartRow() { return startRow; }