Files
EchoChat/test-report-phase2e-2-meeting.md
bujinyuan 2452ca7332 docs(phase2e-2): Task 16 收官 · 同步 6 份文档 + 落盘 test-report-phase2e-2-meeting.md
Phase 2e-2 会议 MVP 整体切 (Task 0-16 全部完成),本提交是 Task 16 最后一步
"文档同步"批次,配合历次 commit(cdaa39d P0 / ea2bf96 P1 / f5ae095 资源生命周期 /
5ed14c2 E2E 剧本 / c35097a P2+Nit)完成阶段收官。

同步范围:
- docs/progress/CURRENT_STATUS.md
  · 顶部元信息:"当前阶段"从"代码开发阶段 🚧"切为"已完成 "
  · 新增 2026-04-24 Task 16 收官小节:资源生命周期专项 / Playwright 5 场景 /
    P2+Nit 收尾 / 完成度汇总(Task 0-16 + 审查 4 P0 + 8 P1 + 7 P2 + 7 Nit)
- docs/plans/2026-04-21-phase2e-2-implementation.plan.md
  · 状态切 ;Task 16 行切  + 实际交付小节(5 commits + 5 场景剧本 + 6 份文档)
- docs/plans/2026-04-20-phase2e-design.md
  · Phase 2e 顶层状态:2e-2 切 ;最后更新写明 Task 16 收官
- docs/reviews/2026-04-23-phase2e-2-code-review.md
  · (历次 commit c35097a 已追加)"Task 16 修复追踪"小节包含已修复表 + 推迟登记表
- .cursor/rules/project-context.mdc
  · Phase 2e 进度条更新;新增 Task 15 / 16 两个完整 bullet(Task 16 含 5 commit 详解)
- test-report-phase2e-2-meeting.md(新增)
  · 八章完整验证报告:测试环境 / 构建 / 功能 / E2E 剧本 / 代码审查修复 /
    部署双态 / 已知限制 / 验收结论

构建&清理复核(均在 Task 16 历次 commit 中验证通过):
- go vet ./... / go build ./... / npm run build:h5 / npx tsc --noEmit 全绿
- console.log / TODO / FIXME 扫描 0 残留

Made-with: Cursor
2026-04-23 17:53:54 +08:00

17 KiB
Raw Blame History

Phase 2e-2 会议 MVP · 测试验证报告

创建日期2026-04-24 分支:feature/phase2e-2-meeting-mvp(基于 feature/phase2c-group-read-receipt 衍生) 范围Phase 2e-2 会议 MVP 全部 17 个 Task0-16覆盖 mediasoup media-server + Go meeting 模块 + 前端会议主链路 + 代码审查 4 P0 / 8 P1 / 7 P2 / 7 Nit 修复闭环 相关文档:docs/plans/2026-04-21-phase2e-2-design.mddocs/plans/2026-04-21-phase2e-2-implementation.plan.mddocs/reviews/2026-04-23-phase2e-2-code-review.md.playwright-mcp/task16/scenarios/


一、测试环境

