diff --git a/src/main/java/com/wecom/robot/adapter/ChannelAdapter.java b/src/main/java/com/wecom/robot/adapter/ChannelAdapter.java
new file mode 100644
index 0000000..0cd1cfa
--- /dev/null
+++ b/src/main/java/com/wecom/robot/adapter/ChannelAdapter.java
@@ -0,0 +1,31 @@
+package com.wecom.robot.adapter;
+
+import com.wecom.robot.dto.OutboundMessage;
+
+/**
+ * 渠道适配器核心能力接口
+ *
+ * 所有渠道适配器必须实现此接口,提供渠道类型标识和消息发送能力。
+ * [AC-MCA-01] 渠道适配层核心接口
+ *
+ * @see ServiceStateCapable
+ * @see TransferCapable
+ * @see MessageSyncCapable
+ */
+public interface ChannelAdapter {
+
+ /**
+ * 获取渠道类型标识
+ *
+ * @return 渠道类型,如 "wechat", "douyin", "jd"
+ */
+ String getChannelType();
+
+ /**
+ * 发送消息到渠道
+ *
+ * @param message 出站消息对象
+ * @return 发送是否成功
+ */
+ boolean sendMessage(OutboundMessage message);
+}
diff --git a/src/main/java/com/wecom/robot/adapter/MessageSyncCapable.java b/src/main/java/com/wecom/robot/adapter/MessageSyncCapable.java
new file mode 100644
index 0000000..e19a85a
--- /dev/null
+++ b/src/main/java/com/wecom/robot/adapter/MessageSyncCapable.java
@@ -0,0 +1,22 @@
+package com.wecom.robot.adapter;
+
+import com.wecom.robot.dto.SyncMsgResponse;
+
+/**
+ * 消息同步能力接口(可选)
+ *
+ * 提供从渠道同步历史消息的能力。
+ * 渠道适配器可选择性实现此接口。
+ * [AC-MCA-01] 渠道适配层可选能力接口
+ */
+public interface MessageSyncCapable {
+
+ /**
+ * 同步消息
+ *
+ * @param kfId 客服账号ID
+ * @param cursor 游标(用于分页获取)
+ * @return 同步消息响应
+ */
+ SyncMsgResponse syncMessages(String kfId, String cursor);
+}
diff --git a/src/main/java/com/wecom/robot/adapter/ServiceStateCapable.java b/src/main/java/com/wecom/robot/adapter/ServiceStateCapable.java
new file mode 100644
index 0000000..d95c7a9
--- /dev/null
+++ b/src/main/java/com/wecom/robot/adapter/ServiceStateCapable.java
@@ -0,0 +1,33 @@
+package com.wecom.robot.adapter;
+
+import com.wecom.robot.dto.ServiceStateResponse;
+
+/**
+ * 服务状态管理能力接口(可选)
+ *
+ * 提供渠道服务状态的获取和变更能力。
+ * 渠道适配器可选择性实现此接口。
+ * [AC-MCA-01] 渠道适配层可选能力接口
+ */
+public interface ServiceStateCapable {
+
+ /**
+ * 获取服务状态
+ *
+ * @param kfId 客服账号ID
+ * @param customerId 客户ID
+ * @return 服务状态响应
+ */
+ ServiceStateResponse getServiceState(String kfId, String customerId);
+
+ /**
+ * 变更服务状态
+ *
+ * @param kfId 客服账号ID
+ * @param customerId 客户ID
+ * @param newState 新状态值
+ * @param servicerId 人工客服ID(可选)
+ * @return 变更是否成功
+ */
+ boolean transServiceState(String kfId, String customerId, int newState, String servicerId);
+}
diff --git a/src/main/java/com/wecom/robot/adapter/TransferCapable.java b/src/main/java/com/wecom/robot/adapter/TransferCapable.java
new file mode 100644
index 0000000..54845f1
--- /dev/null
+++ b/src/main/java/com/wecom/robot/adapter/TransferCapable.java
@@ -0,0 +1,30 @@
+package com.wecom.robot.adapter;
+
+/**
+ * 转人工能力接口(可选)
+ *
+ * 提供将客户转入待接入池或转给指定人工客服的能力。
+ * 渠道适配器可选择性实现此接口。
+ * [AC-MCA-01] 渠道适配层可选能力接口
+ */
+public interface TransferCapable {
+
+ /**
+ * 转入待接入池
+ *
+ * @param kfId 客服账号ID
+ * @param customerId 客户ID
+ * @return 转移是否成功
+ */
+ boolean transferToPool(String kfId, String customerId);
+
+ /**
+ * 转给指定人工客服
+ *
+ * @param kfId 客服账号ID
+ * @param customerId 客户ID
+ * @param servicerId 人工客服ID
+ * @return 转移是否成功
+ */
+ boolean transferToManual(String kfId, String customerId, String servicerId);
+}
diff --git a/src/main/java/com/wecom/robot/dto/InboundMessage.java b/src/main/java/com/wecom/robot/dto/InboundMessage.java
new file mode 100644
index 0000000..c1136cd
--- /dev/null
+++ b/src/main/java/com/wecom/robot/dto/InboundMessage.java
@@ -0,0 +1,49 @@
+package com.wecom.robot.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InboundMessage {
+
+ private String channelType;
+
+ private String channelMessageId;
+
+ private String sessionKey;
+
+ private String customerId;
+
+ private String kfId;
+
+ private String sender;
+
+ private String content;
+
+ private String msgType;
+
+ private String rawPayload;
+
+ private Long timestamp;
+
+ private SignatureInfo signatureInfo;
+
+ private Map metadata;
+
+ public static final String CHANNEL_WECHAT = "wechat";
+ public static final String CHANNEL_DOUYIN = "douyin";
+ public static final String CHANNEL_JD = "jd";
+
+ public static final String MSG_TYPE_TEXT = "text";
+ public static final String MSG_TYPE_IMAGE = "image";
+ public static final String MSG_TYPE_VOICE = "voice";
+ public static final String MSG_TYPE_VIDEO = "video";
+ public static final String MSG_TYPE_EVENT = "event";
+}
diff --git a/src/main/java/com/wecom/robot/dto/OutboundMessage.java b/src/main/java/com/wecom/robot/dto/OutboundMessage.java
new file mode 100644
index 0000000..cb5420a
--- /dev/null
+++ b/src/main/java/com/wecom/robot/dto/OutboundMessage.java
@@ -0,0 +1,27 @@
+package com.wecom.robot.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class OutboundMessage {
+
+ private String channelType;
+
+ private String receiver;
+
+ private String kfId;
+
+ private String content;
+
+ private String msgType;
+
+ private Map metadata;
+}
diff --git a/src/main/java/com/wecom/robot/dto/SignatureInfo.java b/src/main/java/com/wecom/robot/dto/SignatureInfo.java
new file mode 100644
index 0000000..ade7eff
--- /dev/null
+++ b/src/main/java/com/wecom/robot/dto/SignatureInfo.java
@@ -0,0 +1,21 @@
+package com.wecom.robot.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SignatureInfo {
+
+ private String signature;
+
+ private String timestamp;
+
+ private String nonce;
+
+ private String algorithm;
+}
diff --git a/src/test/java/com/wecom/robot/dto/InboundMessageTest.java b/src/test/java/com/wecom/robot/dto/InboundMessageTest.java
new file mode 100644
index 0000000..bc397ac
--- /dev/null
+++ b/src/test/java/com/wecom/robot/dto/InboundMessageTest.java
@@ -0,0 +1,84 @@
+package com.wecom.robot.dto;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class InboundMessageTest {
+
+ @Test
+ void testInboundMessageBuilder() {
+ SignatureInfo signatureInfo = SignatureInfo.builder()
+ .signature("test-signature")
+ .timestamp("1234567890")
+ .nonce("test-nonce")
+ .algorithm("sha256")
+ .build();
+
+ Map metadata = new HashMap<>();
+ metadata.put("key1", "value1");
+
+ InboundMessage message = InboundMessage.builder()
+ .channelType(InboundMessage.CHANNEL_WECHAT)
+ .channelMessageId("msg-123")
+ .sessionKey("session-key-001")
+ .customerId("customer-001")
+ .kfId("kf-001")
+ .sender("user-001")
+ .content("Hello World")
+ .msgType(InboundMessage.MSG_TYPE_TEXT)
+ .rawPayload("{\"raw\":\"data\"}")
+ .timestamp(1234567890L)
+ .signatureInfo(signatureInfo)
+ .metadata(metadata)
+ .build();
+
+ assertEquals(InboundMessage.CHANNEL_WECHAT, message.getChannelType());
+ assertEquals("msg-123", message.getChannelMessageId());
+ assertEquals("session-key-001", message.getSessionKey());
+ assertEquals("customer-001", message.getCustomerId());
+ assertEquals("kf-001", message.getKfId());
+ assertEquals("user-001", message.getSender());
+ assertEquals("Hello World", message.getContent());
+ assertEquals(InboundMessage.MSG_TYPE_TEXT, message.getMsgType());
+ assertEquals("{\"raw\":\"data\"}", message.getRawPayload());
+ assertEquals(1234567890L, message.getTimestamp());
+ assertNotNull(message.getSignatureInfo());
+ assertEquals("test-signature", message.getSignatureInfo().getSignature());
+ assertNotNull(message.getMetadata());
+ assertEquals("value1", message.getMetadata().get("key1"));
+ }
+
+ @Test
+ void testInboundMessageSetters() {
+ InboundMessage message = new InboundMessage();
+ message.setChannelType(InboundMessage.CHANNEL_DOUYIN);
+ message.setChannelMessageId("msg-456");
+ message.setSessionKey("session-key-002");
+ message.setContent("Test message");
+
+ assertEquals(InboundMessage.CHANNEL_DOUYIN, message.getChannelType());
+ assertEquals("msg-456", message.getChannelMessageId());
+ assertEquals("session-key-002", message.getSessionKey());
+ assertEquals("Test message", message.getContent());
+ }
+
+ @Test
+ void testChannelTypeConstants() {
+ assertEquals("wechat", InboundMessage.CHANNEL_WECHAT);
+ assertEquals("douyin", InboundMessage.CHANNEL_DOUYIN);
+ assertEquals("jd", InboundMessage.CHANNEL_JD);
+ }
+
+ @Test
+ void testMsgTypeConstants() {
+ assertEquals("text", InboundMessage.MSG_TYPE_TEXT);
+ assertEquals("image", InboundMessage.MSG_TYPE_IMAGE);
+ assertEquals("voice", InboundMessage.MSG_TYPE_VOICE);
+ assertEquals("video", InboundMessage.MSG_TYPE_VIDEO);
+ assertEquals("event", InboundMessage.MSG_TYPE_EVENT);
+ }
+}
diff --git a/src/test/java/com/wecom/robot/dto/OutboundMessageTest.java b/src/test/java/com/wecom/robot/dto/OutboundMessageTest.java
new file mode 100644
index 0000000..04d2a81
--- /dev/null
+++ b/src/test/java/com/wecom/robot/dto/OutboundMessageTest.java
@@ -0,0 +1,50 @@
+package com.wecom.robot.dto;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class OutboundMessageTest {
+
+ @Test
+ void testOutboundMessageBuilder() {
+ Map metadata = new HashMap<>();
+ metadata.put("priority", "high");
+
+ OutboundMessage message = OutboundMessage.builder()
+ .channelType(InboundMessage.CHANNEL_WECHAT)
+ .receiver("customer-001")
+ .kfId("kf-001")
+ .content("Reply message")
+ .msgType("text")
+ .metadata(metadata)
+ .build();
+
+ assertEquals(InboundMessage.CHANNEL_WECHAT, message.getChannelType());
+ assertEquals("customer-001", message.getReceiver());
+ assertEquals("kf-001", message.getKfId());
+ assertEquals("Reply message", message.getContent());
+ assertEquals("text", message.getMsgType());
+ assertNotNull(message.getMetadata());
+ assertEquals("high", message.getMetadata().get("priority"));
+ }
+
+ @Test
+ void testOutboundMessageSetters() {
+ OutboundMessage message = new OutboundMessage();
+ message.setChannelType(InboundMessage.CHANNEL_JD);
+ message.setReceiver("jd-customer-001");
+ message.setKfId("jd-kf-001");
+ message.setContent("JD reply");
+ message.setMsgType("text");
+
+ assertEquals(InboundMessage.CHANNEL_JD, message.getChannelType());
+ assertEquals("jd-customer-001", message.getReceiver());
+ assertEquals("jd-kf-001", message.getKfId());
+ assertEquals("JD reply", message.getContent());
+ assertEquals("text", message.getMsgType());
+ }
+}
diff --git a/src/test/java/com/wecom/robot/dto/SignatureInfoTest.java b/src/test/java/com/wecom/robot/dto/SignatureInfoTest.java
new file mode 100644
index 0000000..b11f5b0
--- /dev/null
+++ b/src/test/java/com/wecom/robot/dto/SignatureInfoTest.java
@@ -0,0 +1,61 @@
+package com.wecom.robot.dto;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SignatureInfoTest {
+
+ @Test
+ void testSignatureInfoBuilder() {
+ SignatureInfo signatureInfo = SignatureInfo.builder()
+ .signature("abc123signature")
+ .timestamp("1708700000")
+ .nonce("random-nonce-value")
+ .algorithm("hmac-sha256")
+ .build();
+
+ assertEquals("abc123signature", signatureInfo.getSignature());
+ assertEquals("1708700000", signatureInfo.getTimestamp());
+ assertEquals("random-nonce-value", signatureInfo.getNonce());
+ assertEquals("hmac-sha256", signatureInfo.getAlgorithm());
+ }
+
+ @Test
+ void testSignatureInfoSetters() {
+ SignatureInfo signatureInfo = new SignatureInfo();
+ signatureInfo.setSignature("test-sig");
+ signatureInfo.setTimestamp("12345");
+ signatureInfo.setNonce("test-nonce");
+ signatureInfo.setAlgorithm("md5");
+
+ assertEquals("test-sig", signatureInfo.getSignature());
+ assertEquals("12345", signatureInfo.getTimestamp());
+ assertEquals("test-nonce", signatureInfo.getNonce());
+ assertEquals("md5", signatureInfo.getAlgorithm());
+ }
+
+ @Test
+ void testSignatureInfoNoArgsConstructor() {
+ SignatureInfo signatureInfo = new SignatureInfo();
+ assertNull(signatureInfo.getSignature());
+ assertNull(signatureInfo.getTimestamp());
+ assertNull(signatureInfo.getNonce());
+ assertNull(signatureInfo.getAlgorithm());
+ }
+
+ @Test
+ void testSignatureInfoAllArgsConstructor() {
+ SignatureInfo signatureInfo = new SignatureInfo(
+ "full-sig",
+ "9999",
+ "full-nonce",
+ "sha1"
+ );
+
+ assertEquals("full-sig", signatureInfo.getSignature());
+ assertEquals("9999", signatureInfo.getTimestamp());
+ assertEquals("full-nonce", signatureInfo.getNonce());
+ assertEquals("sha1", signatureInfo.getAlgorithm());
+ }
+}