feat(wecom): add group bot webhook sender and dispatch by account type

This commit is contained in:
2026-07-14 17:15:32 +08:00
parent 4bc643024d
commit 0bef33bbb8
5 changed files with 139 additions and 4 deletions

View File

@@ -23,6 +23,10 @@
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId> <artifactId>spring-context</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId> <artifactId>spring-boot-autoconfigure</artifactId>

View File

@@ -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<String, Object> 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<Map<String, Object>> 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<String, Object> buildBody(String contentType, Map<String, Object> content) {
Map<String, Object> body = new HashMap<>();
switch (contentType) {
case "text" -> {
body.put("msgtype", "text");
Map<String, Object> text = new HashMap<>();
text.put("content", getString(content, "text"));
body.put("text", text);
}
case "markdown" -> {
body.put("msgtype", "markdown");
Map<String, Object> markdown = new HashMap<>();
markdown.put("content", getString(content, "text"));
body.put("markdown", markdown);
}
default -> {
return null;
}
}
return body;
}
private String getString(Map<String, Object> map, String key) {
if (map == null) {
return null;
}
Object value = map.get(key);
return value == null ? null : value.toString();
}
}

View File

@@ -35,6 +35,7 @@ public class WeComChannelAdapter implements ChannelAdapter {
private static final Logger log = LoggerFactory.getLogger(WeComChannelAdapter.class); private static final Logger log = LoggerFactory.getLogger(WeComChannelAdapter.class);
private final WeComServiceFactory serviceFactory; private final WeComServiceFactory serviceFactory;
private final WeComBotSender weComBotSender;
@Override @Override
public ChannelType getChannelType() { public ChannelType getChannelType() {
@@ -84,6 +85,11 @@ public class WeComChannelAdapter implements ChannelAdapter {
return result; return result;
} }
Integer accountType = getAccountType(request);
if (accountType != null && accountType == 6) {
return weComBotSender.send(request);
}
WxCpService service = resolveService(request); WxCpService service = resolveService(request);
if (service == null) { if (service == null) {
result.setSuccess(false); result.setSuccess(false);
@@ -277,6 +283,13 @@ public class WeComChannelAdapter implements ChannelAdapter {
return receiverId.replace(',', '|').replaceAll("\\s+", ""); 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) { private String getString(Map<?, ?> map, String key) {
if (map == null) { if (map == null) {
return null; return null;

View File

@@ -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);
}
}

View File

@@ -59,7 +59,7 @@ class WeComChannelAdapterTest {
WeComServiceFactory factory = mock(WeComServiceFactory.class); WeComServiceFactory factory = mock(WeComServiceFactory.class);
when(factory.getOrCreateService(any(Map.class))).thenReturn(service); 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()); SendMessageResult result = adapter.sendMessage(textMessageRequest());
assertThat(result.isSuccess()).isTrue(); assertThat(result.isSuccess()).isTrue();
@@ -82,7 +82,7 @@ class WeComChannelAdapterTest {
WeComServiceFactory factory = mock(WeComServiceFactory.class); WeComServiceFactory factory = mock(WeComServiceFactory.class);
when(factory.getOrCreateService(any(Map.class))).thenReturn(service); when(factory.getOrCreateService(any(Map.class))).thenReturn(service);
WeComChannelAdapter adapter = new WeComChannelAdapter(factory); WeComChannelAdapter adapter = new WeComChannelAdapter(factory, new WeComBotSender());
ChannelInitRequest request = new ChannelInitRequest(); ChannelInitRequest request = new ChannelInitRequest();
request.setConfig(validConfig()); request.setConfig(validConfig());
@@ -98,7 +98,7 @@ class WeComChannelAdapterTest {
WeComServiceFactory factory = mock(WeComServiceFactory.class); WeComServiceFactory factory = mock(WeComServiceFactory.class);
when(factory.getOrCreateService(any(Map.class))).thenReturn(null); when(factory.getOrCreateService(any(Map.class))).thenReturn(null);
WeComChannelAdapter adapter = new WeComChannelAdapter(factory); WeComChannelAdapter adapter = new WeComChannelAdapter(factory, new WeComBotSender());
ChannelInitRequest request = new ChannelInitRequest(); ChannelInitRequest request = new ChannelInitRequest();
request.setConfig(new HashMap<>()); request.setConfig(new HashMap<>());
@@ -120,7 +120,7 @@ class WeComChannelAdapterTest {
WeComServiceFactory factory = mock(WeComServiceFactory.class); WeComServiceFactory factory = mock(WeComServiceFactory.class);
when(factory.getOrCreateService(any(Map.class))).thenReturn(service); when(factory.getOrCreateService(any(Map.class))).thenReturn(service);
WeComChannelAdapter adapter = new WeComChannelAdapter(factory); WeComChannelAdapter adapter = new WeComChannelAdapter(factory, new WeComBotSender());
SendMessageRequest request = textMessageRequest(); SendMessageRequest request = textMessageRequest();
request.setConversationId("chat123"); request.setConversationId("chat123");