feat(phase2e-2): 落地 HTTPMediaOrchestrator 打通 Go↔Node 媒体链路(Task 7)

- 新增 MediaServerConfig + config.{dev,docker}.yaml 的 media_server 段
  (base_url / internal_token / timeout_ms / close_timeout_ms / close_retry)
- 新建 HTTPMediaOrchestrator(8 方法 + sync.Map 缓存 roomCode→routerID
  + 关闭类指数退避重试 200/500ms + ErrMediaResourceNotFound/ErrMediaServerError
  两类错误)实现 MediaOrchestrator 接口
- wire 绑定由 NoopMediaOrchestrator 切换到 HTTPMediaOrchestrator;
  MeetingService / MeetingSignalService 调用侧零改动
- E2E 脚本 docs/verify/meeting_t7_verify.mjs 证明 16/16 PASS:
  健康检查/错token 401/REST 创房加入/WS room.join/真实 mediasoup
  transport.create(ICE/DTLS 指纹均由 Node 返回非占位)/404 幂等关 producer
  /host 结束会议触发 CloseRouter
- 同步更新 13 份文档:CURRENT_STATUS / implementation.plan / design
  变更记录 / project-context / api 导览 / api websocket / api frontend meeting
  / system-architecture / media-server README / 顶层 README 等
- go build ./... 全绿

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-04-21 17:47:11 +08:00
parent dfd9b6011c
commit 55d6352c6a
19 changed files with 795 additions and 46 deletions

View File

@@ -17,7 +17,7 @@
| [frontend/websocket.md](frontend/websocket.md) | WebSocket | ✅ Phase 2a | 前端 WebSocket 连接管理、事件协议、心跳、重连 |
| [frontend/im.md](frontend/im.md) | 即时通讯 | ✅ Phase 2b | 7 个 API会话列表/置顶/删除/清空、历史消息、全局搜索、未读数 |
| [frontend/group.md](frontend/group.md) | 群聊管理 | ✅ Phase 2c | 16 个 API建群/管理/成员/角色/禁言/公告/搜索/入群审批 |
| [frontend/meeting.md](frontend/meeting.md) | 会议 | ✅ Phase 2e-2 Task 5/6 | 12 个 REST 接口(创建/加入/离开/结束/详情/列表/邀请/踢人/转让主持人/发起聊天/拉聊天历史/邀请链接兑换)+ 13 个 WebSocket 信令事件8 C→S + 5 核心 S→C 广播 + 会议内聊天/被踢定向推送Redis 媒体资源追踪 + host 权限校验 |
| [frontend/meeting.md](frontend/meeting.md) | 会议 | ✅ Phase 2e-2 Task 5/6/7 | 12 个 REST 接口(创建/加入/离开/结束/详情/列表/邀请/踢人/转让主持人/发起聊天/拉聊天历史/邀请链接兑换)+ 13 个 WebSocket 信令事件8 C→S + 5 核心 S→C 广播 + 会议内聊天/被踢定向推送Redis 媒体资源追踪 + host 权限校验Task 7 (2026-04-21) 已接入真实 mediasoup返回值为 Node media-server 真实 Transport/Router/DTLS 参数 |
| [frontend/notify.md](frontend/notify.md) | 通知中心 | ✅ Phase 2e-1 | 5 个 API通知列表游标分页/未读数/标记已读/全部已读/管理员广播 + 2 个 WS 事件notify.new/notify.unread.total |
### 后台管理端 (`admin/`)
@@ -199,7 +199,7 @@ docs/api/
│ ├── websocket.md # WebSocket 事件协议 ✅ Phase 2a
│ ├── im.md # 即时通讯8 个 API ✅ Phase 2b/2c
│ ├── group.md # 群聊管理16 个 API ✅ Phase 2c
│ ├── meeting.md # 会议12 REST + 13 WS 事件) ✅ Phase 2e-2 (Task 5/6)
│ ├── meeting.md # 会议12 REST + 13 WS 事件) ✅ Phase 2e-2 (Task 5/6/7真实 mediasoup)
│ └── notify.md # 通知5 API + 2 WS 事件) ✅ Phase 2e-1
├── admin/ # 后台管理端 API
│ ├── auth.md # 管理员认证 ✅ Phase 1

View File

