feat(MCA): TASK-010 定义 ChannelAdapter 接口 [AC-MCA-01]
- 创建 ChannelAdapter 核心能力接口 - 创建 ServiceStateCapable 可选能力接口 - 创建 TransferCapable 可选能力接口 - 创建 MessageSyncCapable 可选能力接口 接口定义与 design.md 3.1 一致,sendMessage 使用 OutboundMessage 参数
This commit is contained in:
parent
b9792c8673
commit
4e9c5ba2eb
|
|
@ -0,0 +1,31 @@
|
|||
package com.wecom.robot.adapter;
|
||||
|
||||
import com.wecom.robot.dto.OutboundMessage;
|
||||
|
||||
/**
|
||||
* 渠道适配器核心能力接口
|
||||
* <p>
|
||||
* 所有渠道适配器必须实现此接口,提供渠道类型标识和消息发送能力。
|
||||
* [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);
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.wecom.robot.adapter;
|
||||
|
||||
import com.wecom.robot.dto.SyncMsgResponse;
|
||||
|
||||
/**
|
||||
* 消息同步能力接口(可选)
|
||||
* <p>
|
||||
* 提供从渠道同步历史消息的能力。
|
||||
* 渠道适配器可选择性实现此接口。
|
||||
* [AC-MCA-01] 渠道适配层可选能力接口
|
||||
*/
|
||||
public interface MessageSyncCapable {
|
||||
|
||||
/**
|
||||
* 同步消息
|
||||
*
|
||||
* @param kfId 客服账号ID
|
||||
* @param cursor 游标(用于分页获取)
|
||||
* @return 同步消息响应
|
||||
*/
|
||||
SyncMsgResponse syncMessages(String kfId, String cursor);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.wecom.robot.adapter;
|
||||
|
||||
import com.wecom.robot.dto.ServiceStateResponse;
|
||||
|
||||
/**
|
||||
* 服务状态管理能力接口(可选)
|
||||
* <p>
|
||||
* 提供渠道服务状态的获取和变更能力。
|
||||
* 渠道适配器可选择性实现此接口。
|
||||
* [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);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.wecom.robot.adapter;
|
||||
|
||||
/**
|
||||
* 转人工能力接口(可选)
|
||||
* <p>
|
||||
* 提供将客户转入待接入池或转给指定人工客服的能力。
|
||||
* 渠道适配器可选择性实现此接口。
|
||||
* [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);
|
||||
}
|
||||
|
|
@ -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<String, Object> 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";
|
||||
}
|
||||
|
|
@ -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<String, Object> metadata;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<String, Object> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, Object> 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue