Files
EchoChat/docs/plans/2026-04-20-phase2e-design.md
bujinyuan 6913b9e72f docs(task14): 同步 Phase 2e-2 Task 14 进度至全量项目文档
- README.md:脚本表格新增 deploy-public.sh + full 子命令说明;
  "方式二" 改为 scripts/start.sh full + 指向 docs/deployment/meeting-mvp.md
- docs/api/README.md:meeting 模块状态标注 Task 5/6/7/13 + extra 8 字段
- docs/architecture/system-architecture.md:meeting 状态升至 Task 0-14 ;
  media-server / coturn 行同步标注 Task 14 双态编排落地
- docs/plans/2026-04-20-phase2e-design.md:最后更新 + 变更记录新增 04-24 条目
- media-server/README.md:后续任务新增 Task 9  + Task 14 ,指向部署指南

保证所有项目级 README / 架构 / 路线图文档都处于 Task 0-14  最新状态。

Made-with: Cursor
2026-04-22 18:04:20 +08:00

21 KiB
Raw Blame History

Phase 2e 设计文档:会议与通知系统

状态: 🚧 进行中2e-1 已完成2e-2 📋 设计阶段完成待进入编码2e-3 📋 待开发) 分支: feature/phase2e-meeting-notification2e-1feature/phase2e-2-meeting-mvp2e-2基于 origin/feature/phase2c-group-read-receipt 前置依赖: Phase 2a联系人 + WS、Phase 2b即时通讯、Phase 2c群聊+已读、Phase 2d消息类型扩展全部完成 最后更新: 2026-04-24Phase 2e-2 Task 0-14 已完成PoC / media-server 骨架 / REST / DDL / Go 骨架 / 会议 REST / WS 信令 / Go↔Node HTTP / 生命周期 / 前端 mediasoup / 会议前后端页面 / 会议聊天 / meeting_invite 卡片 / docker-compose 双态;剩 Task 15 UI 打磨 + Task 16 E2E 收官) 子阶段专用文档:

  • 2e-1docs/plans/2026-04-20-phase2e-1-design.md + docs/plans/2026-04-20-phase2e-1-implementation.plan.md 已完成,含 TabBar「我的」未读红点优化
  • 2e-2docs/plans/2026-04-21-phase2e-2-design.md16 章节)+ docs/plans/2026-04-21-phase2e-2-implementation.plan.md17 个 Task约 17 人日)

一、设计目标

基于 Phase 2a-2d 建立的 WebSocket + 消息 + 联系人 + 群聊基础设施,实现 MVP 第一期收官的两大核心能力:

  1. 统一通知系统:消除散落在好友/群聊/会议各模块的通知死角,提供"提醒 + 历史"双通道
  2. 多人音视频会议:基于 mediasoup SFU 架构支持即时会议MVP→ 预约会议 + 邀请(增强)

核心交付物(按子阶段):

  • Phase 2e-1 通知系统3-4 人日) 已完成:统一通知中心 + 11 种通知类型预留 + 跨模块 Pusher 接口 + TabBar「我的」未读红点
  • Phase 2e-2 会议 MVP(约 17 人日)📋 设计阶段完成mediasoup Node 媒体服务 + 即时会议≤8 人)+ 密码/邀请链接/通知邀请三合一 + 设备预览页 + 主持人四件套 + 会议内聊天 + 双态部署(本机/公网 coturn+ 桌面/手机响应式
  • Phase 2e-3 会议增强7-10 人日):预约会议 + 定时提醒 + 等候室/锁定会议 + 设备预览高级参数(降噪/回声/虚拟背景)

不包含(明确推迟):§九 后续规划清单


二、阶段拆分与路线图

┌──────────────────────┐   ┌──────────────────────┐   ┌──────────────────────┐
│  Phase 2e-1          │   │  Phase 2e-2          │   │  Phase 2e-3          │
│  通知系统            │─▶│  会议 MVP            │─▶│  会议增强            │
│  3-4 人日            │   │  10-14 人日          │   │  7-10 人日           │
│                      │   │                      │   │                      │
│ ✓ 好友/群聊事件通知  │   │ ✓ mediasoup Node     │   │ ✓ 预约会议+定时提醒  │
│ ✓ 系统广播(后端)   │   │ ✓ 即时会议≤8人   │   │ ✓ 会议邀请(复用 2e-1│
│ ✓ meeting_invite /   │   │ ✓ 音视频+主持人控制  │   │   通知类型)         │
│   meeting_reminder   │   │ ✓ 会议号/密码        │   │ ✓ 入会前预览         │
│   类型预留           │   │                      │   │                      │
└──────────────────────┘   └──────────────────────┘   └──────────────────────┘

拆分理由:

  • 2e-1 风险最低、收益最高:纯业务逻辑扩展,复用 Phase 2a-2c 的 WS 基础设施;能立即解决"好友申请无感知"等体验死角
  • 2e-2 是风险核心引入全新技术栈Node.js + mediasoup + WebRTC需独立周期聚焦
  • 2e-3 是收尾增强:依赖 2e-1邀请/提醒走通知通道)和 2e-2会议能运行做在最后

三、Phase 2e-1 详细设计(通知系统)

3.1 需求决策记录

决策项 选择 理由
推送样式 双通道(持久化入库 + 底部 mini-toast 不遗漏 + 不打扰,参照微信
主入口位置 「我的」Tab 顶部铃铛图标 + 数字徽标 不占用底部 Tab 位(已满 4 个)
列表组织 顶部 Tab 分类:全部 / 好友 / 群聊 / 会议 / 系统 结构清晰,便于筛选
保留期限 30 天(每日定时任务清理已读通知) 平衡存储与体验
历史追溯 不追溯,上线当天起新事件才入通知中心 无数据迁移风险
多端已读同步 暂不支持WS Hub 单端连接架构,同一用户仅最新连接有效) 架构约束,已推迟到 Phase 2f/二期
点击行为 Deep-link 按类型分发;群/会议邀请支持内联操作按钮 减少跳转步骤
Toast 点击 无交互(避免误触),仅 2 秒自动消失 防止误操作
通知分类开关 不做(统一打开) 简化 MVP有需要再加

3.2 通知类型枚举11 种,含 2e-2/2e-3 预留)

type 触发场景 触发模块 落地 Phase Deep-Link 跳转
friend_request 收到好友申请 contact 2e-1 pages/contact/request
friend_accepted 好友申请被接受 contact 2e-1 pages/contact/detail?id=<actor_id>
friend_rejected 好友申请被拒绝 contact 2e-1 无跳转(告知型)
group_invite 被邀请加入群聊 group 2e-1 内联接受/拒绝
group_join_request 收到入群申请(群管理员) group 2e-1 pages/group/join-requests?groupId=<target_id>
group_join_approved 入群申请被批准 group 2e-1 直接进入群会话
group_join_rejected 入群申请被拒绝 group 2e-1 无跳转(告知型)
group_kicked 被踢出群聊 group 2e-1 无跳转(告知型)
group_role_changed 被设/撤管理员、群主转让 group 2e-1 群详情页
system_broadcast 系统广播 notifyadmin 触发) 2e-1 仅后端 API 通知详情页
meeting_invite 会议邀请 meeting 2e-2 对接 内联加入/稍后
meeting_reminder 预约会议开始前 N 分钟 meeting 2e-3 对接 内联加入

3.3 数据库设计(新增 1 张表)

CREATE TABLE notify_notifications (
    id          BIGSERIAL    PRIMARY KEY,
    user_id     BIGINT       NOT NULL,
    type        VARCHAR(40)  NOT NULL,
    title       VARCHAR(200) NOT NULL,
    content     TEXT,
    extra       JSONB,
    actor_id    BIGINT,
    target_type VARCHAR(40),
    target_id   BIGINT,
    is_read     BOOLEAN      NOT NULL DEFAULT FALSE,
    read_at     TIMESTAMPTZ,
    created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_notify_user_unread ON notify_notifications(user_id, is_read, created_at DESC);
CREATE INDEX idx_notify_user_type   ON notify_notifications(user_id, type, created_at DESC);

字段说明:

  • extra:按类型存附加信息,例如 group_invite 存 {group_id, group_name, group_avatar, inviter_name}、meeting_invite 存 {room_code, room_title, host_name, scheduled_at}
  • actor_id:触发者(如好友申请发起人)
  • target_type + target_id关联对象friend_request / group / meeting / join_request

清理任务(每日 02:00 执行):

DELETE FROM notify_notifications WHERE created_at < NOW() - INTERVAL '30 days' AND is_read = true;

3.4 后端 API4 个)

GET  /api/v1/notifications                  列表query: type, is_read, before_id, limit
GET  /api/v1/notifications/unread-count     未读数统计(按 type 分组)
PUT  /api/v1/notifications/:id/read         标记单条已读
PUT  /api/v1/notifications/read-all         全部已读(可按 type 过滤)