@@ -3,7 +3,7 @@
> 通用规范(认证、响应包络、通用错误码)见 [README.md](../README.md)
> 会议内实时信令Transport / Producer / Consumer / 控制事件)通过 WebSocket 完成,见 [websocket.md](../websocket.md)
**实施状态**:本文档对应 Phase 2e-2 Task 5 / Task 6 已落地的 12 个 REST 接口 + 13 个 WebSocket 信令事件,统一前缀 `/api/v1/meeting`REST`/ws`WebSocket全部需要 JWT 认证。Task 5 完成时间2026-04-21Task 6 完成时间2026-04-21
**实施状态**:本文档对应 Phase 2e-2 Task 5 / Task 6 / Task 7 已落地的 12 个 REST 接口 + 13 个 WebSocket 信令事件,统一前缀 `/api/v1/meeting`REST`/ws`WebSocket全部需要 JWT 认证。Task 5/6/7 完成时间2026-04-21。自 Task 7 起 **Go 后端直连 Node media-server**`transport.id` / `iceCandidates` / `dtlsParameters.fingerprints` 等字段均由真实 mediasoup 返回(不再是 stub 占位)
**设计口径**:以 [`docs/plans/2026-04-21-phase2e-2-design.md`](../../plans/2026-04-21-phase2e-2-design.md) §6.2 为单一事实来源SSOT
@@ -198,7 +198,7 @@
}
```
`router_id` 当前为 Noop 占位Task 7 接入 Node media-server 后改为真实 mediasoup Router ID前端据此建立 WebSocket 订阅
`router_id` Task 7 起为 Node media-server 返回的真实 mediasoup Router IDPhase 2e-2 Task 7 完成2026-04-21)。前端据此建立 WebSocket 订阅也可忽略仅依赖 WS `meeting.transport.create` 的返回值创建 mediasoup-client Transport
---
@@ -606,7 +606,7 @@
- **MeetingWSHandler**controller thin adapter仅负责 ws.Hub 事件注册 + JSON 反序列化 + ACK 回写位于 `app/meeting/controller/meeting_ws_handler.go`
- **MeetingSignalService**service 承载 8 CS 事件的业务逻辑活跃参会校验host 权限校验mediaOrchestrator 调用Redis 资源追踪广播位于 `app/meeting/service/meeting_signal_service.go`
- **MeetingBroadcaster**service 封装 `BroadcastToMeeting`查询活跃 participant 列表 逐个 `PubSub.PublishToUser` `PublishToUser` REST / WS 两个入口统一使用位于 `app/meeting/service/meeting_broadcaster.go`
- **MediaOrchestrator**interface定义 9 mediasoup 操作方法Task 6 使 `NoopMediaOrchestrator` 占位返回 stub IDsTask 7 替换为 `HTTPMediaOrchestrator` 对接 Node media-server
- **MediaOrchestrator**interface定义 8 mediasoup 操作方法 9 `ResumeConsumer` 推迟到 Task 9Task 6 `NoopMediaOrchestrator` 占位**Task 7 (2026-04-21) 替换为 `HTTPMediaOrchestrator`**通过 `X-Internal-Token` 鉴权直连 Node media-server `/internal/v1/*` 接口错误类型 `ErrMediaResourceNotFound`Node 404 关闭类幂等转 nil `ErrMediaServerError`5xx / 超时 / 网络错可供上层 `errors.Is` 区分关闭类操作指数退避 200ms500ms 最多 `CloseRetry+1`
### 错误处理
@@ -632,11 +632,18 @@
- `meeting.producer.close` + `meeting.member.producer.new closed=true` 广播
- `meeting.room.leave` + `meeting.member.left` 广播
- 不存在会议号 `meeting.room.join` ACK `code=-1`
- **Task 7** 端到端验证脚本`docs/verify/meeting_t7_verify.mjs`结果**16/16 PASS**覆盖
- media-server `/healthz` + `/internal/info`正确 token 200错误 token 401
- 注册登录 2 用户 host 创建会议HTTP 201 + 内部调 `CreateRouter`)→ 第二用户 REST 加入再次 `CreateRouter`
- 双端 WS `meeting.room.join`
- `meeting.transport.create`direction=send返回**真实** mediasoup `id` `noop-` 前缀+ `iceParameters` 对象 + `iceCandidates[]` 非空 + `dtlsParameters.fingerprints[]` 非空
- 虚构 producer_id `meeting.producer.close` Node 返回 404 Go 幂等转 `code=0`
- host `POST /rooms/:code/end` 触发 `CloseRouter`media-server 日志确认 `router closed explicitly`
---
## 后续任务关联
- **Task 7**Go Node HTTP 客户端 `NoopMediaOrchestrator` 替换为 `HTTPMediaOrchestrator`接入真实 mediasoup Router此时 WS 白名单事件契约与本文档完全不变 `iceParameters / producer_id` 等字段由 stub 变为真实值
- **Task 8**Vue 前端 mediasoup-client 接入按本文 WS 契约实现 `mediasoup.Transport` `connect / produce` 回调
- **Task 8**会议生命周期状态机host 宽限期 + 自动转让 + 空房 TTL同时修复"CreateRoom + JoinRoom 重复调 `CreateRouter`"遗留行为
- **Task 9**Vue 前端 mediasoup-client 接入按本文 WS 契约实现 `mediasoup.Transport` `connect / produce` 回调补齐 `ResumeConsumer`Node REST 已就绪或在 Go 侧改为创建 Consumer 后自动 resume
- **Task 13**通知卡片 UI 补齐 `meeting_invite` 内联按钮

View File

@@ -203,7 +203,7 @@
---
## 会议信令事件Phase 2e-2 Task 6 已落地)
## 会议信令事件Phase 2e-2 Task 6 已落地Task 7 起 mediasoup 返回值为真实值
> **SSOT**:会议相关的全部 WS 事件详细契约(请求/ACK/广播载荷、权限、错误处理、资源追踪)见 [`docs/api/frontend/meeting.md`](./frontend/meeting.md) §WebSocket 信令协议。本节仅列事件总览。
@@ -232,6 +232,7 @@
- **ACK 规则**:每个 C→S 事件服务端必回 `<event>.ack`;成功 `code=0`,业务失败 `code=-1` + 中文 `message`(与 REST 领域错误口径一致)。
- **错误码示例**`会议不存在` / `你当前未在会议中` / `仅主持人可执行此操作` / `会议已结束`
- **资源追踪**:服务端对每用户在每会议的 transport/producer/consumer 用 Redis Set `echo:meeting:resources:{room_id}:{user_id}` 记录WS 断开或 `room.leave` 时自动清理。
- **真实 mediasoupTask 7 起)**`meeting.transport.create` ACK 的 `id` / `iceParameters` / `iceCandidates` / `dtlsParameters`,以及 `meeting.produce.start` / `meeting.consume.start` 的 ID 均来自 Node media-server 真实 mediasoup WorkerNode 404如关闭不存在 producer在 Go 侧幂等转 `code=0`
---