From 872f0a5d75a2f2520cf652736a2f4afc30d5c26c Mon Sep 17 00:00:00 2001 From: MerCry Date: Tue, 24 Feb 2026 00:51:29 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E5=A4=9A=E6=B8=A0?= =?UTF-8?q?=E9=81=93=E9=80=82=E9=85=8D=E4=B8=BB=E6=A1=86=E6=9E=B6=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E8=AE=BE=E8=AE=A1=20(design.md)=20[AC-MCA-01]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec/ai-robot/design.md | 636 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 636 insertions(+) create mode 100644 spec/ai-robot/design.md diff --git a/spec/ai-robot/design.md b/spec/ai-robot/design.md new file mode 100644 index 0000000..ae6c56d --- /dev/null +++ b/spec/ai-robot/design.md @@ -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 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 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 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 + + io.github.resilience4j + resilience4j-spring-boot2 + 2.1.0 + +``` + +### 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 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 |