响应示例GET /api/v1/notifications?limit=20

{
  "code": 0, "message": "success",
  "data": {
    "list": [
      {
        "id": 1001, "type": "group_invite",
        "title": "张三邀请你加入群聊「产品团队」",
        "content": "",
        "extra": { "group_id": 5, "group_name": "产品团队", "inviter_name": "张三" },
        "actor_id": 7, "target_type": "group", "target_id": 5,
        "is_read": false, "created_at": "2026-04-20T10:00:00Z"
      }
    ],
    "has_more": false
  }
}

3.5 WebSocket 事件2 个,单端连接架构

事件 方向 Payload 说明
notify.new S→C 完整通知对象 新通知到达,前端入 store + 更新徽标
notify.unread.total S→C { total: 10, by_category: { friend: 3, group: 5, ... } } 连接建立/断线重连时补偿推送(权威值覆盖本地)

说明:由于现有 ws.Hub 为单端连接(同一用户仅保留最新连接),不再设计 notify.read.ack 跨设备广播。多端已读同步作为独立技术债推迟到 Phase 2f详见 §九)。

3.6 跨模块 Pusher 接口(沿用 Phase 2a 接口注入标准)

// app/notify/service/pusher.go
type Pusher interface {
    Push(ctx context.Context, userID int64, req *PushRequest) error
}

type PushRequest struct {
    Type       string                 // 通知类型
    Title      string
    Content    string
    Extra      map[string]interface{}
    ActorID    int64
    TargetType string
    TargetID   int64
}

使用方示例contact 模块好友申请):

// contact/service/contact_service.go
s.notifyPusher.Push(ctx, receiverID, &notify.PushRequest{
    Type: "friend_request",
    Title: fmt.Sprintf("%s 请求添加你为好友", applicant.Nickname),
    Content: applicationMessage,
    ActorID: applicantID,
    TargetType: "friend_request",
    TargetID: requestID,
})

3.7 模块结构

backend/go-service/app/notify/
├── constants/notify_types.go              # 11 种 type 枚举常量
├── model/notification.go                  # GORM 模型
├── dao/notification_dao.go                # CRUD
├── service/
│   ├── notify_service.go                  # 业务逻辑:创建/标已读/清理
│   └── pusher.go                          # Pusher 接口 + Impl持久化 + WS 推送)
├── controller/notification_controller.go  # HTTP 接口
└── provider/wire.go                       # Wire 依赖注入

backend/go-service/app/dto/notify_dto.go   # DTO

3.8 前端页面

路径 变更 说明
store/notify.js 新建 Pinia Storestate/fetch/markRead/initWs
pages/notify/index.vue 新建 列表页 + 顶部 5 个分类 Tab + 下拉刷新 + 上拉加载
pages/profile/index.vue 修改 顶部添加「🔔 通知」入口 + 数字徽标
components/notify/NotifyItem.vue 新建 通知卡片(按类型渲染不同 UI支持内联操作按钮

3.9 Pinia Store 设计要点

// frontend/src/store/notify.js
export const useNotifyStore = defineStore('notify', () => {
  const notifications = ref([])          // 当前已加载的通知列表
  const unreadCount = ref(0)             // 总未读数TabBar 徽标)
  const unreadByType = ref({})           // 分类型未读数
  const hasMore = ref(true)
  const currentFilter = ref('all')       // all / friend / group / meeting / system

  // WS 监听notify.new / notify.unread.total单端架构无 notify.read.ack
  const initWsListeners = () => { ... }

  const fetchNotifications = async (filter, beforeId) => { ... }
  const markRead = async (id) => { ... }
  const markAllRead = async (type) => { ... }
})

四、Phase 2e-2 会议 MVP 范围锁定(摘要 + 引用专用设计)

📘 详细设计已独立出文档:

本节仅保留摘要,作为总路线图的衔接锚点。任何细节调整请以上述两份专用文档为准(本文不再跟随更新)。

4.1 范围摘要(与专用设计 §2.2 一致)

  • 即时会议(XXX-XXX-XXX 会议号)+ 可选密码bcrypt+ 邀请链接 + 通知中心 meeting_invite
  • ≤ 8 人音视频mediasoup SFU + simulcast 三档)
  • 入会前设备预览页(摄像头/麦克风/扬声器选择 + 本地预览 + 音量检测)
  • 主持人四件套:静音他人 / 移除成员 / 转让主持人 / 结束会议
  • 生命周期host 掉线 2 分钟宽限 + 自动转让(最早加入者)+ 空房 5 分钟 TTL
  • 会议内文字聊天(独立 meeting_chats24 小时后清理)
  • 桌面 + 手机双端响应式3×3 / 2×2 / 单列+抽屉)
  • 双态部署:本机 Docker Compose + 公网 announcedIp + coturn--profile public
  • 预约会议 / 提醒 / 等候室 / 锁定 → Phase 2e-3
  • 屏幕共享 / 录制 / 虚拟背景 → 第二期
  • 管理端会议列表 / 详情 / 强制关闭 → Phase 2f

