From 94f319514e36c6cd786175086fd0ed95bbb25a8e Mon Sep 17 00:00:00 2001 From: van Date: Sat, 11 Apr 2026 22:35:39 +0800 Subject: [PATCH] 1 --- .../jarvis/TencentDocController.java | 28 +++++- .../controller/monitor/ServerController.java | 87 +++++++++++++++---- .../src/main/resources/application-dev.yml | 2 + .../config/JarvisGoofishProperties.java | 7 ++ .../domain/TencentDocBatchPushRecord.java | 2 +- .../TencentDocBatchPushRecordMapper.java | 7 ++ .../service/ITencentDocBatchPushService.java | 5 ++ .../ruoyi/jarvis/service/IWxSendService.java | 7 +- .../service/goofish/GoofishOrderPipeline.java | 43 ++++++++- .../impl/TencentDocBatchPushServiceImpl.java | 63 ++++++++++++++ .../TencentDocDelayedPushServiceImpl.java | 27 +++++- .../service/impl/WxSendServiceImpl.java | 5 ++ .../wecom/WxSendGoofishNotifyClient.java | 33 +++++++ .../TencentDocBatchPushRecordMapper.xml | 10 ++- 14 files changed, 296 insertions(+), 30 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 077475f..dd92040 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 @@ -55,6 +55,9 @@ public class TencentDocController extends BaseController { @Autowired private com.ruoyi.jarvis.service.ITencentDocDelayedPushService delayedPushService; + @Autowired + private com.ruoyi.jarvis.wecom.WxSendGoofishNotifyClient wxSendGoofishNotifyClient; + /** 单次请求最大行数(腾讯文档 API:行数≤1000) */ private static final int API_MAX_ROWS_PER_REQUEST = 200; /** 用 rowTotal 时接口实际单次只能读 200 行 */ @@ -938,6 +941,7 @@ public class TencentDocController extends BaseController { @Anonymous @PostMapping("/fillLogisticsByOrderNo") public AjaxResult fillLogisticsByOrderNo(@RequestBody Map params) { + String batchId = null; try { // 直接尝试刷新token(如果失败,说明需要首次授权) String accessToken; @@ -955,7 +959,7 @@ public class TencentDocController extends BaseController { } // 从参数获取批次ID(如果是批量调用会传入) - String batchId = params.get("batchId") != null ? String.valueOf(params.get("batchId")) : null; + batchId = params.get("batchId") != null ? String.valueOf(params.get("batchId")) : null; // 从参数或配置中获取文档信息 String fileId = (String) params.get("fileId"); @@ -1920,7 +1924,7 @@ public class TencentDocController extends BaseController { if (errorCount > 0 && successUpdates == 0) { status = "FAILED"; } else if (errorCount > 0) { - status = "PARTIAL_SUCCESS"; + status = "PARTIAL"; } batchPushService.updateBatchPushRecord(batchId, status, successUpdates, skippedCount, errorCount, message, null); @@ -1948,7 +1952,21 @@ public class TencentDocController extends BaseController { return AjaxResult.success("填充物流链接完成", result); } catch (Exception e) { log.error("填充物流链接失败", e); - return AjaxResult.error("填充物流链接失败: " + e.getMessage()); + String errMsg = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + if (batchId != null && !batchId.trim().isEmpty()) { + try { + batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, errMsg); + } catch (Exception ex) { + log.error("异常后更新批量推送记录失败 batchId={}", batchId, ex); + } + try { + wxSendGoofishNotifyClient.pushGoofishAgentText(null, + "【腾讯文档推送】批量同步异常\n批次: " + batchId + "\n" + errMsg); + } catch (Exception ex) { + log.warn("腾讯文档推送异常企微通知失败: {}", ex.toString()); + } + } + return AjaxResult.error("填充物流链接失败: " + errMsg); } } @@ -2093,6 +2111,10 @@ public class TencentDocController extends BaseController { String fileId = tencentDocConfig.getFileId(); String sheetId = tencentDocConfig.getSheetId(); + if (fileId != null && !fileId.trim().isEmpty()) { + batchPushService.reconcileStaleRunningRecords(fileId); + } + if (fileId != null && sheetId != null) { com.ruoyi.jarvis.domain.TencentDocBatchPushRecord lastSuccess = batchPushService.getLastSuccessRecord(fileId, sheetId); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java index 82675d2..2309ab3 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java @@ -5,6 +5,7 @@ import com.alibaba.fastjson2.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.core.domain.AjaxResult; @@ -12,6 +13,7 @@ import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.framework.web.domain.Server; import com.ruoyi.jarvis.service.ILogisticsService; import com.ruoyi.jarvis.service.IWxSendService; +import com.ruoyi.jarvis.wecom.WxSendGoofishNotifyClient; import javax.annotation.Resource; import java.util.HashMap; @@ -32,6 +34,9 @@ public class ServerController @Resource private IWxSendService wxSendService; + @Resource + private WxSendGoofishNotifyClient wxSendGoofishNotifyClient; + /** Ollama 服务地址,用于健康检查 */ @Value("${jarvis.ollama.base-url:http://192.168.8.34:11434}") private String ollamaBaseUrl; @@ -72,23 +77,23 @@ public class ServerController healthMap.put("logistics", logisticsMap); } - // 微信推送服务健康检测 - try { - IWxSendService.HealthCheckResult wxSendHealth = wxSendService.checkHealth(); - Map wxSendMap = new HashMap<>(); - wxSendMap.put("healthy", wxSendHealth.isHealthy()); - wxSendMap.put("status", wxSendHealth.getStatus()); - wxSendMap.put("message", wxSendHealth.getMessage()); - wxSendMap.put("serviceUrl", wxSendHealth.getServiceUrl()); - healthMap.put("wxSend", wxSendMap); - } catch (Exception e) { - Map wxSendMap = new HashMap<>(); - wxSendMap.put("healthy", false); - wxSendMap.put("status", "异常"); - wxSendMap.put("message", "健康检测异常: " + e.getMessage()); - wxSendMap.put("serviceUrl", ""); - healthMap.put("wxSend", wxSendMap); - } + // 微信推送:不在此自动下发消息,仅展示配置地址;真实检测见 POST /monitor/server/health/wx-send-test + Map wxSendMap = new HashMap<>(); + wxSendMap.put("manualOnly", true); + wxSendMap.put("healthy", null); + wxSendMap.put("status", "未检测"); + wxSendMap.put("message", "点击「测试」将发送一条健康检查消息(会真实推送到微信)"); + wxSendMap.put("serviceUrl", wxSendService.getHealthCheckServiceUrl()); + healthMap.put("wxSend", wxSendMap); + + // 企微闲鱼通知:仅展示接口地址;真实检测见 POST /monitor/server/health/goofish-notify-test + Map goofishMap = new HashMap<>(); + goofishMap.put("manualOnly", true); + goofishMap.put("healthy", null); + goofishMap.put("status", "未检测"); + goofishMap.put("message", "点击「测试」将经 wxSend 向企微闲鱼应用发送一条测试文本"); + goofishMap.put("serviceUrl", wxSendGoofishNotifyClient.getGoofishPushEndpointDisplay()); + healthMap.put("goofishNotify", goofishMap); // Ollama 服务健康检测(调试用) try { @@ -115,6 +120,54 @@ public class ServerController return AjaxResult.success(healthMap); } + + /** + * 手动测试微信推送(会真实下发一条消息) + */ + @PreAuthorize("@ss.hasPermi('monitor:server:list')") + @PostMapping("/health/wx-send-test") + public AjaxResult testWxSendHealth() { + try { + IWxSendService.HealthCheckResult r = wxSendService.checkHealth(); + Map m = new HashMap<>(); + m.put("manualOnly", true); + m.put("healthy", r.isHealthy()); + m.put("status", r.getStatus()); + m.put("message", r.getMessage()); + m.put("serviceUrl", r.getServiceUrl()); + return AjaxResult.success(m); + } catch (Exception e) { + Map m = new HashMap<>(); + m.put("manualOnly", true); + m.put("healthy", false); + m.put("status", "异常"); + m.put("message", "检测异常: " + e.getMessage()); + m.put("serviceUrl", wxSendService.getHealthCheckServiceUrl()); + return AjaxResult.success(m); + } + } + + /** + * 手动测试企微闲鱼通知(经 wxSend goofish-active-push) + */ + @PreAuthorize("@ss.hasPermi('monitor:server:list')") + @PostMapping("/health/goofish-notify-test") + public AjaxResult testGoofishNotify() { + String err = wxSendGoofishNotifyClient.testGoofishNotify(); + Map m = new HashMap<>(); + m.put("manualOnly", true); + m.put("serviceUrl", wxSendGoofishNotifyClient.getGoofishPushEndpointDisplay()); + if (err == null) { + m.put("healthy", true); + m.put("status", "正常"); + m.put("message", "闲鱼通知测试消息已发送"); + } else { + m.put("healthy", false); + m.put("status", "异常"); + m.put("message", err); + } + return AjaxResult.success(m); + } private void putOllamaUnhealthy(Map healthMap, String url, String message) { Map ollamaMap = new HashMap<>(); diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index d947f10..40a060a 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -288,6 +288,8 @@ jarvis: pull-max-update-time-range-seconds: 15552000 # 全量拉单起点:距今多少天(默认约 3 年) pull-full-history-days: 1095 + # 列表拉单是否仅拉 auto-ship-order-statuses(默认待发货);false 时恢复按时间窗全状态(全量回补可临时关闭) + pull-list-only-auto-ship-statuses: true auto-ship-batch-size: 20 diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/JarvisGoofishProperties.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/JarvisGoofishProperties.java index 2d49702..9bbfbd2 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/JarvisGoofishProperties.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/config/JarvisGoofishProperties.java @@ -58,6 +58,13 @@ public class JarvisGoofishProperties { */ private String autoShipOrderStatuses = "12"; + /** + * 定时/增量「订单列表」拉单是否仅请求 {@link #autoShipOrderStatuses} 中的状态(通常即待发货)。 + * 为 true 时可显著减少列表与后续详情拉取次数;其余状态依赖开放平台推送回调刷新。 + * 全量历史回补若需全状态,可临时设为 false。 + */ + private boolean pullListOnlyAutoShipStatuses = true; + /** * 未在 erp_open_config 配置 express_code 时,自动发货使用的默认快递公司编码(官方列表中日日顺多为 rrs)。 */ diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/TencentDocBatchPushRecord.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/TencentDocBatchPushRecord.java index bd206a5..a4ee087 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/TencentDocBatchPushRecord.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/TencentDocBatchPushRecord.java @@ -59,7 +59,7 @@ public class TencentDocBatchPushRecord extends BaseEntity { /** 错误数量 */ private Integer errorCount; - /** 状态:RUNNING-执行中,SUCCESS-成功,PARTIAL-部分成功,FAILED-失败 */ + /** 状态:RUNNING-执行中,SUCCESS-成功,PARTIAL-部分成功,FAILED-失败,INTERRUPTED-已中断(超时/未正常结束) */ private String status; /** 结果消息 */ diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/TencentDocBatchPushRecordMapper.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/TencentDocBatchPushRecordMapper.java index dc0489f..a8fdf32 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/TencentDocBatchPushRecordMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/TencentDocBatchPushRecordMapper.java @@ -3,6 +3,7 @@ package com.ruoyi.jarvis.mapper; import com.ruoyi.jarvis.domain.TencentDocBatchPushRecord; import org.apache.ibatis.annotations.Param; +import java.util.Date; import java.util.List; /** @@ -41,5 +42,11 @@ public interface TencentDocBatchPushRecordMapper { */ TencentDocBatchPushRecord selectLastSuccessRecord(@Param("fileId") String fileId, @Param("sheetId") String sheetId); + + /** + * 仍为 RUNNING 且开始时间早于指定时间的批次(用于超时归档) + */ + List selectRunningRecordsBefore(@Param("fileId") String fileId, + @Param("beforeTime") Date beforeTime); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ITencentDocBatchPushService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ITencentDocBatchPushService.java index 64c9e19..6bfde51 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ITencentDocBatchPushService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ITencentDocBatchPushService.java @@ -41,5 +41,10 @@ public interface ITencentDocBatchPushService { * 获取推送状态和倒计时信息 */ Map getPushStatusAndCountdown(); + + /** + * 将长时间仍处于 RUNNING 的批次归档为 INTERRUPTED(并可选发企微告警,见实现类配置) + */ + void reconcileStaleRunningRecords(String fileId); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWxSendService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWxSendService.java index 0c82eb1..18c1052 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWxSendService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWxSendService.java @@ -5,10 +5,15 @@ package com.ruoyi.jarvis.service; */ public interface IWxSendService { /** - * 检查微信推送服务健康状态 + * 检查微信推送服务健康状态(会真实下发一条测试消息,仅用于服务监控页「手动测试」) * @return 健康状态信息,包含是否健康、状态描述等 */ HealthCheckResult checkHealth(); + + /** + * 已配置的微信推送健康检查 URL(展示用,不发起请求) + */ + String getHealthCheckServiceUrl(); /** * 健康检测结果 diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java index efed77a..87a0cba 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java @@ -73,7 +73,11 @@ public class GoofishOrderPipeline { ErpGoofishOrder row = upsertFromNotify(appKey, item, lastNotifyJson, "LIST_UPSERT"); tryLinkJdOrder(row); mergeSummaryFromOrderDetailShape(row, item, "LIST"); - refreshDetail(row); + // 仅待发货等需判物流/发货的状态拉详情;其它状态由推送回调更新,减轻开放平台与本地压力 + List awaitingShip = resolveAutoShipOrderStatuses(); + if (row.getOrderStatus() != null && awaitingShip.contains(row.getOrderStatus())) { + refreshDetail(row); + } syncWaybillFromRedis(row); tryAutoShip(row); } @@ -903,6 +907,36 @@ public class GoofishOrderPipeline { log.warn("闲管家拉单: pull-max-pages 与 pull-page-size 乘积超过 10000,已收敛 maxPages={}", maxPages); } int saved = 0; + List listStatusFilters = goofishProperties.isPullListOnlyAutoShipStatuses() + ? resolveAutoShipOrderStatuses() + : null; + if (listStatusFilters != null && listStatusFilters.isEmpty()) { + listStatusFilters = null; + } + if (listStatusFilters == null) { + saved += pullForAppKeyUpdateTimeRangeOnceForStatuses(appKey, cred, authorizeIds, updateTimeStartSec, + updateTimeEndSec, pageSize, maxPages, null); + } else { + for (Integer st : listStatusFilters) { + saved += pullForAppKeyUpdateTimeRangeOnceForStatuses(appKey, cred, authorizeIds, updateTimeStartSec, + updateTimeEndSec, pageSize, maxPages, st); + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + } + return saved; + } + + /** + * @param orderStatusFilter null 表示列表请求不带 order_status(拉全状态) + */ + private int pullForAppKeyUpdateTimeRangeOnceForStatuses(String appKey, IERPAccount cred, List authorizeIds, + long updateTimeStartSec, long updateTimeEndSec, int pageSize, int maxPages, Integer orderStatusFilter) { + int saved = 0; for (Long aid : authorizeIds) { int page = 1; while (page <= maxPages) { @@ -910,6 +944,9 @@ public class GoofishOrderPipeline { q.setAuthorizeId(aid); q.setUpdateTime(updateTimeStartSec, updateTimeEndSec); q.setPage(page, pageSize); + if (orderStatusFilter != null) { + q.setOrderStatus(orderStatusFilter); + } String resp; try { resp = q.getResponseBody(); @@ -936,8 +973,8 @@ public class GoofishOrderPipeline { break; } if (page == maxPages) { - log.warn("闲管家拉单已达最大页数 appKey={} aid={} 区间[{},{}];若订单更多请缩小 pull-time-chunk-seconds", - appKey, aid, updateTimeStartSec, updateTimeEndSec); + log.warn("闲管家拉单已达最大页数 appKey={} aid={} 区间[{},{}] orderStatus={};若订单更多请缩小 pull-time-chunk-seconds", + appKey, aid, updateTimeStartSec, updateTimeEndSec, orderStatusFilter); break; } page++; diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocBatchPushServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocBatchPushServiceImpl.java index 082a60c..ce931a4 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocBatchPushServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocBatchPushServiceImpl.java @@ -6,6 +6,10 @@ import com.ruoyi.jarvis.domain.TencentDocOperationLog; import com.ruoyi.jarvis.mapper.TencentDocBatchPushRecordMapper; import com.ruoyi.jarvis.mapper.TencentDocOperationLogMapper; import com.ruoyi.jarvis.service.ITencentDocBatchPushService; +import com.ruoyi.jarvis.wecom.WxSendGoofishNotifyClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -18,6 +22,10 @@ import java.util.concurrent.TimeUnit; @Service public class TencentDocBatchPushServiceImpl implements ITencentDocBatchPushService { + private static final Logger log = LoggerFactory.getLogger(TencentDocBatchPushServiceImpl.class); + + private static final String REDIS_STALE_BATCH_NOTIFY_KEY = "tendoc:batch:stale-notified:"; + @Resource private TencentDocBatchPushRecordMapper batchPushRecordMapper; @@ -27,6 +35,13 @@ public class TencentDocBatchPushServiceImpl implements ITencentDocBatchPushServi @Resource private RedisCache redisCache; + @Resource + private WxSendGoofishNotifyClient wxSendGoofishNotifyClient; + + /** 仍为 RUNNING 超过该分钟数则归档为 INTERRUPTED(可配置) */ + @Value("${jarvis.tencent-doc.batch-push.stale-running-threshold-minutes:45}") + private int staleRunningThresholdMinutes; + private static final String DELAYED_PUSH_TASK_KEY = "tendoc:delayed_push:task_scheduled"; private static final String DELAYED_PUSH_SCHEDULE_TIME_KEY = "tendoc:delayed_push:next_time"; @@ -81,6 +96,10 @@ public class TencentDocBatchPushServiceImpl implements ITencentDocBatchPushServi @Override public TencentDocBatchPushRecord getBatchPushRecord(String batchId) { TencentDocBatchPushRecord record = batchPushRecordMapper.selectByBatchId(batchId); + if (record != null && record.getFileId() != null && !record.getFileId().trim().isEmpty()) { + reconcileStaleRunningRecords(record.getFileId()); + record = batchPushRecordMapper.selectByBatchId(batchId); + } if (record != null) { // 加载关联的操作日志 List logs = operationLogMapper.selectLogsByBatchId(batchId); @@ -91,6 +110,8 @@ public class TencentDocBatchPushServiceImpl implements ITencentDocBatchPushServi @Override public List getBatchPushRecordListWithLogs(String fileId, String sheetId, Integer limit) { + reconcileStaleRunningRecords(fileId); + TencentDocBatchPushRecord query = new TencentDocBatchPushRecord(); query.setFileId(fileId); query.setSheetId(sheetId); @@ -156,5 +177,47 @@ public class TencentDocBatchPushServiceImpl implements ITencentDocBatchPushServi return result; } + + @Override + public void reconcileStaleRunningRecords(String fileId) { + if (fileId == null || fileId.trim().isEmpty() || staleRunningThresholdMinutes <= 0) { + return; + } + Date before = new Date(System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(staleRunningThresholdMinutes)); + List stale = batchPushRecordMapper.selectRunningRecordsBefore(fileId, before); + if (stale == null || stale.isEmpty()) { + return; + } + for (TencentDocBatchPushRecord r : stale) { + String bid = r.getBatchId(); + if (bid == null || bid.isEmpty()) { + continue; + } + TencentDocBatchPushRecord fresh = batchPushRecordMapper.selectByBatchId(bid); + if (fresh == null || !"RUNNING".equals(fresh.getStatus())) { + continue; + } + String resultMsg = String.format( + "任务已中断:超过 %d 分钟仍处于「执行中」(可能请求超时、进程退出或服务重启),系统已自动标记为结束。批次: %s", + staleRunningThresholdMinutes, bid); + String errMsg = "长时间未完成,自动归档为已中断"; + try { + updateBatchPushRecord(bid, "INTERRUPTED", 0, 0, 0, resultMsg, errMsg); + log.warn("归档超时未结束的批量推送记录 batchId={} thresholdMinutes={}", bid, staleRunningThresholdMinutes); + } catch (Exception e) { + log.error("归档超时批量推送记录失败 batchId={}", bid, e); + continue; + } + String dedupeKey = REDIS_STALE_BATCH_NOTIFY_KEY + bid; + if (redisCache.getCacheObject(dedupeKey) != null) { + continue; + } + String pushText = "【腾讯文档推送】批次长时间未结束,已标记为「已中断」\n" + resultMsg; + boolean ok = wxSendGoofishNotifyClient.pushGoofishAgentText(null, pushText); + if (ok) { + redisCache.setCacheObject(dedupeKey, "1", 7, TimeUnit.DAYS); + } + } + } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocDelayedPushServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocDelayedPushServiceImpl.java index b934c22..72d64d9 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocDelayedPushServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/TencentDocDelayedPushServiceImpl.java @@ -1,7 +1,9 @@ package com.ruoyi.jarvis.service.impl; +import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.jarvis.config.TencentDocConfig; +import com.ruoyi.jarvis.wecom.WxSendGoofishNotifyClient; import com.ruoyi.jarvis.service.ITencentDocBatchPushService; import com.ruoyi.jarvis.service.ITencentDocDelayedPushService; import com.ruoyi.jarvis.service.ITencentDocTokenService; @@ -45,6 +47,9 @@ public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushS @Autowired private ITencentDocBatchPushService batchPushService; + @Autowired + private WxSendGoofishNotifyClient wxSendGoofishNotifyClient; + @Autowired private TencentDocConfig tencentDocConfig; @@ -342,13 +347,25 @@ public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushS Object result = method.invoke(controller, params); log.info("✓ 批量同步执行完成,结果: {}", result); + if (result instanceof AjaxResult) { + AjaxResult ar = (AjaxResult) result; + if (!ar.isSuccess() && batchId != null) { + Object msgObj = ar.get(AjaxResult.MSG_TAG); + String msg = msgObj != null ? String.valueOf(msgObj) : "同步接口返回失败"; + batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, msg); + wxSendGoofishNotifyClient.pushGoofishAgentText(null, + "【腾讯文档推送】定时批量同步失败\n批次: " + batchId + "\n" + msg); + } + } // 不再将 nextStartRow 写入 Redis,下次定时执行时从接口获取 rowCount 决定范围 } catch (Exception ex) { log.error("批量同步调用失败", ex); if (batchId != null) { - batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, - null, "批量同步调用失败: " + ex.getMessage()); + String msg = "批量同步调用失败: " + (ex.getMessage() != null ? ex.getMessage() : ex.getClass().getSimpleName()); + batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, msg); + wxSendGoofishNotifyClient.pushGoofishAgentText(null, + "【腾讯文档推送】定时批量同步异常\n批次: " + batchId + "\n" + msg); } } @@ -357,8 +374,10 @@ public class TencentDocDelayedPushServiceImpl implements ITencentDocDelayedPushS // 更新批量推送记录为失败状态 if (batchId != null) { try { - batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, - null, "执行批量同步失败: " + e.getMessage()); + String msg = "执行批量同步失败: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName()); + batchPushService.updateBatchPushRecord(batchId, "FAILED", 0, 0, 0, null, msg); + wxSendGoofishNotifyClient.pushGoofishAgentText(null, + "【腾讯文档推送】定时批量同步异常\n批次: " + batchId + "\n" + msg); } catch (Exception ex) { log.error("更新批量推送记录失败", ex); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WxSendServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WxSendServiceImpl.java index ebfc4a5..0d945d4 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WxSendServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WxSendServiceImpl.java @@ -32,6 +32,11 @@ public class WxSendServiceImpl implements IWxSendService { healthCheckUrl = wxSendBaseUrl + wxSendHealthPath; logger.info("微信推送服务健康检查地址已初始化: {}", healthCheckUrl); } + + @Override + public String getHealthCheckServiceUrl() { + return healthCheckUrl; + } @Override public IWxSendService.HealthCheckResult checkHealth() { diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/wecom/WxSendGoofishNotifyClient.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/wecom/WxSendGoofishNotifyClient.java index f6b822d..5528de6 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/wecom/WxSendGoofishNotifyClient.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/wecom/WxSendGoofishNotifyClient.java @@ -150,6 +150,39 @@ public class WxSendGoofishNotifyClient { return b; } + /** + * 服务监控展示:闲鱼企微应用推送接口完整 URL(不发起请求) + */ + public String getGoofishPushEndpointDisplay() { + if (!StringUtils.hasText(wxsendBaseUrl)) { + return ""; + } + return normalizeBase(wxsendBaseUrl) + "/wecom/goofish-active-push"; + } + + /** + * 服务监控手动测试:经 wxSend 向企微「闲鱼」应用发一条文本。 + * + * @return null 表示 HTTP 2xx 且调用链未抛错;非空为可直接展示的失败原因 + */ + public String testGoofishNotify() { + if (!StringUtils.hasText(wxsendBaseUrl)) { + return "未配置 jarvis.wecom.wxsend-base-url"; + } + if (!StringUtils.hasText(goofishPushSecret)) { + return "未配置 jarvis.wecom.goofish-push-secret"; + } + if (!StringUtils.hasText(goofishNotifyTouser)) { + return "未配置 jarvis.wecom.goofish-notify-touser(接收人为空)"; + } + String content = "【服务监控·闲鱼通知测试】RuoYi 手动触发 " + new java.util.Date(); + boolean ok = pushGoofishAgentText(null, content); + if (ok) { + return null; + } + return "推送未成功:请核对 wxSend 与企微应用、密钥及接收人,或查看服务端日志"; + } + private void postJson(String url, String toUser, String content) throws Exception { postJsonReturnsOk(url, toUser, content); } diff --git a/ruoyi-system/src/main/resources/mapper/jarvis/TencentDocBatchPushRecordMapper.xml b/ruoyi-system/src/main/resources/mapper/jarvis/TencentDocBatchPushRecordMapper.xml index d853293..60e3447 100644 --- a/ruoyi-system/src/main/resources/mapper/jarvis/TencentDocBatchPushRecordMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/jarvis/TencentDocBatchPushRecordMapper.xml @@ -91,10 +91,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" WHERE file_id = #{fileId} AND sheet_id = #{sheetId} - AND status IN ('SUCCESS', 'PARTIAL') + AND status IN ('SUCCESS', 'PARTIAL', 'PARTIAL_SUCCESS') ORDER BY end_time DESC LIMIT 1 + +