package service import ( "context" roomv1 "hyapp.local/api/proto/room/v1" "hyapp/pkg/appcode" "hyapp/pkg/roomid" "hyapp/pkg/xerr" "hyapp/services/room-service/internal/room/state" ) // GetRoomSnapshot 返回房间页主动同步所需的完整 Room Cell 快照。 // 该查询只读,不刷新 presence、不写 command log,也不隐式 JoinRoom。 func (s *Service) GetRoomSnapshot(ctx context.Context, req *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) roomID := req.GetRoomId() viewerUserID := req.GetViewerUserId() if !roomid.ValidStringID(roomID) { // 快照查询同样受腾讯 IM GroupID 和 RTC strRoomId 格式约束,避免脏 room_id 进入恢复路径。 return nil, xerr.New(xerr.InvalidArgument, "room_id is invalid") } if viewerUserID <= 0 { // viewer_user_id 必须来自 gateway 鉴权,room-service 不接受匿名读取完整 presence。 return nil, xerr.New(xerr.InvalidArgument, "viewer_user_id is required") } closed, err := s.isRoomClosed(ctx, roomID) if err != nil { return nil, err } if closed { return nil, xerr.New(xerr.RoomClosed, "room closed") } snapshot, err := s.currentSnapshot(ctx, roomID) if err != nil { return nil, err } if snapshot == nil || snapshot.GetRoomId() == "" { return nil, xerr.New(xerr.NotFound, "room not found") } if snapshot.GetStatus() != state.RoomStatusActive { return nil, xerr.New(xerr.RoomClosed, "room closed") } if snapshotUserBanned(snapshot, viewerUserID, s.clock.Now().UnixMilli()) || findProtoUser(snapshot, viewerUserID) == nil { // 完整快照包含在线用户、麦位和房管集合,必须要求 viewer 仍在业务 presence 内。 return nil, xerr.New(xerr.PermissionDenied, "viewer is not in room") } followed, err := s.repository.IsRoomFollowed(ctx, viewerUserID, roomID) if err != nil { return nil, err } return &roomv1.GetRoomSnapshotResponse{ Room: snapshot, ServerTimeMs: s.clock.Now().UnixMilli(), IsFollowed: followed, }, nil }