4.2 技术选型(锁定)

组件 选型 备注
媒体服务目录 media-server/ 根级子项目(与 backend/ / frontend/ / admin/ 并列) TypeScript + Fastify + mediasoup v3
客户端库 mediasoup-client JS SDK markRaw 包裹避免 Pinia 响应式代理
信令通道 复用现有 WebSocket Hub 新增 MeetingSignalDispatcher 接口注入
Go ↔ Node HTTP REST容器内网X-Internal-Token 鉴权) 9 个 APIRouter/Transport/Producer/Consumer 生命周期
数据库表 meeting_rooms + meeting_participants + meeting_chats 在总设计基础上修订:password_hashbcrypt + ended_reason + meeting_chats 新表
Redis 键 echo:meeting:room:{code} / members:{code} / transport:{code}:{user_id} / invite:{token} / host_grace:{code} 后两个为新增

4.3 WS 信令事件11 个,详见专用设计 §6.3

房间组 3 个 + 成员组 4 个 + 媒体组 5 个,命名统一 meeting.* 前缀。

4.4 架构变化关键点(衔接 Phase 2e-1

  • Go 主控 + Node 无状态包装:所有权威状态在 GoNode 仅做 mediasoup HTTP 封装
  • 跨模块通信模式延续meeting.service.NotifyPusher 接口 ← 由 notify.service.NotifyService 实现Wire 注入(与 Phase 2a 的 ws.FriendIDsGetter / Phase 2e-1 的 contact.service.NotifyPusher 完全同构)
  • 单端 WS 连接架构:继续沿用,不因会议而改造;多端改造仍保留给 Phase 2f

五、Phase 2e-3 会议增强范围锁定

注:原计划在 2e-3 的「会议邀请(meeting_invite)」与「入会前设备预览」已上移到 Phase 2e-2(见 Phase 2e-2 专用设计 §2.2.1 与 §十。Phase 2e-3 聚焦「预约 / 提醒 / 等候室 / 锁定」。

  • 预约会议(meeting_rooms.type=2+ 前端预约表单
  • 定时器:到预约时间前 N 分钟触发 meeting_reminder 通知
  • 等候室waiting room+ 锁定会议lock
  • 设备预览高级参数:降噪 / 回声消除 / 虚拟背景(若浏览器支持)

六、Pusher 调用点汇总Phase 2e-1 必须改动的现有代码)

文件 变更类型 说明
app/contact/service/contact_service.go 新增 Pusher 注入 + 4 处调用 申请/接受/拒绝 3 个事件
app/group/service/group_service.go 新增 Pusher 注入 + 6 处调用 邀请/入群申请/审批/踢人/角色变更
app/provider/wire.go 注册 notify 模块依赖 Wire 依赖注入
frontend/src/store/contact.js 删除冗余的 notify.friend.request 处理(由 notify store 接管) 避免重复提示
frontend/src/store/group.js 删除 _onJoinRequest toast由 notify store 统一) 避免重复提示
frontend/src/components/CustomTabBar.vue 保持不变 联系人 Tab 徽标仍显示"待处理"数,与通知中心徽标并存

重要兼容性约束

  • 联系人 Tab 徽标pendingCount保留不变,继续承担"待办事项"入口
  • 通知中心徽标仅计"未读通知",两套红点语义不同,互不影响

七、风险与应对

风险 等级 应对
Pusher 调用失败导致业务阻塞 采用"异步推送"Pusher 调用失败仅记日志,不阻塞业务主流程
通知表膨胀 30 天清理任务 + user+is_read 索引 + 按用户分表留作未来优化
多端已读同步消息风暴 不适用:当前 ws.Hub 单端连接架构已不做多端同步,该风险天然规避
mediasoup2e-2技术深度 单独做技术 Spike必要时先搭 PoC 验证
Docker Compose 增加 Node 服务后的资源占用 设置资源限额,文档化本机运行要求

八、验收标准Phase 2e-1 已完成

  • 11 种通知类型中的 10 种(除 meeting_invite/meeting_reminder)都能正确触发并落库
  • 用户 A 给 B 发好友申请 → B 端 WS 实时收到 notify.new通知中心出现记录「我的」Tab 红点 +1
  • 多设备登录同一账号同步架构限制不支持(单端 WS Hub已在 §3.1/§3.5 修订;仅当前连接设备可感知实时更新,其他设备依赖下次重连时的 notify.unread.total 补偿
  • 30 天清理任务能正确运行,已读过期通知被删除
  • 通知中心列表按 5 个分类 Tab 筛选正确
  • 群邀请通知卡片支持内联"接受/拒绝"按钮并触发正确业务逻辑
  • 验证清单:test-report-phase2e-1-notification.mdPlaywright 自动化推迟到 CI 建设阶段Phase 2f 统一接入)

九、后续规划清单(必须留档)

9.1 推迟到 Phase 2fMVP 收尾 + 管理端扩展)