组件 版本/路径 启动方式
Backend Go 服务 backend/go-service/ go run cmd/server/main.go(端口 8085
Node Media 服务 media-server/ npm run dev(端口 3300mediasoup RTC 40000-40199 UDP/TCP
Frontend 用户端 frontend/uni-app H5 npm run dev:h5HTTPS + 自签证书getUserMedia 要求 secure context
PostgreSQL 14+ deploy/docker/postgres/{init.sql,phase2e2_migration.sql} 初始化
Redis 7+ JWT Token / WS Pub/Sub / 会议生命周期 key / 聊天限流 / 资源追踪
MinIO 8.0+ 对象存储(本阶段仅作依赖启动,未新增 bucket 使用)
coturn 4.6+(可选) docker compose --profile public up -d coturn 公网部署态启用

DDLmeeting_rooms / meeting_participants / meeting_chats 三张表 + meeting_invite_redemptions 辅助(见 phase2e2_migration.sql)。 媒体端口mediasoup WebRTC 40000-40199 UDP+TCPcoturn STUN/TURN 3478 UDP+TCP + TLS 5349 + relay 49152-65535 UDP。


二、构建验证

2.1 Go 服务

cd backend/go-service && go vet ./... && go build ./...
# 输出:无报错

Wire 注入链路已同步:ws.ProvideWSHandler 新增 *config.ServerConfig 入参CheckOrigin 白名单需要),provider.go 新增 provideServerConfig 并注册到 InfraSetwire_gen.go 重生成通过。

2.2 Node media-server

cd media-server && npx tsc --noEmit
# 输出:无报错

DELETE /internal/v1/transports/:id 新路由transport 闭环清理)+ internal-auth isPrivatePath 按 path 匹配(剔除 query/hash均通过类型检查。

2.3 前端 H5 构建

cd frontend && npm run build:h5
# 输出DONE Build complete.(仅遗留 uni-app legacy JS API / dynamic import 历史告警0 本阶段新增 warning

2.4 遗留 console / TODO / FIXME 扫描

rg -n 'console\.log' frontend/src | wc -l   # 9全部为 WS 生命周期日志入口 + 默认 logger 包装)
rg -n 'TODO|FIXME' backend/go-service/app/meeting media-server/src frontend/src/pages/meeting
# 0

三、功能验证清单

3.1 会议核心 REST API12 个接口)

# 接口 方法 验收点 状态
1 POST /api/v1/meeting/rooms 发起人 9 位会议号 + password_hashbcrypt+ router_id/rtp_capabilities 返回
2 GET /api/v1/meeting/rooms/:code 任一登录用户 房间信息 + host 信息 + 成员数量
3 GET /api/v1/meeting/rooms/:code/participants 成员 活跃成员列表(含 role
4 POST /api/v1/meeting/rooms/:code/join 用户 密码校验 + 单点参会 + Router 复用
5 POST /api/v1/meeting/rooms/:code/leave 成员 host 离会自动转让最早加入者 + 空房转 empty_ttl
6 POST /api/v1/meeting/rooms/:code/end host 事务 + SELECT FOR UPDATE 快照活跃成员 + MarkEnded + LeaveAllActiveP1-4 修复)
7 POST /api/v1/meeting/rooms/:code/transfer-host host 事务合并 role + host_id 双更新P1-1 修复)
8 POST /api/v1/meeting/rooms/:code/kick host 广播 meeting.member.kicked + 被踢者 lastEndedReason=kicked
9 POST /api/v1/meeting/rooms/:code/chats 成员 服务端 utf8 500 字符上限 + Redis INCR 30 条/60s 限流P2-7 修复)
10 GET /api/v1/meeting/rooms/:code/chats?before=&limit= 成员 游标分页P1-2 修复:由 GORM 直查改走 DAO
11 POST /api/v1/meeting/rooms/:code/invites host 邀请 token 仅走 NotifyPusher 下发,不回写调用方
12 POST /api/v1/meeting/invite-tokens/:token/redeem 用户 Redis TTL 600s + expired_at 随 payload 下发
GET /api/v1/meeting/rooms/mine 用户 meeting_rooms JOIN meeting_participants + DISTINCT + 游标分页P1-3 修复N+1 消除)

3.2 WebSocket 信令协议14 事件)

方向 事件 验收点 状态
C→S meeting.room.join ACK + 同步补推 existing producers / member statesP1-8 修复:改为 join 返回前补推)
C→S meeting.room.leave 仅清 media 资源,不删 participant 行
C→S meeting.member.state.changed 支持 target_user_id 主持人静音他人host-only 校验)
C→S meeting.transport.create / .connect 归属校验走 Redis SetP0-1 修复)
C→S meeting.produce.start Redis Set 追加 producer:<id> 便于清理
C→S meeting.consume.start / .resume transport_id 归属校验P0-2 修复)
C→S meeting.producer.close 只能关自己的 producerP0-1 修复)
S→C meeting.member.joined / .left / .kicked 广播时机与 REST 顺序归一
S→C meeting.member.state.changed 含自己广播,确保 history 回放正确
S→C meeting.member.producer.new closed 布尔字段驱动前端 _onProducerNew
S→C meeting.host.changed 主动/被动转让统一口径
S→C meeting.room.ended reason 映射前端 MEETING_ENDED_REASON_LABELP2-8 修复:补 kicked

