From 217ea3afdceee7daf68c5fc6b90619dd55a10f19 Mon Sep 17 00:00:00 2001 From: van Date: Sat, 9 May 2026 23:38:10 +0800 Subject: [PATCH] 1 --- .../wecom/PhoneForwardActivePushImpl.java | 4 +- .../jarvis/wecom/WxSendWeComPushClient.java | 101 +++++++++++++----- .../service/IPhoneForwardActivePush.java | 5 +- .../service/impl/OpenPhoneForwardService.java | 40 ++++++- 4 files changed, 119 insertions(+), 31 deletions(-) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/jarvis/wecom/PhoneForwardActivePushImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/jarvis/wecom/PhoneForwardActivePushImpl.java index 7d44124..07ebee0 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/jarvis/wecom/PhoneForwardActivePushImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/jarvis/wecom/PhoneForwardActivePushImpl.java @@ -12,7 +12,7 @@ public class PhoneForwardActivePushImpl implements IPhoneForwardActivePush { private WxSendWeComPushClient wxSendWeComPushClient; @Override - public void schedulePushChunks(String toUser, List chunks) { - wxSendWeComPushClient.scheduleActivePushes(toUser, chunks); + public boolean schedulePushChunks(String toUser, List chunks) { + return wxSendWeComPushClient.pushAfterPassiveDelaySync(toUser, chunks); } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/jarvis/wecom/WxSendWeComPushClient.java b/ruoyi-admin/src/main/java/com/ruoyi/jarvis/wecom/WxSendWeComPushClient.java index 16d2c66..623ef04 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/jarvis/wecom/WxSendWeComPushClient.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/jarvis/wecom/WxSendWeComPushClient.java @@ -13,6 +13,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -33,29 +34,76 @@ public class WxSendWeComPushClient { private String pushSecret; /** - * 在被动回复返回后延迟再发,保证企微侧先出现首条被动消息。 + * 与 {@link #scheduleActivePushes(String, List)} 相同顺序与延迟,但在当前线程同步执行,并返回是否全部成功。 + * 供「开/慢开」异步 TG 查询结束后立刻知晓推送结果并打错误日志。 + */ + public boolean pushAfterPassiveDelaySync(String toUser, List contents) { + final String userId = toUser != null ? toUser.trim() : ""; + final List list = contents != null ? new ArrayList<>(contents) : Collections.emptyList(); + + if (!StringUtils.hasText(wxsendBaseUrl)) { + log.error("企微主动推送未执行:未配置 jarvis.wecom.wxsend-base-url(用户会话中收不到查询结果或报错)"); + return false; + } + if (!StringUtils.hasText(pushSecret)) { + log.error("企微主动推送未执行:未配置 jarvis.wecom.push-secret"); + return false; + } + if (!StringUtils.hasText(userId)) { + log.error("企微主动推送未执行:目标成员 UserID 为空"); + return false; + } + if (list.isEmpty()) { + log.warn("企微主动推送未执行:推送内容为空 userId={}", userId); + return false; + } + + try { + Thread.sleep(450); + String base = normalizeBase(wxsendBaseUrl); + String url = base + "/wecom/active-push"; + boolean anySent = false; + boolean allOk = true; + for (String c : list) { + if (!StringUtils.hasText(c)) { + continue; + } + anySent = true; + if (!postJson(url, userId, c.trim())) { + allOk = false; + } + Thread.sleep(120); + } + if (!anySent) { + log.error("企微主动推送:无有效正文段 userId={}", userId); + return false; + } + if (!allOk) { + log.error("企微主动推送部分失败 userId={}(请检查 wxSend /wecom/active-push 与密钥)", userId); + } + return allOk; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("企微主动推送被中断 userId={}", userId, e); + return false; + } catch (Exception e) { + log.error("企微主动推送异常 userId={}", userId, e); + return false; + } + } + + /** + * 在被动回复返回后再发,保证企微侧先出现首条被动消息。 */ public void scheduleActivePushes(String toUser, List contents) { - if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(pushSecret) - || !StringUtils.hasText(toUser) || contents == null || contents.isEmpty()) { - return; - } - final String userId = toUser.trim(); - final List list = new ArrayList<>(contents); + final String userId = toUser != null ? toUser.trim() : ""; + final List list = contents != null ? new ArrayList<>(contents) : Collections.emptyList(); CompletableFuture.runAsync(() -> { - try { - Thread.sleep(450); - String base = normalizeBase(wxsendBaseUrl); - String url = base + "/wecom/active-push"; - for (String c : list) { - if (!StringUtils.hasText(c)) { - continue; - } - postJson(url, userId, c.trim()); - Thread.sleep(120); - } - } catch (Exception e) { - log.warn("企微主动推送任务异常 userId={} msg={}", userId, e.toString()); + boolean ok = pushAfterPassiveDelaySync(userId, list); + if (!ok) { + log.error( + "scheduleActivePushes 未完全成功 userId={}(用户可能未收到会话内的后续分段)", + userId); } }); } @@ -68,7 +116,8 @@ public class WxSendWeComPushClient { return b; } - private void postJson(String url, String toUser, String content) { + /** @return HTTP 2xx 且无异常时为 true */ + private boolean postJson(String url, String toUser, String content) { JSONObject body = new JSONObject(); body.put("toUser", toUser); body.put("content", content); @@ -89,12 +138,14 @@ public class WxSendWeComPushClient { InputStream is = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream(); String resp = readAll(is); if (code < 200 || code >= 300) { - log.warn("wxSend active-push HTTP {} body={}", code, resp); - } else { - log.debug("wxSend active-push OK http={} resp={}", code, resp); + log.error("wxSend active-push HTTP {} url={} body={}", code, url, resp); + return false; } + log.debug("wxSend active-push OK http={} resp={}", code, resp); + return true; } catch (Exception e) { - log.warn("wxSend active-push 请求失败 url={} err={}", url, e.toString()); + log.error("wxSend active-push 请求失败 url={} err={}", url, e.toString(), e); + return false; } finally { if (conn != null) { conn.disconnect(); diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IPhoneForwardActivePush.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IPhoneForwardActivePush.java index 74db217..82353b2 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IPhoneForwardActivePush.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IPhoneForwardActivePush.java @@ -9,10 +9,11 @@ import java.util.List; public interface IPhoneForwardActivePush { /** - * 异步排队推送到企微成员 + * 在被动回复已发出后,按序推送到企微成员(首条前短暂延迟,避免次序错乱)。 * * @param toUser 成员 UserID(FromUserName) * @param chunks 每段不超过 UTF-8 2048 字节的正文 + * @return {@code true} 表示配置齐全且每一段非空正文均收到 wxSend 2xx 响应 */ - void schedulePushChunks(String toUser, List chunks); + boolean schedulePushChunks(String toUser, List chunks); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/OpenPhoneForwardService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/OpenPhoneForwardService.java index dbc53b1..ef84389 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/OpenPhoneForwardService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/OpenPhoneForwardService.java @@ -258,20 +258,56 @@ public class OpenPhoneForwardService { private void runForwardAndPush(String toUser, String phone, String bot, String cKey) { try { + log.info("phone-forward 异步 TG 查询开始 phone={} bot={} toUser={}", phone, bot, toUser); String reply = doForward(phone, bot, dedupEnabled ? cKey : null); List chunks = WeComUtf8ChunkUtil.splitUtf8Chunks(reply, WeComUtf8ChunkUtil.WE_COM_TEXT_MAX_UTF8_BYTES); chunks.removeIf(s -> !StringUtils.hasText(s)); if (chunks.isEmpty()) { chunks = Collections.singletonList("(无返回内容)"); } - phoneForwardActivePush.schedulePushChunks(toUser, chunks); + boolean pushed = phoneForwardActivePush.schedulePushChunks(toUser, chunks); + if (!pushed) { + log.error( + "phone-forward 结果未能推送到企微 user={} phone={}(可能为 TG/转发错误或服务端返回的正文,请检查 wxSend 与 jarvis.wecom)", + toUser, phone); + } } catch (Exception e) { - log.warn("phone-forward 异步推送异常 phone={} err={}", phone, e.toString()); + log.error("phone-forward 异步处理异常 phone={} user={}", phone, toUser, e); + pushForwardFailureNotice(toUser, userVisibleThrowableMessage(e)); } finally { clearForwardInflight(cKey); } } + private void pushForwardFailureNotice(String toUser, String line) { + if (phoneForwardActivePush == null || !StringUtils.hasText(toUser)) { + return; + } + try { + List parts = WeComUtf8ChunkUtil.splitUtf8Chunks(line, WeComUtf8ChunkUtil.WE_COM_TEXT_MAX_UTF8_BYTES); + if (parts.isEmpty()) { + parts = Collections.singletonList("「转发服务」异常,请稍后重试。"); + } + boolean ok = phoneForwardActivePush.schedulePushChunks(toUser.trim(), parts); + if (!ok) { + log.error("phone-forward 异常说明未能推送到企微 user={}", toUser); + } + } catch (Exception e2) { + log.warn("phone-forward 异常说明推送过程失败 err={}", e2.toString()); + } + } + + private static String userVisibleThrowableMessage(Throwable e) { + String m = e.getMessage(); + if (!StringUtils.hasText(m)) { + m = e.getClass().getSimpleName(); + } + if (m.length() > 500) { + m = m.substring(0, 500) + "…"; + } + return "「转发服务」异常:" + m + "。请稍后重试。"; + } + private String dedupGet(String key) { if (!dedupEnabled || key == null) { return null;