功能 原计划阶段 说明
WS Hub 多端连接支持改造 2e-1 当前 ws.Hubclients map[int64]*Client 仅支持单连接,新登录会踢掉旧设备。需改为 map[int64]map[deviceID]*Client 并适配 PublishToUser / ws.OnlineService / Pusher 等下游消费者
多端已读同步notify.read.ack 2e-1 WS Hub 多端就绪后,再补 notify.read.ack 事件广播给同一用户的其他设备
管理端会议列表/详情/强制关闭 2e /api/v1/admin/meetings* 已在总设计文档定义,仅缺实现
管理端会议统计仪表板 2e /api/v1/admin/meetings/stats
管理端通知广播发布 UI 2e-1 后端 API 已就绪,仅缺前端表单页
管理端用户会议记录 2e /api/v1/admin/users/:id/meetings
管理端操作日志页面 2e 数据表 admin_operation_logs 已设计
管理端仪表板总览 2e /api/v1/admin/dashboard
系统配置管理 2e /api/v1/admin/system/config
通知分类开关设置 2e-1 用户级通知偏好push/不push
Playwright E2E 自动化 CI 2e-1 当前仅 test-report-*.md 手动验证CI 接入需评估整套 e2e 基础设施

9.2 推迟到第二期

功能 说明
屏幕共享 mediasoup Producer 扩展为 screen 类型
会议录制与回放 需 mediasoup 录制插件 + 对象存储MinIO 已就绪)
虚拟背景 / 背景模糊 客户端 WebRTC 滤镜
微信授权登录 OAuth + UnionID
互动直播(主播/观众/弹幕) 独立直播流架构
消息撤回时间延长 / 管理员无时限撤回的审计日志 ——
视频消息type=4 Phase 2d 已显式推迟
表情包 / 自定义贴纸 Phase 2d 已显式推迟
消息转发 / 合并转发 / 引用回复 Phase 2d 已显式推迟

9.3 推迟到第三期

  • 微服务拆分auth / im / meeting / notify 拆独立服务)
  • Kubernetes 部署编排
  • 跨服务器会议(多 mediasoup Worker 集群 + Router Pipe
  • AI 辅助:语音转文字、会议纪要、智能摘要

十、文档同步与变更记录

日期 变更
2026-04-20 Phase 2e 规划完成:拆分为 2e-1/2e-2/2e-3 三个子阶段;本文档落盘
2026-04-20 Phase 2e-1 实施完成:落实 notify 模块(后端 11 种类型 / 5 REST + 2 WS / 30 天清理)+ 前端通知中心§3.1/§3.5/§八/§九 同步修订「单端 WS 连接」约束与推迟项
2026-04-24 Phase 2e-2 推进 Task 0-14 全部完成mediasoup PoC / media-server 9 REST / 3 表 DDL + DAO / Go 12 REST + WS 13 事件 / HTTPMediaOrchestrator / 生命周期状态机host 宽限 + 自动转让 + 空房 TTL/ 前端 mediasoup-client + Pinia store / 会议主页面Hub/Create/Join/Preview/Room+ 核心组件 / 会议内聊天 / meeting_invite 通知卡片(含过期态 + deep-link/ docker-compose 双态扩展media-server + coturn public profile+ 三份 .env 模板 + deploy-public.sh + 部署指南。剩 Task 15 UI 打磨 + Task 16 E2E 收官