diff --git a/docs/architecture/2026-07-06-message-customer-interaction-design.md b/docs/architecture/2026-07-06-message-customer-interaction-design.md new file mode 100644 index 0000000..dc554fa --- /dev/null +++ b/docs/architecture/2026-07-06-message-customer-interaction-design.md @@ -0,0 +1,2164 @@ +# 消息与客户互动中台架构设计 + +> **需求来源**:1333 统一消息中心、1851 企微机器人报备信息、2092 IVR 语音配置 +> **版本**:v0.7 +> **日期**:2026-07-06 +> **状态**:草案(新增项目结构、升级 Spring Boot 4.1) + +--- + +## 变更记录 + +| 版本 | 日期 | 变更说明 | +|------|------|---------| +| v0.1 | 2026-07-01 | 完成第一节整体架构、第二节核心领域模型与数据表设计 | +| v0.2 | 2026-07-01 | 完成第三节消息编排层详细设计 | +| v0.3 | 2026-07-01 | 完成第四节技术选型 | +| v0.4 | 2026-07-03 | 范围收缩:移除业务事件触发规则,出站发送由业务系统直接调用;保留入站消息处理工作流和 IVR 独立流程 | +| v0.5 | 2026-07-03 | 进一步简化:中台不保存模板和变量;去掉中台调用业务系统能力接口;去掉定时发送;发送任务自带 callback_url;新增账号体系设计 | +| v0.6 | 2026-07-06 | 数据模型修正:合并发送任务/日志;租户=业务系统;企微主体扩展为渠道主体;身份与账号重新定义;账号-群绑定增加收消息主备;会话存档复用消息事件表;角色统一成标签;会话增加标签接口;补充渠道账号初始化 API | +| v0.7 | 2026-07-06 | 新增第五节项目目录结构;技术栈升级为 Spring Boot 4.1.x;明确项目独立于 sa-server 位于平级目录;标签表去掉分类字段;新增入站消息轻量流程编排设计;前端增加入站流程编排页面 | + +--- + +# 第一节:整体架构设计 + +## 1.1 设计背景与目标 + +基于三个需求(1333 统一消息中心、1851 企微机器人报备、2092 IVR 语音外呼)和现有 `sa-server` 代码,抽象出一个面向未来的**消息与客户互动中台**。 + +**核心目标**: +- 把“收发消息、联系客户、企微客户营销”统一成一套平台级能力; +- 首期聚焦 1851 的企微场景; +- 为 1333(内部通知/流转)和 2092(IVR 外呼)预留扩展位; +- 通过事件总线兼容现有业务系统,不强制迁移; +- 实现“客户说话 → 中台识别 → 调用业务系统 → 回写结果 → 回复客户”的闭环。 + +**范围约束**: +- 中台定位为**消息发送网关 + 渠道资源管理中心**; +- 中台不保存模板和变量,业务系统直接传入已渲染的最终内容; +- 中台不主动调用业务系统能力接口,入站消息通过 webhook/事件推送给业务系统,由业务系统自行处理并回呼中台发送; +- 中台不承载业务系统的“触发条件 + 发送规则”; +- 中台保留的配置:渠道资源、发送策略、发送记录、账号权限、入站消息 webhook、IVR 流程(预留)。 + +## 1.2 整体架构图 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 业务系统 │ +│ 数据服务:合同 / 供应商 / 订单 / 人员 / 组织 / 客户 │ +│ 业务能力 API:开案 / 咨询 / 查状态 / 审批 / 创建跟进 / 下发 KPI │ +│ 模板与变量:业务系统自行维护 │ +└─────────────────────────┬───────────────────────────────────┘ + │ ① 调发送 API ② 接收入站 webhook + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 消息与客户互动中台 │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ 管理后台 │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ 渠道资源 │ │ 账号权限 │ │ 发送策略 │ │ │ +│ │ │ 管理 │ │ 管理 │ │ 配置 │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ 入站 webhook│ │ 发送记录 │ │ IVR 流程 │ │ │ +│ │ │ 配置 │ │ 查询 │ │ (2092 预留)│ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ 消息编排层 │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ 入站处理 │ │ 出站调度 │ │ 策略执行 │ │ │ +│ │ │ 归一化/意图 │ │ 渠道选择 │ │ 频率/重试 │ │ │ +│ │ │ 识别/webhook│ │ 精准定位 │ │ │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ 渠道适配层 │ │ +│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ +│ │ │ 微信渠道 │ │ 钉钉适配 │ │ 飞书适配 │ │ IVR 适配 │ │ +│ │ │(企微收 + │ │(预留扩展)│ │(预留扩展)│ │(预留扩展)│ │ +│ │ │个人号发) │ │ │ │ │ │ │ │ +│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ 共享服务层 │ │ +│ │ 客户关系中心 + 身份中心 + 标签中心 + 事件总线 │ │ +│ └───────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 1.3 各层职责 + +| 层级 | 核心职责 | 首期 1851 范围 | +|------|---------|---------------| +| 业务系统 | 维护合同、供应商、人员、客户等主数据;维护模板和变量;暴露业务能力 API;自行判断触发条件 | 复用现有模块 | +| 管理后台 | 渠道资源、账号权限、发送策略、入站 webhook、发送记录、IVR 流程配置 | 各业务系统独立管理自己的资源 | +| 消息编排层 | 入站消息归一化、LLM 意图识别、webhook 推送;出站目标解析与渠道调度、策略执行 | 实现核心骨架,服务 1851 场景 | +| 渠道适配层 | 封装各渠道的发送/接收/资源管理/回调 | 实现微信渠道(企微收 + 个人号发) | +| 共享服务层 | 客户关系、身份映射、标签、事件总线 | 建最小可用版本 | + +## 1.4 渠道适配层详细设计 + +### 微信渠道(首期实现) + +| 方向 | 通道 | 技术方式 | 关键能力 | +|------|------|---------|---------| +| 收消息 | 企业微信 | 会话存档回调通知 + 主动拉取 / 群机器人 / Webhook 回调 | 企微只推 `msgaudit_notify` 通知,中台主动拉取消息内容 | +| 发消息 | 个人微信 | pywechat + PC 微信客户端 | 个人号在群里发消息,需要账号池和调度 | + +### 微信渠道需要维护的映射 + +| 映射 | 内容 | 用途 | +|------|------|------| +| 群 → 企微 chat_id | 会话存档中的群 ID | 收消息时识别群 | +| 群 → 个人微信号 | 哪个个人号在群里、发送主号/发送备用号 | 发消息时选号 | +| 群 → 业务渠道/合同/机构 | 群对应的业务实体 | 上下文匹配和意图识别 | + +### 企微会话存档收消息机制 + +复用 `supplier-manager` 已有技术选型: + +- `weixin-java-cp`(me.chanjar.weixin.cp)封装企微 CP API; +- `com.tencent.wework.Finance` SDK(C 库通过 JNI 调用)进行会话存档拉取与解密; +- 动态加载 native 库(Windows `.dll` / Linux `.so`)。 + +企微不会直接推送消息内容,只推送**有新消息的通知事件** `msgaudit_notify`(回调间隔 15 秒)。中台收到通知后,通过 Finance SDK 的 `GetChatData` 按 seq 游标分页拉取,再调用 `DecryptData` 解密。 + +``` +企微 ──msgaudit_notify──► 中台适配器 + │ + ▼ + Finance.GetChatData(seq, limit) + │ + ▼ + Finance.DecryptData + │ + ▼ + 归一化为 MessageEvent + │ + ▼ + message_source = session_archive +``` + +企微会话存档相关 API: + +| API | 用途 | +|-----|------| +| 产生会话回调事件 | 企微推送 `msgaudit_notify` 通知 | +| 获取会话内容 | 拉取实际聊天消息 | +| 获取会话内容存档开启成员列表 | 知道哪些员工已开启存档 | +| 获取会话内容存档内部群信息 | 拉取内部群基本信息 | +| 获取会话同意情况 | 外部联系人是否同意存档 | +| 客户同意进行聊天内容存档事件回调 | 同意/拒绝状态变更回调 | +| 获取媒体文件 | 下载图片/语音/视频/文件等媒体内容 | + +### 其他渠道(预留扩展) + +| 渠道 | 收发方式 | 备注 | +|------|---------|------| +| 钉钉 | Webhook 收发 | 后期按同样模式接入 | +| 飞书 | Webhook 收发 | 后期按同样模式接入 | +| IVR | 天润 API + 回调 | 2092 阶段接入 | + +## 1.5 共享服务层 + +### 客户关系中心 +维护一张**客户关系图谱**: + +| 实体 | 说明 | +|------|------| +| 联系人 | 业务人员/客户/司机,含业务身份和渠道身份 | +| 渠道身份 | 企微 external_userid、个人微信号、钉钉 userid、飞书 open_id | +| 群聊 | chat_id、群名、关联业务实体、收发账号绑定 | +| 关系 | 人与群、人与人的关系 | + +### 身份中心 +维护“业务人员 ID → 各渠道账号”的映射: + +```json +{ + "person_id": "P001", + "wecom_external_userid": "wxf1xxx", + "wechat_personal_id": "wxid_xxx", + "dingtalk_userid": "ding1xxx", + "feishu_open_id": "fei1xxx", + "phone": "138..." +} +``` + +### 标签中心 +支持两类标签: + +| 标签类型 | 示例 | +|---------|------| +| 业务标签 | 销售、区域经理、子公司、华北、合同 C001 | +| 渠道标签 | 企微活跃用户、群管理员、培训未读 | + +### 事件总线 +- 所有入站/出站事件通过事件总线流转; +- 业务系统可订阅事件。 + +## 1.6 业务身份模型 + +每个人在中台都有一个多维业务身份: + +```json +{ + "business_identity": { + "person_id": "P001", + "type": "internal", + "org": { + "institution": "华北分公司", + "department": "销售一部" + }, + "tags": ["sales", "华北分公司", "销售一部"], + "supplier": { + "supplier_id": "S001", + "is_subsidiary": true, + "region": "华北" + }, + "contract": { + "contract_id": "C001", + "contract_tag": "sales" + } + } +} +``` + +## 1.7 编排层核心能力 + +| 组件 | 职责 | +|------|------| +| 入站处理 | 接收消息 → 归一化 → 上下文匹配 → LLM 意图识别 → 推送到业务系统 webhook | +| 出站调度 | 目标解析 → 渠道选择 → 账号选择 → 发送(由业务系统直接触发) | +| 意图识别引擎 | LLM / 规则识别消息意图和槽位 | +| 精准定位引擎 | 根据业务系统传入的标签/属性/业务条件解析目标接收人 | +| 策略执行 | 频率控制、重试、时间窗、黑名单、主备切换 | + +## 1.8 入站消息处理流程 + +> 示例:服务商在群里发“我要下单,客户张三,地址北京” + +1. **收消息**:企微推送 `msgaudit_notify` 回调事件,中台调用「获取会话内容」API 拉取群消息; +2. **归一化**:微信适配器转成标准 `MessageEvent`; +3. **来源绑定**:根据群 ID 找到绑定的 pywechat 发信号; +4. **上下文匹配**:根据群/人找到合同、机构、服务商、角色; +5. **入站流程编排**:按租户配置的入站流程执行,节点包括意图识别、条件分支、webhook 推送、自动回复; +6. **推送业务系统**:将消息内容、上下文、意图识别结果推送到流程节点指定的业务系统 webhook URL; +7. **业务系统处理**:业务系统根据意图调用自身能力(开案、创建咨询、查状态); +8. **业务系统回呼中台**:业务系统调用中台发送 API,传入最终回复内容; +9. **中台发送回复**:中台选择渠道和发信号,回复群聊。 + +> 说明:中台做入站消息接收、归一化、上下文绑定、流程编排、LLM 意图识别和 webhook 推送;不主动调用业务系统能力;流程可通过前端轻量画布配置。 + +### 入站 webhook 推送示例 + +```json +POST {business_system_inbound_webhook_url} +Headers: X-Msg-Platform-Signature: xxx + +{ + "event_id": "evt_20260703120000_001", + "tenant_code": "rescue", + "channel_type": "wecom", + "direction": "in", + "event_type": "message", + "conversation_id": 1001, + "person_id": 2001, + "account_id": 3001, + "content": { + "type": "text", + "body": "我要下单,客户张三,地址北京" + }, + "intent": { + "intent": "create_order", + "confidence": 0.95, + "slots": { + "customer_name": "张三", + "address": "北京" + }, + "missing_slots": ["phone"], + "requires_confirmation": true + }, + "business_context": { + "contract_id": "C001", + "supplier_id": "S001", + "institution_id": "I001", + "tag": "dispatcher" + }, + "create_time": "2026-07-03T12:00:00" +} +``` + +## 1.9 出站消息处理流程 + +> 示例:业务系统要发培训通知 + +1. 业务系统自行判断触发时机、业务条件、文案变量,并渲染出最终内容; +2. 业务系统调用中台发送接口,传入目标描述(标签/属性/人员/会话)和最终内容; +3. 中台精准定位引擎解析目标描述; +4. 从客户关系中心查出目标人/会话; +5. 从身份中心查这些人的可用渠道; +6. 编排层按策略选渠道和发信号; +7. 渠道适配器实际发送; +8. 发送结果通过发送记录中的 `callback_url` 回传业务系统。 + +> **说明**:模板、变量、触发规则、业务条件、变量取值均由业务系统完成;中台只做“接收发送请求 → 解析目标 → 选择渠道 → 执行发送 → 回收结果 → 回调”。 + +### 发送请求示例 + +```json +POST /msg-platform/api/v1/send +Headers: X-Tenant-Code: rescue + +{ + "target": { + "type": "rule", + "rule": { + "tag": "rescue_dispatcher", + "contract_id": "C001" + } + }, + "content": { + "type": "text", + "body": "技师姓名:张师傅\n技师电话:13800138000\n预计到达时间:30分钟" + }, + "channel_strategy": { + "priority": ["wechat_personal"] + }, + "callback_url": "https://rescue.sino.com/api/msg-platform/callback" +} +``` + +## 1.10 关键设计原则 + +1. **中台是消息发送网关 + 渠道资源管理中心**; +2. **业务系统自行控制触发时机、发送规则、模板和变量**; +3. **业务系统只按业务语义描述目标**,不感知具体渠道账号; +4. **中台不主动调用业务系统能力接口**,入站消息通过 webhook 推送; +5. **渠道适配器只暴露统一接口**:发送、接收、资源同步、结果回调; +6. **中台沉淀渠道身份和客户关系**:业务身份由业务系统维护; +7. **发送结果通过任务级 callback_url 回传**; +8. **首期聚焦、预留扩展**:1851 企微先行,1333/2092 按相同分层接入。 + +## 1.11 与现有系统的关系 + +| 现有模块 | 在新架构中的位置 | 处理方式 | +|---------|----------------|---------| +| `admin/supplier-manage` 企微 CP | 渠道适配层(企微收)+ 客户关系中心数据来源 | 复用并扩展 | +| `api/call-center` | 渠道适配层(短信/IVR) | 复用底层通道能力 | +| `ns/sa-admin` 事件流转 | 业务系统(触发规则由业务系统维护) | 预留接入 | +| `ns/sa-admin` 合同/供应商/订单 | 业务系统 | 提供数据和 API | + +--- + +# 第二节:核心领域模型与数据表设计 + +## 2.1 核心领域实体 + +``` +``` +┌─────────────────────────────────────────────────────────────┐ +│ 业务身份域 │ +│ ┌─────────┐ │ +│ │ Person │──1:N Tag(业务标签:角色/合同/机构/供应商等) │ +│ │(联系人) │ │ +│ └────┬────┘ │ +│ │ 1:N │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ ChannelIdentity │ 人在渠道上的身份标识 │ +│ │ (渠道身份) │ 如企微 external_userid、个微 wxid │ +│ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + │ N:M + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 群聊与消息域 │ +│ ┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ +│ │Conversation │──│ ConversationParticipant │ │ +│ │ (会话) │ │ (会话参与者) │ │ Tag(群标签) │ │ +│ └──────┬──────┘ └──────────────────┘ │ │ +│ │ │ │ +│ │ 1:N │ │ +│ ▼ │ │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ MessageEvent (消息事件) │ │ +│ │ 入站 / 出站 / 回调 / 状态变更 / 会话存档 │ │ +│ └───────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + │ 触发 / 引用 + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 编排与账号域 │ +│ ┌────────────────┐ ┌─────────────┐ ┌─────────────────┐ │ +│ │FlowDefinition │ │ Tenant │ │ PlatformUser │ │ +│ │ (流程定义) │ │ (业务系统) │ │ (中台用户) │ │ +│ └────────────────┘ └─────────────┘ └─────────────────┘ │ +│ ┌────────────────┐ ┌─────────────────────────────────┐ │ +│ │ ChannelSubject │ │ SendRecord │ │ +│ │ (渠道主体) │ │ 发送记录 │ │ +│ └────────────────┘ └─────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 2.2 实体关系说明 + +| 实体 | 说明 | 关键关系 | +|------|------|---------| +| Person | 统一联系人 | 1 个 Person 有 N 个 ChannelIdentity;N 个 Tag | +| ChannelIdentity | **人在渠道上的身份标识**(如企微 external_userid、个微 wxid、钉钉 userid) | 属于 1 个 Person,关联 1 个 ChannelSubject | +| ChannelAccount | **用于收发消息的载体账号**(如企微内部账号、pywechat 个微、钉钉机器人、飞书机器人) | 属于 1 个 ChannelSubject | +| ChannelSubject | 渠道主体,一个企业在一个渠道下的主体 | 目前支持 wecom / dingtalk / feishu | +| Conversation | 会话(单聊/群聊) | 有 N 个 ConversationParticipant;N 个 Tag | +| ConversationParticipant | 会话参与者 | 关联 Person + ChannelIdentity | +| ChannelAccountConversation | 账号与群的绑定关系(收/发、主/备) | 关联 ChannelAccount + Conversation | +| MessageEvent | 标准化后的消息事件 | 关联 Conversation、Person、ChannelAccount;会话存档消息 source=session_archive | +| FlowDefinition | 流程编排定义 | IVR 等独立流程 | +| Tag | 标签 | 与 Person / Conversation 多对多;可表达角色、合同、机构、供应商等业务属性 | +| SendRecord | 发送任务与发送日志合并表 | 按 task_id 分组,一条目标对应一条记录 | +| Tenant | 业务系统租户,等同于业务系统 | 中台账号和资源顶层隔离 | +| PlatformUser | 中台用户 | 属于某个 Tenant,拥有角色权限 | + +> **ChannelIdentity vs ChannelAccount 区分**: +> - `ChannelIdentity` 是“**人是谁**”在渠道上的映射,比如张三是企微用户 `wxf1xxx`、个微 `wxid_abc`。 +> - `ChannelAccount` 是“**用哪个号收发**”的载体,比如用企微员工号 `ZhangSan` 收消息、用 pywechat 个微 `wxid_bot01` 发消息、用钉钉机器人发 webhook。 +> - 一个 Person 可以有多个 Identity;一个 ChannelAccount 可能被多个人通过会话间接关联,但账号本身属于渠道主体。 + +## 2.3 首期 1851 核心表设计 + +### 2.3.1 租户/业务系统表 `mci_tenant` + +> **租户即业务系统**,一个业务系统对应一个租户,作为中台资源和账号的顶层隔离单位。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 租户编码,与业务系统编码一致,如 `contract`、`supplier`、`order`、`rescue` | +| tenant_name | varchar | 租户名称 | +| auth_config | json | 业务系统调用中台时的认证配置 | +| inbound_webhook_url | varchar | 入站消息推送地址 | +| status | tinyint | 状态 | +| create_time | datetime | 创建时间 | + +### 2.3.2 联系人表 `mci_person` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| person_code | varchar | 业务人员编码 | +| person_name | varchar | 姓名 | +| phone | varchar | 手机号 | +| person_type | tinyint | 类型:1内部人员 2供应商人员 3客户 4司机 | +| institution_id | bigint | 所属机构/公司 | +| supplier_id | bigint | 供应商 ID | +| status | tinyint | 状态 | +| create_time | datetime | 创建时间 | +| update_time | datetime | 更新时间 | + +### 2.3.3 渠道主体表 `mci_channel_subject` + +> 由 `mci_wecom_subject` 扩展而来,支持企微、钉钉、飞书多个渠道主体。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户,为空表示平台级共享主体 | +| channel_type | varchar | 渠道类型:`wecom` / `dingtalk` / `feishu` | +| subject_code | varchar | 主体编码 | +| subject_name | varchar | 主体名称 | +| corp_id | varchar | 企微 corp_id(企微用) | +| corp_secret | varchar | 通讯录 secret(企微用) | +| agent_id | varchar | 应用 agent_id(企微用) | +| agent_secret | varchar | 应用 secret(企微用) | +| session_archive_enabled | tinyint | 是否开通会话存档能力 | +| session_archive_private_key | text | 会话存档 RSA 私钥 | +| status | tinyint | 状态 | +| create_time | datetime | 创建时间 | + +### 2.3.4 渠道身份表 `mci_channel_identity` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户 | +| person_id | bigint | 联系人 ID | +| channel_type | varchar | 渠道:wecom / wechat_personal / dingtalk / feishu | +| channel_user_id | varchar | 渠道用户 ID | +| channel_user_name | varchar | 渠道昵称 | +| subject_id | bigint | 所属渠道主体 `mci_channel_subject` | +| status | tinyint | 状态 | +| ext_info | json | 渠道扩展信息 | +| create_time | datetime | 创建时间 | + +> 说明:`subject_id` 对企微、钉钉、飞书都有意义,指向 `mci_channel_subject`;个微无主体概念时可为空。 + +### 2.3.5 渠道账号表 `mci_channel_account` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户 | +| account_type | tinyint | 1企微内部账号 2企微应用 3pywechat 个人号 4钉钉机器人 5飞书机器人 | +| channel_type | varchar | wecom / wechat_personal / dingtalk / feishu | +| subject_id | bigint | 所属渠道主体 `mci_channel_subject` | +| account_id | varchar | 账号唯一标识:企微员工=userid,企微应用=agent_id,个微=wxid/bot_code,机器人=robot_id | +| account_name | varchar | 账号名称 | +| session_archive_enabled | tinyint | 是否开启会话存档(企微内部账号用) | +| server_host | varchar | 所在服务器(pywechat 用) | +| login_status | tinyint | 登录状态 | +| online_status | tinyint | 在线状态 | +| risk_level | tinyint | 风险等级 | +| ext_info | json | 渠道扩展字段 | +| status | tinyint | 状态 | +| last_heartbeat | datetime | 最后心跳 | +| create_time | datetime | 创建时间 | + +`ext_info` 示例: + +```json +// 企微内部账号 +{ + "userid": "ZhangSan", + "department": [1, 2] +} + +// 企微应用 +{ + "agent_id": "1000002", + "agent_secret": "..." +} + +// pywechat 个人号 +{ + "windows_client_id": "client-01", + "wechat_version": "3.9.10", + "qr_code_url": "...", + "max_daily_send": 200 +} + +// 钉钉机器人 +{ + "webhook_url": "https://oapi.dingtalk.com/robot/send?access_token=xxx", + "secret": "SECxxx" +} + +// 飞书机器人 +{ + "webhook_url": "https://open.feishu.cn/open-apis/bot/v2/hook/xxx", + "secret": "..." +} +``` + +### 2.3.6 会话表 `mci_conversation` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户 | +| conversation_type | tinyint | 1单聊 2群聊 | +| channel_type | varchar | wecom / wechat_personal / dingtalk / feishu | +| channel_conversation_id | varchar | 渠道会话 ID | +| owner_account_id | bigint | 群主/所有者账号 | +| subject_id | bigint | 所属渠道主体 | +| conversation_name | varchar | 会话名称 | +| create_source | tinyint | 来源:1渠道同步 2API 创建 3手动创建 | +| create_task_id | bigint | 建群任务 ID | +| institution_id | bigint | 关联机构 | +| supplier_id | bigint | 关联供应商 | +| contract_id | bigint | 关联合同 | +| channel_code | varchar | 业务渠道编码 | +| receive_config | json | 收消息配置 | +| status | tinyint | 状态 | +| create_time | datetime | 创建时间 | + +### 2.3.7 会话参与者表 `mci_conversation_participant` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| conversation_id | bigint | 会话 ID | +| person_id | bigint | 人员 ID | +| channel_identity_id | bigint | 渠道身份 ID | +| participant_type | tinyint | 1成员 2群主 3管理员 | +| join_time | datetime | 加入时间 | + +### 2.3.8 账号-会话绑定表 `mci_channel_account_conversation` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| account_id | bigint | 渠道账号 ID | +| conversation_id | bigint | 会话 ID | +| binding_type | tinyint | 绑定类型:1发送主号 2发送备用号 3接收主号 4接收备用号 | +| sort_order | int | 优先级 | +| status | tinyint | 状态 | + +> 单聊约束:同一单聊会话中,收发账号的 `channel_type` 必须一致。 + +### 2.3.9 建群任务表 `mci_conversation_create_task` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户 | +| task_type | tinyint | 1创建群聊 2邀请成员 | +| channel_type | varchar | 渠道 | +| account_id | bigint | 使用哪个账号创建 | +| conversation_id | bigint | 创建成功后回填 | +| participant_ids | json | 参与者人员 ID 列表 | +| task_status | tinyint | 0待执行 1执行中 2成功 3失败 | +| channel_result | json | 渠道返回结果 | +| retry_count | int | 重试次数 | +| create_time | datetime | 创建时间 | +| finish_time | datetime | 完成时间 | + +### 2.3.10 标签表 `mci_tag` + +> 标签支持树形层级,可表达角色、合同、机构、供应商、业务自定义等属性,替代原人员角色表。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户 | +| parent_id | bigint | 父标签 ID,0 表示根标签 | +| tag_path | varchar | 标签路径,如 `销售/销售一部/华北组` | +| level | tinyint | 层级 | +| tag_name | varchar | 标签名 | +| tag_source | varchar | 来源:system / business_sync / manual | +| status | tinyint | 状态 | +| create_time | datetime | 创建时间 | + +### 2.3.11 人员标签绑定表 `mci_person_tag_binding` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户 | +| tag_id | bigint | 标签 ID | +| person_id | bigint | 联系人 ID | +| create_time | datetime | 创建时间 | + +### 2.3.12 消息事件表 `mci_message_event` + +> 会话存档消息不进独立表,通过 `message_source=session_archive` 标识。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| event_id | varchar | 事件唯一 ID | +| tenant_code | varchar | 所属租户/业务系统 | +| channel_type | varchar | 渠道 | +| channel_subject_id | bigint | 渠道主体 | +| direction | tinyint | 方向:1入站 2出站 | +| event_type | varchar | 事件类型:message / callback / status / keypress | +| message_source | varchar | 来源:`realtime` / `session_archive` | +| conversation_id | bigint | 会话 ID | +| person_id | bigint | 联系人 ID | +| account_id | bigint | 发送/接收账号 | +| content | json | 内容:`{"type": "text", "body": "..."}` | +| raw_payload | json | 渠道原始报文 | +| business_context | json | 业务上下文 | +| is_processed | tinyint | 是否已处理 | +| status | tinyint | 状态 | +| create_time | datetime | 创建时间 | + +**唯一索引**:`event_id` 全局唯一,保证幂等。 + +**分区方案**:按 `create_time` RANGE 月分区,核心索引: +- `(tenant_code, message_source, create_time)` +- `(conversation_id, create_time)` +- `(person_id, create_time)` +- `(account_id, create_time)` + +### 2.3.13 媒体文件表 `mci_message_media` + +> 媒体文件存 SeaweedFS,通过 S3 协议访问。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 租户 | +| msgid | varchar | 关联的消息 ID | +| sdkfileid | varchar | 企微媒体文件 ID | +| media_type | varchar | image / voice / video / file / emotion 等 | +| file_name | varchar | 文件名 | +| file_size | bigint | 文件大小 | +| md5sum | varchar | MD5 | +| storage_type | tinyint | 2SeaweedFS | +| bucket | varchar | SeaweedFS bucket | +| object_key | varchar | SeaweedFS object key | +| access_url | varchar | 访问 URL | +| status | tinyint | 0未下载 1下载中 2已下载 3下载失败 | +| create_time | datetime | 创建时间 | + +### 2.3.14 发送记录表 `mci_send_record` + +> 原发送任务表与发送日志表合并。一次发送请求产生一个 `task_id`,每个目标(人/群)一条记录。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| task_id | varchar | 发送任务 ID,一次 API 调用一个 task_id | +| tenant_code | varchar | 所属租户/业务系统 | +| target_type | varchar | 目标类型:person / person_list / conversation / tag / rule | +| target_value | json | 目标值:当 target_type 为 person/conversation/tag 时,存放具体 ID 列表 | +| audience_rule | json | 受众规则:当 target_type 为 rule 时,存放标签/属性组合条件 | +| conversation_id | bigint | 实际发送到的会话 ID | +| person_id | bigint | 实际发送到的联系人 ID | +| account_id | bigint | 实际使用的发送账号 ID | +| channel_type | varchar | 实际使用的渠道 | +| channel_strategy | json | 渠道策略 | +| content_type | varchar | 内容类型:text / markdown / image / file / voice / video / news / mpnews / textcard / template_card / miniprogram | +| content_body | text | 实际发送内容 | +| callback_url | varchar | 发送结果回调地址(任务级) | +| send_status | tinyint | 发送状态:0待发送 1发送中 2成功 3失败 | +| channel_result | json | 渠道返回结果 | +| send_time | datetime | 实际发送时间 | +| finish_time | datetime | 最终完成时间 | +| create_time | datetime | 创建时间 | + +**唯一索引**:`(task_id, conversation_id, person_id)` 保证同一任务对同一目标幂等。 + +**分区方案**:按 `create_time` RANGE 月分区,索引: +- `(tenant_code, task_id)` +- `(tenant_code, create_time)` +- `(conversation_id, create_time)` + +### 2.3.15 会话标签绑定表 `mci_conversation_tag_binding` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户 | +| tag_id | bigint | 标签 ID | +| conversation_id | bigint | 会话 ID | +| create_time | datetime | 创建时间 | + +### 2.3.16 中台用户表 `mci_user` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户 | +| username | varchar | 用户名 | +| password | varchar | 密码(加密存储) | +| real_name | varchar | 真实姓名 | +| phone | varchar | 手机号 | +| email | varchar | 邮箱 | +| status | tinyint | 状态 | +| create_time | datetime | 创建时间 | + +### 2.3.17 平台角色表 `mci_platform_role` + +> 中台自身权限体系,首期只保留 3 个角色。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| role_code | varchar | 角色编码 | +| role_name | varchar | 角色名称 | +| permissions | json | 权限列表 | +| status | tinyint | 状态 | + +**首期角色**: + +| 角色编码 | 角色名称 | 权限范围 | +|---------|---------|---------| +| admin | 租户管理员 | 渠道资源、账号、策略、发送记录等全部权限 | +| operator | 运营操作员 | 发送、查看发送记录、配置渠道账号 | +| viewer | 只读用户 | 查看发送记录、统计 | + +### 2.3.18 用户角色关系表 `mci_user_role` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| user_id | bigint | 用户 ID | +| role_id | bigint | 角色 ID | +| create_time | datetime | 创建时间 | + +## 2.4 关键业务规则 + +| 规则 | 说明 | +|------|------| +| 一人多身份 | 一个 Person 可以有多个 ChannelIdentity,但同一渠道+主体下唯一 | +| 一人多标签 | 一个 Person 可以有多个 Tag,标签支持层级,可表达角色/合同/机构/供应商 | +| 会话唯一 | 同一渠道+主体+channel_conversation_id 唯一 | +| 收发主备 | 一个会话可绑定发送主备号、接收主备号;群聊允许跨渠道收发,单聊要求 channel_type 一致 | +| 标签来源 | 业务标签可由业务系统同步或中台管理;渠道标签由渠道同步 | +| 事件幂等 | MessageEvent 的 event_id 保证幂等,避免重复处理 | +| 群发拆解 | 按人员/标签发送时,拆解为多个单聊/单群发送记录 | +| 会话存档复用 | 会话存档消息不进独立表,`message_source=session_archive` | +| 中台不存模板 | 业务系统直接传入最终发送内容 | +| 发送结果回调 | 通过发送任务中的 callback_url 回传 | +| 租户隔离 | 各业务系统的账号、资源、日志完全隔离 | + +## 2.5 与现有表的映射 + +| 新表 | 可能复用的现有表/模块 | +|------|---------------------| +| mci_person | 业务系统人员表、供应商表、司机表 | +| mci_channel_identity (wecom) | `admin/supplier-manage` 企微 CP 外部联系人表 | +| mci_conversation (wecom) | 企微 CP 群聊相关表 | +| mci_channel_account (wechat_personal) | 新建,pywechat 账号池 | +| mci_message_event | 新建,统一事件 | +| mci_send_record | 参考 `BaseMsgInfo` 短信发送记录 | +| mci_tenant / mci_user / mci_platform_role | 新建,中台账号权限体系 | + +## 2.6 数据库选型与分表策略 + +基于 `sa-server` 现有技术栈,首期统一使用 **MySQL 8 + Redis + RabbitMQ**。 + +### 2.6.1 存储用途 + +| 数据类型 | 推荐存储 | 说明 | +|---------|---------|------| +| 关系型配置数据 | MySQL 8 | 人员、会话、账号、标签、策略、流程定义 | +| 消息事件 | MySQL 8 + RANGE 分区 | 单表统一存实时消息 + 会话存档,按时间分区 | +| 发送记录 | MySQL 8 + RANGE 分区 | 发送任务与日志合并,按时间分区 | +| 媒体元数据 | MySQL 8 | `mci_message_media` 存 `sdkfileid`、SeaweedFS key、状态 | +| 媒体文件 | SeaweedFS | 企微会话存档媒体文件下载后存 SeaweedFS,S3 协议访问 | +| 缓存 / 限频 / 状态 | Redis 7 | 账号在线、会话上下文、频率控制 | +| 事件总线 | RabbitMQ | 项目已有 `common-mq` | + +### 2.6.2 分区方案 + +| 表 | 分区键 | 分区方式 | 索引 | +|---|--------|---------|------| +| `mci_message_event` | `create_time` | RANGE 月分区 | `(tenant_code, message_source, create_time)`、`(conversation_id, create_time)`、`(person_id, create_time)`、`(account_id, create_time)` | +| `mci_send_record` | `create_time` | RANGE 月分区 | `(tenant_code, task_id)`、`(tenant_code, create_time)`、`(conversation_id, create_time)` | +| `mci_conversation` | `create_time` | RANGE 月分区(可选) | `(tenant_code, channel_conversation_id)`、`(tenant_code, create_time)` | + +### 2.6.3 物理分表边界 + +首期不分库分表,仅做 MySQL 分区。当满足以下条件时,再考虑物理分表/分库: + +| 触发条件 | 方案 | +|---------|------| +| 单表数据过亿 | 按 `tenant_code` 分库分表 | +| 单个租户数据量过大 | 大租户独立实例 | +| 会话存档占满磁盘/拖慢实时查询 | 会话存档独立集群或冷备 | + +### 2.6.4 消息来源与表的关系 + +- `mci_message_event` 统一存所有消息事件; +- `message_source` 区分 `realtime`(实时)和 `session_archive`(会话存档); +- 不做历史导入表,历史导入按 `session_archive` 处理或首期不支持。 + +## 2.7 账号体系说明 + +### 中台账号体系 + +中台为每个业务系统创建一个租户(`mci_tenant`),租户下可创建多个中台用户(`mci_user`)。 + +**开通流程**: + +``` +平台管理员创建租户 rescue + │ + ▼ +创建租户主账号(admin 角色) + │ + ▼ +业务系统通过 API 创建子账号 + │ + ▼ +子账号登录中台管理后台 +``` + +**子账号创建接口**: + +```json +POST /msg-platform/api/v1/account/user +Headers: X-Tenant-Code: rescue, X-Admin-Token: xxx + +{ + "username": "operator01", + "password": "xxx", + "real_name": "运营一号", + "phone": "13800138000", + "role_codes": ["operator"] +} +``` + +### 渠道账号初始化 + +统一初始化接口: + +```json +POST /msg-platform/api/v1/channel-account/init +Headers: X-Tenant-Code: rescue + +{ + "channel_type": "wecom", + "subject_id": 1, + "account_type": 1, + "config": { + "userid": "ZhangSan" + } +} +``` + +#### 企微内部员工账号 / 应用 + +| 步骤 | 说明 | +|------|------| +| 1. 创建渠道主体 | 在 `mci_channel_subject` 录入 `corp_id`、`corp_secret`、`agent_id`、`agent_secret` | +| 2. 初始化账号 | 调用 `/v1/channel-account/init`,指定 `channel_type=wecom`、`account_type=1/2` | +| 3. 同步通讯录 | 从企微拉取员工/部门,写入 `mci_person` + `mci_channel_identity` | +| 4. 开启会话存档 | 在企微管理后台操作,中台标记 `session_archive_enabled=1` | +| 5. 入群 | 确保企微员工在需要收消息的群里 | + +#### pywechat 个人微信号 + +| 步骤 | 说明 | +|------|------| +| 1. 录入账号 | 在 `mci_channel_account` 录入微信号、昵称、所属服务器 | +| 2. 触发登录 | 调用 `/v1/channel-account/init`,channel_type=wechat_personal,返回二维码 | +| 3. 扫码授权 | 人工扫码后,pywechat 客户端上线,更新 `online_status=1` | +| 4. 心跳监控 | pywechat 定时上报心跳,超时下线告警 | +| 5. 加群绑定 | 把个人号拉进业务群,维护 `mci_channel_account_conversation` 发送主备 | +| 6. 风控记录 | 记录发送量、加群数、封号状态 | + +#### 钉钉 / 飞书机器人 + +| 步骤 | 说明 | +|------|------| +| 1. 创建机器人 | 在钉钉/飞书群里添加机器人,获取 Webhook URL 和 secret | +| 2. 初始化账号 | 调用 `/v1/channel-account/init`,channel_type=dingtalk/feishu,config 中传 webhook + secret | +| 3. 回调配置 | 在钉钉/飞书开发者后台配置回调 URL,指向中台适配器 | + +#### 已有群聊同步 + +```json +POST /msg-platform/api/v1/channel-account/{account_id}/sync-conversations +``` + +抓取该账号所在的所有群聊,导入 `mci_conversation` 和 `mci_conversation_participant`,并自动绑定该账号为默认发送/接收账号。 + +--- + +*第一节、第二节已按 v0.6 更新。* + +--- + +# 第三节:消息编排层详细设计 + +## 3.1 编排层定位 + +消息编排层是中台的核心,承上启下: + +- **向上**:为业务系统提供统一的发送接口; +- **向下**:调用渠道适配器完成实际收发; +- **向内**:实现入站消息归一化、LLM 意图识别、webhook 推送、出站目标解析与渠道调度、策略执行; +- **向外**:仅通过 webhook/回调与业务系统交互,不主动调用业务系统能力接口。 + +它让业务系统**只关心“要做什么”和“发给谁(业务语义)”**,而不关心**“通过哪个渠道、哪个账号发、怎么回收结果”**。 + +**范围约束**: +- 中台不承载业务系统的“触发条件 + 发送规则”配置; +- 中台不保存模板和变量; +- 出站发送由业务系统直接调用中台 API 触发; +- 入站消息做归一化、上下文匹配、轻量流程编排(意图识别/条件分支/webhook/自动回复),由业务系统自行处理业务逻辑。 + +## 3.2 整体架构 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 业务系统 │ +│ supplier-manager order contract crm ... │ +│ 模板/变量/触发规则/业务能力 均由业务系统自行维护 │ +└─────────────────────────────┬───────────────────────────────────────────┘ + │ ① 调用发送 API ② 接收中台入站 webhook + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ 消息编排层(Message Orchestration) │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 入站处理管线 │ │ +│ │ │ │ +│ │ 渠道回调/Webhook ──► 接收适配器 ──► 事件归一化 ──► 上下文匹配 │ │ +│ │ │ │ +│ │ ▼ │ │ +│ │ ┌─────────────┐ │ │ +│ │ │ 意图识别引擎 │ ← LLM / 规则 │ │ +│ │ └──────┬──────┘ │ │ +│ │ ▼ │ │ +│ │ 推送业务系统入站 webhook URL │ │ +│ │ (包含原始消息 + 上下文 + 意图识别结果) │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 出站调度管线 │ │ +│ │ │ │ +│ │ 业务系统调用发送 API │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ 目标解析 ──► 精准定位引擎 ──► 渠道选择 ──► 发送节点 │ │ +│ │ │ │ +│ │ ▼ │ │ +│ │ 策略执行引擎(频率/时间窗/黑名单/重试) │ │ +│ │ │ │ +│ │ ▼ │ │ +│ │ 渠道适配器 ──► 实际发送 │ │ +│ │ │ │ +│ │ 发送结果:同步返回 / 任务级 callback_url 回调 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ 共享能力 │ │ +│ │ │ │ +│ │ 统一内容格式 │ 事件总线 │ 缓存/限流 │ 重试/死信 │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## 3.3 统一事件模型 + +所有进出编排层的事件都统一为 `MessageEvent`: + +```json +{ + "event_id": "evt_20260701120000_001", + "tenant_code": "order", + "channel_type": "wecom", + "channel_subject_id": 1, + "direction": "in", + "event_type": "message", + "message_source": "realtime", + "conversation_id": 1001, + "person_id": 2001, + "account_id": 3001, + "content": { + "type": "text", + "body": "我要下单,客户张三,地址北京" + }, + "business_context": { + "contract_id": "C001", + "supplier_id": "S001", + "institution_id": "I001", + "tag": "dispatcher" + }, + "raw_payload": { }, + "is_processed": 0, + "status": 1, + "create_time": "2026-07-01T12:00:00" +} +``` + +事件类型: + +| event_type | 说明 | +|-----------|------| +| message | 普通消息 | +| callback | 渠道回调(加好友、进群、状态变更) | +| status | 消息状态回执(已送达、已读) | +| keypress | IVR 按键事件(预留) | +| command | 业务系统指令(已废弃,触发规则由业务系统自行控制) | + +## 3.4 入站处理管线 + +### 流程图 + +``` +渠道回调 / Webhook + │ + ▼ +┌─────────────┐ +│ 接收适配器 │ ← 渠道适配层 +│ 校验/解密 │ +└──────┬──────┘ + ▼ +┌─────────────┐ +│ 事件归一化 │ ← 转成标准 MessageEvent +└──────┬──────┘ + ▼ +┌─────────────┐ +│ 上下文匹配 │ ← 会话/人员 → 合同/供应商/机构/角色 +└──────┬──────┘ + ▼ +┌─────────────────────────────┐ +│ 入站流程编排 │ +│ intent → condition → │ +│ webhook / reply │ +│ (按租户配置的轻量流程) │ +└─────────────────────────────┘ + │ + ▼ + 业务系统处理 / 自动回复 + │ + ▼ + 业务系统回呼中台 /v1/send 发送回复 +``` + +### 入站 webhook 推送示例 + +```json +POST {business_system_inbound_webhook_url} +Headers: X-Msg-Platform-Signature: xxx + +{ + "event_id": "evt_20260703120000_001", + "tenant_code": "rescue", + "channel_type": "wecom", + "direction": "in", + "event_type": "message", + "conversation_id": 1001, + "person_id": 2001, + "account_id": 3001, + "content": { + "type": "text", + "body": "我要下单,客户张三,地址北京" + }, + "business_context": { + "contract_id": "C001", + "supplier_id": "S001", + "institution_id": "I001", + "tag": "dispatcher" + }, + "create_time": "2026-07-03T12:00:00" +} +``` + +## 3.5 出站调度管线 + +> 说明:出站发送由业务系统直接调用中台 API 触发,中台不维护“业务事件 → 发送”的触发规则。 + +### 流程图 + +``` +业务系统判断触发条件和业务规则 + │ + ▼ +业务系统组装发送请求(目标 + 内容/模板变量) + │ + ▼ +┌─────────────────────────┐ +│ 调用中台 /send 或 /send/preview │ +└─────────────────────────┘ + │ + ▼ +┌─────────────┐ +│ 发送规模预览 │ ← 可选,先估算影响 +└──────┬──────┘ + │ + 确认发送 + ▼ +┌─────────────┐ +│ 取渠道策略 │ +└──────┬──────┘ + ▼ +┌─────────────┐ +│ 目标解析 │ ← person / tag / rule / conversation +└──────┬──────┘ + ▼ +┌─────────────┐ +│ 精准定位 │ ← 解析成具体人员/会话列表 +└──────┬──────┘ + ▼ +┌─────────────┐ +│ 渠道选择 │ ← 按策略选渠道 +└──────┬──────┘ + ▼ +┌─────────────┐ +│ 策略执行 │ ← 频率/黑名单/时间窗/重试 +└──────┬──────┘ + ▼ +┌─────────────┐ +│ 渠道发送 │ ← 调用渠道适配器 +└──────┬──────┘ + ▼ +┌─────────────┐ +│ 结果回收 │ ← 记录发送记录 +└─────────────┘ +``` + +## 3.6 意图识别引擎 + +中台对入站消息做 LLM / 规则意图识别,把识别结果随 webhook 一起推送给业务系统。 + +### 两种方式 + +| 方式 | 适用场景 | 说明 | +|------|---------|------| +| 规则匹配 | 意图明确、格式固定 | 关键词、正则、模板匹配 | +| LLM 识别 | 意图复杂、口语化 | 调用大模型,输出结构化意图 | + +### 意图输出格式 + +```json +{ + "intent": "create_order", + "confidence": 0.95, + "slots": { + "customer_name": "张三", + "address": "北京", + "phone": "" + }, + "missing_slots": ["phone"], + "requires_confirmation": true +} +``` + +**首期 1851 建议**:先用规则匹配覆盖主要场景(下单、查状态、确认、投诉),复杂场景引入 LLM 作为补充。 + +## 3.7 入站消息与出站发送的边界 + +| 职责 | 业务系统 | 中台 | +|------|---------|------| +| 模板和变量 | ✅ 自行维护 | ❌ 不参与 | +| 触发条件和业务规则 | ✅ 自行判断 | ❌ 不参与 | +| 内容渲染 | ✅ 传入最终内容 | ❌ 不参与 | +| 意图识别 | 接收识别结果 | ✅ LLM / 规则识别 | +| 目标人群解析 | 提供业务语义 | ✅ 解析成具体渠道账号/群 | +| 渠道选择 | ❌ | ✅ | +| 发送执行 | ❌ | ✅ | +| 入站消息处理 | ✅ 接收 webhook 后自行处理 | ✅ 归一化、意图识别、推送 | +| 发送结果回收 | 接收 callback_url 回调 | ✅ 统一回收并回调 | + +> IVR 外呼/呼入流程作为独立流程在中台配置,不依赖业务系统能力调用。2092 阶段详细设计。 + +## 3.8 精准定位引擎 + +把业务语义转换成具体接收人: + +| 输入类型 | 示例 | 输出 | +|---------|------|------| +| 人员 ID | `person_id=1001` | 该人员的所有可用渠道身份 | +| 标签 | `tag=培训未完成` | 打标签的人员列表 | +| 规则 | `tag=调度 AND contract=C001` | 匹配的人员列表 | +| 会话 | `conversation_id=2001` | 该会话的所有参与者 | + +规则表达式示例: + +```json +{ + "rule": { + "and": [ + {"field": "tag", "op": "eq", "value": "dispatcher"}, + {"field": "contract_id", "op": "in", "value": ["C001", "C002"]}, + {"field": "supplier.is_subsidiary", "op": "eq", "value": true} + ] + } +} +``` + +## 3.9 业务系统调用中台 + +### 3.9.1 发送接口 + +```json +POST /msg-platform/api/v1/send +Headers: X-Tenant-Code: rescue + +{ + "target": { + "type": "rule", + "rule": { + "tag": "dispatcher", + "contract_id": "C001" + } + }, + "content": { + "type": "text", + "body": "技师姓名:张师傅\n技师电话:13800138000\n预计到达时间:30分钟" + }, + "channel_strategy": { + "priority": ["wechat_personal"] + }, + "callback_url": "https://rescue.sino.com/api/msg-platform/callback" +} +``` + +### 3.9.2 发送规模预览接口 + +```json +POST /msg-platform/api/v1/send/preview +Headers: X-Tenant-Code: rescue + +{ + "target": { + "type": "rule", + "rule": { + "tag": "dispatcher", + "contract_id": "C001" + } + }, + "channel_strategy": { + "priority": ["wechat_personal"] + } +} +``` + +返回: + +```json +{ + "total_person_count": 156, + "channel_breakdown": { + "wechat_personal": 150, + "unreachable": 6 + }, + "account_usage": { + "wx_bot_01": 80, + "wx_bot_02": 70 + }, + "policy_check": { + "rate_limit_warning": "wx_bot_01 今日已发送 90 条,再发 80 条将接近上限", + "time_window_ok": true, + "blacklist_hit": 0 + }, + "estimated_time_seconds": 180, + +} +``` + +## 3.10 发送结果回调 + +| 方式 | 触发时机 | 说明 | +|------|---------|------| +| 同步返回 | 发送 API 调用后立即返回 | 仅表示中台接收成功 | +| 异步回调 | 渠道返回最终状态后 | 通过发送记录中的 callback_url 回传 | +| 主动查询 | 业务方按需查询 | 通过对账接口查询 | + +> 说明:不维护固定 webhook 注册,每次发送记录自带 callback_url。 + +异步回调示例: + +```json +POST https://rescue.sino.com/api/msg-platform/callback + +{ + "task_id": "task_001", + "conversation_id": 1001, + "person_id": 2001, + "channel_type": "wechat_personal", + "event": "delivered", + "status": 1, + "channel_result": { }, + "event_time": "2026-07-03T12:01:00" +} +``` + +## 3.11 策略执行引擎 + +### 策略类型 + +| 策略 | 说明 | +|------|------| +| 频率控制 | 单账号/单用户/单群每日/每小时发送上限 | +| 时间窗口 | 只允许在指定时段发送 | +| 黑名单 | 禁止向指定人员/群发送 | +| 免打扰 | 用户设置免打扰时段 | +| 重试 | 发送失败后按策略重试 | +| 主备切换 | 账号掉线时切换备用账号 | +| 优先级 | 重要消息优先发送 | +| 并发控制 | 限制同时发送数量 | + +### 策略配置示例 + +```json +{ + "policy_group_code": "default", + "tenant_code": "supplier-manager", + "policies": [ + { + "type": "rate_limit", + "scope": "account", + "limit": 100, + "window": "1h" + }, + { + "type": "time_window", + "allow": ["09:00-21:00"] + }, + { + "type": "blacklist", + "person_ids": [1001, 1002] + }, + { + "type": "retry", + "max_retry": 3, + "interval": [5, 10, 30] + } + ] +} +``` + +## 3.12 按租户/业务系统隔离 + +每个业务系统(租户)在消息中台有独立的策略、渠道配置、账号、发送记录;模板和触发规则由业务系统自行维护,不在中台配置: + +| 资源 | 隔离字段 | 说明 | +|------|---------|------| +| 策略组 | `tenant_code` | 中台页面配置 | +| 渠道配置 | `tenant_code`(可选,为空则平台共享) | 中台页面配置 | +| 中台用户 | `tenant_code` | 中台账号体系 | +| 发送记录 | `tenant_code` | 按租户隔离 | +| 模板 | ❌ 不在中台 | 业务系统自行维护 | +| 触发规则 | ❌ 不在中台 | 业务系统自行维护 | + +### 策略组表 `mci_policy_group` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户/业务系统 | +| group_code | varchar | 策略组编码 | +| group_name | varchar | 策略组名称 | +| policies | json | 策略规则 JSON | +| status | tinyint | 状态 | +| create_time | datetime | 创建时间 | + +## 3.13 企微应用消息推送 + +中台支持企微应用消息发送,内容格式对齐企微 API。业务系统直接传入已渲染内容,中台按企微格式组装: + +| 企微消息类型 | 说明 | +|------------|------| +| 文本消息 | text | +| Markdown | markdown | +| 图片 | image | +| 图文 | news / mpnews | +| 文件 | file | +| 语音 | voice | +| 视频 | video | +| 文本卡片 | textcard | +| 模板卡片 | template_card | +| 小程序 | miniprogram | + +发送对象:指定用户、指定部门、指定标签、群发。 + +## 3.14 supplier-manager 解耦 + +| 职责 | supplier-manager | 消息中台 | +|------|-----------------|----------| +| 内容管理 | ✅ | ❌ | +| 模板和变量 | ✅ | ❌ | +| 触发规则 | ✅ | ❌ | +| 发送执行 | ❌ | ✅ | +| 发送记录/统计 | ❌ | ✅ | +| 渠道账号管理 | ❌ | ✅ | + +supplier-manager 自行判断触发条件、渲染内容,通过中台发送接口发起发送,中台负责实际执行。 + +## 3.15 错误处理与重试 + +### 错误分类 + +| 错误类型 | 示例 | 处理方式 | +|---------|------|---------| +| 渠道临时错误 | 网络超时、企微频率限制 | 按策略重试 | +| 渠道永久错误 | 账号被封、群不存在 | 标记失败,切备用,告警 | +| 业务系统入站处理错误 | 业务系统 webhook 处理失败 | 记录日志,中台不主动重试入站推送 | +| 中台内部错误 | 数据缺失、规则异常 | 入死信队列,人工处理 | + +### 重试队列 + +``` +发送失败 + │ + ▼ +┌─────────────┐ +│ 延迟队列 │ ← 按重试策略等待 +└──────┬──────┘ + ▼ +┌─────────────┐ +│ 重试发送 │ +└──────┬──────┘ + │ + 成功 ←──── 失败 + │ + ▼ + ┌─────────────┐ + │ 超过重试次数 │ + └──────┬──────┘ + ▼ + ┌─────────────┐ + │ 死信队列 │ ← 人工介入 + └─────────────┘ +``` + +### 幂等保障 + +- `MessageEvent.event_id` 全局唯一; +- 发送任务使用 `task_id + conversation_id + person_id` 幂等。 + +## 3.16 第三节补充数据表 + +### 3.16.1 策略组表 + +见 3.12 `mci_policy_group`。 + +### 3.16.2 流程定义表 `mci_flow_definition` + +> 用于入站消息处理流程编排、IVR 等独立流程。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | bigint | 主键 | +| tenant_code | varchar | 所属租户/业务系统 | +| flow_code | varchar | 流程编码 | +| flow_name | varchar | 流程名称 | +| flow_type | tinyint | 流程类型:1入站消息流程 2IVR 流程 3其他独立流程 | +| version | int | 版本号 | +| definition_json | json | 工作流 JSON 定义 | +| status | tinyint | 0草稿 1已发布 2已下线 | +| publish_time | datetime | 发布时间 | +| create_time | datetime | 创建时间 | + +## 3.17 入站消息流程编排(轻量画布) + +首期 1851 需要在前端通过轻量画布配置入站消息的处理流程。流程节点简单、固定类型,不比 IVR 流程复杂。 + +### 3.17.1 节点类型 + +| 节点类型 | 职责 | 配置项 | +|---------|------|--------| +| `receive` | 接收消息事件 | 指定来源渠道/会话标签/账号 | +| `intent` | LLM / 规则意图识别 | 选择识别方式、配置提示词/规则 | +| `condition` | 条件分支 | 基于意图、标签、发送人、会话属性判断 | +| `webhook` | 推送到业务系统 | 选择租户 webhook 或自定义 URL、签名方式 | +| `reply` | 自动回复(可选) | 固定回复内容或调用发送接口 | + +### 3.17.2 示例流程 + +``` +[receive: 企微会话存档消息] + │ + ▼ +[intent: LLM 识别意图] + │ + ▼ +[condition: 意图 == "create_order"] + │ yes + ▼ +[webhook: 推送到 order 业务系统] + │ + ▼ +[reply: "已收到,正在为您处理…"] +``` + +### 3.17.3 执行方式 + +- 流程定义以 JSON 形式保存在 `mci_flow_definition.definition_json`; +- 发布时加载到内存,入站消息按租户匹配已发布的流程; +- 流程执行由编排层统一调度,节点按顺序/分支执行; +- 错误节点统一进入死信队列,人工处理。 + +### 3.17.4 与 IVR 流程的关系 + +| 对比项 | 入站消息流程 | IVR 流程 | +|--------|-------------|---------| +| 触发源 | 渠道消息事件 | 电话呼入/外呼事件 | +| 节点复杂度 | 低,固定 5 种节点 | 高,支持语音、按键、转人工等 | +| 画布实现 | Vue Flow 轻量画布 | Vue Flow 完整画布 | +| 执行引擎 | 编排层内置轻量执行器 | 自研 Graph-State DAG 执行器 | + +--- + +*第三节已按 v0.7 更新:新增入站消息流程编排设计。* + +--- + +# 第四节:技术选型 + +## 4.1 选型原则 + +1. **与现有技术栈兼容**:优先复用 `sa-server` 已有的 Java/Spring Boot/MySQL/Redis/RabbitMQ 能力; +2. **渠道适配灵活**:pywechat 个人微信控制必须走 Python,独立为渠道服务; +3. **轻量流程编排用于入站消息处理**:通过 Vue Flow 画布配置接收 → 意图识别 → 条件分支 → webhook 推送;IVR 等复杂流程使用自研 DAG 工作流引擎; +4. **LLM 可插拔**:通过 Spring AI 统一抽象,兼容 OpenAI 接口及腾讯云混元等国产模型; +5. **数据存储统一**:核心数据用 MySQL 8,消息归档通过分区表扩展,避免过早引入异构数据库; +6. **可观测可运维**:服务拆分清晰,监控、日志、告警配套。 + +## 4.2 后端技术栈 + +| 技术 | 用途 | +|------|------| +| **Java 21** | 主开发语言,使用 Virtual Threads 提升并发处理能力 | +| **Spring Boot 4.1.x** | 基础框架 | +| **Spring AI** | LLM 统一接入层,兼容 OpenAI 接口规范及腾讯云混元自定义适配 | +| **MyBatis Plus / JPA** | ORM 框架,持久化工作流、人员、会话、账号等数据 | +| **JGraphT** | DAG 图算法:拓扑排序、环检测、可达性分析 | +| **Spring SpEL** | 工作流条件表达式 | +| **Jackson** | JSON 序列化/反序列化 | +| **Quartz / Spring Scheduler** | 定时触发、超时处理 | +| **CompletableFuture / Virtual Threads** | 节点异步执行 | +| **weixin-java-cp** | 企微 CP API 封装 | +| **com.tencent.wework.Finance SDK** | 企微会话存档拉取与解密(C 库 JNI) | + +## 4.3 前端技术栈 + +| 技术 | 用途 | +|------|------| +| **Vue 3** | 前端框架 | +| **Element Plus** | UI 组件库 | +| **Vue Flow** | 工作流可视化画布(节点拖拽、连线、缩放) | +| **Monaco Editor** | Python/HTTP 脚本编辑 | +| **Ajv / JSON Schema** | 节点配置表单校验 | + +## 4.4 数据存储 + +| 数据 | 存储 | 说明 | +|------|------|------| +| 核心配置数据 | MySQL 8 | 人员、标签、会话、账号、工作流定义、账号权限等 | +| 消息事件 | MySQL 8 + RANGE 分区 | 实时消息 + 会话存档统一单表,按时间分区 | +| 发送记录 | MySQL 8 + RANGE 分区 | 发送任务与日志合并,按时间分区 | +| 媒体元数据 | MySQL 8 | `mci_message_media` 表 | +| 媒体文件 | SeaweedFS | 企微会话存档媒体文件下载后存 SeaweedFS | +| 缓存/状态 | Redis 7 | 账号在线状态、会话上下文、限流、频率控制 | +| 事件总线 | RabbitMQ | 异步事件流转 | + +## 4.5 基础设施 + +| 技术 | 用途 | +|------|------| +| **RabbitMQ** | 事件总线,解耦业务系统与中台 | +| **Redis** | 缓存、分布式锁、限流、状态存储 | +| **Nginx / Gateway** | API 网关、负载均衡 | +| **Prometheus + Grafana** | 监控告警 | +| **ELK / Loki** | 日志收集与分析 | + +## 4.6 pywechat 渠道服务 + +| 技术 | 用途 | +|------|------| +| **Python 3.11** | 开发语言 | +| **FastAPI** | Web 服务框架 | +| **pywechat / pywinauto** | 控制 PC 微信客户端 | +| **WebSocket / HTTP** | 与 Java 中台通信 | +| **PyInstaller** | 打包 Windows 安装包 | +| **原始 HTML + Web Components** | 客户端监控界面 | + +pywechat 服务部署在 Windows 服务器上,每台服务器运行多个微信 PC 客户端,由 pywechat 进程统一管理和调度。 + +## 4.7 工作流引擎自研组件(仅 IVR 等独立流程) + +| 组件 | 依赖/实现 | +|------|----------| +| DAG 执行器 | 自研 + JGraphT 图算法 | +| 状态持久化 | MyBatis Plus + Redis 缓存 | +| 节点注册机制 | Spring Bean 自动扫描注册 | +| 条件表达式 | Spring SpEL | +| 异步执行 | CompletableFuture / Virtual Threads | +| 事件唤醒 | RabbitMQ + 信号机制 | +| 前端画布 | Vue Flow | + +> 说明:首期 1851 聊天流程不依赖工作流引擎,直接通过 webhook 推送 + LLM 意图识别处理。 + +## 4.8 部署架构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 用户 / 管理员 │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Nginx / API 网关 │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐ +│ Java 消息中台 │ │ Vue 管理后台 │ │ Python 渠道服务 │ +│ (Spring Boot) │ │ │ │ (FastAPI) │ +└────────┬────────┘ └─────────────┘ └────────┬────────┘ + │ │ + │ ┌─────────────┐ │ + └────────►│ RabbitMQ │◄──────────┘ + │ Redis │ + │ MySQL 8 │ + └─────────────┘ + │ + ▼ + ┌─────────────────────┐ + │ Windows 服务器 │ + │ PC 微信客户端 x N │ + │ (pywechat 管理) │ + └─────────────────────┘ +``` + +## 4.9 技术栈总览 + +| 层级 | 技术 | +|------|------| +| 编排中台 | Java 21 + Spring Boot 4.1.x + Spring AI | +| 工作流引擎 | 自研 Graph-State DAG(JGraphT + SpEL + Vue Flow),首期仅用于 IVR 等独立流程 | +| LLM 接入 | Spring AI,兼容 OpenAI 接口规范,支持腾讯云混元 | +| 前端 | Vue 3 + Element Plus + Vue Flow | +| pywechat 服务 | Python 3.11 + FastAPI + PyInstaller | +| pywechat 客户端监控 | 原始 HTML + Web Components | +| 企微会话存档 | `weixin-java-cp` + `com.tencent.wework.Finance` SDK(C 库 JNI) | +| 媒体文件存储 | SeaweedFS(S3 协议) | +| 数据库 | MySQL 8 + Redis 7 | +| 消息队列 | RabbitMQ | +| 消息归档 | MySQL 8 RANGE 分区表,未来可扩展 PostgreSQL/MongoDB | +| 监控 | Prometheus + Grafana + ELK/Loki | + +## 4.10 关键不引入的技术 + +| 技术 | 不引入原因 | +|------|-----------| +| PostgreSQL(首期) | 团队熟悉 MySQL,避免异构数据库运维成本;消息归档通过 MySQL 分区表先支撑 | +| Dify / n8n / Langflow | 无法同时满足 IVR、渠道发送等需求,异构服务维护成本高 | +| Flowable / Camunda | BPMN 模型与 LLM/聊天流程不够贴合,且 IVR 节点扩展成本高 | +| Netflix Conductor | 维护活跃度下降,社区风险 | +| MongoDB(首期) | 消息量未明确前,先用 MySQL 分区表,避免过度设计 | + +--- + +*第四节已按 v0.6 更新。* + +--- + +# 第五节:中台对外 API 能力 + +> 范围说明:中台定位为消息发送网关 + 渠道资源管理中心 + 入站消息 LLM 意图识别。 +> - 不保存模板和变量; +> - 不主动调用业务系统能力接口; +> - 出站发送由业务系统直接调用 API 触发; +> - 入站消息做归一化、上下文匹配、LLM 意图识别、轻量流程编排,结果通过 webhook 推送给业务系统; +> - 发送结果通过任务级 `callback_url` 回传; +> - 中台页面用于配置渠道资源、账号权限、发送策略、入站 webhook、发送记录、IVR 流程。 + +## 5.1 API 设计原则 + +1. **按租户/业务系统隔离**:所有 API 均通过 `X-Tenant-Code` 识别来源; +2. **统一返回结构**:`{ code, message, data, request_id }`; +3. **幂等设计**:写操作支持幂等键; +4. **异步结果通过 callback_url 返回**:同步接口仅表示中台受理成功; +5. **权限分级**:中台用户按角色访问自己被授权的资源。 + +## 5.2 发送类 API + +| API | 用途 | +|-----|------| +| `POST /v1/send` | 通用发送接口(单聊/群聊/按标签/按规则/直接内容) | +| `POST /v1/send/preview` | 发送规模预览 | +| `POST /v1/send/{task_id}/cancel` | 取消发送任务 | +| `GET /v1/send/{task_id}/status` | 查询发送任务状态 | +| `POST /v1/send/{task_id}/records` | 查询发送记录明细 | +| `POST /v1/send/test` | 测试发送(模拟发送,不实际调用渠道) | + +### 5.2.1 发送接口示例 + +```json +POST /msg-platform/api/v1/send +Headers: X-Tenant-Code: rescue + +{ + "target": { + "type": "rule", + "rule": { + "tag": "rescue_dispatcher", + "contract_id": "C001" + } + }, + "content": { + "type": "text", + "body": "技师姓名:张师傅\n技师电话:13800138000\n预计到达时间:30分钟" + }, + "channel_strategy": { + "priority": ["wechat_personal"] + }, + "callback_url": "https://rescue.sino.com/api/msg-platform/callback" +} +``` + +### 5.2.2 测试发送接口 + +```json +POST /msg-platform/api/v1/send/test +Headers: X-Tenant-Code: rescue + +{ + "target": { + "type": "rule", + "rule": { + "tag": "rescue_dispatcher", + "contract_id": "C001" + } + }, + "content": { + "type": "text", + "body": "测试消息" + }, + "channel_strategy": { + "priority": ["wechat_personal"] + } +} +``` + +返回模拟的发送记录列表,**不实际调用渠道**,用于业务系统联调测试和发送逻辑验证。 + +## 5.3 客户关系类 API + +| API | 用途 | +|-----|------| +| `POST /v1/person/query` | 按条件查询联系人 | +| `GET /v1/person/{person_id}` | 查询联系人详情 | +| `POST /v1/person/create-or-update` | 创建或更新联系人 | +| `POST /v1/person/{person_id}/identities` | 查询某人的所有渠道身份 | +| `POST /v1/person/batch-bind-tags` | 批量打标签 | +| `POST /v1/person/batch-unbind-tags` | 批量解绑标签 | + +## 5.4 渠道资源类 API + +| API | 用途 | +|-----|------| +| `POST /v1/channel-account/init` | 初始化渠道账号(企微/个微/钉钉/飞书) | +| `POST /v1/channel-account/{account_id}/sync-conversations` | 同步该账号已有群聊 | +| `POST /v1/channel-account/wecom/qr-code` | 获取企微账号加好友二维码 | +| `POST /v1/channel-account/wechat-personal/qr-code` | 获取 pywechat 个人号登录二维码 | +| `GET /v1/channel-account/{account_id}/status` | 查询账号在线/登录/风控状态 | +| `POST /v1/channel-account/list` | 查询渠道账号列表 | +| `POST /v1/channel-account/{account_id}/refresh-qr-code` | 刷新登录二维码 | +| `POST /v1/channel-account/{account_id}/logout` | 强制下线 | +| `GET /v1/channel-account/{account_id}/groups` | 查询账号已加入的群聊 | + +### 5.4.1 初始化渠道账号示例 + +```json +POST /msg-platform/api/v1/channel-account/init +Headers: X-Tenant-Code: rescue + +{ + "channel_type": "wecom", + "account_type": 1, + "subject_id": 1, + "config": { + "userid": "ZhangSan" + } +} +``` + +### 5.4.2 同步已有群聊示例 + +```json +POST /msg-platform/api/v1/channel-account/1001/sync-conversations +Headers: X-Tenant-Code: rescue +``` + +## 5.5 会话/群聊类 API + +| API | 用途 | +|-----|------| +| `POST /v1/conversation/create` | 创建群聊 | +| `POST /v1/conversation/{conversation_id}/invite` | 邀请成员进群 | +| `GET /v1/conversation/{conversation_id}` | 查询群聊详情 | +| `POST /v1/conversation/list` | 按条件查询群聊 | +| `POST /v1/conversation/{conversation_id}/participants` | 查询群成员 | +| `POST /v1/conversation/{conversation_id}/bind-account` | 绑定收发账号(发送主/备、接收主/备) | +| `POST /v1/conversation/{conversation_id}/unbind-account` | 解绑收发账号 | +| `POST /v1/conversation/{conversation_id}/bind-tags` | 给会话打标签 | +| `POST /v1/conversation/{conversation_id}/unbind-tags` | 解绑会话标签 | +| `POST /v1/conversation/list-by-tags` | 按标签查询会话 | +| `POST /v1/conversation/{conversation_id}/messages` | 查询群聊历史消息 | + +### 5.5.1 绑定收发账号示例 + +```json +POST /msg-platform/api/v1/conversation/1001/bind-account +Headers: X-Tenant-Code: rescue + +{ + "account_id": 2001, + "binding_type": 1, + "sort_order": 1 +} +``` + +`binding_type`:1发送主号 / 2发送备用号 / 3接收主号 / 4接收备用号 + +### 5.5.2 会话打标签示例 + +```json +POST /msg-platform/api/v1/conversation/1001/bind-tags +Headers: X-Tenant-Code: rescue + +{ + "tag_ids": [101, 102] +} +``` + +## 5.6 媒体文件类 API + +| API | 用途 | +|-----|------| +| `GET /v1/media/{media_id}` | 下载/获取媒体文件 | +| `POST /v1/media/{media_id}/download` | 触发异步下载媒体文件 | + +### 5.6.1 媒体下载示例 + +```json +GET /msg-platform/api/v1/media/1001 +Headers: X-Tenant-Code: rescue +``` + +- 已下载:从 SeaweedFS 读取并返回文件流,或 302 跳转; +- 未下载:返回 `202 Accepted`,后台异步调用 Finance SDK `GetMediaData` 下载后写入 SeaweedFS。 + +## 5.7 入站配置类 API + +| API | 用途 | +|-----|------| +| `GET /v1/inbound/webhook` | 查询业务系统入站 webhook 配置 | +| `POST /v1/inbound/webhook` | 配置/更新入站 webhook URL | +| `POST /v1/inbound/webhook/test` | 测试入站 webhook 连通性 | +| `POST /v1/intent/recognize` | 对文本做意图识别(调试) | + +### 5.7.1 入站流程编排类 API + +| API | 用途 | +|-----|------| +| `POST /v1/inbound/flow/list` | 查询入站流程列表 | +| `GET /v1/inbound/flow/{flow_code}` | 查询流程定义 | +| `POST /v1/inbound/flow` | 创建/更新入站流程 | +| `POST /v1/inbound/flow/{flow_code}/publish` | 发布流程 | +| `POST /v1/inbound/flow/{flow_code}/offline` | 下线流程 | + +## 5.8 IVR 流程类 API(预留) + +| API | 用途 | +|-----|------| +| `POST /v1/flow/list` | 查询 IVR 流程列表 | +| `GET /v1/flow/{flow_code}` | 查询 IVR 流程定义 | +| `POST /v1/flow/create` | 创建 IVR 流程 | +| `POST /v1/flow/{flow_code}/publish` | 发布 IVR 流程 | + +## 5.9 账号权限类 API + +中台自维护账号体系,开通租户时创建主账号,业务系统可通过接口创建子账号。 + +| API | 用途 | +|-----|------| +| `POST /v1/account/tenant` | 创建租户(平台管理员) | +| `POST /v1/account/user` | 创建中台用户(租户管理员) | +| `POST /v1/account/user/{user_id}/reset-password` | 重置密码 | +| `POST /v1/account/user/{user_id}/bind-role` | 绑定角色 | +| `POST /v1/account/login` | 中台用户登录 | +| `POST /v1/account/sso-token` | 业务系统申请跳转 token | + +### 5.9.1 创建子账号示例 + +```json +POST /msg-platform/api/v1/account/user +Headers: X-Tenant-Code: rescue, X-Admin-Token: xxx + +{ + "username": "operator01", + "password": "xxx", + "real_name": "运营一号", + "phone": "13800138000", + "role_codes": ["operator"] +} +``` + +## 5.10 页面跳转入口 + +业务系统通过带 token 的 URL 跳转到中台页面: + +``` +https://msg-platform.sino.com/msg-platform/channel-account?tenant=rescue&token=JWT +https://msg-platform.sino.com/msg-platform/inbound-webhook?tenant=rescue&token=JWT +https://msg-platform.sino.com/msg-platform/send-record?tenant=rescue&token=JWT +https://msg-platform.sino.com/msg-platform/flow?tenant=rescue&token=JWT +``` + +中台页面通过 JWT 识别用户身份、所属 tenant 和权限。 + +## 5.11 中台保留的页面 vs 业务系统自行维护的页面 + +| 页面 | 归属 | 说明 | +|------|------|------| +| 渠道资源管理 | 中台 | 业务系统跳转使用 | +| 入站 webhook 配置 | 中台 | 业务系统跳转使用 | +| 入站流程编排 | 中台 | 业务系统跳转使用;轻量画布配置接收 → 意图识别 → 分支 → webhook/自动回复 | +| 发送策略配置 | 中台 | 业务系统跳转使用 | +| 发送记录/统计 | 中台 | 业务系统跳转使用 | +| IVR 流程编排 | 中台 | 业务系统跳转使用(2092 阶段) | +| 账号权限管理 | 中台 | 租户管理员使用 | +| 模板管理 | ❌ 不在中台 | 业务系统自行维护 | +| 业务触发规则配置 | ❌ 不在中台 | 业务系统自行维护 | +| 变量取值逻辑 | ❌ 不在中台 | 业务系统自行维护 | + +--- + +*第五节随 v0.6 数据模型修正更新。* + +--- + +# 第六节:项目目录结构 + +## 6.1 仓库定位 + +`sino-mci` 是**独立于 `sa-server` 的仓库/项目**,位于 `sa-server` 的平级目录: + +``` +/sino-project +├── sa-server/ # 现有业务系统 +├── sino-mci/ # 消息与客户互动中台(本项目) +│ ├── backend/ +│ ├── admin-web/ +│ ├── channel-service/ +│ ├── infrastructure/ +│ └── docs/ +``` + +## 6.2 目录结构(按技术栈分层) + +```text +sino-mci/ +├── README.md +├── docs/ +│ ├── architecture/ +│ │ └── 2026-07-06-message-customer-interaction-design.md +│ ├── api/ +│ │ ├── openapi-send.yaml +│ │ ├── openapi-channel.yaml +│ │ └── openapi-account.yaml +│ └── deployment/ +│ └── deployment-guide.md +│ +├── backend/ # Java 21 + Spring Boot 4.1.x 主中台 +│ ├── mci-server/ # 主服务:编排层、对外 API、管理后台接口 +│ │ ├── src/main/java/com/sino/mci/ +│ │ │ ├── MciServerApplication.java +│ │ │ ├── api/ # REST / Webhook 入口 +│ │ │ ├── orchestration/ # 入站/出站管线、意图识别、策略执行 +│ │ │ ├── admin/ # 租户、用户、角色权限 +│ │ │ ├── config/ +│ │ │ └── channel/ # 渠道适配 SPI 注册(可选) +│ │ ├── src/main/resources/ +│ │ │ ├── application.yml +│ │ │ ├── application-dev.yml +│ │ │ └── db/migration/ # Flyway 脚本 +│ │ ├── src/test/java/com/sino/mci/ +│ │ └── pom.xml +│ │ +│ ├── mci-channel-common/ # 渠道适配公共接口与模型 +│ │ └── src/main/java/com/sino/mci/channel/ +│ │ ├── ChannelAdapter.java +│ │ ├── ChannelSender.java +│ │ ├── ChannelReceiver.java +│ │ └── dto/ +│ │ +│ ├── mci-channel-wecom/ # 企微渠道适配(首期实现) +│ │ └── src/main/java/com/sino/mci/channel/wecom/ +│ │ ├── WecomChannelAdapter.java +│ │ ├── WecomMessageReceiver.java +│ │ ├── WecomMessageSender.java +│ │ ├── finance/ # com.tencent.wework.Finance SDK 封装 +│ │ └── callback/ # 企微回调 Controller +│ │ +│ ├── mci-channel-dingtalk/ # 钉钉适配(预留) +│ ├── mci-channel-feishu/ # 飞书适配(预留) +│ ├── mci-channel-ivr/ # IVR 适配(2092 预留) +│ │ +│ ├── mci-shared/ # 公共实体、工具、MQ/Redis 封装 +│ │ └── src/main/java/com/sino/mci/shared/ +│ │ +│ └── pom.xml # Maven 4 多模块 parent +│ +├── admin-web/ # Vue 3 + Element Plus 管理后台 +│ ├── src/ +│ │ ├── api/ # 中台 API 调用 +│ │ ├── views/ +│ │ │ ├── channel-account/ # 渠道账号管理 +│ │ │ ├── channel-subject/ # 渠道主体管理 +│ │ │ ├── conversation/ # 会话/群聊管理 +│ │ │ ├── send-record/ # 发送记录 +│ │ │ ├── send-policy/ # 发送策略 +│ │ │ ├── inbound-webhook/ # 入站配置 +│ │ │ ├── inbound-flow/ # 入站流程编排(轻量画布) +│ │ │ ├── tag/ # 标签管理 +│ │ │ └── tenant-user/ # 租户/用户/角色 +│ │ ├── components/ +│ │ ├── router/ +│ │ ├── stores/ +│ │ └── App.vue +│ ├── package.json +│ ├── vite.config.ts +│ └── Dockerfile +│ +├── channel-service/ # Python 3.11 + FastAPI 渠道服务 +│ ├── app/ +│ │ ├── main.py +│ │ ├── api/ +│ │ │ ├── wechat_personal.py # pywechat 个人号 API +│ │ │ ├── health.py +│ │ │ └── monitor.py # 状态/心跳上报 +│ │ ├── adapters/ +│ │ │ ├── wechat_personal_adapter.py +│ │ │ └── base.py +│ │ ├── clients/ +│ │ │ └── pywechat_client.py +│ │ ├── services/ +│ │ │ ├── login_service.py +│ │ │ ├── send_service.py +│ │ │ └── heartbeat_service.py +│ │ └── models/ +│ ├── monitor/ # 原始 HTML + Web Components 监控界面 +│ │ ├── index.html +│ │ ├── client-status.js +│ │ └── send-log.js +│ ├── requirements.txt +│ ├── Dockerfile +│ └── pyproject.toml +│ +├── infrastructure/ # 基础设施与部署 +│ ├── docker-compose.yml # MySQL + Redis + RabbitMQ + SeaweedFS +│ ├── docker/ +│ │ ├── Dockerfile.backend +│ │ ├── Dockerfile.admin-web +│ │ └── Dockerfile.channel-service +│ ├── k8s/ +│ │ ├── backend/ +│ │ ├── admin-web/ +│ │ ├── channel-service/ +│ │ └── infra/ +│ └── sql/ +│ ├── init/ # 初始数据:角色、平台管理员 +│ └── migration/ # 按版本号组织的 DDL(Flyway 备份) +│ +└── scripts/ # 构建/部署/本地启动脚本 + ├── local-up.sh # 一键启动本地环境 + ├── build-backend.sh + ├── build-admin-web.sh + ├── build-channel-service.sh + └── deploy-k8s.sh +``` + +## 6.3 关键目录设计决策 + +| 决策 | 说明 | +|------|------| +| **单仓库 monorepo** | 后端、前端、Python 服务、文档、基础设施统一版本管理,降低联调成本。 | +| **Maven 4 多模块** | `backend/pom.xml` 作为 parent,各渠道适配器独立模块;首期只实现 `mci-channel-wecom`,其余预留。 | +| **`mci-server` 合并 admin** | 中台用户、租户、角色权限直接放在 `mci-server` 的 `admin` 包下,不单独拆 `mci-admin` 模块。 | +| **渠道适配 SPI** | `mci-channel-common` 定义统一接口,主服务通过 Spring 自动扫描加载适配器。 | +| **Flyway 脚本放在 `mci-server`** | 数据库迁移由主服务启动时执行,脚本按 `V{版本}__{描述}.sql` 命名。 | +| **Python 服务独立目录** | pywechat 依赖 Windows + Python 环境,与 Java 中台通过 HTTP/WebSocket 通信;监控页用原始 HTML + Web Components。 | +| **基础设施即代码** | `docker-compose.yml` 统一拉起 MySQL、Redis、RabbitMQ、SeaweedFS,降低本地开发和测试成本。 | +| **文档集中** | 设计文档、API 文档、部署文档全部进 `docs/`,不再散落在各模块中。 | + +## 6.4 与 `sa-server` 的关系 + +| 项目 | 职责 | 交互方式 | +|------|------|---------| +| `sa-server` | 业务系统(合同、供应商、订单、人员等) | 调用 `sino-mci` 发送 API;接收 `sino-mci` 入站 webhook | +| `sino-mci` | 消息中台(发送网关 + 渠道资源管理) | 提供 REST API;向业务系统推送入站消息事件 | + +`sino-mci` 不依赖 `sa-server` 的业务代码,只通过 HTTP API 与业务系统交互。 + +--- + +*第六节为 v0.7 新增。*