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 f3d26e1..8fa7d80 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 @@ -1680,5 +1680,252 @@ public class TencentDocController extends BaseController { return AjaxResult.error("取消待推送任务失败: " + e.getMessage()); } } + + /** + * 反向同步第三方单号 + * 从腾讯文档的物流单号列读取链接,通过链接匹配本地订单,将腾讯文档的单号列值写入到订单的第三方单号字段 + * + * @param params 包含 fileId, sheetId, startRow(起始行,默认850) + * @return 同步结果 + */ + @PostMapping("/reverseSyncThirdPartyOrderNo") + public AjaxResult reverseSyncThirdPartyOrderNo(@RequestBody Map params) { + String batchId = java.util.UUID.randomUUID().toString().replace("-", ""); + + try { + // 获取访问令牌 + String accessToken; + try { + accessToken = tencentDocTokenService.refreshAccessToken(); + log.info("成功刷新访问令牌"); + } catch (Exception e) { + log.error("刷新访问令牌失败", e); + try { + accessToken = tencentDocTokenService.getValidAccessToken(); + } catch (Exception e2) { + return AjaxResult.error("访问令牌无效,请先完成授权。获取授权URL: GET /jarvis/tendoc/authUrl"); + } + } + + // 从参数或配置中获取文档信息 + String fileId = (String) params.get("fileId"); + String sheetId = (String) params.get("sheetId"); + + final String CONFIG_KEY_PREFIX = "tencent:doc:auto:config:"; + if (fileId == null || fileId.isEmpty()) { + fileId = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "fileId"); + if (fileId == null || fileId.isEmpty()) { + fileId = tencentDocConfig.getFileId(); + } + } + if (sheetId == null || sheetId.isEmpty()) { + sheetId = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "sheetId"); + if (sheetId == null || sheetId.isEmpty()) { + sheetId = tencentDocConfig.getSheetId(); + } + } + + // 从配置中读取表头行 + Integer headerRow = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "headerRow"); + if (headerRow == null) { + headerRow = tencentDocConfig.getHeaderRow(); + } + + // 起始行,默认850 + Integer startRow = params.get("startRow") != null ? + Integer.valueOf(params.get("startRow").toString()) : 850; + + // 结束行,默认读取200行 + Integer endRow = params.get("endRow") != null ? + Integer.valueOf(params.get("endRow").toString()) : (startRow + 199); + + if (accessToken == null || fileId == null || sheetId == null) { + return AjaxResult.error("文档配置不完整,请先配置 fileId 和 sheetId"); + } + + log.info("反向同步第三方单号开始 - fileId: {}, sheetId: {}, 起始行: {}, 结束行: {}", + fileId, sheetId, startRow, endRow); + + // 读取表头,识别列位置 + String headerRange = String.format("A%d:Z%d", headerRow, headerRow); + JSONObject headerData = tencentDocService.readSheetData(accessToken, fileId, sheetId, headerRange); + + if (headerData == null || !headerData.containsKey("values")) { + return AjaxResult.error("读取表头失败"); + } + + JSONArray headerValues = headerData.getJSONArray("values"); + if (headerValues == null || headerValues.isEmpty()) { + return AjaxResult.error("表头数据为空"); + } + + JSONArray headerRowData = headerValues.getJSONArray(0); + if (headerRowData == null || headerRowData.isEmpty()) { + return AjaxResult.error("无法识别表头"); + } + + // 识别列位置 + Integer orderNoColumn = null; // "单号"列 + Integer logisticsLinkColumn = null; // "物流单号"列 + + for (int i = 0; i < headerRowData.size(); i++) { + String cellValue = headerRowData.getString(i); + if (cellValue != null) { + String cellValueTrim = cellValue.trim(); + + if (orderNoColumn == null && cellValueTrim.contains("单号") && !cellValueTrim.contains("物流")) { + orderNoColumn = i; + log.info("✓ 识别到 '单号' 列:第 {} 列(索引{})", i + 1, i); + } + + if (logisticsLinkColumn == null && (cellValueTrim.contains("物流单号") || cellValueTrim.contains("物流链接"))) { + logisticsLinkColumn = i; + log.info("✓ 识别到 '物流单号' 列:第 {} 列(索引{})", i + 1, i); + } + } + } + + if (orderNoColumn == null || logisticsLinkColumn == null) { + return AjaxResult.error("无法识别表头列,请确保表头包含'单号'和'物流单号'列"); + } + + // 读取数据行 + String dataRange = String.format("A%d:Z%d", startRow, endRow); + log.info("读取数据范围: {}", dataRange); + + JSONObject dataResponse = tencentDocService.readSheetData(accessToken, fileId, sheetId, dataRange); + if (dataResponse == null || !dataResponse.containsKey("values")) { + return AjaxResult.error("读取数据失败"); + } + + JSONArray rows = dataResponse.getJSONArray("values"); + if (rows == null || rows.isEmpty()) { + return AjaxResult.error("数据为空"); + } + + // 统计结果 + int successCount = 0; + int skippedCount = 0; + int errorCount = 0; + + // 处理每一行 + for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) { + JSONArray row = rows.getJSONArray(rowIndex); + if (row == null || row.size() <= Math.max(orderNoColumn, logisticsLinkColumn)) { + skippedCount++; + continue; + } + + int actualRow = startRow + rowIndex; + String logisticsLink = row.getString(logisticsLinkColumn); + String orderNoFromDoc = row.getString(orderNoColumn); + + // 跳过物流链接为空的行 + if (logisticsLink == null || logisticsLink.trim().isEmpty()) { + log.debug("跳过第 {} 行:物流链接为空", actualRow); + skippedCount++; + logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", null, actualRow, null, + "SKIPPED", "物流链接为空"); + continue; + } + + // 跳过单号为空的行 + if (orderNoFromDoc == null || orderNoFromDoc.trim().isEmpty()) { + log.debug("跳过第 {} 行:单号为空", actualRow); + skippedCount++; + logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", null, actualRow, logisticsLink, + "SKIPPED", "单号为空"); + continue; + } + + // 清理物流链接(去除空格等) + logisticsLink = logisticsLink.trim(); + + try { + // 通过物流链接查找订单 + JDOrder order = jdOrderService.selectJDOrderByLogisticsLink(logisticsLink); + + if (order == null) { + log.warn("未找到匹配的订单 - 行: {}, 物流链接: {}", actualRow, logisticsLink); + errorCount++; + logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", null, actualRow, logisticsLink, + "FAILED", "未找到匹配的订单"); + continue; + } + + // 检查订单是否已有第三方单号(如果已有且与文档中的不同,跳过) + if (order.getThirdPartyOrderNo() != null && !order.getThirdPartyOrderNo().trim().isEmpty()) { + if (!order.getThirdPartyOrderNo().trim().equals(orderNoFromDoc.trim())) { + log.info("跳过第 {} 行:订单已有第三方单号且不同 - 现有: {}, 文档: {}", + actualRow, order.getThirdPartyOrderNo(), orderNoFromDoc); + skippedCount++; + logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", order.getRemark(), actualRow, logisticsLink, + "SKIPPED", "订单已有第三方单号且不同"); + continue; + } + // 如果相同,继续执行(清除物流链接) + } + + // 更新订单的第三方单号 + order.setThirdPartyOrderNo(orderNoFromDoc.trim()); + int updateResult = jdOrderService.updateJDOrder(order); + + if (updateResult <= 0) { + log.error("更新订单失败 - 行: {}, 订单ID: {}, 单号: {}", + actualRow, order.getId(), order.getRemark()); + errorCount++; + logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", order.getRemark(), actualRow, logisticsLink, + "FAILED", "更新订单失败"); + continue; + } + + log.info("✓ 更新订单成功 - 行: {}, 订单: {}, 第三方单号: {}", + actualRow, order.getRemark(), orderNoFromDoc); + + successCount++; + + // 记录成功日志 + logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", order.getRemark(), actualRow, logisticsLink, + "SUCCESS", String.format("已将单号 %s 写入订单的第三方单号字段", orderNoFromDoc)); + + } catch (Exception e) { + log.error("处理第 {} 行失败", actualRow, e); + errorCount++; + logOperation(batchId, fileId, sheetId, "REVERSE_SYNC", null, actualRow, logisticsLink, + "FAILED", "处理异常: " + e.getMessage()); + } + + // 添加延迟,避免API调用频率过高 + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + JSONObject result = new JSONObject(); + result.put("batchId", batchId); + result.put("startRow", startRow); + result.put("endRow", endRow); + result.put("successCount", successCount); + result.put("skippedCount", skippedCount); + result.put("errorCount", errorCount); + + String message = String.format( + "✓ 反向同步完成:成功 %d 条,跳过 %d 条,错误 %d 条\n" + + " 处理范围:第 %d-%d 行\n" + + " 批次ID:%s", + successCount, skippedCount, errorCount, startRow, endRow, batchId); + result.put("message", message); + + log.info("反向同步第三方单号完成 - {}", message); + + return AjaxResult.success("反向同步完成", result); + + } catch (Exception e) { + log.error("反向同步第三方单号失败", e); + return AjaxResult.error("反向同步失败: " + e.getMessage()); + } + } }