fix: 渠道账号群聊同步缓存 key 及成员名称兜底;统一 Docker/Maven 项目约定
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
# 消息中台管理后台菜单重构设计
|
||||
# 消息中台管理后台菜单重构与界面设计
|
||||
|
||||
> **日期**:2026-07-09
|
||||
> **状态**:已确认,待实现
|
||||
> **关联文档**:[消息与客户互动中台架构设计](../architecture/2026-07-06-message-customer-interaction-design.md)
|
||||
> **关联文档**:[消息与客户互动中台架构设计](../architecture/2026-07-06-message-customer-interaction-design.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -20,16 +20,20 @@
|
||||
|
||||
## 2. 设计目标
|
||||
|
||||
1. **按工作流组织顶层菜单**:让用户按“接入 → 维护资产 → 运营发送 → 编排流程 → 系统管理”的路径找到功能。
|
||||
1. **按工作流组织顶层菜单**:让用户按“接入 → 客户关系 → 运营发送 → 编排流程 → 系统管理”的路径找到功能。
|
||||
2. **按业务对象聚合二级功能**:同一对象(渠道账号、会话、联系人)的关联操作放在同一模块下。
|
||||
3. **明确功能边界**:每个菜单项都有清晰的功能定义、面向角色和与现有页面的映射关系。
|
||||
4. **预留扩展位**:IVR 流程等 2092 阶段功能先不实现,但保留模块位置说明。
|
||||
4. **统一设计语言**:
|
||||
- 列表页 = 筛选 + 表格 + 顶部「新增」按钮 + 分页
|
||||
- 新增 = 弹窗/小抽屉,只填核心字段
|
||||
- 编辑/详情 = 大抽屉,基础信息区 + 标签字段 + 关联内容区
|
||||
5. **预留扩展位**:钉钉/飞书/IVR 本期仅保留入口,不实现具体能力。
|
||||
|
||||
---
|
||||
|
||||
## 3. 设计决策
|
||||
|
||||
采用 **混合式组织(候选 C)**:
|
||||
采用 **混合式组织**:
|
||||
|
||||
- 顶层按 **工作流/职责域** 划分,降低日常操作路径长度。
|
||||
- 模块内按 **业务对象** 分组,保持数据模型与 UI 结构一致。
|
||||
@@ -57,8 +61,8 @@
|
||||
|---------|------|---------|
|
||||
| 概览 | 运营总览、快捷入口 | 全部 |
|
||||
| 渠道接入 | 渠道资源与账号接入 | 租户管理员、运营人员 |
|
||||
| 客户与群 | 联系人、会话、标签资产 | 租户管理员、运营人员 |
|
||||
| 消息运营 | 发送、记录、策略、消息查询 | 租户管理员、运营人员 |
|
||||
| 客户关系 | 联系人、会话、标签资产 | 租户管理员、运营人员 |
|
||||
| 消息运营 | 发送、记录、消息查询 | 租户管理员、运营人员 |
|
||||
| 流程编排 | 入站消息处理配置 | 租户管理员 |
|
||||
| 系统管理 | 租户、用户、角色 | 平台管理员、租户管理员 |
|
||||
|
||||
@@ -73,46 +77,42 @@
|
||||
|
||||
| 二级菜单 | 功能定义 | 页面/备注 |
|
||||
|---------|---------|----------|
|
||||
| 渠道主体 | 维护企业在各渠道的主体信息:企微 corp_id/secret、钉钉/飞书主体;支持会话存档开关 | 现有 ChannelSubject 页面迁移 |
|
||||
| 渠道账号 | 维护用于收发消息的载体账号:新增、编辑、启用/停用、查看在线/登录/风控状态、强制下线 | 现有 ChannelAccount 页面迁移 |
|
||||
| 账号初始化 | 统一入口引导初始化:企微员工/应用、个微扫码登录、机器人 webhook | 从 ChannelAccount 页面拆分出的引导流程 |
|
||||
| 应用管理 | 企微应用等渠道应用的配置:agent_id、agent_secret、应用消息推送能力 | 新增(原散落在渠道账号中) |
|
||||
| 渠道主体 | 维护企业在各渠道的主体信息;企微主体支持会话存档、员工/客户/群聊/会话存档同步 | 现有 ChannelSubject 页面增强 |
|
||||
| 渠道账号 | 维护用于收发消息的载体账号;支持按 ID 拉取企微信息、pywechat 扫码登录、同步会话 | 现有 ChannelAccount 页面迁移 |
|
||||
| 应用管理 | 企微应用的配置:agent_id、agent_secret、能力验证;本期只实现企微,其他占位 | 新增 |
|
||||
|
||||
### 5.4 客户与群
|
||||
### 5.4 客户关系
|
||||
|
||||
| 二级菜单 | 功能定义 | 页面/备注 |
|
||||
|---------|---------|----------|
|
||||
| 联系人 | 维护 Person(业务身份),含 person_code、person_type、phone、institution_id、supplier_id、contract_id;支持新增/编辑/启用停用 | 现有 Person 页面增强 |
|
||||
| 渠道身份 | 管理 Person 在各渠道的身份映射:企微 external_userid、个微 wxid、钉钉 userid、飞书 open_id | 新增独立入口 |
|
||||
| 标签体系 | 树形标签管理:新增/编辑/删除、查看人员与会话标签分布;标签表达角色/合同/机构/供应商等业务属性 | 现有 Tag 页面升级为体系化管理 |
|
||||
| 会话/群聊 | 维护 Conversation:新增、编辑、同步群成员、查看历史消息、绑定业务字段(contract_id 等) | 现有 Conversation 页面迁移 |
|
||||
| 账号-会话绑定 | 为会话配置发送主备号、接收主备号及优先级;单聊约束需校验 channel_type 一致性 | 从 Conversation 页面拆分出的独立管理 |
|
||||
| 联系人 | 维护 Person;详情抽屉涵盖基本信息、渠道身份、标签、所属会话 | 现有 Person 页面增强 |
|
||||
| 会话/群聊 | 维护 Conversation;详情抽屉涵盖基本信息、成员、收发账号绑定、标签、历史消息 | 现有 Conversation 页面迁移 |
|
||||
| 标签体系 | 树形标签管理;标签用于表达角色/合同/机构/供应商等业务属性 | 现有 Tag 页面升级为体系化管理 |
|
||||
|
||||
### 5.5 消息运营
|
||||
|
||||
| 二级菜单 | 功能定义 | 页面/备注 |
|
||||
|---------|---------|----------|
|
||||
| 发送记录 | 按任务查询发送明细、状态、结果、重试记录;支持取消待发送任务 | 现有 SendRecord 页面迁移 |
|
||||
| 测试发送 | 模拟发送,不实际调用渠道,用于业务系统联调和发送逻辑验证 | 现有 MessageTest 页面迁移并改名 |
|
||||
| 发送策略 | 策略组配置:频率控制、时间窗口、黑名单、重试、主备切换 | 现有 SendPolicy 页面迁移 |
|
||||
| 消息查询 | 统一查询入站/出站消息事件(MessageEvent),支持按会话、人员、账号、来源(realtime / session_archive)筛选 | 新增 |
|
||||
| 发送记录 | 按任务查询发送明细、状态、结果;支持取消待发送任务;失败固定重试 2 次 | 现有 SendRecord 页面迁移 |
|
||||
| 测试发送 | 模拟出站/入站消息,用于业务系统联调和发送逻辑验证 | 现有 MessageTest 页面迁移并改名 |
|
||||
| 消息查询 | 聊天界面查看入站/出站消息事件,支持按会话、人员、来源筛选 | 新增 |
|
||||
|
||||
### 5.6 流程编排
|
||||
|
||||
| 二级菜单 | 功能定义 | 页面/备注 |
|
||||
|---------|---------|----------|
|
||||
| 入站 Webhook | 配置业务系统接收入站消息的 webhook URL、签名方式、测试连通性 | 现有 InboundWebhook 页面迁移 |
|
||||
| 入站流程 | 轻量画布配置入站消息处理流程:receive → intent → condition → webhook/reply | 现有 InboundFlow 页面迁移 |
|
||||
| 入站流程 | 画布配置入站消息处理流程,支持多轮上下文与槽位收集 | 现有 InboundFlow 页面增强 |
|
||||
|
||||
> **本期不做**:IVR 流程(2092 阶段接入)。
|
||||
> **本期不做**:IVR 流程、发送策略配置。
|
||||
|
||||
### 5.7 系统管理
|
||||
|
||||
| 二级菜单 | 功能定义 | 页面/备注 |
|
||||
|---------|---------|----------|
|
||||
| 租户管理 | 平台管理员创建/编辑租户;租户管理员维护本租户基础信息、认证配置、入站 webhook | 现有 TenantManagement 页面增强 |
|
||||
| 租户管理 | 平台管理员创建/编辑租户;租户管理员维护本租户基础信息 | 现有 TenantManagement 页面增强 |
|
||||
| 用户管理 | 中台用户增删改查、重置密码、启用/停用、绑定角色 | 现有 UserManagement 页面迁移 |
|
||||
| 角色权限 | 平台角色 admin/operator/viewer 的权限配置与分配 | 新增(首期角色固定,可配置权限列表) |
|
||||
| 角色权限 | 平台角色 admin/operator/viewer 的权限配置与分配 | 新增 |
|
||||
|
||||
---
|
||||
|
||||
@@ -121,62 +121,678 @@
|
||||
| 当前菜单 | 调整方式 | 新位置 |
|
||||
|---------|---------|--------|
|
||||
| 概览 | 保留并增强 | 概览 → 运营总览 |
|
||||
| 渠道主体 | 保留 | 渠道接入 → 渠道主体 |
|
||||
| 渠道账号 | 拆分 | 渠道接入 → 渠道账号 / 账号初始化 / 应用管理 |
|
||||
| 会话/群聊 | 拆分 | 客户与群 → 会话/群聊 / 账号-会话绑定 |
|
||||
| 联系人 | 保留,增强 | 客户与群 → 联系人 |
|
||||
| 标签 | 升级为体系 | 客户与群 → 标签体系 |
|
||||
| 渠道主体 | 保留并增强 | 渠道接入 → 渠道主体 |
|
||||
| 渠道账号 | 拆分 | 渠道接入 → 渠道账号 / 应用管理 |
|
||||
| 会话/群聊 | 保留,绑定功能移入详情 | 客户关系 → 会话/群聊 |
|
||||
| 联系人 | 保留,增强 | 客户关系 → 联系人 |
|
||||
| 标签 | 升级为体系 | 客户关系 → 标签体系 |
|
||||
| 发送记录 | 保留 | 消息运营 → 发送记录 |
|
||||
| 消息测试 | 保留,改名 | 消息运营 → 测试发送 |
|
||||
| 发送策略 | 保留 | 消息运营 → 发送策略 |
|
||||
| 入站流程 | 保留 | 流程编排 → 入站流程 |
|
||||
| 发送策略 | 本期移除 | — |
|
||||
| 入站流程 | 保留并增强 | 流程编排 → 入站流程 |
|
||||
| IVR 流程 | 本期移除 | — |
|
||||
| 入站 Webhook | 保留 | 流程编排 → 入站 Webhook |
|
||||
| 流程追踪 | 合并 | 消息运营 → 发送记录(详情)/ 消息查询 |
|
||||
| 用户管理 | 保留 | 系统管理 → 用户管理 |
|
||||
| 租户管理 | 保留 | 系统管理 → 租户管理 |
|
||||
| 账号初始化 | 删除独立菜单 | 能力分散到渠道主体/账号/联系人/会话 |
|
||||
| 渠道身份 | 删除独立菜单 | 并入联系人详情 |
|
||||
| 账号-会话绑定 | 删除独立菜单 | 并入会话详情 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 新增页面清单
|
||||
## 7. 统一页面设计语言
|
||||
|
||||
| 页面 | 所属模块 | 功能说明 |
|
||||
|------|---------|---------|
|
||||
| 账号初始化 | 渠道接入 | 引导式初始化各渠道账号 |
|
||||
| 应用管理 | 渠道接入 | 企微应用等渠道应用配置 |
|
||||
| 渠道身份 | 客户与群 | 管理 Person 在各渠道的身份映射 |
|
||||
| 账号-会话绑定 | 客户与群 | 会话与收发账号的绑定管理 |
|
||||
| 消息查询 | 消息运营 | 统一查询入站/出站消息事件 |
|
||||
| 角色权限 | 系统管理 | 中台角色与权限配置 |
|
||||
### 7.1 列表页
|
||||
|
||||
- 顶部筛选区(可折叠)
|
||||
- 右上角「新增」按钮
|
||||
- 表格展示核心字段 + 标签 + 关联数量
|
||||
- 行内操作:编辑 / 详情 / 删除 / 启用禁用
|
||||
- 底部分页
|
||||
|
||||
### 7.2 新增
|
||||
|
||||
- 弹窗或小抽屉
|
||||
- 只收集创建必需的字段
|
||||
- 创建成功后刷新列表
|
||||
|
||||
### 7.3 编辑/详情
|
||||
|
||||
- 大抽屉
|
||||
- 基础信息区(可编辑)
|
||||
- 标签字段(树形多选,随基础信息一起保存)
|
||||
- 关联内容区(只读列表 + 快捷操作)
|
||||
- 不同对象按自身关联内容展示,不拆成多个 Tab
|
||||
|
||||
---
|
||||
|
||||
## 8. 不在本期范围内的功能
|
||||
## 8. 渠道接入模块详细设计
|
||||
|
||||
| 功能 | 原因 | 后续阶段 |
|
||||
|------|------|---------|
|
||||
| IVR 流程 | 属于 2092 IVR 语音配置需求,本期聚焦 1851 企微场景 | 2092 阶段 |
|
||||
| 模板管理 | 中台不保存模板,由业务系统自行维护 | 不涉及 |
|
||||
| 业务触发规则 | 中台不承载业务系统触发规则 | 不涉及 |
|
||||
### 8.1 渠道主体
|
||||
|
||||
#### 列表字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 渠道 | wecom / dingtalk / feishu(本期仅 wecom 可用) |
|
||||
| 主体编码 | `subject_code` |
|
||||
| 主体名称 | `subject_name` |
|
||||
| CorpID | `corp_id` |
|
||||
| 会话存档 | 开启 / 关闭 |
|
||||
| 状态 | 启用 / 禁用 |
|
||||
| 操作 | 编辑 / 详情 / 删除 / 启用禁用 |
|
||||
|
||||
#### 新增/编辑
|
||||
|
||||
- 渠道类型
|
||||
- 主体编码
|
||||
- 主体名称
|
||||
- CorpID
|
||||
- CorpSecret(通讯录同步 secret)
|
||||
- AgentID
|
||||
- AgentSecret
|
||||
- 会话存档开关
|
||||
- 会话存档 RSA 私钥
|
||||
|
||||
#### 详情抽屉(企微能力区)
|
||||
|
||||
| 能力 | 所需凭证 | 操作 |
|
||||
|------|---------|------|
|
||||
| 验证主体连通 | `corp_id + corp_secret` | 点击验证 |
|
||||
| 同步企业员工 | `corp_id + corp_secret` | 拉取通讯录,写入 `mci_person` + `mci_channel_identity` + `department_path` |
|
||||
| 同步客户/客户群 | 主体下配置的企微应用 `agent_secret` | 拉取外部联系人和客户群 |
|
||||
| 同步会话存档 | 应用管理中配置的会话存档应用 secret | 用 Finance SDK 拉取最近消息 |
|
||||
|
||||
每个能力执行后展示状态、上次执行时间、成功/失败数量、错误信息,并写入 `mci_channel_subject_sync_task` 日志表。
|
||||
|
||||
### 8.2 渠道账号
|
||||
|
||||
#### 列表字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 账号类型 | 企微内部账号 / 企微应用 / pywechat 个人号 |
|
||||
| 渠道 | wecom / wechat_personal |
|
||||
| 账号 ID | `account_id` |
|
||||
| 名称 | `account_name` |
|
||||
| 所属主体 | `subject_name` |
|
||||
| 登录状态 | 已登录 / 未登录 |
|
||||
| 在线状态 | 在线 / 离线 |
|
||||
| 状态 | 启用 / 禁用 |
|
||||
| 操作 | 编辑 / 删除 / 启用禁用 / 初始化 / 同步会话 |
|
||||
|
||||
#### 企微内部账号
|
||||
|
||||
- 创建/编辑时输入 `userid` 后,可点击「从企微拉取」回填姓名、部门信息
|
||||
- 会话存档开关(标记该员工是否开启存档)
|
||||
|
||||
#### 企微应用
|
||||
|
||||
- 应用凭证在「应用管理」维护
|
||||
- 渠道账号中创建时选择应用,自动带出 `agent_id`
|
||||
- 提供「验证应用」按钮
|
||||
|
||||
#### pywechat 个人号
|
||||
|
||||
- 创建时填写 `wxid / bot_code` 和 `server_host`
|
||||
- 行操作「扫码登录」:返回二维码,轮询登录状态
|
||||
- 行操作「同步会话」:拉取该个人号所在群聊
|
||||
|
||||
### 8.3 应用管理
|
||||
|
||||
#### 列表字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 应用名称 | `app_name` |
|
||||
| 应用类型 | wecom / wecom_session_archive / dingtalk / feishu |
|
||||
| agent_id | 企微应用 ID |
|
||||
| 所属主体 | `subject_name` |
|
||||
| 状态 | 启用 / 禁用 |
|
||||
| 操作 | 编辑 / 验证 / 删除 / 启用禁用 |
|
||||
|
||||
#### 新增/编辑
|
||||
|
||||
- 应用名称
|
||||
- 所属主体
|
||||
- 应用类型
|
||||
- agent_id
|
||||
- agent_secret
|
||||
- 状态
|
||||
|
||||
#### 操作
|
||||
|
||||
- **验证并获取能力**:用 `corp_id + agent_id + agent_secret` 获取 access_token,验证通过更新状态
|
||||
- 钉钉/飞书应用仅保留表单入口,验证按钮禁用
|
||||
|
||||
---
|
||||
|
||||
## 9. 实现要点
|
||||
## 9. 客户关系模块详细设计
|
||||
|
||||
1. **前端路由**:按新菜单结构重排 `admin-web/src/router/index.ts`,路径建议保持语义化,如 `/channel-subject`、`/channel-account/init`、`/customer/person`、`/message/record`。
|
||||
2. **布局组件**:`Layout.vue` 从平铺 `el-menu-item` 改为分组 `el-sub-menu`,支持一级展开/收起。
|
||||
3. **权限控制**:菜单可见性通过 `authStore.roleCodes` 或后端返回的权限列表控制;URL 级权限同步调整。
|
||||
4. **页面拆分**:
|
||||
- `Conversation.vue` 中的“绑定账号”功能拆出为 `ConversationAccountBinding.vue`。
|
||||
- `ChannelAccount.vue` 中的初始化流程拆出为 `ChannelAccountInit.vue`。
|
||||
5. **空状态处理**:新增页面先实现最小可用版本,避免阻塞菜单上线。
|
||||
### 9.1 联系人
|
||||
|
||||
#### 列表字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 人员编码 | `person_code` |
|
||||
| 姓名 | `person_name` |
|
||||
| 手机号 | `phone` |
|
||||
| 类型 | 内部人员 / 供应商人员 / 客户 / 司机 |
|
||||
| 所属机构 | `institution_id` 名称 |
|
||||
| 部门路径 | 多个标签式展示 |
|
||||
| 标签 | 直接展示绑定的标签 |
|
||||
| 渠道身份数 | 几个渠道身份 |
|
||||
| 参与会话数 | 该人员在多少个会话中 |
|
||||
| 状态 | 启用 / 禁用 |
|
||||
| 操作 | 编辑 / 详情 / 删除 / 启用禁用 |
|
||||
|
||||
#### 新增
|
||||
|
||||
弹窗字段:
|
||||
- 人员编码
|
||||
- 姓名
|
||||
- 手机号
|
||||
- 类型
|
||||
- 所属机构
|
||||
- 状态
|
||||
|
||||
#### 编辑/详情抽屉
|
||||
|
||||
**基础信息区**
|
||||
- 人员编码、姓名、手机号
|
||||
- 类型
|
||||
- 所属机构、供应商、合同
|
||||
- 部门路径(支持多条)
|
||||
- 状态
|
||||
|
||||
**标签字段**
|
||||
- 树形多选,已选标签平铺展示
|
||||
|
||||
**渠道身份区**
|
||||
- 列表展示所有渠道身份
|
||||
- 行内新增 / 编辑 / 删除
|
||||
- 新增时:选择渠道 → 选择主体 → 输入渠道用户 ID → 「拉取企微信息」回填昵称
|
||||
|
||||
**所属会话区**
|
||||
- 展示该人员参与的会话列表
|
||||
- 点击会话名称打开会话详情抽屉
|
||||
|
||||
### 9.2 会话/群聊
|
||||
|
||||
#### 列表字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 会话类型 | 单聊 / 群聊 |
|
||||
| 渠道 | wecom / wechat_personal |
|
||||
| 渠道会话 ID | `channel_conversation_id` |
|
||||
| 会话名称 | `conversation_name` |
|
||||
| 群主/负责人 | `owner_account_id` 名称 |
|
||||
| 所属主体 | `subject_id` 名称 |
|
||||
| 标签 | 直接展示 |
|
||||
| 成员数 | 群聊显示成员数量 |
|
||||
| 业务绑定 | 机构 / 供应商 / 合同 / 渠道编码 |
|
||||
| 状态 | 启用 / 禁用 |
|
||||
| 操作 | 编辑 / 详情 / 删除 / 同步成员 |
|
||||
|
||||
#### 新增
|
||||
|
||||
弹窗字段:
|
||||
- 会话类型
|
||||
- 渠道
|
||||
- 所属主体
|
||||
- 渠道会话 ID
|
||||
- 会话名称
|
||||
- 负责人账号
|
||||
- 状态
|
||||
|
||||
#### 编辑/详情抽屉
|
||||
|
||||
**基础信息区**
|
||||
- 会话类型、渠道、所属主体
|
||||
- 渠道会话 ID、会话名称
|
||||
- 负责人账号
|
||||
- 关联机构 / 供应商 / 合同 / 渠道编码
|
||||
- 状态
|
||||
|
||||
**标签字段**
|
||||
- 树形多选
|
||||
|
||||
**成员区**
|
||||
- 列表展示 `mci_conversation_participant`
|
||||
- 字段:人员姓名、渠道身份、角色
|
||||
- 操作:同步成员、移除
|
||||
- 人员名称可点击进入联系人详情
|
||||
|
||||
**收发账号绑定区**
|
||||
- 展示发送主号 / 发送备用号 / 接收主号 / 接收备用号
|
||||
- 行内新增、编辑优先级、删除
|
||||
- 单聊限制:只能绑定同 `channel_type` 的账号
|
||||
- 群聊允许跨渠道绑定,但给出提示
|
||||
|
||||
**历史消息区**
|
||||
- 展示该会话最近消息列表
|
||||
- 可跳转消息查询页面
|
||||
|
||||
### 9.3 标签体系
|
||||
|
||||
#### 列表字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 标签名 | `tag_name` |
|
||||
| 路径 | `tag_path` |
|
||||
| 层级 | `level` |
|
||||
| 来源 | system / business_sync / manual |
|
||||
| 状态 | 启用 / 禁用 |
|
||||
| 关联人员数 | 使用数量 |
|
||||
| 关联会话数 | 使用数量 |
|
||||
| 操作 | 新增子标签 / 编辑 / 删除 / 启用禁用 |
|
||||
|
||||
#### 新增/编辑
|
||||
|
||||
弹窗字段:
|
||||
- 标签名
|
||||
- 父标签(树形选择,顶级为根)
|
||||
- 来源
|
||||
- 状态
|
||||
|
||||
#### 详情抽屉
|
||||
|
||||
- 标签基本信息
|
||||
- 关联人员列表(分页)
|
||||
- 关联会话列表(分页)
|
||||
|
||||
---
|
||||
|
||||
## 10. 验收标准
|
||||
## 10. 消息运营模块详细设计
|
||||
|
||||
- [ ] 左侧导航按新 6 大模块分组展示,无 IVR 流程入口。
|
||||
### 10.1 发送记录
|
||||
|
||||
#### 页面结构
|
||||
|
||||
- 顶部统计卡片:今日发送总数 / 成功 / 失败 / 待发送
|
||||
- 筛选区:任务 ID、发送时间、渠道、状态、目标类型
|
||||
- 记录列表
|
||||
- 详情抽屉
|
||||
|
||||
#### 列表字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 任务 ID | `task_id` |
|
||||
| 目标类型 | 单人会话 / 群聊 / 人员 / 标签 / 规则 |
|
||||
| 目标摘要 | 会话名称 / 人员姓名 / 标签名 / 规则描述 |
|
||||
| 渠道 | wecom / wechat_personal |
|
||||
| 发送账号 | 实际使用的账号名称 |
|
||||
| 状态 | 待发送 / 发送中 / 成功 / 失败 |
|
||||
| 发送时间 | `send_time` |
|
||||
| 完成时间 | `finish_time` |
|
||||
| 操作 | 详情 / 取消(仅待发送) |
|
||||
|
||||
#### 详情抽屉
|
||||
|
||||
- 基础信息:任务 ID、目标、渠道、发送账号、内容摘要
|
||||
- 内容区:完整 `content_body`
|
||||
- 策略与回调:`channel_strategy`、`callback_url`
|
||||
- 渠道结果:`channel_result` 格式化展示
|
||||
- 关联跳转:会话 / 人员 / 账号 / 消息事件
|
||||
- 时间线:创建 → 发送中 → 完成 / 失败
|
||||
|
||||
> 失败固定重试 2 次,由后端策略处理,前台不配置。
|
||||
|
||||
### 10.2 测试发送
|
||||
|
||||
工具型页面,两个 Tab:
|
||||
|
||||
#### Tab 1:出站测试
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 目标类型 | 会话 / 人员 / 标签 / 规则 |
|
||||
| 目标 | 根据类型选择或输入 |
|
||||
| 内容类型 | text / markdown / image / file / news 等 |
|
||||
| 内容 | 对应内容编辑器 |
|
||||
| 渠道策略 | 优先渠道、备用渠道 |
|
||||
| 回调地址 | 可选 |
|
||||
|
||||
点击「发送测试」后展示返回的 `task_id`,并刷新发送记录。
|
||||
|
||||
#### Tab 2:入站模拟
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 事件 ID | 自定义事件 ID |
|
||||
| 渠道类型 | wecom / wechat_personal |
|
||||
| 消息来源 | realtime / session_archive |
|
||||
| 会话 ID | 选择已有会话 |
|
||||
| 发送人 | 选择人员或输入 channel_user_id |
|
||||
| 接收账号 | 选择接收账号 |
|
||||
| 内容类型 | text / image / voice 等 |
|
||||
| 内容 | 消息内容 |
|
||||
|
||||
点击「模拟入站」后触发入站流程编排,展示处理结果。
|
||||
|
||||
### 10.3 消息查询
|
||||
|
||||
#### 页面布局
|
||||
|
||||
三栏式聊天界面:
|
||||
|
||||
```
|
||||
┌─────────────┬─────────────────────┬─────────────┐
|
||||
│ 会话列表 │ 聊天内容 │ 会话信息 │
|
||||
│ 搜索/筛选 │ │ 成员/标签 │
|
||||
└─────────────┴─────────────────────┴─────────────┘
|
||||
```
|
||||
|
||||
#### 左侧:会话列表
|
||||
|
||||
- 按时间倒序展示会话
|
||||
- 每个会话卡片:会话名称、最后一条消息预览、最后消息时间、标签小圆点
|
||||
- 搜索:会话名称、成员姓名、消息内容
|
||||
- 筛选:渠道、消息来源(实时/会话存档)、会话类型、标签
|
||||
|
||||
#### 中间:聊天内容区
|
||||
|
||||
- 按时间顺序展示消息气泡
|
||||
- 区分入站消息(左侧)、出站消息(右侧)、系统事件(中间)
|
||||
- 根据 `msgtype` 渲染:文本、图片、语音、视频、文件、撤回提示等
|
||||
- 支持按时间范围筛选,滚动加载历史消息
|
||||
|
||||
#### 右侧:会话信息
|
||||
|
||||
- 基础信息:会话名称、渠道、来源、群主
|
||||
- 业务绑定:机构 / 供应商 / 合同 / 渠道编码
|
||||
- 标签:直接展示,可编辑
|
||||
- 成员列表
|
||||
- 快捷操作:查看发送记录、跳转联系人详情、同步成员
|
||||
|
||||
---
|
||||
|
||||
## 11. 流程编排模块详细设计
|
||||
|
||||
### 11.1 入站 Webhook
|
||||
|
||||
每个租户独立配置一个入站推送地址。
|
||||
|
||||
#### 页面内容
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| Webhook URL | 业务系统接收中台入站消息的地址 |
|
||||
| 签名算法 | HMAC-SHA256 / HMAC-SHA512 / 无 |
|
||||
| Webhook Secret | 签名密钥 |
|
||||
| 超时时间 | 推送超时秒数,默认 10s |
|
||||
| 失败重试 | 固定重试 2 次 |
|
||||
|
||||
#### 操作
|
||||
|
||||
- 保存
|
||||
- 测试推送
|
||||
- 查看最近推送日志(状态、响应码、响应摘要)
|
||||
|
||||
> 这是流程中 Webhook 节点的默认推送地址,只有流程走到该节点才会触发。
|
||||
|
||||
### 11.2 入站流程
|
||||
|
||||
#### 页面布局
|
||||
|
||||
保留三栏画布:
|
||||
|
||||
```
|
||||
┌────────────┬─────────────────────────┬────────────┐
|
||||
│ 流程列表 │ 流程画布 │ 节点配置 │
|
||||
│ 组件面板 │ │ │
|
||||
└────────────┴─────────────────────────┴────────────┘
|
||||
```
|
||||
|
||||
#### 流程节点
|
||||
|
||||
| 节点 | 作用 |
|
||||
|------|------|
|
||||
| 接收消息 | 流程入口,指定渠道 |
|
||||
| 加载上下文 | 读取当前会话已有的 intent / slots / 轮次 |
|
||||
| 意图识别 | LLM / 规则识别本轮意图和槽位 |
|
||||
| 合并槽位 | 把本轮新槽位合并到会话上下文 |
|
||||
| 槽位校验 | 检查必需槽位是否齐全 |
|
||||
| 自动追问 | 缺槽位时自动生成追问文案并回复 |
|
||||
| Webhook | 槽位齐全后推送给业务系统 |
|
||||
| 回复消息 | 可配置固定回复 |
|
||||
| 保存上下文 | 把更新后的 intent / slots 写回 |
|
||||
| 清空上下文 | Webhook 成功后结束当前会话 |
|
||||
|
||||
#### 多轮流程示例
|
||||
|
||||
```
|
||||
接收消息
|
||||
│
|
||||
▼
|
||||
加载上下文
|
||||
│
|
||||
▼
|
||||
意图识别(结合上下文)
|
||||
│
|
||||
▼
|
||||
合并槽位
|
||||
│
|
||||
▼
|
||||
槽位校验
|
||||
├─ 缺槽位 ──▶ 自动追问 ──▶ 保存上下文 ──▶ 结束
|
||||
│
|
||||
└─ 槽位齐全 ──▶ Webhook ──▶ 回复客户 ──▶ 清空上下文 ──▶ 结束
|
||||
```
|
||||
|
||||
#### 上下文存储
|
||||
|
||||
新增 `mci_conversation_context` 表:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| conversation_id | 会话 ID |
|
||||
| intent | 当前意图 |
|
||||
| slots | JSON,已收集槽位 |
|
||||
| missing_slots | JSON,还缺哪些槽位 |
|
||||
| turn_count | 当前轮次 |
|
||||
| status | collecting / complete / expired |
|
||||
| expire_time | 上下文过期时间 |
|
||||
| update_time | 最后更新时间 |
|
||||
|
||||
超过一定时间(如 10 分钟)无新消息,上下文自动过期。
|
||||
|
||||
---
|
||||
|
||||
## 12. 系统管理模块详细设计
|
||||
|
||||
### 12.1 租户管理
|
||||
|
||||
#### 列表字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 租户编码 | `tenant_code` |
|
||||
| 租户名称 | `tenant_name` |
|
||||
| 状态 | 启用 / 禁用 |
|
||||
| 用户数量 | 该租户下用户总数 |
|
||||
| 创建时间 | `create_time` |
|
||||
| 操作 | 编辑 / 删除 / 启用禁用 |
|
||||
|
||||
**平台管理员**:可操作全部租户。
|
||||
**租户管理员**:只能查看/编辑本租户,不可删除。
|
||||
|
||||
#### 新增(仅平台管理员)
|
||||
|
||||
- 租户编码
|
||||
- 租户名称
|
||||
- 入站 Webhook URL
|
||||
- 初始化管理员账号(默认 `admin`)
|
||||
- 初始化管理员密码
|
||||
- 状态
|
||||
|
||||
创建成功后自动创建该租户的第一个管理员用户。
|
||||
|
||||
#### 编辑
|
||||
|
||||
- 租户名称
|
||||
- 入站 Webhook URL
|
||||
- 状态
|
||||
|
||||
默认租户(如 `platform`)不可删除。
|
||||
|
||||
### 12.2 用户管理
|
||||
|
||||
#### 列表字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 用户名 | `username` |
|
||||
| 真实姓名 | `real_name` |
|
||||
| 手机号 | `phone` |
|
||||
| 所属租户 | `tenant_name`(平台管理员可见) |
|
||||
| 角色 | 角色名称,多个用逗号 |
|
||||
| 状态 | 启用 / 禁用 |
|
||||
| 创建时间 | `create_time` |
|
||||
| 操作 | 编辑 / 重置密码 / 删除 / 启用禁用 |
|
||||
|
||||
#### 新增
|
||||
|
||||
- 所属租户(平台管理员选择,租户管理员固定为本租户)
|
||||
- 用户名
|
||||
- 密码
|
||||
- 真实姓名
|
||||
- 手机号
|
||||
- 角色(多选)
|
||||
- 状态
|
||||
|
||||
#### 编辑
|
||||
|
||||
可编辑:真实姓名、手机号、角色、状态。
|
||||
不可编辑:用户名、所属租户。
|
||||
|
||||
### 12.3 角色权限
|
||||
|
||||
#### 列表字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 角色编码 | `role_code` |
|
||||
| 角色名称 | `role_name` |
|
||||
| 权限数量 | 已配置的权限项数 |
|
||||
| 状态 | 启用 / 禁用 |
|
||||
| 操作 | 配置权限 / 启用禁用 |
|
||||
|
||||
**首期固定角色**:
|
||||
|
||||
| 角色编码 | 角色名称 | 默认权限 |
|
||||
|---------|---------|---------|
|
||||
| `platform_admin` | 平台管理员 | 全部权限 |
|
||||
| `admin` | 租户管理员 | 除租户创建外的全部权限 |
|
||||
| `operator` | 运营人员 | 发送、查看记录、维护联系人/会话、配置渠道账号 |
|
||||
| `viewer` | 只读用户 | 仅查看发送记录、统计、消息查询 |
|
||||
|
||||
#### 权限配置
|
||||
|
||||
抽屉中展示权限树:
|
||||
|
||||
```
|
||||
□ 全部
|
||||
□ 概览
|
||||
□ 查看
|
||||
□ 渠道接入
|
||||
□ 渠道主体(查看 / 新增 / 编辑 / 删除)
|
||||
□ 渠道账号(查看 / 新增 / 编辑 / 删除)
|
||||
□ 应用管理(查看 / 新增 / 编辑 / 删除)
|
||||
□ 客户关系
|
||||
□ 联系人(查看 / 新增 / 编辑 / 删除)
|
||||
□ 会话/群聊(查看 / 新增 / 编辑 / 删除)
|
||||
□ 标签体系(查看 / 新增 / 编辑 / 删除)
|
||||
□ 消息运营
|
||||
□ 发送记录(查看 / 取消)
|
||||
□ 测试发送
|
||||
□ 消息查询
|
||||
□ 流程编排
|
||||
□ 入站 Webhook(查看 / 编辑)
|
||||
□ 入站流程(查看 / 新增 / 编辑 / 删除 / 发布)
|
||||
□ 系统管理
|
||||
□ 租户管理(查看 / 新增 / 编辑 / 删除)
|
||||
□ 用户管理(查看 / 新增 / 编辑 / 删除 / 重置密码)
|
||||
□ 角色权限(查看 / 配置)
|
||||
```
|
||||
|
||||
保存后同步到 `mci_platform_role` 表的 `permissions` JSON。
|
||||
|
||||
### 12.4 全局管理员登录
|
||||
|
||||
- 默认创建 `platform` 租户
|
||||
- 通过 Flyway 初始化 `admin` 用户,角色 `platform_admin`
|
||||
- 初始密码在配置文件中设置,首次登录强制修改
|
||||
|
||||
---
|
||||
|
||||
## 13. 数据模型补充
|
||||
|
||||
### 13.1 新增/调整字段
|
||||
|
||||
| 表 | 字段 | 类型 | 说明 |
|
||||
|---|------|------|------|
|
||||
| `mci_person` | `department_path` | JSON | 多个部门路径,如 `["销售中心/销售一部", "销售中心/华北组"]` |
|
||||
| `mci_channel_subject_sync_task` | — | 新表 | 记录各渠道主体同步任务 |
|
||||
| `mci_conversation_context` | — | 新表 | 入站流程多轮会话上下文 |
|
||||
|
||||
### 13.2 同步任务日志表
|
||||
|
||||
`mci_channel_subject_sync_task`:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| subject_id | 渠道主体 ID |
|
||||
| sync_type | employee / external_contact / group / session_archive |
|
||||
| status | 0待执行 1执行中 2成功 3失败 |
|
||||
| total_count | 总数 |
|
||||
| success_count | 成功数 |
|
||||
| fail_count | 失败数 |
|
||||
| error_msg | 错误信息 |
|
||||
| create_time / finish_time | 时间 |
|
||||
|
||||
---
|
||||
|
||||
## 14. 不在本期范围内的功能
|
||||
|
||||
| 功能 | 原因 |
|
||||
|------|------|
|
||||
| IVR 流程 | 属于 2092 IVR 语音配置需求 |
|
||||
| 发送策略配置 | 本期失败固定重试 2 次,不开放前台配置 |
|
||||
| 模板管理 | 中台不保存模板 |
|
||||
| 业务触发规则 | 中台不承载业务系统触发规则 |
|
||||
| 钉钉/飞书具体能力 | 仅保留入口 |
|
||||
|
||||
---
|
||||
|
||||
## 15. 实现要点
|
||||
|
||||
1. **前端路由**:按新菜单结构重排 `admin-web/src/router/index.ts`。
|
||||
2. **布局组件**:`Layout.vue` 从平铺 `el-menu-item` 改为分组 `el-sub-menu`。
|
||||
3. **权限控制**:菜单可见性通过 `authStore.roleCodes` 或后端返回的权限列表控制。
|
||||
4. **页面拆分与合并**:
|
||||
- 删除 `ChannelAccountInit.vue` 独立页面
|
||||
- 删除独立的渠道身份、账号-会话绑定页面
|
||||
- `Conversation.vue` 增强详情抽屉,包含绑定功能
|
||||
- `Person.vue` 增强详情抽屉,包含渠道身份
|
||||
5. **消息查询**:新增聊天界面组件。
|
||||
6. **入站流程**:增强节点配置,新增上下文存储能力。
|
||||
7. **空状态处理**:新增页面先实现最小可用版本。
|
||||
|
||||
---
|
||||
|
||||
## 16. 验收标准
|
||||
|
||||
- [ ] 左侧导航按新 6 大模块分组展示,无 IVR 流程、发送策略、账号初始化入口。
|
||||
- [ ] 每个二级菜单有明确页面,无 404。
|
||||
- [ ] 租户管理员、运营人员、平台管理员看到的菜单符合角色定义。
|
||||
- [ ] 联系人、会话详情抽屉涵盖所有关联内容。
|
||||
- [ ] 标签在列表和编辑表单中直接展示/编辑。
|
||||
- [ ] 消息查询实现聊天界面。
|
||||
- [ ] 入站流程支持多轮上下文与槽位收集。
|
||||
- [ ] 现有功能在迁移后行为保持一致。
|
||||
- [ ] 新增页面至少具备列表/查询能力,复杂编辑可二期补齐。
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
# channel-service 与 mci-server 双向集成设计
|
||||
|
||||
> **需求来源**:继续完成 pywechat 客户端功能,打通 Python 渠道服务与 Java 消息中台的双向通信。
|
||||
> **日期**:2026-07-10
|
||||
> **状态**:待实现
|
||||
> **关联文档**:`docs/architecture/2026-07-06-message-customer-interaction-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标与范围
|
||||
|
||||
### 1.1 目标
|
||||
|
||||
让 `channel-service`(Python FastAPI)与 `mci-server`(Java Spring Boot)完成双向 HTTP 集成:
|
||||
|
||||
- **下行(mci-server → channel-service)**:mci-server 继续通过现有 REST API 调用 channel-service 的发送、登录控制等指令。
|
||||
- **上行(channel-service → mci-server)**:channel-service 把心跳、登录/在线状态变更、发送结果、异常事件持久化到本地 SQLite,并由后台 worker 可靠地推送到 mci-server。
|
||||
- **可靠性**:服务重启不丢事件;网络异常按指数退避重试;超次进死信;本地事件保留 10 天后自动清理。
|
||||
|
||||
### 1.2 范围
|
||||
|
||||
- **channel-service**:新增事件模型、SQLite 持久化、后台事件发布 worker、mci-server HTTP 客户端、配置项扩展、API 行为调整。
|
||||
- **mci-server**:新增 `/api/v1/channel-account/event` 接收端点,处理心跳/状态/发送结果事件并更新 `mci_channel_account`、`mci_send_record`。
|
||||
- **不在本期范围**:
|
||||
- 真实 pywechat/pywinauto 接入(仍为 mock)。
|
||||
- RabbitMQ/MQ 等外部消息总线。
|
||||
- 入站消息从 channel-service 回传(本期只传运行态事件和发送结果)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ mci-server │
|
||||
│ Java 21 + Spring Boot 4.1.x │
|
||||
│ POST /api/v1/channel-account/event │
|
||||
└───────────────────────┬─────────────────────────────────────┘
|
||||
│ HTTP 上行事件
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ channel-service │
|
||||
│ Python 3.11 + FastAPI │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌──────────────────┐ ┌────────────┐ │
|
||||
│ │ HTTP API │───►│ 业务操作 + 事件 │───►│ outbound_ │ │
|
||||
│ │ │ │ 写入 SQLite │ │ events │ │
|
||||
│ └─────────────┘ └──────────────────┘ └─────┬──────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ EventPublisher │ │
|
||||
│ │ 单线程 worker │ │
|
||||
│ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ mci_server_client│ │
|
||||
│ │ HTTP POST │ │
|
||||
│ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
核心原则:**状态变更/发送操作与事件写入在同一个 SQLite 事务内完成**,保证原子性。
|
||||
|
||||
---
|
||||
|
||||
## 3. channel-service 设计
|
||||
|
||||
### 3.1 新增模块
|
||||
|
||||
| 模块 | 路径 | 职责 |
|
||||
|------|------|------|
|
||||
| 事件模型 | `app/models/event.py` | `OutboundEvent` 数据类、事件类型枚举、payload schema |
|
||||
| 事件存储 | `app/repositories/event_store.py` | SQLite 建表、CRUD、事务、清理 |
|
||||
| 事件发布 | `app/services/event_publisher.py` | 后台单线程 worker、重试、死信 |
|
||||
| mci-server 客户端 | `app/clients/mci_server_client.py` | HTTP POST 到 mci-server |
|
||||
| 配置扩展 | `app/config.py` | 新增事件存储、重试、清理相关配置 |
|
||||
|
||||
### 3.2 SQLite 事件表 `outbound_events`
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS outbound_events (
|
||||
id TEXT PRIMARY KEY,
|
||||
event_type TEXT NOT NULL,
|
||||
payload TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
attempt_count INTEGER NOT NULL DEFAULT 0,
|
||||
next_retry_at TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_error TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_status_next_retry
|
||||
ON outbound_events(status, next_retry_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_created_at
|
||||
ON outbound_events(created_at);
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | TEXT PK | UUID v4,即 `event_id` |
|
||||
| `event_type` | TEXT | `heartbeat` / `login_status_changed` / `online_status_changed` / `send_result` / `error` |
|
||||
| `payload` | TEXT | JSON 字符串,见第 3.3 节 |
|
||||
| `status` | TEXT | `pending` / `retrying` / `delivered` / `dead_letter` |
|
||||
| `attempt_count` | INTEGER | 已尝试次数,上限 `max_retry` |
|
||||
| `next_retry_at` | TIMESTAMP | 下次重试时间;`pending` 事件为创建时间 |
|
||||
| `created_at` | TIMESTAMP | 创建时间,用于 10 天清理 |
|
||||
| `updated_at` | TIMESTAMP | 更新时间 |
|
||||
| `last_error` | TEXT | 最近一次失败摘要 |
|
||||
|
||||
### 3.3 事件 payload 统一结构
|
||||
|
||||
所有事件统一外层:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_id": "evt_20260710_xxx",
|
||||
"event_type": "heartbeat",
|
||||
"account_id": "wx-bot-01",
|
||||
"tenant_code": "rescue",
|
||||
"timestamp": "2026-07-10T10:00:00Z",
|
||||
"data": { }
|
||||
}
|
||||
```
|
||||
|
||||
`data` 按事件类型定义:
|
||||
|
||||
#### heartbeat
|
||||
|
||||
```json
|
||||
{
|
||||
"login_status": "success",
|
||||
"online_status": "online",
|
||||
"risk_level": 10,
|
||||
"sent_count_today": 5,
|
||||
"server_host": "win-svr-01"
|
||||
}
|
||||
```
|
||||
|
||||
#### login_status_changed
|
||||
|
||||
```json
|
||||
{
|
||||
"previous": "pending",
|
||||
"current": "success",
|
||||
"qr_code_url": "https://mock.pywechat.qrcode/...",
|
||||
"error_message": null
|
||||
}
|
||||
```
|
||||
|
||||
#### online_status_changed
|
||||
|
||||
```json
|
||||
{
|
||||
"previous": "offline",
|
||||
"current": "online"
|
||||
}
|
||||
```
|
||||
|
||||
#### send_result
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": "task_001",
|
||||
"record_id": 1001,
|
||||
"conversation_id": "room-001",
|
||||
"success": true,
|
||||
"message_id": "msg-123",
|
||||
"channel_result": {},
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
> 说明:`record_id` 由 mci-server 在调用 send API 时传入;channel-service 原样回传,用于 mci-server 更新 `mci_send_record`。
|
||||
|
||||
#### error
|
||||
|
||||
```json
|
||||
{
|
||||
"operation": "send_text",
|
||||
"error_code": "ACCOUNT_OFFLINE",
|
||||
"error_message": "account wx-bot-01 not online"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 API 行为变更
|
||||
|
||||
| API | 变更 |
|
||||
|------|------|
|
||||
| `POST /v1/wechat-personal/login/start` | 创建/更新账号、生成二维码,并在同一事务写入 `login_status_changed` 事件 |
|
||||
| `POST /v1/wechat-personal/login/{id}/confirm` | 确认登录后,在同一事务写入 `login_status_changed` + `online_status_changed` |
|
||||
| `POST /v1/wechat-personal/login/{id}/expire` | 二维码过期后,写入 `login_status_changed` 事件 |
|
||||
| `POST /v1/wechat-personal/send/text` | 改为**异步受理**:立即返回 `{"accepted": true, "message_id": "..."}`;真实发送完成后写入 `send_result` 事件 |
|
||||
| `POST /v1/wechat-personal/send/image` | 同上 |
|
||||
| `POST /v1/heartbeat` | 更新本地状态,并在同一事务写入 `heartbeat` 事件 |
|
||||
| `POST /v1/admin/retry-dead-letter`(新增) | 手动把 `dead_letter` 状态重置为 `retrying`,用于人工恢复 |
|
||||
|
||||
### 3.5 发送异步化与风控
|
||||
|
||||
当前仍为 mock 客户端,但接口行为需与真实 pywechat 一致:
|
||||
|
||||
1. `send_text` / `send_image` 收到请求后,先校验账号在线、风控等级。
|
||||
2. 校验通过后返回 `accepted=true`,并立即写入 `send_result` 事件。
|
||||
3. mock 场景下该事件直接表示成功;真实 pywechat 场景下,发送完成后用同一 `event_id` 更新 `send_result` 事件内容,或删除旧事件后写入最终结果事件。
|
||||
|
||||
**风控处理**:
|
||||
|
||||
- 风控等级 `risk_level >= 80` 的账号,发送请求直接返回失败,不进入重试。
|
||||
- 因微信风控导致的发送失败(如 `error_code` 为 `RISK_CONTROL_BLOCKED`、`ACCOUNT_BANNED`),事件直接标记为 `dead_letter`,不再重试。
|
||||
- 因网络超时、mci-server 不可达等**临时错误**,才进入指数退避重试。
|
||||
- `sent_count_today` 每日重置,channel-service 在心跳中上报,mci-server 可据此做策略判断。
|
||||
|
||||
### 3.6 EventPublisher 后台 worker
|
||||
|
||||
- **单线程执行**:使用一个 `asyncio` 任务循环消费,避免并发导致 SQLite 锁竞争或事件乱序。
|
||||
- **扫描频率**:每 5 秒一次。
|
||||
- **每次拉取**:最多 100 条 `status IN ('pending','retrying') AND next_retry_at <= now()`,按 `next_retry_at` 升序。
|
||||
- **推送目标**:`{mci_server_url}/api/v1/channel-account/event`。
|
||||
- **成功**:`status='delivered'`,`attempt_count` 不变。
|
||||
- **失败**:
|
||||
- `attempt_count += 1`
|
||||
- `next_retry_at = now + base_delay * 2^(attempt_count - 1)`(首次重试为 `base_delay`,第二次为 `2*base_delay`,依此类推)
|
||||
- `status='retrying'`
|
||||
- `last_error` 记录 HTTP 状态码或异常摘要
|
||||
- 若 `attempt_count >= max_retry`,则 `status='dead_letter'`
|
||||
- **最大重试次数**:由配置 `max_retry` 控制,默认 `5`,上限 `10`(代码中硬上限,防止配置错误)。
|
||||
- **服务启动**:从 `main.py` 启动时创建 worker;启动后自动恢复 `pending` / `retrying` 事件。
|
||||
- **服务关闭**:优雅关闭时等待当前批次处理完成(超时 5 秒)。
|
||||
|
||||
### 3.7 数据清理
|
||||
|
||||
- 每天凌晨 3:00 启动清理任务(或每次 worker 循环顺带清理)。
|
||||
- 删除 `status IN ('delivered','dead_letter')` 且 `created_at < CURRENT_TIMESTAMP - INTERVAL '10 days'` 的事件。
|
||||
- 保留 `pending` / `retrying` 事件,无论创建时间。
|
||||
|
||||
### 3.8 配置项
|
||||
|
||||
```env
|
||||
# mci-server 地址
|
||||
MCI_SERVER_URL=http://localhost:8080/msg-platform
|
||||
|
||||
# SQLite 事件库路径
|
||||
EVENT_STORE_PATH=./data/events.db
|
||||
|
||||
# 心跳间隔(秒)
|
||||
HEARTBEAT_INTERVAL_SECONDS=30
|
||||
|
||||
# 最大重试次数(代码硬上限 10)
|
||||
MAX_RETRY=5
|
||||
|
||||
# 基础重试延迟(秒)
|
||||
BASE_RETRY_DELAY_SECONDS=5
|
||||
|
||||
# 事件保留天数
|
||||
EVENT_RETENTION_DAYS=10
|
||||
|
||||
# 后台 worker 扫描间隔(秒)
|
||||
EVENT_PUBLISH_INTERVAL_SECONDS=5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. mci-server 设计
|
||||
|
||||
### 4.1 新增端点
|
||||
|
||||
#### POST `/api/v1/channel-account/event`
|
||||
|
||||
接收 channel-service 推送的统一事件。
|
||||
|
||||
**请求头**:
|
||||
|
||||
```http
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**请求体**:见第 3.3 节统一 payload。
|
||||
|
||||
**处理逻辑**:
|
||||
|
||||
| event_type | 处理 |
|
||||
|------------|------|
|
||||
| `heartbeat` | 根据 `account_id` 查询 `mci_channel_account`;更新 `login_status`、`online_status`、`last_heartbeat`、`risk_level`、`sent_count_today` |
|
||||
| `login_status_changed` | 更新 `mci_channel_account.login_status`;如果 `current=success` 则同时更新 `online_status=1` |
|
||||
| `online_status_changed` | 更新 `mci_channel_account.online_status` |
|
||||
| `send_result` | 根据 `record_id` 查询 `mci_send_record`;更新 `send_status`(`success=true` 则为 2,否则 3)、`finish_time`、`channel_result`;成功后调用 `SendCallbackService.sendCallback()` 向业务系统回调 |
|
||||
| `error` | 记录告警日志,可扩展发送告警通知 |
|
||||
|
||||
**状态字段映射**(channel-service 字符串 → mci-server 整数):
|
||||
|
||||
| channel-service 字段 | mci-server 字段 | 映射关系 |
|
||||
|----------------------|-----------------|----------|
|
||||
| `login_status` | `mci_channel_account.login_status` | `pending=0`, `scanning=0`, `success=1`, `expired=2`, `failed=3` |
|
||||
| `online_status` | `mci_channel_account.online_status` | `offline=0`, `online=1`, `error=2` |
|
||||
|
||||
账号定位:使用 `tenant_code` + `account_id` 联合查询 `mci_channel_account`,确保租户隔离。
|
||||
|
||||
**幂等**:
|
||||
|
||||
- 使用 `event_id` 作为幂等键。
|
||||
- 第一次收到时写入 `mci_channel_event` 去重表;重复事件直接返回 200。
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "ok",
|
||||
"data": null,
|
||||
"request_id": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 保持现有端点
|
||||
|
||||
- `POST /api/v1/channel-account/heartbeat`:保持兼容,但建议新接入走 `/event`。
|
||||
- `POST /api/v1/channel-account/init`:继续调用 `WechatPersonalChannelAdapter`,由 adapter 调用 channel-service `/v1/wechat-personal/login/start`。
|
||||
|
||||
### 4.3 发送流程补齐
|
||||
|
||||
为了让 `send_result` 能回写到正确的 `mci_send_record`,需要在 mci-server 调用 channel-service 发送时传入 `record_id`:
|
||||
|
||||
1. `SendService.dispatchRecord(record)` 调用 `ChannelAdapter.sendMessage(request)`。
|
||||
2. `SendMessageRequest.extra` 中带上 `record_id`。
|
||||
3. `WechatPersonalChannelAdapter.sendMessage()` 把 `record_id` 放入 body,调用 channel-service `/v1/wechat-personal/send/text` 或 `/send/image`。
|
||||
4. channel-service 异步完成后,通过 `/event` 回传 `send_result` 并携带 `record_id`。
|
||||
|
||||
### 4.4 数据库表扩展
|
||||
|
||||
#### 4.4.1 新增幂等表 `mci_channel_event`
|
||||
|
||||
用于防止 channel-service 重试导致重复处理。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `event_id` | VARCHAR PK | channel-service 事件 ID |
|
||||
| `account_id` | VARCHAR | 账号 ID |
|
||||
| `event_type` | VARCHAR | 事件类型 |
|
||||
| `create_time` | DATETIME | 接收时间 |
|
||||
|
||||
索引:`CREATE INDEX idx_account_time ON mci_channel_event(account_id, create_time);`
|
||||
|
||||
清理策略:保留 30 天后删除(可配置)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误处理与边界
|
||||
|
||||
| 场景 | 处理 |
|
||||
|------|------|
|
||||
| mci-server 不可达 | 事件留在 SQLite,worker 按指数退避重试 |
|
||||
| 服务重启 | 启动时恢复 worker,继续推送 `pending` / `retrying` 事件 |
|
||||
| payload 序列化失败 | 直接标记 `dead_letter`,不阻塞其他事件 |
|
||||
| mci-server 返回业务错误(如账号不存在) | 视为失败重试;若连续失败达到上限,进 `dead_letter` |
|
||||
| 微信风控/账号被封 | 对应 `error_code` 直接标记 `dead_letter`,不重试 |
|
||||
| SQLite 磁盘满/损坏 | worker 记录错误并停止推送;监控告警 |
|
||||
| 单条事件推送超时 | 使用 `httpx` timeout(默认 10s),超时即失败进入重试 |
|
||||
| 重试次数达到上限 | `status='dead_letter'`,记录 `last_error`,等待人工处理或手动重试 |
|
||||
| 事件堆积 | worker 每批最多 100 条;如果持续堆积,考虑后续改为批量 POST 或引入 MQ |
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试策略
|
||||
|
||||
### 6.1 channel-service 单元测试
|
||||
|
||||
- `test_event_store.py`:验证 SQLite CRUD、事务、状态流转、清理。
|
||||
- `test_event_publisher.py`:mock mci-server HTTP,验证重试、死信、幂等。
|
||||
- `test_send_async.py`:验证 send API 改为异步受理后,事件正确写入 `outbound_events`。
|
||||
- `test_heartbeat_event.py`:验证心跳接口产生事件。
|
||||
|
||||
### 6.2 mci-server 单元测试
|
||||
|
||||
- `ChannelAccountEventControllerTest`:使用 MockWebServer 或 `MockMvc` 接收 channel-service 事件,验证:
|
||||
- heartbeat 更新 `mci_channel_account`
|
||||
- send_result 更新 `mci_send_record`
|
||||
- event_id 幂等
|
||||
|
||||
### 6.3 联调测试
|
||||
|
||||
- docker-compose 启动 mci-server + channel-service。
|
||||
- 调用 mci-server 发送 API。
|
||||
- 验证 channel-service 收到指令、产生 send_result 事件、推回 mci-server。
|
||||
- 验证 mci-server 更新 SendRecord 并向业务系统 callback_url 回调。
|
||||
|
||||
---
|
||||
|
||||
## 7. 部署与配置
|
||||
|
||||
### 7.1 channel-service 新增环境变量
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
MCI_SERVER_URL: http://mci-server:8080/msg-platform
|
||||
EVENT_STORE_PATH: /app/data/events.db
|
||||
HEARTBEAT_INTERVAL_SECONDS: "30"
|
||||
MAX_RETRY: "5"
|
||||
BASE_RETRY_DELAY_SECONDS: "5"
|
||||
EVENT_RETENTION_DAYS: "10"
|
||||
EVENT_PUBLISH_INTERVAL_SECONDS: "5"
|
||||
```
|
||||
|
||||
### 7.2 数据卷
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- channel-service-data:/app/data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 后续扩展
|
||||
|
||||
- 入站消息从 channel-service 回传 mci-server(复用 `/event` 新增 `message_received` 类型)。
|
||||
- 批量事件上报:把多条事件打包为一个 HTTP 请求,降低 mci-server 压力。
|
||||
- 当事件量增大时,可考虑用 RabbitMQ 替代 SQLite + HTTP worker。
|
||||
- 真实 pywechat 接入时,把 mock 客户端替换为 `pywechat`/`pywinauto` 驱动,事件模型保持不变。
|
||||
|
||||
---
|
||||
|
||||
*设计完成,待评审。*
|
||||
BIN
docs/verification/conversation-bind-account-popup.png
Normal file
BIN
docs/verification/conversation-bind-account-popup.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 241 KiB |
BIN
docs/verification/send-records-integration.png
Normal file
BIN
docs/verification/send-records-integration.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 344 KiB |
Reference in New Issue
Block a user