feat(wecom): add group bot webhook sender and dispatch by account type
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user