package http import ( "net/http" "strconv" "strings" roomv1 "hyapp.local/api/proto/room/v1" userv1 "hyapp.local/api/proto/user/v1" "hyapp/pkg/appcode" "hyapp/services/gateway-service/internal/auth" ) // listRooms 按当前登录用户的服务端 region_id 查询房间发现列表。 func (h *Handler) listRooms(writer http.ResponseWriter, request *http.Request) { if h.userProfileClient == nil || h.roomQueryClient == nil { // 列表入口必须同时依赖 user-service 区域归属和 room-service 读模型。 writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error") return } limit, ok := parseRoomListLimit(request.URL.Query().Get("limit")) if !ok { writeError(writer, request, http.StatusBadRequest, codeInvalidArgument, "invalid argument") return } viewerUserID := auth.UserIDFromContext(request.Context()) userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ Meta: authRequestMeta(request, ""), UserId: viewerUserID, }) if err != nil { // user-service 是 region_id 的唯一 owner,查询失败不能用 IP 国家兜底。 writeRPCError(writer, request, err) return } resp, err := h.roomQueryClient.ListRooms(request.Context(), &roomv1.ListRoomsRequest{ Meta: meta(request, "", ""), ViewerUserId: viewerUserID, VisibleRegionId: userResp.GetUser().GetRegionId(), Tab: request.URL.Query().Get("tab"), Cursor: request.URL.Query().Get("cursor"), Limit: limit, Query: roomListQuery(request), }) write(writer, request, resp, err) } func roomListQuery(request *http.Request) string { if query := strings.TrimSpace(request.URL.Query().Get("q")); query != "" { return query } return strings.TrimSpace(request.URL.Query().Get("query")) } // parseRoomListLimit 只做 HTTP 字符串到数字的传输层解析。 // 默认值和最大值仍由 room-service 统一裁剪,避免 gateway 和查询服务出现双份策略。 func parseRoomListLimit(raw string) (int32, bool) { raw = strings.TrimSpace(raw) if raw == "" { return 0, true } value, err := strconv.Atoi(raw) if err != nil { return 0, false } return int32(value), true } // createRoom 把创建房间 HTTP JSON 请求转换为 room-service CreateRoom 命令。 func (h *Handler) createRoom(writer http.ResponseWriter, request *http.Request) { var body struct { CommandID string `json:"command_id"` SeatCount int32 `json:"seat_count"` Mode string `json:"mode"` RoomName string `json:"room_name"` RoomAvatar string `json:"room_avatar"` RoomDescription string `json:"room_description"` } if !decode(writer, request, &body) { return } body.Mode = strings.TrimSpace(body.Mode) body.RoomName = strings.TrimSpace(body.RoomName) body.RoomAvatar = strings.TrimSpace(body.RoomAvatar) body.RoomDescription = strings.TrimSpace(body.RoomDescription) if body.SeatCount <= 0 || body.Mode == "" || body.RoomName == "" { // gateway mirrors room-service's hard requirements so client errors stay INVALID_ARGUMENT. writeError(writer, request, http.StatusBadRequest, codeInvalidArgument, "invalid argument") return } if h.userProfileClient == nil { // CreateRoom 的 visible_region_id 必须来自 user-service,不能让客户端传入或 gateway 猜测。 writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error") return } userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ Meta: authRequestMeta(request, ""), UserId: auth.UserIDFromContext(request.Context()), }) if err != nil { writeRPCError(writer, request, err) return } roomID := appcode.NewScopedID(appcode.FromContext(request.Context())) roomShortID := userResp.GetUser().GetDisplayUserId() resp, err := h.roomClient.CreateRoom(request.Context(), &roomv1.CreateRoomRequest{ Meta: meta(request, roomID, body.CommandID), SeatCount: body.SeatCount, Mode: body.Mode, VisibleRegionId: userResp.GetUser().GetRegionId(), RoomName: body.RoomName, RoomAvatar: body.RoomAvatar, RoomDescription: body.RoomDescription, RoomShortId: roomShortID, }) write(writer, request, resp, err) } // joinRoom 把进房 HTTP JSON 请求转换为 room-service JoinRoom 命令。 func (h *Handler) joinRoom(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` Role string `json:"role"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.JoinRoom(request.Context(), &roomv1.JoinRoomRequest{ Meta: meta(request, body.RoomID, body.CommandID), Role: body.Role, }) write(writer, request, resp, err) } // roomHeartbeat 把客户端显式心跳转换为 room-service presence refresh 命令。 func (h *Handler) roomHeartbeat(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.RoomHeartbeat(request.Context(), &roomv1.RoomHeartbeatRequest{ Meta: meta(request, body.RoomID, body.CommandID), }) write(writer, request, resp, err) } // leaveRoom 把退房 HTTP JSON 请求转换为 room-service LeaveRoom 命令。 func (h *Handler) leaveRoom(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.LeaveRoom(request.Context(), &roomv1.LeaveRoomRequest{ Meta: meta(request, body.RoomID, body.CommandID), }) write(writer, request, resp, err) } // micUp 把上麦 HTTP JSON 请求转换为 room-service MicUp 命令。 func (h *Handler) micUp(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` SeatNo int32 `json:"seat_no"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.MicUp(request.Context(), &roomv1.MicUpRequest{ Meta: meta(request, body.RoomID, body.CommandID), SeatNo: body.SeatNo, }) write(writer, request, resp, err) } // micDown 把下麦 HTTP JSON 请求转换为 room-service MicDown 命令。 func (h *Handler) micDown(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` TargetUserID int64 `json:"target_user_id"` Reason string `json:"reason"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.MicDown(request.Context(), &roomv1.MicDownRequest{ Meta: meta(request, body.RoomID, body.CommandID), TargetUserId: body.TargetUserID, Reason: body.Reason, }) write(writer, request, resp, err) } // changeMicSeat 把换麦位 HTTP JSON 请求转换为 room-service ChangeMicSeat 命令。 func (h *Handler) changeMicSeat(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` TargetUserID int64 `json:"target_user_id"` SeatNo int32 `json:"seat_no"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.ChangeMicSeat(request.Context(), &roomv1.ChangeMicSeatRequest{ Meta: meta(request, body.RoomID, body.CommandID), TargetUserId: body.TargetUserID, SeatNo: body.SeatNo, }) write(writer, request, resp, err) } // confirmMicPublishing 把客户端 SDK 发流成功回调转换为 room-service 发流确认命令。 func (h *Handler) confirmMicPublishing(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` TargetUserID int64 `json:"target_user_id"` MicSessionID string `json:"mic_session_id"` RoomVersion int64 `json:"room_version"` EventTimeMS int64 `json:"event_time_ms"` Source string `json:"source"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.ConfirmMicPublishing(request.Context(), &roomv1.ConfirmMicPublishingRequest{ Meta: meta(request, body.RoomID, body.CommandID), TargetUserId: body.TargetUserID, MicSessionId: body.MicSessionID, RoomVersion: body.RoomVersion, EventTimeMs: body.EventTimeMS, Source: body.Source, }) write(writer, request, resp, err) } // setMicSeatLock 把锁麦 HTTP JSON 请求转换为 room-service SetMicSeatLock 命令。 func (h *Handler) setMicSeatLock(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` SeatNo int32 `json:"seat_no"` Locked bool `json:"locked"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.SetMicSeatLock(request.Context(), &roomv1.SetMicSeatLockRequest{ Meta: meta(request, body.RoomID, body.CommandID), SeatNo: body.SeatNo, Locked: body.Locked, }) write(writer, request, resp, err) } // setChatEnabled 把公屏开关 HTTP JSON 请求转换为 room-service SetChatEnabled 命令。 func (h *Handler) setChatEnabled(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` Enabled bool `json:"enabled"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.SetChatEnabled(request.Context(), &roomv1.SetChatEnabledRequest{ Meta: meta(request, body.RoomID, body.CommandID), Enabled: body.Enabled, }) write(writer, request, resp, err) } // setRoomAdmin 把管理员增删 HTTP JSON 请求转换为 room-service SetRoomAdmin 命令。 func (h *Handler) setRoomAdmin(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` TargetUserID int64 `json:"target_user_id"` Enabled bool `json:"enabled"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.SetRoomAdmin(request.Context(), &roomv1.SetRoomAdminRequest{ Meta: meta(request, body.RoomID, body.CommandID), TargetUserId: body.TargetUserID, Enabled: body.Enabled, }) write(writer, request, resp, err) } // transferRoomHost 把主持转移 HTTP JSON 请求转换为 room-service TransferRoomHost 命令。 func (h *Handler) transferRoomHost(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` TargetUserID int64 `json:"target_user_id"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.TransferRoomHost(request.Context(), &roomv1.TransferRoomHostRequest{ Meta: meta(request, body.RoomID, body.CommandID), TargetUserId: body.TargetUserID, }) write(writer, request, resp, err) } // muteUser 把禁言 HTTP JSON 请求转换为 room-service MuteUser 命令。 func (h *Handler) muteUser(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` TargetUserID int64 `json:"target_user_id"` Muted bool `json:"muted"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.MuteUser(request.Context(), &roomv1.MuteUserRequest{ Meta: meta(request, body.RoomID, body.CommandID), TargetUserId: body.TargetUserID, Muted: body.Muted, }) write(writer, request, resp, err) } // kickUser 把踢人 HTTP JSON 请求转换为 room-service KickUser 命令。 func (h *Handler) kickUser(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` TargetUserID int64 `json:"target_user_id"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.KickUser(request.Context(), &roomv1.KickUserRequest{ Meta: meta(request, body.RoomID, body.CommandID), TargetUserId: body.TargetUserID, }) write(writer, request, resp, err) } // unbanUser 把解封 HTTP JSON 请求转换为 room-service UnbanUser 命令。 func (h *Handler) unbanUser(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` TargetUserID int64 `json:"target_user_id"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.UnbanUser(request.Context(), &roomv1.UnbanUserRequest{ Meta: meta(request, body.RoomID, body.CommandID), TargetUserId: body.TargetUserID, }) write(writer, request, resp, err) } // sendGift 把送礼 HTTP JSON 请求转换为 room-service SendGift 命令。 // 扣费、幂等、房间表现和 outbox 仍由 room-service 的命令链路统一处理。 func (h *Handler) sendGift(writer http.ResponseWriter, request *http.Request) { var body struct { RoomID string `json:"room_id"` CommandID string `json:"command_id"` TargetUserID int64 `json:"target_user_id"` GiftID string `json:"gift_id"` GiftCount int32 `json:"gift_count"` } if !decode(writer, request, &body) { return } resp, err := h.roomClient.SendGift(request.Context(), &roomv1.SendGiftRequest{ Meta: meta(request, body.RoomID, body.CommandID), TargetUserId: body.TargetUserID, GiftId: body.GiftID, GiftCount: body.GiftCount, }) write(writer, request, resp, err) }