3.3 会议生命周期状态机Task 8

场景 预期 状态
host WS 掉线 120s 内重连 host_grace key 存在local timer + Redis key 双保险取消
host WS 掉线超过 120s HandleHostGraceExpired 独立 SETNX 锁抢占P1-7 修复)+ 自动转让最早加入者
全员离开后 300s 空房 empty_ttl TTL 过期 → HandleEmptyRoomExpiredMarkEnded(empty_ttl_expired) + 清 Redis 残留
空房 TTL 期间有人入会 CancelEmptyTTL 撤销定时器Router 复用
4 小时 stale 活跃房 MeetingCleanupTask 扫表 MarkEnded(system_error)
go-service 重启 RescheduleFromRedis 按 PTTL 重建 local timer

3.4 前端主链路Task 9-15

模块 验收点 状态
utils/mediasoup-client.js Device/Transport/Producer/Consumer 全生命周期 + ensureSendTransport/ensureRecvTransport in-flight Promise 锁 + markRaw 防代理
store/meeting.js 本地状态机 + 14 WS 事件桥 + 20+ action + _reset 清空所有 Map/Set + _pendingBroadcastTimers 集中清理
pages/meeting/preview.vue 设备预览 + previewSeq 序号防竞态 + 200ms 切换防抖P2-2 修复)
pages/meeting/room.vue 会议室主页 + 6 项原创 UI + onLoad redirectTo 后 returnP2-3 修复)
pages/meeting/join.vue + preview.vue 密码 in-memory storeP0-4 修复:不再拼 URL query
components/meeting/MemberPanel.vue 主持人四件套(请他静音/开麦/转让/踢出)
components/meeting/SelfVideoFloat.vue 桌面 280×180 恒浮窗 + 四角吸附 + 图钉切换
components/meeting/NetworkBadge.vue 3 条 SVG 波浪 + 相位错开 + @keyframes wave-flow
components/meeting/ChatPanel.vue 会议内聊天 + WS ACK Promise 化 + REST 列表游标分页

四、Playwright MCP E2E 剧本

位置:.playwright-mcp/task16/scenarios/

编号 文件 覆盖要点
01 01-create-and-join-by-code.md 发起会议 + 会议号加入 + 互推音视频 + 结束会议
02 02-invite-link-with-password.md 邀请链接 + 密码入会 + 密码错 / 会议已结束边界
03 03-notify-quickjoin.md 通知中心"立即加入"一键入会(meeting_invite 卡片)
04 04-host-toolkit-and-transfer.md 主持人四件套 + 主动转让 + 掉线 120s 被动自动转让
05 05-reopen-5times-resource-cleanup.md 连续 5 次开/散会压测 Redis resourceTrackKey + mediasoup Router + 定时器无泄漏
manual-regression.md 纯手工验收点(浏览器权限/移动端横竖屏/设备热插拔)

回归执行方式:本地启动 docker compose up -dmedia-server npm run devbackend go run cmd/server/main.gofrontend npm run dev:h5HTTPS 模式) → 两浏览器分别登录两账号 → 按剧本逐步点击验证。剧本支持直接交给 Playwright MCP 驱动半自动回归。

Task 15 已归档的 7 屏截图回归.playwright-mcp/task15/{01..07}-*.png(首页 / 设备预览 / 空场浮窗+波浪 / 单人静音 / 流光+柔性 3-tri+氛围色 / 图钉切回 4 人网格 / 主持人菜单条件渲染),作为 UI 视觉基线保留。


五、代码审查修复验证Task 16 核心交付)

5.1 code-reviewer 全栈审计

  • 调用 code-reviewer 子代理对 Go meeting 模块 + media-server + frontend meeting + Task 15 三轮回归补丁做一次重写版全栈审计
  • 输出:docs/reviews/2026-04-23-phase2e-2-code-review.md4 P0 + 8 P1 + 8 P2 + 11 Nit
  • 亮点章节Redis Hash member_state 补推机制、HTTPMediaOrchestrator sync.Map + LoadOrStore 幂等、mediasoup-client ensureSendTransport/ensureRecvTransport in-flight 锁、MeetingCleanupTask 双保险兜底

