feat(channel): add mci-channel-wechat-personal adapter
- New module: mci-channel-wechat-personal - Implements ChannelAdapter SPI for personal WeChat - Calls Python channel-service pywechat HTTP APIs: - /v1/wechat-personal/login/start for initAccount QR code - /v1/wechat-personal/send/text and /send/image for sendMessage - Configurable base URL via sino.channel.wechat-personal.base-url - Added MockWebServer unit tests - Wired module into backend parent and mci-server
This commit is contained in:
49
backend/mci-channel-wechat-personal/pom.xml
Normal file
49
backend/mci-channel-wechat-personal/pom.xml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.sino</groupId>
|
||||||
|
<artifactId>sino-mci-parent</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>mci-channel-wechat-personal</artifactId>
|
||||||
|
<name>mci-channel-wechat-personal</name>
|
||||||
|
<description>Personal WeChat channel adapter</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sino</groupId>
|
||||||
|
<artifactId>mci-channel-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>mockwebserver</artifactId>
|
||||||
|
<version>4.12.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.sino.mci.channel.wechat.personal;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(WechatPersonalProperties.class)
|
||||||
|
public class WechatPersonalAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public WechatPersonalChannelAdapter wechatPersonalChannelAdapter(WechatPersonalProperties properties) {
|
||||||
|
return new WechatPersonalChannelAdapter(properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
package com.sino.mci.channel.wechat.personal;
|
||||||
|
|
||||||
|
import com.sino.mci.channel.common.dto.CallbackPayload;
|
||||||
|
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.common.model.ChannelType;
|
||||||
|
import com.sino.mci.channel.common.spi.ChannelAdapter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
import org.springframework.web.client.RestClientException;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class WechatPersonalChannelAdapter implements ChannelAdapter {
|
||||||
|
|
||||||
|
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE =
|
||||||
|
new ParameterizedTypeReference<>() {};
|
||||||
|
|
||||||
|
private final WechatPersonalProperties properties;
|
||||||
|
|
||||||
|
private RestClient restClient;
|
||||||
|
|
||||||
|
public WechatPersonalChannelAdapter(WechatPersonalProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestClient restClient() {
|
||||||
|
if (restClient == null) {
|
||||||
|
restClient = RestClient.builder().baseUrl(properties.getBaseUrl()).build();
|
||||||
|
}
|
||||||
|
return restClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelType getChannelType() {
|
||||||
|
return ChannelType.WECHAT_PERSONAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelInitResult initAccount(ChannelInitRequest request) {
|
||||||
|
ChannelInitResult result = new ChannelInitResult();
|
||||||
|
if (request == null || request.getConfig() == null) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("个人微信初始化失败:请求或配置为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String accountId = getString(request.getConfig(), "account_id");
|
||||||
|
if (isBlank(accountId)) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("个人微信初始化失败:缺少 account_id");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String accountName = getString(request.getConfig(), "account_name");
|
||||||
|
|
||||||
|
Map<String, Object> body = new HashMap<>();
|
||||||
|
body.put("account_id", accountId);
|
||||||
|
body.put("account_name", accountName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Python 侧 login/start 会 get_or_create 账号,因此该调用同时完成注册校验并返回二维码
|
||||||
|
Map<String, Object> response = post("/v1/wechat-personal/login/start", body);
|
||||||
|
if (response == null) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("个人微信初始化失败:Python 服务返回为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String qrCodeUrl = getString(response, "qr_code_url");
|
||||||
|
if (isBlank(qrCodeUrl)) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("个人微信初始化失败:Python 服务未返回二维码");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.setSuccess(true);
|
||||||
|
result.setAccountId(accountId);
|
||||||
|
result.setAccountName(isBlank(accountName) ? accountId : accountName);
|
||||||
|
result.setQrCodeUrl(qrCodeUrl);
|
||||||
|
result.setMessage("个人微信账号初始化成功,请扫描二维码登录");
|
||||||
|
} catch (RestClientException e) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("个人微信初始化失败:" + e.getMessage());
|
||||||
|
log.warn("[WechatPersonal] 初始化账号失败", e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SendMessageResult sendMessage(SendMessageRequest request) {
|
||||||
|
SendMessageResult result = new SendMessageResult();
|
||||||
|
if (request == null) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("发送请求为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String accountId = resolveAccountId(request);
|
||||||
|
if (isBlank(accountId)) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("个人微信发送失败:缺少 account_id");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String conversationId = defaultString(request.getConversationId());
|
||||||
|
if (isBlank(conversationId)) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("个人微信发送失败:缺少 conversation_id");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String contentType = defaultString(request.getContentType()).toLowerCase();
|
||||||
|
try {
|
||||||
|
Map<String, Object> response;
|
||||||
|
if ("image".equals(contentType)) {
|
||||||
|
response = sendImage(accountId, conversationId, request.getContent());
|
||||||
|
} else {
|
||||||
|
response = sendText(accountId, conversationId, request.getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("个人微信发送失败:Python 服务返回为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean success = getBoolean(response, "success");
|
||||||
|
if (!Boolean.TRUE.equals(success)) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("个人微信发送失败:" + getString(response, "error"));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.setSuccess(true);
|
||||||
|
result.setChannelMessageId(getString(response, "message_id"));
|
||||||
|
} catch (RestClientException e) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setMessage("个人微信发送失败:" + e.getMessage());
|
||||||
|
log.warn("[WechatPersonal] 发送消息失败", e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackPayload parseCallback(String rawBody) {
|
||||||
|
CallbackPayload payload = new CallbackPayload();
|
||||||
|
payload.setChannelType(ChannelType.WECHAT_PERSONAL.name());
|
||||||
|
payload.setRawBody(rawBody);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> sendText(String accountId, String conversationId, Map<String, Object> content) {
|
||||||
|
String text = getString(content, "text");
|
||||||
|
if (isBlank(text)) {
|
||||||
|
throw new IllegalArgumentException("文本内容为空");
|
||||||
|
}
|
||||||
|
Map<String, Object> body = new HashMap<>();
|
||||||
|
body.put("account_id", accountId);
|
||||||
|
body.put("conversation_id", conversationId);
|
||||||
|
body.put("text", text);
|
||||||
|
return post("/v1/wechat-personal/send/text", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> sendImage(String accountId, String conversationId, Map<String, Object> content) {
|
||||||
|
String imageUrl = getString(content, "image_url");
|
||||||
|
if (isBlank(imageUrl)) {
|
||||||
|
throw new IllegalArgumentException("图片地址为空");
|
||||||
|
}
|
||||||
|
Map<String, Object> body = new HashMap<>();
|
||||||
|
body.put("account_id", accountId);
|
||||||
|
body.put("conversation_id", conversationId);
|
||||||
|
body.put("image_url", imageUrl);
|
||||||
|
return post("/v1/wechat-personal/send/image", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> post(String uri, Map<String, Object> body) {
|
||||||
|
return restClient().post()
|
||||||
|
.uri(uri)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.body(body)
|
||||||
|
.retrieve()
|
||||||
|
.body(MAP_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveAccountId(SendMessageRequest request) {
|
||||||
|
String accountId = getString(request.getExtra(), "account_id");
|
||||||
|
if (isNotBlank(accountId)) {
|
||||||
|
return accountId;
|
||||||
|
}
|
||||||
|
return getString(request.getContent(), "account_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getString(Map<?, ?> map, String key) {
|
||||||
|
if (map == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object value = map.get(key);
|
||||||
|
return value == null ? null : Objects.toString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean getBoolean(Map<?, ?> map, String key) {
|
||||||
|
if (map == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object value = map.get(key);
|
||||||
|
if (value instanceof Boolean b) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
if (value instanceof String s) {
|
||||||
|
return "true".equalsIgnoreCase(s);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBlank(String value) {
|
||||||
|
return value == null || value.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNotBlank(String value) {
|
||||||
|
return !isBlank(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultString(String value) {
|
||||||
|
return value == null ? "" : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.sino.mci.channel.wechat.personal;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties(prefix = "sino.channel.wechat-personal")
|
||||||
|
public class WechatPersonalProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Python channel-service 基础地址。
|
||||||
|
*/
|
||||||
|
private String baseUrl = "http://mci-channel-service:8000";
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
com.sino.mci.channel.wechat.personal.WechatPersonalAutoConfiguration
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package com.sino.mci.channel.wechat.personal;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
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.common.model.ChannelType;
|
||||||
|
import okhttp3.mockwebserver.MockResponse;
|
||||||
|
import okhttp3.mockwebserver.MockWebServer;
|
||||||
|
import okhttp3.mockwebserver.RecordedRequest;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class WechatPersonalChannelAdapterTest {
|
||||||
|
|
||||||
|
private MockWebServer mockWebServer;
|
||||||
|
private WechatPersonalChannelAdapter adapter;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws Exception {
|
||||||
|
mockWebServer = new MockWebServer();
|
||||||
|
mockWebServer.start();
|
||||||
|
|
||||||
|
WechatPersonalProperties properties = new WechatPersonalProperties();
|
||||||
|
properties.setBaseUrl(mockWebServer.url("/").toString());
|
||||||
|
|
||||||
|
adapter = new WechatPersonalChannelAdapter(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() throws Exception {
|
||||||
|
mockWebServer.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnChannelType() {
|
||||||
|
assertThat(adapter.getChannelType()).isEqualTo(ChannelType.WECHAT_PERSONAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldInitAccountAndReturnQrCodeUrl() throws InterruptedException {
|
||||||
|
mockWebServer.enqueue(new MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.setHeader("Content-Type", "application/json")
|
||||||
|
.setBody(toJson(Map.of(
|
||||||
|
"login_id", "login-123",
|
||||||
|
"qr_code_url", "https://example.com/qr.png",
|
||||||
|
"status", "pending"))));
|
||||||
|
|
||||||
|
ChannelInitRequest request = new ChannelInitRequest();
|
||||||
|
Map<String, Object> config = new HashMap<>();
|
||||||
|
config.put("account_id", "wx-123");
|
||||||
|
config.put("account_name", "Test Account");
|
||||||
|
request.setConfig(config);
|
||||||
|
|
||||||
|
ChannelInitResult result = adapter.initAccount(request);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
assertThat(result.getAccountId()).isEqualTo("wx-123");
|
||||||
|
assertThat(result.getAccountName()).isEqualTo("Test Account");
|
||||||
|
assertThat(result.getQrCodeUrl()).isEqualTo("https://example.com/qr.png");
|
||||||
|
|
||||||
|
RecordedRequest recorded = mockWebServer.takeRequest();
|
||||||
|
assertThat(recorded.getMethod()).isEqualTo("POST");
|
||||||
|
assertThat(recorded.getPath()).isEqualTo("/v1/wechat-personal/login/start");
|
||||||
|
assertThat(recorded.getBody().readUtf8()).contains("wx-123", "Test Account");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFailInitAccountWhenQrCodeMissing() {
|
||||||
|
mockWebServer.enqueue(new MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.setHeader("Content-Type", "application/json")
|
||||||
|
.setBody(toJson(Map.of(
|
||||||
|
"login_id", "login-123",
|
||||||
|
"status", "error"))));
|
||||||
|
|
||||||
|
ChannelInitRequest request = new ChannelInitRequest();
|
||||||
|
Map<String, Object> config = new HashMap<>();
|
||||||
|
config.put("account_id", "wx-123");
|
||||||
|
request.setConfig(config);
|
||||||
|
|
||||||
|
ChannelInitResult result = adapter.initAccount(request);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isFalse();
|
||||||
|
assertThat(result.getMessage()).contains("未返回二维码");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSendTextMessage() throws InterruptedException {
|
||||||
|
mockWebServer.enqueue(new MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.setHeader("Content-Type", "application/json")
|
||||||
|
.setBody(toJson(Map.of(
|
||||||
|
"success", true,
|
||||||
|
"message_id", "msg-123"))));
|
||||||
|
|
||||||
|
SendMessageRequest request = new SendMessageRequest();
|
||||||
|
request.setConversationId("chat-123");
|
||||||
|
request.setContentType("text");
|
||||||
|
Map<String, Object> content = new HashMap<>();
|
||||||
|
content.put("text", "hello");
|
||||||
|
request.setContent(content);
|
||||||
|
Map<String, Object> extra = new HashMap<>();
|
||||||
|
extra.put("account_id", "wx-123");
|
||||||
|
request.setExtra(extra);
|
||||||
|
|
||||||
|
SendMessageResult result = adapter.sendMessage(request);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
assertThat(result.getChannelMessageId()).isEqualTo("msg-123");
|
||||||
|
|
||||||
|
RecordedRequest recorded = mockWebServer.takeRequest();
|
||||||
|
assertThat(recorded.getMethod()).isEqualTo("POST");
|
||||||
|
assertThat(recorded.getPath()).isEqualTo("/v1/wechat-personal/send/text");
|
||||||
|
String body = recorded.getBody().readUtf8();
|
||||||
|
assertThat(body).contains("wx-123", "chat-123", "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSendImageMessage() throws InterruptedException {
|
||||||
|
mockWebServer.enqueue(new MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.setHeader("Content-Type", "application/json")
|
||||||
|
.setBody(toJson(Map.of(
|
||||||
|
"success", true,
|
||||||
|
"message_id", "img-123"))));
|
||||||
|
|
||||||
|
SendMessageRequest request = new SendMessageRequest();
|
||||||
|
request.setConversationId("chat-456");
|
||||||
|
request.setContentType("image");
|
||||||
|
Map<String, Object> content = new HashMap<>();
|
||||||
|
content.put("image_url", "https://example.com/img.png");
|
||||||
|
request.setContent(content);
|
||||||
|
Map<String, Object> extra = new HashMap<>();
|
||||||
|
extra.put("account_id", "wx-123");
|
||||||
|
request.setExtra(extra);
|
||||||
|
|
||||||
|
SendMessageResult result = adapter.sendMessage(request);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
assertThat(result.getChannelMessageId()).isEqualTo("img-123");
|
||||||
|
|
||||||
|
RecordedRequest recorded = mockWebServer.takeRequest();
|
||||||
|
assertThat(recorded.getPath()).isEqualTo("/v1/wechat-personal/send/image");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnFailureWhenPythonReportsError() {
|
||||||
|
mockWebServer.enqueue(new MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.setHeader("Content-Type", "application/json")
|
||||||
|
.setBody(toJson(Map.of(
|
||||||
|
"success", false,
|
||||||
|
"error", "account wx-123 not registered"))));
|
||||||
|
|
||||||
|
SendMessageRequest request = new SendMessageRequest();
|
||||||
|
request.setConversationId("chat-123");
|
||||||
|
request.setContentType("text");
|
||||||
|
Map<String, Object> content = new HashMap<>();
|
||||||
|
content.put("text", "hello");
|
||||||
|
request.setContent(content);
|
||||||
|
Map<String, Object> extra = new HashMap<>();
|
||||||
|
extra.put("account_id", "wx-123");
|
||||||
|
request.setExtra(extra);
|
||||||
|
|
||||||
|
SendMessageResult result = adapter.sendMessage(request);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isFalse();
|
||||||
|
assertThat(result.getMessage()).contains("account wx-123 not registered");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnFailureWhenAccountIdMissing() {
|
||||||
|
SendMessageRequest request = new SendMessageRequest();
|
||||||
|
request.setConversationId("chat-123");
|
||||||
|
request.setContentType("text");
|
||||||
|
request.setContent(Map.of("text", "hello"));
|
||||||
|
|
||||||
|
SendMessageResult result = adapter.sendMessage(request);
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isFalse();
|
||||||
|
assertThat(result.getMessage()).contains("account_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldParseCallback() {
|
||||||
|
assertThat(adapter.parseCallback("raw-body").getChannelType())
|
||||||
|
.isEqualTo(ChannelType.WECHAT_PERSONAL.name());
|
||||||
|
assertThat(adapter.parseCallback("raw-body").getRawBody()).isEqualTo("raw-body");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toJson(Object value) {
|
||||||
|
try {
|
||||||
|
return new ObjectMapper().writeValueAsString(value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,10 @@
|
|||||||
<groupId>com.sino</groupId>
|
<groupId>com.sino</groupId>
|
||||||
<artifactId>mci-channel-wecom</artifactId>
|
<artifactId>mci-channel-wecom</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sino</groupId>
|
||||||
|
<artifactId>mci-channel-wechat-personal</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
<module>mci-shared</module>
|
<module>mci-shared</module>
|
||||||
<module>mci-channel-common</module>
|
<module>mci-channel-common</module>
|
||||||
<module>mci-channel-wecom</module>
|
<module>mci-channel-wecom</module>
|
||||||
|
<module>mci-channel-wechat-personal</module>
|
||||||
<module>mci-server</module>
|
<module>mci-server</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
@@ -50,6 +51,11 @@
|
|||||||
<artifactId>mci-channel-wecom</artifactId>
|
<artifactId>mci-channel-wecom</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sino</groupId>
|
||||||
|
<artifactId>mci-channel-wechat-personal</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
|||||||
Reference in New Issue
Block a user