Files
sino-mci/docs/architecture/2026-07-06-message-customer-interaction-design.md

2165 lines
90 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 消息与客户互动中台架构设计
> **需求来源**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内部通知/流转)和 2092IVR 外呼)预留扩展位;
- 通过事件总线兼容现有业务系统,不强制迁移;
- 实现“客户说话 → 中台识别 → 调用业务系统 → 回写结果 → 回复客户”的闭环。
**范围约束**
- 中台定位为**消息发送网关 + 渠道资源管理中心**
- 中台不保存模板和变量,业务系统直接传入已渲染的最终内容;
- 中台不主动调用业务系统能力接口,入站消息通过 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` SDKC 库通过 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 个 ChannelIdentityN 个 Tag |
| ChannelIdentity | **人在渠道上的身份标识**(如企微 external_userid、个微 wxid、钉钉 userid | 属于 1 个 Person关联 1 个 ChannelSubject |
| ChannelAccount | **用于收发消息的载体账号**如企微内部账号、pywechat 个微、钉钉机器人、飞书机器人) | 属于 1 个 ChannelSubject |
| ChannelSubject | 渠道主体,一个企业在一个渠道下的主体 | 目前支持 wecom / dingtalk / feishu |
| Conversation | 会话(单聊/群聊) | 有 N 个 ConversationParticipantN 个 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 | 父标签 ID0 表示根标签 |
| 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 | 企微会话存档媒体文件下载后存 SeaweedFSS3 协议访问 |
| 缓存 / 限频 / 状态 | 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/feishuconfig 中传 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 DAGJGraphT + 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` SDKC 库 JNI |
| 媒体文件存储 | SeaweedFSS3 协议) |
| 数据库 | 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/ # 按版本号组织的 DDLFlyway 备份)
└── 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 新增。*