5.2 修复闭环26 项)

commit cdaa39d · P0 × 4

ID 问题 修复
P0-1 OnProducerClose 不校验 producer 归属 新增 assertOwnsResourceRedis Set echo:meeting:res:<room>:<user>)集成 5 个 C→S 入口
P0-2 OnConsumeStart 不校验 transport_id 归属 同 P0-1 复用
P0-3 CreateRoom Router 失败仅 Warn 继续 fail-closedLeaveRoom + MarkEnded(system_error) 补偿 + ErrMediaServiceUnavailable HTTP 500
P0-4 加入密码明文拼 URL meetingStore.draftJoinPayload = { code, password } in-memory 传递 + preview 读取后立即置空

commit ea2bf96 · P1 × 8

ID 问题 修复
P1-1 主持人转让非原子两事务 DAO TransferHost 事务合并 role + host_idservice/lifecycle 移除冗余 UpdateHost
P1-2 ListChatMessages / ListMyMeetings 绕 DAO 新增 MeetingChatDAO.ListByRoomBefore(反向游标)
P1-3 ListMyMeetings N+1 放大 6 倍 ListJoinedRoomsByUser JOIN + DISTINCT + 游标
P1-4 EndRoom 无行锁幽灵参会者 事务 + SELECT FOR UPDATE + 快照 + MarkEnded + LeaveAllActive
P1-5 后台 goroutine 丢 trace_id logs.DetachContext(ctx) 替换 10+ 处 context.Background()
P1-6 _broadcastSelfState 吞错 最多 2 次指数退避700ms→2100ms+ stale patch 识别放弃
P1-7 TTL 自然过期盲区 独立 SETNX host_grace_handling:* / empty_ttl_handling:*TTL 60s
P1-8 REST + WS 顺序不确定 pushExistingRoomState 改同步补推,消除并行窗口

commit f5ae095 · 资源生命周期专项F1-F3 前端 / B1-B3 后端)

  • 前端 Pinia _reset + exitEndedRoom + _pendingBroadcastTimers 追踪 setTimeout 清理
  • 后端 cleanupRoomRedisResidual DEL Redis resourceTrackKey:* + memberStateKey
  • 后端 MeetingLifecycleService.OnRoomEnded 中央清理钩子撤销 hostGrace/emptyTTL local timer + Redis key
  • room.vue onUnload 兜底清理 MediaEngine

commit 5ed14c2 · Playwright 5 场景剧本 + .gitignore 精细化

commit c35097a · P2 × 7 + Nit × 7

类别 条目 状态
P2-1 cleanupUserResources 新增 transport 分支 + DELETE /internal/v1/transports/:id + MediaOrchestrator.CloseTransport
P2-2 preview.vue previewSeq + 200ms 防抖
P2-3 room.vue onLoad redirectTo 后 return
P2-6 generateUniqueRoomCode 重试耗尽返回 ErrRoomCodeConflict
P2-7 SendChatMessage utf8 500 字符 + Redis INCR 30 条/60s
P2-8 MEETING_ENDED_REASON_KICKED + OnWSDisconnect 常量化
P2-4 / P2-5 WS token 迁移 / ChatService 拆分 推迟 Phase 2f
Nit splitn strings.SplitN 替换手写解析
Nit ttl MeetingResourceTrackTTLSeconds = 3600 中央化
Nit origin ws/handler.go CheckOrigin 白名单(server.ws_allowed_origins + server.mode
Nit timeout TimeoutMS 5000→10000ms + CreateRouterRetry 1 次 300ms 退避
Nit redispass deploy-public.sh REDIS_PASSWORD × redis.conf 联动校验
Nit routepath internal-auth.ts isPrivatePath 按 path 匹配剔除 query/hash
Nit inflight mediasoup-client.js finally 覆盖 resolve/reject + 注释强化
Nit ports / appdata / rfc3339 docker-compose 端口 / producer.appData / 广播时间 RFC3339 推迟
Nit review _onMemberLeft 整槽 vs _onProducerNew closed=true 精确匹配粒度走读 结论正确无需改动

5.3 登记推迟5 项)

