feat/multi-channel-framework [AC-INIT]合并功能代码 #12

Merged
MerCry merged 37 commits from feat/multi-channel-framework into main 2026-02-24 03:55:00 +00:00
1 changed files with 636 additions and 0 deletions
Showing only changes of commit 872f0a5d75 - Show all commits

636
spec/ai-robot/design.md Normal file
View File

@ -0,0 +1,636 @@
---
feature_id: "MCA"
title: "多渠道适配主框架架构设计"
status: "draft"
version: "0.2.0"
owners:
- "architect"
- "backend"
last_updated: "2026-02-24"
---
# 多渠道适配主框架架构设计design.md
## 1. 系统架构
### 1.1 整体架构图
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 外部系统 │
├─────────────────┬─────────────────┬─────────────────┬───────────────────────┤
│ 企业微信 API │ 抖音 API │ 京东 API │ 前端工作台 │
└────────┬────────┴────────┬────────┴────────┬────────┴──────────┬────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Java 主框架 (Spring Boot) │
├─────────────────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 入口层 (Controller Layer) │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │WecomCallback │ │DouyinCallback│ │ JdCallback │ (预留) │ │
│ │ │ Controller │ │ Controller │ │ Controller │ │ │
│ │ └──────┬───────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │ │
│ │ │ 验签/解密/解析 → InboundMessage │ │
│ │ ▼ │ │
│ └─────────┼───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 消息路由层 (Message Router) │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ MessageRouterService (渠道无关) │ │ │
│ │ │ - processInboundMessage(InboundMessage) │ │ │
│ │ │ - routeBySessionState(Session, InboundMessage) │ │ │
│ │ │ - dispatchToAiService(Session, InboundMessage) │ │ │
│ │ │ - dispatchToManualCs(Session, InboundMessage) │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 渠道适配层 (Channel Adapter) │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ ChannelAdapter 接口 (核心能力) │ │ │
│ │ │ - getChannelType() │ │ │
│ │ │ - sendMessage(OutboundMessage) │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ 可选能力接口 (Optional Capabilities) │ │ │
│ │ │ - ServiceStateCapable - TransferCapable - MessageSyncCapable│ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ WeChatAdapter│ │DouyinAdapter │ │ JdAdapter │ (预留) │ │
│ │ │ (已实现) │ │ (预留) │ │ (预留) │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────┼─────────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────────────┐ ┌──────────────┐ │
│ │ AI 服务客户端 │ │ 会话管理层 │ │ WebSocket 服务│ │
│ │AiServiceClient│ │SessionManagerService │ │WebSocketService│ │
│ └──────┬───────┘ └──────────────────────┘ └──────────────┘ │
│ │ │
└─────────┼───────────────────────────────────────────────────────────────────┘
│ HTTP
┌─────────────────────────────────────────────────────────────────────────────┐
│ Python AI 服务 (独立部署) │
├─────────────────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ OpenAI Client│ │DeepSeek Client│ │ 其他模型 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ AI 服务核心逻辑 │ │
│ │ - /ai/chat 生成 AI 回复 │ │
│ │ - /ai/health 健康检查 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 1.2 模块职责
| 模块 | 职责 | 关联 AC |
|-----|------|--------|
| **入口层** | 接收渠道回调,验签/解密/解析,转换为统一的 InboundMessage | AC-MCA-08 |
| **消息路由层** | 渠道无关的消息路由,根据会话状态分发到 AI 或人工 | AC-MCA-08 ~ AC-MCA-10 |
| **渠道适配层** | 封装各渠道 API 差异,提供统一的消息发送接口 | AC-MCA-01 ~ AC-MCA-03 |
| **AI 服务客户端** | 调用 Python AI 服务,处理超时/降级 | AC-MCA-04 ~ AC-MCA-07 |
| **会话管理层** | 管理会话生命周期、状态变更、消息持久化 | AC-MCA-11 ~ AC-MCA-12 |
| **WebSocket 服务** | 实时推送消息到人工客服工作台 | AC-MCA-10 |
| **Python AI 服务** | AI 模型推理、置信度评估、转人工建议 | AC-MCA-04 ~ AC-MCA-05 |
## 2. 统一消息模型
### 2.1 入站消息 (InboundMessage)
```java
@Data
public class InboundMessage {
private String channelType; // 渠道类型: wechat/douyin/jd
private String channelMessageId; // 渠道原始消息ID (用于幂等)
private String sessionKey; // 会话标识 (customerId + kfId 组合)
private String customerId; // 客户ID
private String kfId; // 客服账号ID
private String sender; // 发送者标识
private String content; // 消息内容 (统一字段名)
private String msgType; // 消息类型: text/image/voice 等
private String rawPayload; // 原始消息体 (JSON/XML)
private Long timestamp; // 消息时间戳
private SignatureInfo signatureInfo; // 签名信息
private Map<String, Object> metadata; // 扩展元数据
}
@Data
public class SignatureInfo {
private String signature; // 签名值
private String timestamp; // 签名时间戳
private String nonce; // 随机数
private String algorithm; // 签名算法 (可选)
}
```
### 2.2 出站消息 (OutboundMessage)
```java
@Data
public class OutboundMessage {
private String channelType; // 渠道类型
private String receiver; // 接收者ID (customerId)
private String kfId; // 客服账号ID
private String content; // 消息内容
private String msgType; // 消息类型
private Map<String, Object> metadata; // 扩展元数据
}
```
### 2.3 字段映射策略
> **重要**:内部统一使用 `content` 字段名,与 AI 服务契约 (`currentMessage`) 的映射在 AiServiceClient 层处理。
| 内部字段 | AI 服务契约字段 | 映射位置 |
|---------|----------------|---------|
| `InboundMessage.content` | `ChatRequest.currentMessage` | `AiServiceClient.generateReply()` |
| `InboundMessage.sessionKey` | `ChatRequest.sessionId` | `AiServiceClient.generateReply()` |
| `InboundMessage.channelType` | `ChatRequest.channelType` | `AiServiceClient.generateReply()` |
```java
public ChatRequest toChatRequest(InboundMessage msg, List<Message> history) {
ChatRequest request = new ChatRequest();
request.setSessionId(msg.getSessionKey());
request.setCurrentMessage(msg.getContent()); // content → currentMessage
request.setChannelType(msg.getChannelType());
request.setHistory(history);
return request;
}
```
## 3. 核心接口设计
### 3.1 渠道适配器接口
```java
// 核心能力接口(所有渠道必须实现)
public interface ChannelAdapter {
String getChannelType();
void sendMessage(OutboundMessage message);
}
// 可选能力接口:服务状态管理
public interface ServiceStateCapable {
ServiceState getServiceState(String kfId, String customerId);
boolean transServiceState(String kfId, String customerId, int newState, String servicerId);
}
// 可选能力接口:转人工
public interface TransferCapable {
boolean transferToPool(String kfId, String customerId);
boolean transferToManual(String kfId, String customerId, String servicerId);
}
// 可选能力接口:消息同步
public interface MessageSyncCapable {
SyncMsgResponse syncMessages(String kfId, String cursor);
}
```
### 3.2 消息路由服务接口
```java
public interface MessageRouterService {
void processInboundMessage(InboundMessage message);
void routeBySessionState(Session session, InboundMessage message);
void dispatchToAiService(Session session, InboundMessage message);
void dispatchToManualCs(Session session, InboundMessage message);
void dispatchToPendingPool(Session session, InboundMessage message);
}
```
### 3.3 AI 服务客户端接口
```java
public interface AiServiceClient {
ChatResponse generateReply(ChatRequest request);
boolean healthCheck();
}
```
## 4. 核心流程
### 4.1 消息处理主流程(渠道无关)
```
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 渠道回调入口 │────▶│ 验签/解密/解析 │────▶│ 构建 InboundMessage│
│ (Controller) │ │ (渠道专属逻辑) │ │ (统一消息模型) │
└──────────────────┘ └────────┬─────────┘ └──────────────────┘
┌──────────────────┐
│ MessageRouter │
│ processInbound │
│ Message() │
└────────┬─────────┘
┌──────────────────┐
│ 幂等检查 (msgId) │
│ Redis SETNX │
└────────┬─────────┘
┌──────────────────┐
│ 获取/创建会话 │
│ SessionManager │
└────────┬─────────┘
┌──────────────────┐
│ 获取渠道服务状态 │
│ (可选能力检测) │
└────────┬─────────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ AI 状态 │ │ POOL 状态 │ │MANUAL 状态│
│ │ │ │ │ │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌──────────────┐
│dispatchTo │ │dispatchTo│ │dispatchTo │
│ AiService │ │PendingPool│ │ ManualCs │
└──────┬───────┘ └──────────┘ └──────────────┘
┌──────────────┐
│ 判断是否转人工│
│shouldTransfer│
└──────┬───────┘
┌───────┴───────┐
▼ ▼
┌───────┐ ┌──────────────┐
│发送回复│ │ 转入待接入池 │
│给用户 │ │ TransferCapable│
└───────┘ └──────────────┘
```
### 4.2 AI 服务调用流程
```
┌──────────────────┐
│ 构造 ChatRequest │
│ sessionId │
│ currentMessage │←── content 映射
│ channelType │
│ history (可选) │
└────────┬─────────┘
┌──────────────────┐ ┌──────────────────┐
│ HTTP POST │────▶│ Python AI 服务 │
│ /ai/chat │ │ 超时: 5s │
└────────┬─────────┘ └────────┬─────────┘
│ │
│ ┌──────────────┼──────────────┐
│ ▼ ▼ ▼
│ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ │ 成功响应 │ │ 超时/失败 │ │ 服务不可用│
│ │ 200 OK │ │ Timeout │ │ 503 │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
│ ▼ ▼ ▼
│ ┌──────────┐ ┌──────────────────────┐
│ │ 返回回复 │ │ 降级处理 │
│ │ reply │ │ 返回固定回复 │
│ │confidence│ │ "正在转接人工客服..." │
│ │shouldTransfer│ └──────────┬───────────┘
│ └──────────┘ │
│ ▼
│ ┌──────────────┐
│ │ 触发转人工 │
│ │ TransferCapable│
│ └──────────────┘
┌──────────────────┐
│ 处理响应 │
│ - 保存消息 │
│ - 发送给用户 │
│ - 判断转人工 │
└──────────────────┘
```
## 5. 数据模型
### 5.1 实体关系图
```
┌──────────────────┐ ┌──────────────────┐
│ Session │ │ Message │
├──────────────────┤ ├──────────────────┤
│ sessionId (PK) │──────▶│ msgId (PK) │
│ customerId │ 1:N │ sessionId (FK) │
│ kfId │ │ senderType │
│ channelType (新) │ │ senderId │
│ status │ │ content │
│ wxServiceState │ │ msgType │
│ manualCsId │ │ rawData │
│ createdAt │ │ createdAt │
│ updatedAt │ └──────────────────┘
└──────────────────┘
│ 1:N
┌──────────────────┐
│ TransferLog │
├──────────────────┤
│ id (PK) │
│ sessionId (FK) │
│ triggerReason │
│ triggerTime │
│ acceptedCsId │
│ acceptedTime │
└──────────────────┘
```
### 5.2 数据库变更
> **口径说明**:本次仅做最小 schema 变更,新增 `channel_type` 字段,默认值为 `wechat`;可通过在线 DDL 方式执行;不涉及数据迁移。符合 requirements.md 中"仅增加渠道类型字段,不进行大规模迁移"的范围约定。
| 表名 | 变更类型 | 变更内容 |
|-----|---------|---------|
| `session` | 新增字段 | `channel_type VARCHAR(20) DEFAULT 'wechat'` |
**DDL 示例**
```sql
ALTER TABLE session ADD COLUMN channel_type VARCHAR(20) DEFAULT 'wechat'
COMMENT '渠道类型: wechat/douyin/jd';
```
### 5.3 Redis 缓存结构
| Key 模式 | 类型 | 说明 | TTL |
|---------|------|------|-----|
| `wecom:access_token` | String | 微信 access_token | 7200s - 300s |
| `wecom:cursor:{openKfId}` | String | 消息同步游标 | 永久 |
| `session:status:{sessionId}` | String | 会话状态缓存 | 24h |
| `session:msg_count:{sessionId}` | String | 消息计数 | 24h |
| `idempotent:{msgId}` | String | 消息幂等键 | 1h |
## 6. 跨模块调用策略
### 6.1 AI 服务调用
| 配置项 | 值 | 说明 |
|-------|---|------|
| **超时时间** | 5 秒 | 连接 + 读取总超时 |
| **重试次数** | 0 | 不重试,直接降级 |
| **熔断阈值** | 5 次/分钟 | 连续失败 5 次触发熔断 |
| **熔断时间** | 30 秒 | 熔断后等待时间 |
| **降级策略** | 返回固定回复 + 转人工 | 见下方降级逻辑 |
### 6.2 熔断器选型
> **选型决策**:使用 **Resilience4j** 作为熔断器实现,与 Spring Boot 2.7 兼容。
| 方案 | 说明 |
|-----|------|
| **Resilience4j** | 推荐。轻量级,支持断路器、限流、重试,与 Spring Boot 2.7 兼容良好 |
| 最小实现 | 仅做 timeout + fallback不做熔断不推荐与 requirements 不一致) |
**熔断状态存储**
- 单实例内存存储CircuitBreakerRegistry
- 多实例:可扩展为 Redis 存储(通过 Resilience4j + Redis 实现)
**依赖配置**
```xml
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>2.1.0</version>
</dependency>
```
### 6.3 降级逻辑
```java
@Service
public class AiServiceClientImpl implements AiServiceClient {
@CircuitBreaker(name = "aiService", fallbackMethod = "fallback")
@TimeLimiter(name = "aiService")
public ChatResponse generateReply(ChatRequest request) {
// HTTP 调用 Python AI 服务
}
public ChatResponse fallback(ChatRequest request, Throwable cause) {
log.warn("AI 服务降级: sessionId={}, cause={}",
request.getSessionId(), cause.getMessage());
ChatResponse response = new ChatResponse();
response.setReply("抱歉,我暂时无法回答您的问题,正在为您转接人工客服...");
response.setConfidence(0.0);
response.setShouldTransfer(true);
return response;
}
}
```
### 6.4 错误映射
| AI 服务错误 | 主框架处理 | 用户感知 |
|------------|-----------|---------|
| 200 OK | 正常处理 | 返回 AI 回复 |
| 400 Bad Request | 记录日志,降级 | 转人工 |
| 500 Internal Error | 记录日志,降级 | 转人工 |
| 503 Service Unavailable | 记录日志,降级 | 转人工 |
| Timeout | 记录日志,降级 | 转人工 |
| Connection Refused | 触发熔断,降级 | 转人工 |
## 7. 消息幂等性设计
### 7.1 幂等键
- 使用 `InboundMessage.channelMessageId` 作为幂等键
- 微信渠道:使用微信返回的 `msgId`
- 其他渠道:使用渠道返回的消息 ID 或生成唯一 ID
### 7.2 幂等处理流程
```
┌──────────────────┐
│ 收到消息 │
│ channelMessageId │
└────────┬─────────┘
┌──────────────────┐ ┌──────────────────┐
│ Redis 检查 │────▶│ Key 不存在 │
│ idempotent:{msgId}│ │ 继续处理 │
└────────┬─────────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Key 已存在 │ │ 设置 Key (TTL 1h)│
│ 跳过处理 │ │ 处理消息 │
└──────────────────┘ └──────────────────┘
```
### 7.3 实现代码
```java
public boolean processMessageIdempotent(String channelMessageId, Runnable processor) {
String key = "idempotent:" + channelMessageId;
Boolean absent = redisTemplate.opsForValue()
.setIfAbsent(key, "1", 1, TimeUnit.HOURS);
if (Boolean.TRUE.equals(absent)) {
processor.run();
return true;
}
log.info("重复消息,跳过处理: channelMessageId={}", channelMessageId);
return false;
}
```
## 8. 配置管理
### 8.1 新增配置项
```yaml
# application.yml 新增配置
ai-service:
url: http://ai-service:8080
timeout: 5000
resilience4j:
circuitbreaker:
instances:
aiService:
failure-rate-threshold: 50
sliding-window-size: 10
sliding-window-type: COUNT_BASED
wait-duration-in-open-state: 30s
permitted-number-of-calls-in-half-open-state: 3
timelimiter:
instances:
aiService:
timeout-duration: 5s
channel:
default: wechat
adapters:
wechat:
enabled: true
douyin:
enabled: false
jd:
enabled: false
```
### 8.2 配置类
```java
@Data
@Component
@ConfigurationProperties(prefix = "ai-service")
public class AiServiceConfig {
private String url;
private int timeout = 5000;
}
@Data
@Component
@ConfigurationProperties(prefix = "channel")
public class ChannelConfig {
private String default;
private Map<String, AdapterConfig> adapters;
@Data
public static class AdapterConfig {
private boolean enabled;
}
}
```
## 9. 部署架构
### 9.1 部署拓扑
```
┌─────────────────────────────────────────────────────────────┐
│ 负载均衡器 │
└─────────────────────────────┬───────────────────────────────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Java 主 │ │ Java 主 │ │ Java 主 │
│ 框架实例1│ │ 框架实例2│ │ 框架实例3│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└──────────────┼──────────────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Redis │ │ MySQL │ │Python AI │
│ (Cluster)│ │ (Master) │ │ 服务 │
└──────────┘ └──────────┘ └──────────┘
```
### 9.2 服务依赖
| 服务 | 依赖关系 | 健康检查 |
|-----|---------|---------|
| Java 主框架 | 依赖 Redis, MySQL, Python AI | `/actuator/health` |
| Python AI 服务 | 无外部依赖 | `/ai/health` |
## 10. 安全设计
### 10.1 渠道回调鉴权
| 渠道 | 鉴权方式 | 验证逻辑 |
|-----|---------|---------|
| 微信 | msg_signature + timestamp + nonce | **沿用现有 WeCom 官方验签/解密方案**(复用现有 `WXBizMsgCrypt` 实现) |
| 抖音 | X-Signature + X-Timestamp | 待实现 |
| 京东 | signature + timestamp | 待实现 |
> **说明**:微信回调验签/加解密使用企业微信官方方案,具体算法细节封装在现有 `WXBizMsgCrypt` 类中,不在本设计文档展开。
### 10.2 内部服务鉴权
- Java 主框架 → Python AI 服务:内网调用,无需鉴权(可扩展为 mTLS
- WebSocket 连接:路径参数 `{csId}` 标识身份(可扩展为 Token 验证)
## 11. 监控与告警
> **说明**本节为后续演进预留MVP 阶段可暂不实现。
### 11.1 关键指标
| 指标 | 类型 | 说明 |
|-----|------|------|
| `ai.service.latency` | Histogram | AI 服务调用延迟 |
| `ai.service.error.rate` | Counter | AI 服务错误率 |
| `ai.service.circuit.breaker.open` | Gauge | 熔断器状态 |
| `message.process.count` | Counter | 消息处理数量 |
| `message.idempotent.skip` | Counter | 幂等跳过数量 |
| `session.active.count` | Gauge | 活跃会话数 |
### 11.2 告警规则
| 规则 | 条件 | 级别 |
|-----|------|------|
| AI 服务不可用 | 连续失败 5 次 | Critical |
| AI 服务延迟过高 | P99 > 3s | Warning |
| 熔断器触发 | circuit.breaker.open = 1 | Critical |