feat(channel-wecom): 实现企微会话存档接收骨架(Finance SDK 接口 + 回调 + 异步拉取)
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
package com.sino.mci.channel.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.sino.mci.channel.entity.ChannelSubject;
|
||||
import com.sino.mci.channel.messaging.RabbitConfig;
|
||||
import com.sino.mci.channel.messaging.SessionArchivePullCommand;
|
||||
import com.sino.mci.channel.repository.ChannelSubjectMapper;
|
||||
import com.sino.mci.channel.service.SessionArchivePullService;
|
||||
import com.sino.mci.channel.wecom.archive.FinanceSdkClient;
|
||||
import com.sino.mci.channel.wecom.archive.model.FinanceMessage;
|
||||
import com.sino.mci.inbound.entity.MessageEvent;
|
||||
import com.sino.mci.inbound.repository.MessageEventMapper;
|
||||
import com.sino.mci.message.entity.MessageMedia;
|
||||
import com.sino.mci.message.repository.MessageMediaMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.sino.mci.test.AuthTestHelper.createTenantAndLogin;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@TestPropertySource(properties = {
|
||||
"spring.rabbitmq.listener.simple.auto-startup=false",
|
||||
"spring.main.allow-bean-definition-overriding=true",
|
||||
"sino.channel.wecom.session-archive.listener-enabled=false"
|
||||
})
|
||||
@Transactional
|
||||
class WeComSessionArchiveTest {
|
||||
|
||||
private static final String CORP_ID = "ww-session-archive";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ChannelSubjectMapper channelSubjectMapper;
|
||||
|
||||
@Autowired
|
||||
private MessageEventMapper messageEventMapper;
|
||||
|
||||
@Autowired
|
||||
private MessageMediaMapper messageMediaMapper;
|
||||
|
||||
@Autowired
|
||||
private SessionArchivePullService sessionArchivePullService;
|
||||
|
||||
@Autowired
|
||||
private FinanceSdkClient financeSdkClient;
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
void shouldReceiveMsgAuditNotifyAndPublishPullCommand() throws Exception {
|
||||
ChannelSubject subject = createSessionArchiveSubject();
|
||||
|
||||
String xml = """
|
||||
<xml>
|
||||
<ToUserName><![CDATA[%s]]></ToUserName>
|
||||
<FromUserName><![CDATA[sys]]></FromUserName>
|
||||
<CreateTime>1700000000</CreateTime>
|
||||
<MsgType><![CDATA[event]]></MsgType>
|
||||
<Event><![CDATA[msgaudit_notify]]></Event>
|
||||
</xml>
|
||||
""".formatted(CORP_ID);
|
||||
|
||||
mockMvc.perform(post("/msg-platform/callback/wecom")
|
||||
.contextPath("/msg-platform")
|
||||
.contentType(MediaType.APPLICATION_XML)
|
||||
.content(xml))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string("success"));
|
||||
|
||||
ArgumentCaptor<SessionArchivePullCommand> captor = ArgumentCaptor.forClass(SessionArchivePullCommand.class);
|
||||
verify(rabbitTemplate).convertAndSend(eq(RabbitConfig.SESSION_ARCHIVE_EXCHANGE),
|
||||
eq(RabbitConfig.SESSION_ARCHIVE_ROUTING_KEY), captor.capture());
|
||||
SessionArchivePullCommand command = captor.getValue();
|
||||
assertThat(command.getChannelSubjectId()).isEqualTo(subject.getId());
|
||||
assertThat(command.getCorpId()).isEqualTo(CORP_ID);
|
||||
assertThat(command.getTenantCode()).isEqualTo(subject.getTenantCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPullAndSaveMessageEvent() throws Exception {
|
||||
ChannelSubject subject = createSessionArchiveSubject();
|
||||
when(financeSdkClient.init(anyString(), anyString())).thenReturn(true);
|
||||
when(financeSdkClient.getChatData(anyLong(), anyInt())).thenReturn(List.of(
|
||||
FinanceMessage.builder()
|
||||
.seq(100L)
|
||||
.msgId("msg-100")
|
||||
.publicKeyVer(1)
|
||||
.encryptRandomKey("random-100")
|
||||
.encryptChatMsg("chat-100")
|
||||
.build()
|
||||
));
|
||||
when(financeSdkClient.decryptData(anyString(), anyString())).thenReturn(
|
||||
objectMapper.writeValueAsString(Map.of(
|
||||
"msgid", "msg-100",
|
||||
"msgtype", "text",
|
||||
"text", Map.of("content", "hello")
|
||||
))
|
||||
);
|
||||
|
||||
SessionArchivePullCommand command = new SessionArchivePullCommand();
|
||||
command.setChannelSubjectId(subject.getId());
|
||||
command.setTenantCode(subject.getTenantCode());
|
||||
command.setCorpId(subject.getCorpId());
|
||||
command.setSecret(subject.getCorpSecret());
|
||||
command.setPrivateKey(subject.getSessionArchivePrivateKey());
|
||||
|
||||
sessionArchivePullService.onPullCommand(command);
|
||||
|
||||
MessageEvent event = messageEventMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MessageEvent>()
|
||||
.eq(MessageEvent::getEventId, "msg-100"));
|
||||
assertThat(event).isNotNull();
|
||||
assertThat(event.getMessageSource()).isEqualTo("session_archive");
|
||||
assertThat(event.getChannelSubjectId()).isEqualTo(subject.getId());
|
||||
assertThat(event.getChannelType()).isEqualTo("wecom");
|
||||
assertThat(event.getContent()).contains("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveMediaRecordForImageMessage() throws Exception {
|
||||
ChannelSubject subject = createSessionArchiveSubject();
|
||||
when(financeSdkClient.init(anyString(), anyString())).thenReturn(true);
|
||||
when(financeSdkClient.getChatData(anyLong(), anyInt())).thenReturn(List.of(
|
||||
FinanceMessage.builder()
|
||||
.seq(200L)
|
||||
.msgId("msg-200")
|
||||
.publicKeyVer(1)
|
||||
.encryptRandomKey("random-200")
|
||||
.encryptChatMsg("chat-200")
|
||||
.build()
|
||||
));
|
||||
when(financeSdkClient.decryptData(anyString(), anyString())).thenReturn(
|
||||
objectMapper.writeValueAsString(Map.of(
|
||||
"msgid", "msg-200",
|
||||
"msgtype", "image",
|
||||
"image", Map.of(
|
||||
"sdkfileid", "sdkfileid-200",
|
||||
"filesize", 1024,
|
||||
"md5sum", "abc123"
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
SessionArchivePullCommand command = new SessionArchivePullCommand();
|
||||
command.setChannelSubjectId(subject.getId());
|
||||
command.setTenantCode(subject.getTenantCode());
|
||||
command.setCorpId(subject.getCorpId());
|
||||
command.setSecret(subject.getCorpSecret());
|
||||
command.setPrivateKey(subject.getSessionArchivePrivateKey());
|
||||
|
||||
sessionArchivePullService.onPullCommand(command);
|
||||
|
||||
MessageMedia media = messageMediaMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MessageMedia>()
|
||||
.eq(MessageMedia::getMsgid, "msg-200"));
|
||||
assertThat(media).isNotNull();
|
||||
assertThat(media.getSdkfileid()).isEqualTo("sdkfileid-200");
|
||||
assertThat(media.getMediaType()).isEqualTo("image");
|
||||
assertThat(media.getStatus()).isEqualTo(0);
|
||||
assertThat(media.getFileSize()).isEqualTo(1024L);
|
||||
}
|
||||
|
||||
private ChannelSubject createSessionArchiveSubject() throws Exception {
|
||||
String tenantCode = "sa-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
createTenantAndLogin(mockMvc, tenantCode, "会话存档租户");
|
||||
|
||||
ChannelSubject subject = new ChannelSubject();
|
||||
subject.setTenantCode(tenantCode);
|
||||
subject.setChannelType("wecom");
|
||||
subject.setSubjectCode("archive");
|
||||
subject.setSubjectName("会话存档主体");
|
||||
subject.setCorpId(CORP_ID);
|
||||
subject.setCorpSecret("secret-" + UUID.randomUUID());
|
||||
subject.setSessionArchiveEnabled(1);
|
||||
subject.setSessionArchivePrivateKey("private-key");
|
||||
subject.setStatus(1);
|
||||
channelSubjectMapper.insert(subject);
|
||||
return subject;
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
static class MockConfig {
|
||||
|
||||
@Bean
|
||||
public FinanceSdkClient financeSdkClient() {
|
||||
return mock(FinanceSdkClient.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate() {
|
||||
return mock(RabbitTemplate.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user