详见审查报告 "Task 16 修复追踪 · 推迟到独立阶段" 表P2-4 WS token 迁出 URL query、P2-5 MeetingChatService 拆分、docker-compose 200 端口收敛、consumer.service.ts producer.appData 资格校验、广播时间 RFC3339 统一。这些项属于安全专项 / 架构重构 / 部署专项 / 跨模块协议统一,单独在 Phase 2f/3 落地。


六、部署验证

6.1 本机 Demo 模式

./scripts/start.sh full          # 启动 postgres/redis/minio/go-service/media-server
npm --prefix frontend run dev:h5
  • deploy/.env 使用 deploy/.env.local.exampleMEDIASOUP_ANNOUNCED_IP=""(自动取内网 IPTURN_ENABLED=false
  • 两浏览器互推音视频通过(需要 HTTPS + 自签证书)

6.2 公网模式(--profile public

cp deploy/.env.public.example deploy/.env
# 编辑:替换 _REPLACE_WITH_STRONG_PASSWORD_ / _REPLACE_WITH_YOUR_PUBLIC_IP_ 等
./scripts/deploy-public.sh
  • scripts/deploy-public.sh 四步校验:
    1. env 校验(_REPLACE_WITH_*_ 占位符 + MEDIASOUP_ANNOUNCED_IP 非空 + DEPLOY_MODE=public + REDIS_PASSWORD × redis.conf requirepass 联动校验
    2. 本机端口扫描8085/3300/40000-40199 UDP 段 + 3478/5349 如启用 TURN
    3. Docker daemon + Compose V2 检测
    4. docker compose --profile public up -d --build
  • coturn network_mode: hostLinux 强制)+ profiles: ["public"] 隔离本地 Demo

6.3 CheckOrigin 白名单

公网部署时建议在 deploy/.env 添加:

ECHOCHAT_SERVER_MODE=release
ECHOCHAT_SERVER_WS_ALLOWED_ORIGINS=https://echo.example.com,https://app.example.com

go-service 启动后 WS 握手会按白名单严格匹配 Origin非同源 + 非白名单直接拒绝。


七、已知限制与未来工作

限制 当前缓解 未来计划
多 go-service 实例 WS 广播未接 Redis Pub/Sub 中转 单实例部署 Phase 2f 统一 WS 集群化
WS token 仍在 URL query 反向代理日志需手动脱敏 Phase 2f 安全专项:迁移到 WS 首帧鉴权帧
MeetingChatService 嵌入主 MeetingService 当前 500+ 行仍可维护 Phase 2f 服务分层专项拆分
广播时间戳部分走 Unix 秒,部分走 Gorm default 前端做兼容解析 Phase 2f 跨模块协议梳理统一 RFC3339
mediasoup Worker crash 恢复尚未演练 单 Worker 模式 + died 指数退避 Phase 3 容量与高可用阶段实机演练
TURN ephemeral credentials 未落地 .env.public.example 使用 static credential Phase 3 安全加固
RTCPeerConnection.getStats 未对接真实网络质量 NetworkBadge 当前用占位值 Phase 2e-3 会议增强

八、验收结论

  • 17 个 Task 全部交付,测试环境下 REST + WS + 生命周期 + UI 主链路全部可用
  • 代码审查 4 P0 + 8 P1 + 7 P2 + 7 Nit 修复闭环5 项低优先级事项已登记推迟到 Phase 2f/3
  • 构建链 go vet / go build / npm run build:h5 / npx tsc --noEmit 全绿
  • E2E Playwright 剧本覆盖 5 个关键场景,手工回归 checklist 齐备
  • 资源生命周期(前端 Pinia / 后端 Redis / mediasoup / 定时器)清理闭环,满足"会议结束后资源必须清理干净"硬约束

结论Phase 2e-2 会议 MVP 达到可交付 Demo + 内网试跑水平,分支 feature/phase2e-2-meeting-mvp 具备合并主干条件。