feat(channel-wecom): 实现企微 CP 官方 API 发送器
This commit is contained in:
@@ -23,10 +23,25 @@
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-cp</artifactId>
|
||||
<version>4.6.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -7,11 +7,35 @@ import com.sino.mci.channel.common.dto.SendMessageRequest;
|
||||
import com.sino.mci.channel.common.dto.SendMessageResult;
|
||||
import com.sino.mci.channel.common.model.ChannelType;
|
||||
import com.sino.mci.channel.common.spi.ChannelAdapter;
|
||||
import com.sino.mci.channel.wecom.support.WeComServiceFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.chanjar.weixin.common.api.WxConsts;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.cp.api.WxCpChatService;
|
||||
import me.chanjar.weixin.cp.api.WxCpMessageService;
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.bean.article.NewArticle;
|
||||
import me.chanjar.weixin.cp.bean.message.WxCpAppChatMessage;
|
||||
import me.chanjar.weixin.cp.bean.message.WxCpMessage;
|
||||
import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class WeComChannelAdapter implements ChannelAdapter {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WeComChannelAdapter.class);
|
||||
|
||||
private final WeComServiceFactory serviceFactory;
|
||||
|
||||
@Override
|
||||
public ChannelType getChannelType() {
|
||||
return ChannelType.WECOM;
|
||||
@@ -20,16 +44,73 @@ public class WeComChannelAdapter implements ChannelAdapter {
|
||||
@Override
|
||||
public ChannelInitResult initAccount(ChannelInitRequest request) {
|
||||
ChannelInitResult result = new ChannelInitResult();
|
||||
result.setSuccess(false);
|
||||
result.setMessage("WeCom channel adapter is a stub and has not been implemented yet.");
|
||||
Map<String, Object> config = request == null ? null : request.getConfig();
|
||||
WxCpService service = serviceFactory.getOrCreateService(config);
|
||||
if (service == null) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage("企微 CP 初始化失败:缺少 corp_id/agent_id/secret");
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
// 触发一次 access_token 获取,验证配置可用性
|
||||
service.getAccessToken(false);
|
||||
|
||||
String corpId = getString(config, "corp_id");
|
||||
String agentId = getString(config, "agent_id");
|
||||
|
||||
result.setSuccess(true);
|
||||
result.setAccountId(corpId + ":" + agentId);
|
||||
result.setAccountName("WeCom:" + corpId);
|
||||
result.setMessage("企微 CP 账号初始化成功");
|
||||
} catch (WxErrorException e) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage("企微 CP 初始化失败:" + e.getMessage());
|
||||
log.warn("[WeCom] 初始化账号失败", e);
|
||||
} catch (Exception e) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage("企微 CP 初始化失败:" + e.getMessage());
|
||||
log.warn("[WeCom] 初始化账号异常", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResult sendMessage(SendMessageRequest request) {
|
||||
SendMessageResult result = new SendMessageResult();
|
||||
result.setSuccess(false);
|
||||
result.setMessage("WeCom channel adapter is a stub and has not been implemented yet.");
|
||||
if (request == null) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage("发送请求为空");
|
||||
return result;
|
||||
}
|
||||
|
||||
WxCpService service = resolveService(request);
|
||||
if (service == null) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage("企微 CP 发送失败:无法确定应用账号(请先初始化或在 extra 中提供 corp_id/agent_id/secret)");
|
||||
return result;
|
||||
}
|
||||
|
||||
String contentType = StringUtils.defaultString(request.getContentType()).toLowerCase();
|
||||
boolean toGroupChat = StringUtils.isNotBlank(request.getConversationId());
|
||||
|
||||
try {
|
||||
if (toGroupChat) {
|
||||
sendAppChatMessage(service, request, contentType);
|
||||
} else {
|
||||
WxCpMessageSendResult sendResult = sendAppMessage(service, request, contentType);
|
||||
result.setChannelMessageId(sendResult == null ? null : sendResult.getMsgId());
|
||||
}
|
||||
result.setSuccess(true);
|
||||
} catch (WxErrorException e) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage("企微 CP 发送失败:" + e.getMessage());
|
||||
log.warn("[WeCom] 发送消息失败", e);
|
||||
} catch (Exception e) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage("企微 CP 发送失败:" + e.getMessage());
|
||||
log.warn("[WeCom] 发送消息异常", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -40,4 +121,198 @@ public class WeComChannelAdapter implements ChannelAdapter {
|
||||
payload.setRawBody(rawBody);
|
||||
return payload;
|
||||
}
|
||||
|
||||
private WxCpService resolveService(SendMessageRequest request) {
|
||||
Map<String, Object> extra = request.getExtra();
|
||||
if (extra != null && extra.containsKey("corp_id") && extra.containsKey("agent_id") && extra.containsKey("secret")) {
|
||||
return serviceFactory.getOrCreateService(extra);
|
||||
}
|
||||
|
||||
Map<String, Object> content = request.getContent();
|
||||
if (content != null && content.containsKey("corp_id") && content.containsKey("agent_id") && content.containsKey("secret")) {
|
||||
return serviceFactory.getOrCreateService(content);
|
||||
}
|
||||
|
||||
// 未显式指定账号时,若缓存中仅有一个账号,则使用该账号
|
||||
if (serviceFactory.hasSingleAccount()) {
|
||||
return serviceFactory.getSingleAccountService();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private WxCpMessageSendResult sendAppMessage(WxCpService service, SendMessageRequest request,
|
||||
String contentType) throws WxErrorException {
|
||||
WxCpMessage message = buildAppMessage(request, contentType);
|
||||
WxCpMessageService messageService = service.getMessageService();
|
||||
return messageService.send(message);
|
||||
}
|
||||
|
||||
private void sendAppChatMessage(WxCpService service, SendMessageRequest request,
|
||||
String contentType) throws WxErrorException {
|
||||
WxCpAppChatMessage message = buildAppChatMessage(request, contentType);
|
||||
WxCpChatService chatService = service.getChatService();
|
||||
chatService.sendMsg(message);
|
||||
}
|
||||
|
||||
private WxCpMessage buildAppMessage(SendMessageRequest request, String contentType) {
|
||||
Map<String, Object> content = request.getContent();
|
||||
String toUser = normalizeUserIds(request.getReceiverId());
|
||||
Integer agentId = resolveAgentId(request);
|
||||
|
||||
WxCpMessage message;
|
||||
switch (contentType) {
|
||||
case "image" -> message = WxCpMessage.IMAGE()
|
||||
.agentId(agentId)
|
||||
.toUser(toUser)
|
||||
.mediaId(getString(content, "media_id"))
|
||||
.build();
|
||||
case "file" -> message = WxCpMessage.FILE()
|
||||
.agentId(agentId)
|
||||
.toUser(toUser)
|
||||
.mediaId(getString(content, "media_id"))
|
||||
.build();
|
||||
case "markdown" -> message = WxCpMessage.MARKDOWN()
|
||||
.agentId(agentId)
|
||||
.toUser(toUser)
|
||||
.content(getString(content, "text"))
|
||||
.build();
|
||||
case "news" -> message = WxCpMessage.NEWS()
|
||||
.agentId(agentId)
|
||||
.toUser(toUser)
|
||||
.articles(buildNewsArticles(content))
|
||||
.build();
|
||||
case "miniprogram", "miniprogram_notice" -> message = WxCpMessage.newMiniProgramNoticeBuilder()
|
||||
.agentId(agentId)
|
||||
.toUser(toUser)
|
||||
.appId(getString(content, "app_id"))
|
||||
.page(getString(content, "page"))
|
||||
.title(getString(content, "title"))
|
||||
.description(getString(content, "description"))
|
||||
.emphasisFirstItem(getBoolean(content, "emphasis_first_item"))
|
||||
.contentItems(getStringMap(content, "content_items"))
|
||||
.build();
|
||||
default -> message = WxCpMessage.TEXT()
|
||||
.agentId(agentId)
|
||||
.toUser(toUser)
|
||||
.content(getString(content, "text"))
|
||||
.build();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
private WxCpAppChatMessage buildAppChatMessage(SendMessageRequest request, String contentType) {
|
||||
Map<String, Object> content = request.getContent();
|
||||
String chatId = request.getConversationId();
|
||||
|
||||
return switch (contentType) {
|
||||
case "image" -> WxCpAppChatMessage.builder()
|
||||
.msgType(WxConsts.KefuMsgType.IMAGE)
|
||||
.chatId(chatId)
|
||||
.mediaId(getString(content, "media_id"))
|
||||
.build();
|
||||
case "file" -> WxCpAppChatMessage.builder()
|
||||
.msgType(WxConsts.KefuMsgType.FILE)
|
||||
.chatId(chatId)
|
||||
.mediaId(getString(content, "media_id"))
|
||||
.build();
|
||||
case "markdown" -> WxCpAppChatMessage.builder()
|
||||
.msgType(WxConsts.KefuMsgType.MARKDOWN)
|
||||
.chatId(chatId)
|
||||
.content(getString(content, "text"))
|
||||
.build();
|
||||
case "news" -> WxCpAppChatMessage.builder()
|
||||
.msgType(WxConsts.KefuMsgType.NEWS)
|
||||
.chatId(chatId)
|
||||
.articles(buildNewsArticles(content))
|
||||
.build();
|
||||
default -> WxCpAppChatMessage.buildTextMsg(chatId, getString(content, "text"), false);
|
||||
};
|
||||
}
|
||||
|
||||
private List<NewArticle> buildNewsArticles(Map<String, Object> content) {
|
||||
Object articlesObj = content == null ? null : content.get("articles");
|
||||
if (!(articlesObj instanceof List<?> list)) {
|
||||
return List.of();
|
||||
}
|
||||
List<NewArticle> articles = new ArrayList<>();
|
||||
for (Object item : list) {
|
||||
if (!(item instanceof Map<?, ?> map)) {
|
||||
continue;
|
||||
}
|
||||
NewArticle article = NewArticle.builder()
|
||||
.title(getString(map, "title"))
|
||||
.description(getString(map, "description"))
|
||||
.url(getString(map, "url"))
|
||||
.picUrl(getString(map, "pic_url"))
|
||||
.appid(getString(map, "app_id"))
|
||||
.pagepath(getString(map, "page_path"))
|
||||
.build();
|
||||
articles.add(article);
|
||||
}
|
||||
return articles;
|
||||
}
|
||||
|
||||
private Integer resolveAgentId(SendMessageRequest request) {
|
||||
Map<String, Object> extra = request.getExtra();
|
||||
if (extra != null) {
|
||||
String agentId = getString(extra, "agent_id");
|
||||
if (agentId != null) {
|
||||
return Integer.valueOf(agentId);
|
||||
}
|
||||
}
|
||||
Map<String, Object> content = request.getContent();
|
||||
if (content != null) {
|
||||
String agentId = getString(content, "agent_id");
|
||||
if (agentId != null) {
|
||||
return Integer.valueOf(agentId);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String normalizeUserIds(String receiverId) {
|
||||
if (StringUtils.isBlank(receiverId)) {
|
||||
return null;
|
||||
}
|
||||
return receiverId.replace(',', '|').replaceAll("\\s+", "");
|
||||
}
|
||||
|
||||
private String getString(Map<?, ?> map, String key) {
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = map.get(key);
|
||||
return value == null ? null : value.toString();
|
||||
}
|
||||
|
||||
private Boolean getBoolean(Map<String, Object> map, String key) {
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = map.get(key);
|
||||
if (value instanceof Boolean b) {
|
||||
return b;
|
||||
}
|
||||
if (value instanceof Number n) {
|
||||
return n.intValue() == 1;
|
||||
}
|
||||
if (value instanceof String s) {
|
||||
return "true".equalsIgnoreCase(s) || "1".equals(s);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, String> getStringMap(Map<String, Object> map, String key) {
|
||||
if (map == null) {
|
||||
return Map.of();
|
||||
}
|
||||
Object value = map.get(key);
|
||||
if (value instanceof Map<?, ?> rawMap) {
|
||||
Map<String, String> result = new java.util.HashMap<>();
|
||||
rawMap.forEach((k, v) -> result.put(Objects.toString(k, ""), v == null ? "" : v.toString()));
|
||||
return result;
|
||||
}
|
||||
return Map.of();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.sino.mci.channel.wecom.support;
|
||||
|
||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 企业微信 CP access_token 缓存配置存储。
|
||||
*
|
||||
* <p>在 {@link WxCpDefaultConfigImpl} 的内存缓存基础上,增加 Redis 二级缓存。
|
||||
* 当 Spring 上下文中存在 {@link StringRedisTemplate} 时,token 会写入 Redis 并设置过期时间;
|
||||
* 否则退化为内存缓存。</p>
|
||||
*/
|
||||
public class WeComAccessTokenCache extends WxCpDefaultConfigImpl {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WeComAccessTokenCache.class);
|
||||
|
||||
private static final String KEY_PREFIX = "wecom:cp:access_token:";
|
||||
|
||||
private final String accessTokenKey;
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
public WeComAccessTokenCache(String corpId, Integer agentId, String corpSecret,
|
||||
StringRedisTemplate redisTemplate) {
|
||||
this.accessTokenKey = KEY_PREFIX + corpId + ":" + agentId;
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.setCorpId(corpId);
|
||||
this.setCorpSecret(corpSecret);
|
||||
this.setAgentId(agentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
if (redisTemplate != null) {
|
||||
try {
|
||||
String token = redisTemplate.opsForValue().get(accessTokenKey);
|
||||
if (token != null && !token.isBlank()) {
|
||||
return token;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[WeCom] 读取 Redis access_token 失败,退化到内存缓存", e);
|
||||
}
|
||||
}
|
||||
return super.getAccessToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccessTokenExpired() {
|
||||
if (redisTemplate != null) {
|
||||
try {
|
||||
return !Boolean.TRUE.equals(redisTemplate.hasKey(accessTokenKey));
|
||||
} catch (Exception e) {
|
||||
log.warn("[WeCom] 检查 Redis access_token 过期状态失败,退化到内存缓存", e);
|
||||
}
|
||||
}
|
||||
return super.isAccessTokenExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireAccessToken() {
|
||||
if (redisTemplate != null) {
|
||||
try {
|
||||
redisTemplate.delete(accessTokenKey);
|
||||
} catch (Exception e) {
|
||||
log.warn("[WeCom] 删除 Redis access_token 失败", e);
|
||||
}
|
||||
}
|
||||
super.expireAccessToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void updateAccessToken(WxAccessToken accessToken) {
|
||||
updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) {
|
||||
if (redisTemplate != null && accessToken != null) {
|
||||
try {
|
||||
long ttl = Math.max(expiresInSeconds - 200L, 1L);
|
||||
redisTemplate.opsForValue().set(accessTokenKey, accessToken, Duration.ofSeconds(ttl));
|
||||
} catch (Exception e) {
|
||||
log.warn("[WeCom] 写入 Redis access_token 失败", e);
|
||||
}
|
||||
}
|
||||
super.updateAccessToken(accessToken, expiresInSeconds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.sino.mci.channel.wecom.support;
|
||||
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 企业微信 CP {@link WxCpService} 工厂。
|
||||
*
|
||||
* <p>根据企业 ID、应用 ID、应用 Secret 构建 {@link WxCpService},并按账号维度缓存。
|
||||
* 若上下文中存在 {@link StringRedisTemplate},则 token 使用 Redis 缓存。</p>
|
||||
*/
|
||||
@Component
|
||||
public class WeComServiceFactory {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WeComServiceFactory.class);
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final Map<String, WxCpService> serviceCache = new ConcurrentHashMap<>();
|
||||
|
||||
public WeComServiceFactory(ObjectProvider<StringRedisTemplate> redisTemplateProvider) {
|
||||
this.redisTemplate = redisTemplateProvider.getIfAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 config 获取或创建 WxCpService。
|
||||
*
|
||||
* @param config 必须包含 corp_id、agent_id、secret
|
||||
* @return WxCpService 实例;配置缺失时返回 null
|
||||
*/
|
||||
public WxCpService getOrCreateService(Map<String, Object> config) {
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
String corpId = getString(config, "corp_id");
|
||||
String agentId = getString(config, "agent_id");
|
||||
String secret = getString(config, "secret");
|
||||
if (corpId == null || agentId == null || secret == null) {
|
||||
log.warn("[WeCom] 缺少必要配置:corp_id/agent_id/secret");
|
||||
return null;
|
||||
}
|
||||
return getOrCreateService(corpId, Integer.valueOf(agentId), secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 corpId/agentId/secret 获取或创建 WxCpService。
|
||||
*/
|
||||
public WxCpService getOrCreateService(String corpId, Integer agentId, String secret) {
|
||||
String key = corpId + ":" + agentId;
|
||||
return serviceCache.computeIfAbsent(key, k -> createService(corpId, agentId, secret));
|
||||
}
|
||||
|
||||
private WxCpService createService(String corpId, Integer agentId, String secret) {
|
||||
WeComAccessTokenCache configStorage = new WeComAccessTokenCache(corpId, agentId, secret, redisTemplate);
|
||||
WxCpServiceImpl service = new WxCpServiceImpl();
|
||||
service.setWxCpConfigStorage(configStorage);
|
||||
log.info("[WeCom] 已创建 WxCpService:corpId={}, agentId={}", corpId, agentId);
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前缓存中是否只存在一个账号。
|
||||
*/
|
||||
public boolean hasSingleAccount() {
|
||||
return serviceCache.size() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存中唯一的账号服务;不存在或不唯一时返回 null。
|
||||
*/
|
||||
public WxCpService getSingleAccountService() {
|
||||
if (serviceCache.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
return serviceCache.values().iterator().next();
|
||||
}
|
||||
|
||||
private String getString(Map<String, Object> map, String key) {
|
||||
Object value = map.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return value.toString().trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.sino.mci.channel.wecom;
|
||||
|
||||
import com.sino.mci.channel.common.dto.ChannelInitRequest;
|
||||
import com.sino.mci.channel.common.dto.ChannelInitResult;
|
||||
import com.sino.mci.channel.common.dto.SendMessageRequest;
|
||||
import com.sino.mci.channel.common.dto.SendMessageResult;
|
||||
import com.sino.mci.channel.wecom.support.WeComServiceFactory;
|
||||
import me.chanjar.weixin.common.error.WxError;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.cp.api.WxCpChatService;
|
||||
import me.chanjar.weixin.cp.api.WxCpMessageService;
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.bean.message.WxCpAppChatMessage;
|
||||
import me.chanjar.weixin.cp.bean.message.WxCpMessage;
|
||||
import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class WeComChannelAdapterTest {
|
||||
|
||||
private static Map<String, Object> validConfig() {
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put("corp_id", "ww123");
|
||||
config.put("agent_id", "1000002");
|
||||
config.put("secret", "secret-abc");
|
||||
return config;
|
||||
}
|
||||
|
||||
private static SendMessageRequest textMessageRequest() {
|
||||
SendMessageRequest request = new SendMessageRequest();
|
||||
request.setReceiverId("user1,user2");
|
||||
request.setContentType("text");
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
content.put("text", "hello");
|
||||
request.setContent(content);
|
||||
request.setExtra(validConfig());
|
||||
return request;
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConstructAndSendTextMessage() throws WxErrorException {
|
||||
WxCpMessageService messageService = mock(WxCpMessageService.class);
|
||||
WxCpChatService chatService = mock(WxCpChatService.class);
|
||||
WxCpService service = mock(WxCpService.class);
|
||||
when(service.getMessageService()).thenReturn(messageService);
|
||||
when(service.getChatService()).thenReturn(chatService);
|
||||
when(messageService.send(any(WxCpMessage.class))).thenReturn(new WxCpMessageSendResult());
|
||||
|
||||
WeComServiceFactory factory = mock(WeComServiceFactory.class);
|
||||
when(factory.getOrCreateService(any(Map.class))).thenReturn(service);
|
||||
|
||||
WeComChannelAdapter adapter = new WeComChannelAdapter(factory);
|
||||
SendMessageResult result = adapter.sendMessage(textMessageRequest());
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(service, never()).getChatService();
|
||||
|
||||
ArgumentCaptor<WxCpMessage> captor = ArgumentCaptor.forClass(WxCpMessage.class);
|
||||
verify(messageService).send(captor.capture());
|
||||
WxCpMessage message = captor.getValue();
|
||||
assertThat(message.getMsgType()).isEqualTo("text");
|
||||
assertThat(message.getContent()).isEqualTo("hello");
|
||||
assertThat(message.getToUser()).isEqualTo("user1|user2");
|
||||
assertThat(message.getAgentId()).isEqualTo(1000002);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFailureWhenConfigInvalid() throws WxErrorException {
|
||||
WxCpService service = mock(WxCpService.class);
|
||||
when(service.getAccessToken(false)).thenThrow(new WxErrorException(WxError.builder().errorMsg("invalid credential").build()));
|
||||
|
||||
WeComServiceFactory factory = mock(WeComServiceFactory.class);
|
||||
when(factory.getOrCreateService(any(Map.class))).thenReturn(service);
|
||||
|
||||
WeComChannelAdapter adapter = new WeComChannelAdapter(factory);
|
||||
|
||||
ChannelInitRequest request = new ChannelInitRequest();
|
||||
request.setConfig(validConfig());
|
||||
|
||||
ChannelInitResult result = adapter.initAccount(request);
|
||||
|
||||
assertThat(result.isSuccess()).isFalse();
|
||||
assertThat(result.getMessage()).contains("invalid credential");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFailureWhenConfigMissing() {
|
||||
WeComServiceFactory factory = mock(WeComServiceFactory.class);
|
||||
when(factory.getOrCreateService(any(Map.class))).thenReturn(null);
|
||||
|
||||
WeComChannelAdapter adapter = new WeComChannelAdapter(factory);
|
||||
|
||||
ChannelInitRequest request = new ChannelInitRequest();
|
||||
request.setConfig(new HashMap<>());
|
||||
|
||||
ChannelInitResult result = adapter.initAccount(request);
|
||||
|
||||
assertThat(result.isSuccess()).isFalse();
|
||||
assertThat(result.getMessage()).contains("corp_id/agent_id/secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSendGroupChatMessageWhenConversationIdPresent() throws WxErrorException {
|
||||
WxCpMessageService messageService = mock(WxCpMessageService.class);
|
||||
WxCpChatService chatService = mock(WxCpChatService.class);
|
||||
WxCpService service = mock(WxCpService.class);
|
||||
when(service.getMessageService()).thenReturn(messageService);
|
||||
when(service.getChatService()).thenReturn(chatService);
|
||||
|
||||
WeComServiceFactory factory = mock(WeComServiceFactory.class);
|
||||
when(factory.getOrCreateService(any(Map.class))).thenReturn(service);
|
||||
|
||||
WeComChannelAdapter adapter = new WeComChannelAdapter(factory);
|
||||
|
||||
SendMessageRequest request = textMessageRequest();
|
||||
request.setConversationId("chat123");
|
||||
|
||||
SendMessageResult result = adapter.sendMessage(request);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(service, never()).getMessageService();
|
||||
|
||||
ArgumentCaptor<WxCpAppChatMessage> captor = ArgumentCaptor.forClass(WxCpAppChatMessage.class);
|
||||
verify(chatService).sendMsg(captor.capture());
|
||||
WxCpAppChatMessage message = captor.getValue();
|
||||
assertThat(message.getMsgType()).isEqualTo("text");
|
||||
assertThat(message.getChatId()).isEqualTo("chat123");
|
||||
assertThat(message.getContent()).isEqualTo("hello");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user