fix(meeting): Task 16 会议销毁路径资源清理专项

前端修复:
- _onRoomEnded 保留结束页数据,新增 exitEndedRoom() 动作
  room.vue onUnload 在 ENDED 状态调用,释放 currentRoom / participants / chatMessages 等 pinia state
- endMeeting() API 成功后立即触发 _onRoomEnded 本地兜底,不再依赖 room.ended 广播
  避免 API 成功但 WS 抖动导致 engine / timer / AudioContext 泄漏
- 新增 _pendingBroadcastTimers 集合收纳 _broadcastSelfState 的 setTimeout 句柄
  _cleanupMedia 统一 clearTimeout,避免会议结束后 stale 重试逻辑排队

后端修复:
- MeetingLifecycleService.OnRoomEnded 统一生命周期收尾
  取消 graceTimers / emptyTTLTimers + Pipeline DEL host_grace / empty_ttl / 两个 handling 锁 key
- EndRoom 事务提交后遍历 activesBefore 调 cleanupRoomRedisResidual
  批量 DEL 每个前任活跃成员的 resourceTrackKey + memberStateKey
  补调 lifecycleSvc.OnRoomEnded 清理自身 timer + keys
- HandleEmptyRoomExpired MarkEnded 后补调 cleanupRoomRedisResidual + 清 host_grace 残留

验证:go vet + go build + npm run build:h5 全部通过

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-04-23 17:12:38 +08:00
parent ea2bf96c2f
commit f5ae095033
5 changed files with 123 additions and 3 deletions

View File

@@ -708,3 +708,28 @@ func (s *MeetingSignalService) cleanupUserResources(ctx context.Context, roomCod
zap.Int("resource_count", len(members)))
}
}
// cleanupRoomRedisResidual 批量清理指定 roomCode 下一批用户的 Redis 资源追踪集合 + 音视频状态 Hash
// Task 16 资源清理专项:
// - EndRoom / HandleEmptyRoomExpired 等会议销毁路径调用,避免正常 WS 断开钩子尚未触发就结束会议导致的残留
// - userIDs 为销毁前活跃成员快照;无活跃成员可传 nil/空切片,此时仅走上层 lifecycle 清理
// - 使用 Pipeline 减少 Redis 往返;失败仅 Warn不阻断上层销毁流程
// - package 私有:允许 meeting_service / meeting_lifecycle_service 等同包文件直接调用
func cleanupRoomRedisResidual(ctx context.Context, rdb *redis.Client, roomCode string, userIDs []int64) {
funcName := "service.meeting_signal_service.cleanupRoomRedisResidual"
if rdb == nil || len(userIDs) == 0 {
return
}
pipe := rdb.Pipeline()
for _, uid := range userIDs {
pipe.Del(ctx, resourceTrackKey(roomCode, uid))
pipe.Del(ctx, memberStateKey(roomCode, uid))
}
if _, err := pipe.Exec(ctx); err != nil {
logs.Warn(ctx, funcName, "批量清理用户 Redis 残留失败(忽略,继续)",
zap.String("room_code", roomCode), zap.Int("user_count", len(userIDs)), zap.Error(err))
return
}
logs.Debug(ctx, funcName, "会议销毁,用户 Redis 残留已清理",
zap.String("room_code", roomCode), zap.Int("user_count", len(userIDs)))
}