package service import ( "context" "strings" roomv1 "hyapp.local/api/proto/room/v1" "hyapp/pkg/appcode" "hyapp/pkg/roomid" "hyapp/pkg/xerr" ) const ( // defaultRoomOnlineUsersPageSize 是在线用户弹窗的默认页大小,避免客户端不传 page_size 时一次拉全量。 defaultRoomOnlineUsersPageSize = 50 // maxRoomOnlineUsersPageSize 是在线用户列表单页硬上限,保护 presence 读模型查询。 maxRoomOnlineUsersPageSize = 100 ) // ListRoomOnlineUsers 分页读取房间在线用户列表。 // 该接口用于右上角在线人数弹窗,不能让客户端用完整 RoomSnapshot 做分页。 func (s *Service) ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) roomID := req.GetRoomId() viewerUserID := req.GetViewerUserId() if !roomid.ValidStringID(roomID) { // 在线用户查询必须绑定真实房间 ID,不能让空 room_id 触发宽查询。 return nil, xerr.New(xerr.InvalidArgument, "room_id is invalid") } if viewerUserID <= 0 { // viewer_user_id 来自 gateway 鉴权态,用于限制只有房内用户可看在线列表。 return nil, xerr.New(xerr.InvalidArgument, "viewer_user_id is required") } // 先用用户当前房间读模型做轻量权限判断,避免为了弹窗分页读取完整 RoomSnapshot。 presence, exists, err := s.repository.GetCurrentRoomPresence(ctx, viewerUserID) if err != nil { return nil, err } if !exists || presence.RoomID != roomID || presence.Status != roomPresenceStatusActive { // 不在该房间的用户不能借分页接口枚举房间内成员。 return nil, xerr.New(xerr.PermissionDenied, "viewer is not in room") } page := int(req.GetPage()) if page <= 0 { // page 从 1 开始,0 或负数按第一页处理,保持客户端容错。 page = 1 } pageSize := int(req.GetPageSize()) if pageSize <= 0 { // 缺省页大小走服务端默认,避免前端每个入口复制同一数值。 pageSize = defaultRoomOnlineUsersPageSize } if pageSize > maxRoomOnlineUsersPageSize { // 单页上限在 service 层压实,repository 不接受无限制分页。 pageSize = maxRoomOnlineUsersPageSize } sortMode := strings.TrimSpace(req.GetSort()) if sortMode != "" && sortMode != "online" && sortMode != "gift_value" { return nil, xerr.New(xerr.InvalidArgument, "sort is invalid") } snapshot, err := s.currentSnapshot(ctx, roomID) if err != nil { return nil, err } if snapshot == nil { return nil, xerr.New(xerr.NotFound, "room not found") } adminUsers := make(map[int64]bool, len(snapshot.GetAdminUserIds())) for _, userID := range snapshot.GetAdminUserIds() { if userID > 0 && userID != snapshot.GetOwnerUserId() { adminUsers[userID] = true } } result, err := s.repository.ListRoomOnlineUsers(ctx, RoomOnlineUserQuery{ AppCode: appcode.FromContext(ctx), RoomID: roomID, Page: page, PageSize: pageSize, Sort: sortMode, }) if err != nil { return nil, err } users := make([]*roomv1.RoomUser, 0, len(result.Users)) items := make([]*roomv1.RoomOnlineUser, 0, len(result.Users)) for _, user := range result.Users { // 在线列表只返回轻量房间用户态,完整用户资料仍由 user-service 或 gateway 聚合。 users = append(users, &roomv1.RoomUser{ UserId: user.UserID, Role: user.Role, JoinedAtMs: user.JoinedAtMS, LastSeenAtMs: user.LastSeenAtMS, }) roomRole := "normal" if adminUsers[user.UserID] { roomRole = "admin" } items = append(items, &roomv1.RoomOnlineUser{ UserId: user.UserID, Role: user.Role, RoomRole: roomRole, GiftValue: user.GiftValue, JoinedAtMs: user.JoinedAtMS, LastSeenAtMs: user.LastSeenAtMS, IsOwner: user.UserID == snapshot.GetOwnerUserId(), }) } return &roomv1.ListRoomOnlineUsersResponse{ Users: users, Items: items, Total: result.Total, Page: int32(result.Page), PageSize: int32(result.PageSize), ServerTimeMs: s.clock.Now().UnixMilli(), }, nil }