From 83dc41f3a54fa6160f52d364a1ca218e1db5abfe Mon Sep 17 00:00:00 2001 From: van Date: Sat, 9 May 2026 23:46:33 +0800 Subject: [PATCH] 1 --- .../wecom/PhoneForwardActivePushImpl.java | 4 +- .../jarvis/wecom/WxSendWeComPushClient.java | 20 +++++--- .../jarvis/WeComInboundController.java | 10 +++- .../service/IPhoneForwardActivePush.java | 5 +- .../jarvis/service/SuperAdminService.java | 6 +++ .../service/impl/OpenPhoneForwardService.java | 33 ++++++++----- .../service/impl/SuperAdminServiceImpl.java | 47 +++++++++++++++++++ 7 files changed, 100 insertions(+), 25 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 07ebee0..c5eb243 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 boolean schedulePushChunks(String toUser, List chunks) { - return wxSendWeComPushClient.pushAfterPassiveDelaySync(toUser, chunks); + public boolean schedulePushChunks(String toUser, String wecomCallbackAgentId, List chunks) { + return wxSendWeComPushClient.pushAfterPassiveDelaySync(toUser, wecomCallbackAgentId, 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 623ef04..746ab7c 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 @@ -34,12 +34,13 @@ public class WxSendWeComPushClient { private String pushSecret; /** - * 与 {@link #scheduleActivePushes(String, List)} 相同顺序与延迟,但在当前线程同步执行,并返回是否全部成功。 - * 供「开/慢开」异步 TG 查询结束后立刻知晓推送结果并打错误日志。 + * 与 {@link #scheduleActivePushes(String, String, List)} 相同顺序与延迟,但在当前线程同步执行,并返回是否全部成功。 + * {@code wecomCallbackAgentId} 与回调 XML 中 AgentId 一致时须传入,便于 wxSend 选用对应应用 secret 调用 message/send。 */ - public boolean pushAfterPassiveDelaySync(String toUser, List contents) { + public boolean pushAfterPassiveDelaySync(String toUser, String wecomCallbackAgentId, List contents) { final String userId = toUser != null ? toUser.trim() : ""; final List list = contents != null ? new ArrayList<>(contents) : Collections.emptyList(); + final String agentOpt = StringUtils.hasText(wecomCallbackAgentId) ? wecomCallbackAgentId.trim() : null; if (!StringUtils.hasText(wxsendBaseUrl)) { log.error("企微主动推送未执行:未配置 jarvis.wecom.wxsend-base-url(用户会话中收不到查询结果或报错)"); @@ -69,7 +70,7 @@ public class WxSendWeComPushClient { continue; } anySent = true; - if (!postJson(url, userId, c.trim())) { + if (!postJson(url, userId, c.trim(), agentOpt)) { allOk = false; } Thread.sleep(120); @@ -95,11 +96,13 @@ public class WxSendWeComPushClient { /** * 在被动回复返回后再发,保证企微侧先出现首条被动消息。 */ - public void scheduleActivePushes(String toUser, List contents) { + public void scheduleActivePushes(String toUser, String wecomCallbackAgentId, List contents) { final String userId = toUser != null ? toUser.trim() : ""; + final String agentPass = wecomCallbackAgentId != null ? wecomCallbackAgentId.trim() : ""; final List list = contents != null ? new ArrayList<>(contents) : Collections.emptyList(); CompletableFuture.runAsync(() -> { - boolean ok = pushAfterPassiveDelaySync(userId, list); + boolean ok = pushAfterPassiveDelaySync( + userId, StringUtils.hasText(agentPass) ? agentPass : null, list); if (!ok) { log.error( "scheduleActivePushes 未完全成功 userId={}(用户可能未收到会话内的后续分段)", @@ -117,10 +120,13 @@ public class WxSendWeComPushClient { } /** @return HTTP 2xx 且无异常时为 true */ - private boolean postJson(String url, String toUser, String content) { + private boolean postJson(String url, String toUser, String content, String agentIdOpt) { JSONObject body = new JSONObject(); body.put("toUser", toUser); body.put("content", content); + if (StringUtils.hasText(agentIdOpt)) { + body.put("agentId", agentIdOpt.trim()); + } byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8); HttpURLConnection conn = null; try { diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundController.java index 63756be..32a64c6 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundController.java @@ -5,6 +5,7 @@ import com.ruoyi.jarvis.domain.dto.WeComInboundRequest; import com.ruoyi.jarvis.domain.dto.WeComInboundResult; import com.ruoyi.jarvis.service.IWeComInboundService; import com.ruoyi.jarvis.service.IWeComInboundTraceService; +import com.ruoyi.jarvis.service.SuperAdminService; import com.ruoyi.jarvis.wecom.WxSendWeComPushClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.StringUtils; @@ -32,6 +33,8 @@ public class WeComInboundController { private IWeComInboundTraceService weComInboundTraceService; @Resource private WxSendWeComPushClient wxSendWeComPushClient; + @Resource + private SuperAdminService superAdminService; @PostMapping("/inbound") public AjaxResult inbound( @@ -46,7 +49,12 @@ public class WeComInboundController { Map data = new HashMap<>(4); data.put("reply", result.getPassiveReply()); data.put("activePushCount", result.getActivePushContents().size()); - wxSendWeComPushClient.scheduleActivePushes(req.getFromUserName(), result.getActivePushContents()); + String pipeTouser = superAdminService.resolveTouserPipeForActivePush(req.getFromUserName()); + if (!StringUtils.hasText(pipeTouser) && StringUtils.hasText(req.getFromUserName())) { + pipeTouser = req.getFromUserName().trim(); + } + String callbackAgentId = StringUtils.hasText(req.getAgentId()) ? req.getAgentId().trim() : null; + wxSendWeComPushClient.scheduleActivePushes(pipeTouser, callbackAgentId, result.getActivePushContents()); return AjaxResult.success(data); } } 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 82353b2..135bc88 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 @@ -11,9 +11,10 @@ public interface IPhoneForwardActivePush { /** * 在被动回复已发出后,按序推送到企微成员(首条前短暂延迟,避免次序错乱)。 * - * @param toUser 成员 UserID(FromUserName) + * @param toUser 企微 message/send 的 touser(可与 XML FromUserName 相同,或由超级管理员 touser 解析为 {@code |} 分隔多成员) + * @param wecomCallbackAgentId 回调明文 XML 中的 AgentId(字符串);空则 wxSend 使用默认 {@code qywx.app.agentId} * @param chunks 每段不超过 UTF-8 2048 字节的正文 * @return {@code true} 表示配置齐全且每一段非空正文均收到 wxSend 2xx 响应 */ - boolean schedulePushChunks(String toUser, List chunks); + boolean schedulePushChunks(String toUser, String wecomCallbackAgentId, List chunks); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/SuperAdminService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/SuperAdminService.java index 6485781..1ef9610 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/SuperAdminService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/SuperAdminService.java @@ -61,4 +61,10 @@ public interface SuperAdminService SuperAdmin selectSuperAdminByUnionId(Long unionId); SuperAdmin selectSuperAdminByWecomUserId(String wxid); + + /** + * 企微 {@code message/send} 的 {@code touser}:优先超级管理员表 {@code touser}(支持英文/中文逗号,转换为 {@code |}), + * 否则为发消息成员 UserID。逻辑与 {@link com.ruoyi.jarvis.service.impl.WeComInboundServiceImpl} 中 {@code resolveTouser} 一致。 + */ + String resolveTouserPipeForActivePush(String fromWecomUserId); } 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 ef84389..9bbc3c7 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 @@ -14,6 +14,7 @@ import com.ruoyi.jarvis.domain.TgScalperPhone; import com.ruoyi.jarvis.domain.dto.WeComInboundRequest; import com.ruoyi.jarvis.service.IPhoneForwardActivePush; import com.ruoyi.jarvis.service.ITgScalperPhoneService; +import com.ruoyi.jarvis.service.SuperAdminService; import com.ruoyi.jarvis.util.WeComUtf8ChunkUtil; import java.io.InputStream; @@ -138,6 +139,9 @@ public class OpenPhoneForwardService { @Resource private ITgScalperPhoneService tgScalperPhoneService; + @Resource + private SuperAdminService superAdminService; + @Autowired(required = false) private IPhoneForwardActivePush phoneForwardActivePush; @@ -218,8 +222,9 @@ public class OpenPhoneForwardService { if (asyncResultPushEnabled && phoneForwardActivePush != null && StringUtils.hasText(req.getFromUserName())) { markForwardInflight(cKey); - final String toUser = req.getFromUserName().trim(); - CompletableFuture.runAsync(() -> runForwardAndPush(toUser, phone, bot, cKey)); + final String pushTouserPipe = superAdminService.resolveTouserPipeForActivePush(req.getFromUserName()); + final String callbackAgentId = StringUtils.hasText(req.getAgentId()) ? req.getAgentId().trim() : null; + CompletableFuture.runAsync(() -> runForwardAndPush(pushTouserPipe, callbackAgentId, phone, bot, cKey)); return String.format("收到电话:%s。\n后续结果将通过应用消息推送。", phone); } @@ -256,31 +261,33 @@ public class OpenPhoneForwardService { forwardInflightUntilMs.remove(cKey); } - private void runForwardAndPush(String toUser, String phone, String bot, String cKey) { + private void runForwardAndPush(String pushTouserPipe, String wecomCallbackAgentId, String phone, String bot, String cKey) { try { - log.info("phone-forward 异步 TG 查询开始 phone={} bot={} toUser={}", phone, bot, toUser); + log.info( + "phone-forward 异步 TG 查询开始 phone={} bot={} touser={} callbackAgentId={}", + phone, bot, pushTouserPipe, wecomCallbackAgentId); 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("(无返回内容)"); } - boolean pushed = phoneForwardActivePush.schedulePushChunks(toUser, chunks); + boolean pushed = phoneForwardActivePush.schedulePushChunks(pushTouserPipe, wecomCallbackAgentId, chunks); if (!pushed) { log.error( - "phone-forward 结果未能推送到企微 user={} phone={}(可能为 TG/转发错误或服务端返回的正文,请检查 wxSend 与 jarvis.wecom)", - toUser, phone); + "phone-forward 结果未能推送到企微 touser={} phone={}(可能为 TG/转发错误或服务端返回的正文,请检查 wxSend 与 jarvis.wecom)", + pushTouserPipe, phone); } } catch (Exception e) { - log.error("phone-forward 异步处理异常 phone={} user={}", phone, toUser, e); - pushForwardFailureNotice(toUser, userVisibleThrowableMessage(e)); + log.error("phone-forward 异步处理异常 phone={} user={}", phone, pushTouserPipe, e); + pushForwardFailureNotice(pushTouserPipe, wecomCallbackAgentId, userVisibleThrowableMessage(e)); } finally { clearForwardInflight(cKey); } } - private void pushForwardFailureNotice(String toUser, String line) { - if (phoneForwardActivePush == null || !StringUtils.hasText(toUser)) { + private void pushForwardFailureNotice(String pushTouserPipe, String wecomCallbackAgentId, String line) { + if (phoneForwardActivePush == null || !StringUtils.hasText(pushTouserPipe)) { return; } try { @@ -288,9 +295,9 @@ public class OpenPhoneForwardService { if (parts.isEmpty()) { parts = Collections.singletonList("「转发服务」异常,请稍后重试。"); } - boolean ok = phoneForwardActivePush.schedulePushChunks(toUser.trim(), parts); + boolean ok = phoneForwardActivePush.schedulePushChunks(pushTouserPipe.trim(), wecomCallbackAgentId, parts); if (!ok) { - log.error("phone-forward 异常说明未能推送到企微 user={}", toUser); + log.error("phone-forward 异常说明未能推送到企微 user={}", pushTouserPipe); } } catch (Exception e2) { log.warn("phone-forward 异常说明推送过程失败 err={}", e2.toString()); diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/SuperAdminServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/SuperAdminServiceImpl.java index 2ed2c24..cf2851b 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/SuperAdminServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/SuperAdminServiceImpl.java @@ -3,6 +3,7 @@ package com.ruoyi.jarvis.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import com.ruoyi.jarvis.mapper.SuperAdminMapper; import com.ruoyi.jarvis.domain.SuperAdmin; import com.ruoyi.jarvis.service.SuperAdminService; @@ -15,6 +16,9 @@ import com.ruoyi.jarvis.service.SuperAdminService; @Service public class SuperAdminServiceImpl implements SuperAdminService { + /** 与 {@link com.ruoyi.jarvis.service.impl.WeComInboundServiceImpl#WE_COM_SUPER_USER_ID} 一致 */ + private static final String WE_COM_SUPER_USER_ID = "LinPingFan"; + @Autowired private SuperAdminMapper superAdminMapper; @@ -99,4 +103,47 @@ public class SuperAdminServiceImpl implements SuperAdminService public SuperAdmin selectSuperAdminByWecomUserId(String wxid) { return superAdminMapper.selectSuperAdminByWecomUserId(wxid); } + + @Override + public String resolveTouserPipeForActivePush(String fromWecomUserId) { + if (!StringUtils.hasText(fromWecomUserId)) { + return ""; + } + String from = fromWecomUserId.trim(); + boolean isSuper = WE_COM_SUPER_USER_ID.equals(from); + SuperAdmin row = selectSuperAdminByWecomUserId(from); + String raw = null; + if (row != null && StringUtils.hasText(row.getTouser())) { + raw = row.getTouser().trim(); + } else if (isSuper) { + SuperAdmin ping = selectSuperAdminByWecomUserId(WE_COM_SUPER_USER_ID); + if (ping != null && StringUtils.hasText(ping.getTouser())) { + raw = ping.getTouser().trim(); + } + } + if (raw != null) { + return commaSeparatedToWeComTouser(raw); + } + return from; + } + + /** 企微 API:多个成员 UserID 用 {@code |} 分隔 */ + private static String commaSeparatedToWeComTouser(String raw) { + if (!StringUtils.hasText(raw)) { + return ""; + } + String[] parts = raw.split("[,,]"); + StringBuilder sb = new StringBuilder(); + for (String p : parts) { + String t = p != null ? p.trim() : ""; + if (t.isEmpty()) { + continue; + } + if (sb.length() > 0) { + sb.append('|'); + } + sb.append(t); + } + return sb.length() > 0 ? sb.toString() : raw.trim(); + } }