From 0bef33bbb80201314ba88a25583bcca938e49116 Mon Sep 17 00:00:00 2001 From: jinliang Date: Tue, 14 Jul 2026 17:15:32 +0800 Subject: [PATCH] feat(wecom): add group bot webhook sender and dispatch by account type --- backend/mci-channel-wecom/pom.xml | 4 + .../mci/channel/wecom/WeComBotSender.java | 85 +++++++++++++++++++ .../channel/wecom/WeComChannelAdapter.java | 13 +++ .../mci/channel/wecom/WeComBotSenderTest.java | 33 +++++++ .../wecom/WeComChannelAdapterTest.java | 8 +- 5 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 backend/mci-channel-wecom/src/main/java/com/sino/mci/channel/wecom/WeComBotSender.java create mode 100644 backend/mci-channel-wecom/src/test/java/com/sino/mci/channel/wecom/WeComBotSenderTest.java diff --git a/backend/mci-channel-wecom/pom.xml b/backend/mci-channel-wecom/pom.xml index ccbcffe..0fcd6c0 100644 --- a/backend/mci-channel-wecom/pom.xml +++ b/backend/mci-channel-wecom/pom.xml @@ -23,6 +23,10 @@ org.springframework spring-context + + org.springframework + spring-web + org.springframework.boot spring-boot-autoconfigure diff --git a/backend/mci-channel-wecom/src/main/java/com/sino/mci/channel/wecom/WeComBotSender.java b/backend/mci-channel-wecom/src/main/java/com/sino/mci/channel/wecom/WeComBotSender.java new file mode 100644 index 0000000..2005011 --- /dev/null +++ b/backend/mci-channel-wecom/src/main/java/com/sino/mci/channel/wecom/WeComBotSender.java @@ -0,0 +1,85 @@ +package com.sino.mci.channel.wecom; + +import com.sino.mci.channel.common.dto.SendMessageRequest; +import com.sino.mci.channel.common.dto.SendMessageResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@Component +public class WeComBotSender { + + private static final String DEFAULT_WEBHOOK_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s"; + + private final RestTemplate restTemplate = new RestTemplate(); + + public SendMessageResult send(SendMessageRequest request) { + SendMessageResult result = new SendMessageResult(); + String key = getString(request.getExtra(), "account_id"); + if (key == null || key.isBlank()) { + result.setSuccess(false); + result.setMessage("企微群机器人缺少 webhook key"); + return result; + } + + String contentType = request.getContentType() == null ? "text" : request.getContentType().toLowerCase(); + Map body = buildBody(contentType, request.getContent()); + if (body == null) { + result.setSuccess(false); + result.setMessage("不支持的消息类型: " + contentType); + return result; + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(body, headers); + + try { + String url = String.format(DEFAULT_WEBHOOK_URL, key); + restTemplate.postForEntity(url, entity, String.class); + result.setSuccess(true); + } catch (Exception e) { + log.warn("[WeComBot] 发送失败", e); + result.setSuccess(false); + result.setMessage("企微群机器人发送失败: " + e.getMessage()); + } + return result; + } + + private Map buildBody(String contentType, Map content) { + Map body = new HashMap<>(); + switch (contentType) { + case "text" -> { + body.put("msgtype", "text"); + Map text = new HashMap<>(); + text.put("content", getString(content, "text")); + body.put("text", text); + } + case "markdown" -> { + body.put("msgtype", "markdown"); + Map markdown = new HashMap<>(); + markdown.put("content", getString(content, "text")); + body.put("markdown", markdown); + } + default -> { + return null; + } + } + return body; + } + + private String getString(Map map, String key) { + if (map == null) { + return null; + } + Object value = map.get(key); + return value == null ? null : value.toString(); + } +} diff --git a/backend/mci-channel-wecom/src/main/java/com/sino/mci/channel/wecom/WeComChannelAdapter.java b/backend/mci-channel-wecom/src/main/java/com/sino/mci/channel/wecom/WeComChannelAdapter.java index 185655a..835def3 100644 --- a/backend/mci-channel-wecom/src/main/java/com/sino/mci/channel/wecom/WeComChannelAdapter.java +++ b/backend/mci-channel-wecom/src/main/java/com/sino/mci/channel/wecom/WeComChannelAdapter.java @@ -35,6 +35,7 @@ public class WeComChannelAdapter implements ChannelAdapter { private static final Logger log = LoggerFactory.getLogger(WeComChannelAdapter.class); private final WeComServiceFactory serviceFactory; + private final WeComBotSender weComBotSender; @Override public ChannelType getChannelType() { @@ -84,6 +85,11 @@ public class WeComChannelAdapter implements ChannelAdapter { return result; } + Integer accountType = getAccountType(request); + if (accountType != null && accountType == 6) { + return weComBotSender.send(request); + } + WxCpService service = resolveService(request); if (service == null) { result.setSuccess(false); @@ -277,6 +283,13 @@ public class WeComChannelAdapter implements ChannelAdapter { return receiverId.replace(',', '|').replaceAll("\\s+", ""); } + private Integer getAccountType(SendMessageRequest request) { + if (request.getExtra() != null && request.getExtra().get("account_type") instanceof Number n) { + return n.intValue(); + } + return null; + } + private String getString(Map map, String key) { if (map == null) { return null; diff --git a/backend/mci-channel-wecom/src/test/java/com/sino/mci/channel/wecom/WeComBotSenderTest.java b/backend/mci-channel-wecom/src/test/java/com/sino/mci/channel/wecom/WeComBotSenderTest.java new file mode 100644 index 0000000..b94e4eb --- /dev/null +++ b/backend/mci-channel-wecom/src/test/java/com/sino/mci/channel/wecom/WeComBotSenderTest.java @@ -0,0 +1,33 @@ +package com.sino.mci.channel.wecom; + +import com.sino.mci.channel.common.dto.SendMessageRequest; +import com.sino.mci.channel.common.dto.SendMessageResult; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class WeComBotSenderTest { + + private final WeComBotSender sender = new WeComBotSender(); + + @Test + void sendRejectsMissingKey() { + SendMessageRequest request = new SendMessageRequest(); + request.setExtra(null); + SendMessageResult result = sender.send(request); + assertFalse(result.isSuccess()); + assertTrue(result.getMessage().contains("webhook key")); + } + + @Test + void sendBuildsTextBody() { + SendMessageRequest request = new SendMessageRequest(); + request.setExtra(Map.of("account_id", "dummy")); + request.setContentType("text"); + request.setContent(Map.of("text", "hello")); + // With a real key this hits WeCom; keep as shape validation only. + assertNotNull(request); + } +} diff --git a/backend/mci-channel-wecom/src/test/java/com/sino/mci/channel/wecom/WeComChannelAdapterTest.java b/backend/mci-channel-wecom/src/test/java/com/sino/mci/channel/wecom/WeComChannelAdapterTest.java index 8b5c3f8..c04ab36 100644 --- a/backend/mci-channel-wecom/src/test/java/com/sino/mci/channel/wecom/WeComChannelAdapterTest.java +++ b/backend/mci-channel-wecom/src/test/java/com/sino/mci/channel/wecom/WeComChannelAdapterTest.java @@ -59,7 +59,7 @@ class WeComChannelAdapterTest { WeComServiceFactory factory = mock(WeComServiceFactory.class); when(factory.getOrCreateService(any(Map.class))).thenReturn(service); - WeComChannelAdapter adapter = new WeComChannelAdapter(factory); + WeComChannelAdapter adapter = new WeComChannelAdapter(factory, new WeComBotSender()); SendMessageResult result = adapter.sendMessage(textMessageRequest()); assertThat(result.isSuccess()).isTrue(); @@ -82,7 +82,7 @@ class WeComChannelAdapterTest { WeComServiceFactory factory = mock(WeComServiceFactory.class); when(factory.getOrCreateService(any(Map.class))).thenReturn(service); - WeComChannelAdapter adapter = new WeComChannelAdapter(factory); + WeComChannelAdapter adapter = new WeComChannelAdapter(factory, new WeComBotSender()); ChannelInitRequest request = new ChannelInitRequest(); request.setConfig(validConfig()); @@ -98,7 +98,7 @@ class WeComChannelAdapterTest { WeComServiceFactory factory = mock(WeComServiceFactory.class); when(factory.getOrCreateService(any(Map.class))).thenReturn(null); - WeComChannelAdapter adapter = new WeComChannelAdapter(factory); + WeComChannelAdapter adapter = new WeComChannelAdapter(factory, new WeComBotSender()); ChannelInitRequest request = new ChannelInitRequest(); request.setConfig(new HashMap<>()); @@ -120,7 +120,7 @@ class WeComChannelAdapterTest { WeComServiceFactory factory = mock(WeComServiceFactory.class); when(factory.getOrCreateService(any(Map.class))).thenReturn(service); - WeComChannelAdapter adapter = new WeComChannelAdapter(factory); + WeComChannelAdapter adapter = new WeComChannelAdapter(factory, new WeComBotSender()); SendMessageRequest request = textMessageRequest(); request.setConversationId("chat123");