2026-06-04 18:31:06 +08:00

564 lines
27 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package grpc 将 room-service 领域服务适配成 protobuf gRPC 接口。
package grpc
import (
"context"
"time"
roomv1 "hyapp.local/api/proto/room/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
roomservice "hyapp/services/room-service/internal/room/service"
"hyapp/services/room-service/internal/router"
)
// Server 把 room-service 领域实现挂到 gRPC 接口上。
type Server struct {
// UnimplementedRoomCommandServiceServer 提供未实现 RPC 的标准 gRPC 返回。
roomv1.UnimplementedRoomCommandServiceServer
// UnimplementedRoomGuardServiceServer 提供守卫服务未实现 RPC 的标准 gRPC 返回。
roomv1.UnimplementedRoomGuardServiceServer
// UnimplementedRoomQueryServiceServer 提供查询服务未实现 RPC 的标准 gRPC 返回。
roomv1.UnimplementedRoomQueryServiceServer
// svc 是领域服务入口gRPC 层不保存任何房间状态。
svc *roomservice.Service
// ownerForwarder 在请求命中非 owner 节点时把房间命令转到当前 owner 实例。
ownerForwarder *ownerForwarder
}
// Option 调整 room-service gRPC 边界行为。
type Option func(*Server)
// WithOwnerForwarding 启用 room_id -> owner node -> instance addr 转发。
func WithOwnerForwarding(nodeID string, directory router.Directory, registry router.NodeRegistry, timeout time.Duration) Option {
return func(s *Server) {
s.ownerForwarder = newOwnerForwarder(nodeID, directory, registry, timeout)
}
}
// NewServer 创建一个可注册到 gRPC 的 room server。
func NewServer(svc *roomservice.Service, opts ...Option) *Server {
server := &Server{svc: svc}
for _, opt := range opts {
if opt != nil {
opt(server)
}
}
return server
}
// mapServiceResult 把领域错误固定转换为 gRPC status + ErrorInfo.reason。
// gateway 只认识传输层 gRPC 错误;如果这里直接透传 *xerr.Errorgrpc-go 会降级成 Unknown
// gateway 就无法恢复 INVALID_ARGUMENT/NOT_FOUND 等业务 reason最终只能错误地返回 INTERNAL_ERROR。
func mapServiceResult[T any](result T, err error) (T, error) {
if err != nil {
var zero T
return zero, xerr.ToGRPCError(err)
}
return result, nil
}
// CreateRoom 代理到领域服务。
func (s *Server) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) (*roomv1.CreateRoomResponse, error) {
// 创建房间会获取 Redis lease、写 MySQL meta/command/outbox/snapshot并安装本地 Cell。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.CreateRoomResponse, error) {
return client.CreateRoom(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.CreateRoom(ctx, req))
}
// UpdateRoomProfile 代理到领域服务。
func (s *Server) UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomProfileRequest) (*roomv1.UpdateRoomProfileResponse, error) {
// 房间资料和座位数变更必须经 Room Cell 串行提交gRPC 层不直接修改快照。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.UpdateRoomProfileResponse, error) {
return client.UpdateRoomProfile(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.UpdateRoomProfile(ctx, req))
}
func (s *Server) SaveRoomBackground(ctx context.Context, req *roomv1.SaveRoomBackgroundRequest) (*roomv1.SaveRoomBackgroundResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.SaveRoomBackground(ctx, req))
}
func (s *Server) SetRoomBackground(ctx context.Context, req *roomv1.SetRoomBackgroundRequest) (*roomv1.SetRoomBackgroundResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.SetRoomBackgroundResponse, error) {
return client.SetRoomBackground(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.SetRoomBackground(ctx, req))
}
// JoinRoom 代理到领域服务。
func (s *Server) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error) {
// JoinRoom 只维护 room-service presence真实消息连接由腾讯云 IM SDK 处理。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.JoinRoomResponse, error) {
return client.JoinRoom(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.JoinRoom(ctx, req))
}
// RoomHeartbeat 代理到领域服务。
func (s *Server) RoomHeartbeat(ctx context.Context, req *roomv1.RoomHeartbeatRequest) (*roomv1.RoomHeartbeatResponse, error) {
// 心跳只刷新已有业务 presence不能隐式创建进房状态。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.RoomHeartbeatResponse, error) {
return client.RoomHeartbeat(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.RoomHeartbeat(ctx, req))
}
// LeaveRoom 代理到领域服务。
func (s *Server) LeaveRoom(ctx context.Context, req *roomv1.LeaveRoomRequest) (*roomv1.LeaveRoomResponse, error) {
// LeaveRoom 不直接操作腾讯云 IM 连接,只修改房间业务态。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.LeaveRoomResponse, error) {
return client.LeaveRoom(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.LeaveRoom(ctx, req))
}
// CloseRoom 代理到领域服务。
func (s *Server) CloseRoom(ctx context.Context, req *roomv1.CloseRoomRequest) (*roomv1.CloseRoomResponse, error) {
// CloseRoom 统一关闭房间生命周期、清理 presence/麦位并写出系统事件。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.CloseRoomResponse, error) {
return client.CloseRoom(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.CloseRoom(ctx, req))
}
func (s *Server) AdminUpdateRoom(ctx context.Context, req *roomv1.AdminUpdateRoomRequest) (*roomv1.AdminUpdateRoomResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.AdminUpdateRoomResponse, error) {
return client.AdminUpdateRoom(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.AdminUpdateRoom(ctx, req))
}
func (s *Server) AdminDeleteRoom(ctx context.Context, req *roomv1.AdminDeleteRoomRequest) (*roomv1.AdminDeleteRoomResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.AdminDeleteRoomResponse, error) {
return client.AdminDeleteRoom(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.AdminDeleteRoom(ctx, req))
}
func (s *Server) AdminUpdateRoomTreasureConfig(ctx context.Context, req *roomv1.AdminUpdateRoomTreasureConfigRequest) (*roomv1.AdminUpdateRoomTreasureConfigResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.AdminUpdateRoomTreasureConfig(ctx, req))
}
func (s *Server) AdminUpdateRoomSeatConfig(ctx context.Context, req *roomv1.AdminUpdateRoomSeatConfigRequest) (*roomv1.AdminUpdateRoomSeatConfigResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.AdminUpdateRoomSeatConfig(ctx, req))
}
func (s *Server) AdminCreateRoomPin(ctx context.Context, req *roomv1.AdminCreateRoomPinRequest) (*roomv1.AdminCreateRoomPinResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.AdminCreateRoomPin(ctx, req))
}
func (s *Server) AdminCancelRoomPin(ctx context.Context, req *roomv1.AdminCancelRoomPinRequest) (*roomv1.AdminCancelRoomPinResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.AdminCancelRoomPin(ctx, req))
}
// MicUp 代理到领域服务。
func (s *Server) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1.MicUpResponse, error) {
// 麦位修改由 Room Cell 串行执行gRPC 层不做状态判断。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.MicUpResponse, error) {
return client.MicUp(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.MicUp(ctx, req))
}
// MicDown 代理到领域服务。
func (s *Server) MicDown(ctx context.Context, req *roomv1.MicDownRequest) (*roomv1.MicDownResponse, error) {
// 下麦结果会进入 command log 和 outbox失败不产生部分状态。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.MicDownResponse, error) {
return client.MicDown(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.MicDown(ctx, req))
}
// ChangeMicSeat 代理到领域服务。
func (s *Server) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRequest) (*roomv1.ChangeMicSeatResponse, error) {
// 换位在领域层原子修改源麦位和目标麦位。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.ChangeMicSeatResponse, error) {
return client.ChangeMicSeat(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.ChangeMicSeat(ctx, req))
}
// ConfirmMicPublishing 代理到领域服务。
func (s *Server) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmMicPublishingRequest) (*roomv1.ConfirmMicPublishingResponse, error) {
// 发流确认必须进入 Room Cell 串行状态机,避免旧 RTC 事件覆盖新 mic_session。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.ConfirmMicPublishingResponse, error) {
return client.ConfirmMicPublishing(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.ConfirmMicPublishing(ctx, req))
}
// MicHeartbeat 代理到领域服务。
func (s *Server) MicHeartbeat(ctx context.Context, req *roomv1.MicHeartbeatRequest) (*roomv1.MicHeartbeatResponse, error) {
// 心跳仍由 Room Cell 串行提交,保证 presence、麦位快照和 command log 同版本。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.MicHeartbeatResponse, error) {
return client.MicHeartbeat(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.MicHeartbeat(ctx, req))
}
// SetMicMute 代理到领域服务。
func (s *Server) SetMicMute(ctx context.Context, req *roomv1.SetMicMuteRequest) (*roomv1.SetMicMuteResponse, error) {
// 服务端可见静音态必须经 Room Cell 提交,确保其他用户 UI 和系统消息一致。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.SetMicMuteResponse, error) {
return client.SetMicMute(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.SetMicMute(ctx, req))
}
// ApplyRTCEvent 代理到领域服务。
func (s *Server) ApplyRTCEvent(ctx context.Context, req *roomv1.ApplyRTCEventRequest) (*roomv1.ApplyRTCEventResponse, error) {
// 腾讯 RTC 回调已在 gateway 验签;房间状态变化仍必须由 Room Cell 串行提交。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.ApplyRTCEventResponse, error) {
return client.ApplyRTCEvent(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.ApplyRTCEvent(ctx, req))
}
// SetMicSeatLock 代理到领域服务。
func (s *Server) SetMicSeatLock(ctx context.Context, req *roomv1.SetMicSeatLockRequest) (*roomv1.SetMicSeatLockResponse, error) {
// 锁麦权限和目标麦位状态由 Room Cell 判定。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.SetMicSeatLockResponse, error) {
return client.SetMicSeatLock(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.SetMicSeatLock(ctx, req))
}
// SetChatEnabled 代理到领域服务。
func (s *Server) SetChatEnabled(ctx context.Context, req *roomv1.SetChatEnabledRequest) (*roomv1.SetChatEnabledResponse, error) {
// 公屏开关是 room-service 发言守卫的权威状态。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.SetChatEnabledResponse, error) {
return client.SetChatEnabled(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.SetChatEnabled(ctx, req))
}
// SetRoomPassword 代理到领域服务。
func (s *Server) SetRoomPassword(ctx context.Context, req *roomv1.SetRoomPasswordRequest) (*roomv1.SetRoomPasswordResponse, error) {
// 房间密码影响 JoinRoom 入口,必须由当前 Room Cell owner 串行提交。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.SetRoomPasswordResponse, error) {
return client.SetRoomPassword(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.SetRoomPassword(ctx, req))
}
// SetRoomAdmin 代理到领域服务。
func (s *Server) SetRoomAdmin(ctx context.Context, req *roomv1.SetRoomAdminRequest) (*roomv1.SetRoomAdminResponse, error) {
// 管理员集合只能由 room-service 权限矩阵修改。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.SetRoomAdminResponse, error) {
return client.SetRoomAdmin(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.SetRoomAdmin(ctx, req))
}
// MuteUser 代理到领域服务。
func (s *Server) MuteUser(ctx context.Context, req *roomv1.MuteUserRequest) (*roomv1.MuteUserResponse, error) {
// 禁言状态是腾讯云 IM 发言回调的权威来源。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.MuteUserResponse, error) {
return client.MuteUser(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.MuteUser(ctx, req))
}
// KickUser 代理到领域服务。
func (s *Server) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*roomv1.KickUserResponse, error) {
// 踢人会修改 presence、ban 集合和麦位占用。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.KickUserResponse, error) {
return client.KickUser(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.KickUser(ctx, req))
}
// UnbanUser 代理到领域服务。
func (s *Server) UnbanUser(ctx context.Context, req *roomv1.UnbanUserRequest) (*roomv1.UnbanUserResponse, error) {
// 解封只解除 ban不恢复 presence、管理员身份或麦位。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.UnbanUserResponse, error) {
return client.UnbanUser(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.UnbanUser(ctx, req))
}
// SystemEvictUser 代理到领域服务。
func (s *Server) SystemEvictUser(ctx context.Context, req *roomv1.SystemEvictUserRequest) (*roomv1.SystemEvictUserResponse, error) {
// 系统驱逐由平台封禁或风控触发,仍必须经 Room Cell 串行提交。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.SystemEvictUserResponse, error) {
return client.SystemEvictUser(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.SystemEvictUser(ctx, req))
}
// SendGift 代理到领域服务。
func (s *Server) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) {
// SendGift 先同步扣费,成功后才更新房间热度和本地榜。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardCommand(s, ctx, req.GetMeta(), func(callCtx context.Context, client roomv1.RoomCommandServiceClient) (*roomv1.SendGiftResponse, error) {
return client.SendGift(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.SendGift(ctx, req))
}
// FollowRoom 建立用户对房间的关注关系。
func (s *Server) FollowRoom(ctx context.Context, req *roomv1.FollowRoomRequest) (*roomv1.FollowRoomResponse, error) {
// 房间关注是用户-房间关系事实,不改变 Room Cell 核心状态,因此不需要 owner 转发。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.FollowRoom(ctx, req))
}
// UnfollowRoom 取消用户对房间的关注关系。
func (s *Server) UnfollowRoom(ctx context.Context, req *roomv1.UnfollowRoomRequest) (*roomv1.UnfollowRoomResponse, error) {
// 取消关注只收敛关系表状态;历史房间是否仍有 Cell 不影响幂等取消。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.UnfollowRoom(ctx, req))
}
// CheckSpeakPermission 代理到领域服务。
func (s *Server) CheckSpeakPermission(ctx context.Context, req *roomv1.CheckSpeakPermissionRequest) (*roomv1.CheckSpeakPermissionResponse, error) {
// 外部 IM 发公屏前调用该守卫,避免复制禁言和房间状态规则。
ctx = appcode.WithContext(ctx, req.GetAppCode())
if resp, forwarded, err := forwardGuard(s, ctx, req.GetAppCode(), req.GetRoomId(), func(callCtx context.Context, client roomv1.RoomGuardServiceClient) (*roomv1.CheckSpeakPermissionResponse, error) {
return client.CheckSpeakPermission(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.CheckSpeakPermission(ctx, req))
}
// VerifyRoomPresence 代理到领域服务。
func (s *Server) VerifyRoomPresence(ctx context.Context, req *roomv1.VerifyRoomPresenceRequest) (*roomv1.VerifyRoomPresenceResponse, error) {
// 外部 IM 进群前调用该守卫,防止未进房或被踢用户收消息。
ctx = appcode.WithContext(ctx, req.GetAppCode())
if resp, forwarded, err := forwardGuard(s, ctx, req.GetAppCode(), req.GetRoomId(), func(callCtx context.Context, client roomv1.RoomGuardServiceClient) (*roomv1.VerifyRoomPresenceResponse, error) {
return client.VerifyRoomPresence(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.VerifyRoomPresence(ctx, req))
}
// ListRooms 代理到 room-service 列表读模型。
func (s *Server) AdminListRooms(ctx context.Context, req *roomv1.AdminListRoomsRequest) (*roomv1.AdminListRoomsResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.AdminListRooms(ctx, req))
}
func (s *Server) AdminGetRoom(ctx context.Context, req *roomv1.AdminGetRoomRequest) (*roomv1.AdminGetRoomResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.AdminGetRoom(ctx, req))
}
func (s *Server) AdminGetRoomTreasureConfig(ctx context.Context, req *roomv1.AdminGetRoomTreasureConfigRequest) (*roomv1.AdminGetRoomTreasureConfigResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.AdminGetRoomTreasureConfig(ctx, req))
}
func (s *Server) AdminGetRoomSeatConfig(ctx context.Context, req *roomv1.AdminGetRoomSeatConfigRequest) (*roomv1.AdminGetRoomSeatConfigResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.AdminGetRoomSeatConfig(ctx, req))
}
func (s *Server) AdminListRoomPins(ctx context.Context, req *roomv1.AdminListRoomPinsRequest) (*roomv1.AdminListRoomPinsResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.AdminListRoomPins(ctx, req))
}
// ListRooms 代理到 room-service 列表读模型。
func (s *Server) ListRooms(ctx context.Context, req *roomv1.ListRoomsRequest) (*roomv1.ListRoomsResponse, error) {
// 列表查询只读 MySQL 投影,不扫描 Room Cell 内存集合。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.ListRooms(ctx, req))
}
// ListRoomFeeds 代理到 Mine 页用户房间流读模型。
func (s *Server) ListRoomFeeds(ctx context.Context, req *roomv1.ListRoomFeedsRequest) (*roomv1.ListRoomsResponse, error) {
// feed 查询只读用户到房间索引,并 join 房间卡片投影,不扫描 Room Cell 内存集合。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.ListRoomFeeds(ctx, req))
}
// ListRoomGiftLeaderboard 代理到 Redis zset 房间金币榜读模型。
func (s *Server) ListRoomGiftLeaderboard(ctx context.Context, req *roomv1.ListRoomGiftLeaderboardRequest) (*roomv1.ListRoomGiftLeaderboardResponse, error) {
// 房间榜只读 room-service 维护的金币 zset并用 room_list_entries 批量补卡片。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.ListRoomGiftLeaderboard(ctx, req))
}
// GetMyRoom 代理到 Mine 顶部“我的房间”权威查询。
func (s *Server) GetMyRoom(ctx context.Context, req *roomv1.GetMyRoomRequest) (*roomv1.GetMyRoomResponse, error) {
// 我的房间查询直接读取 rooms 元数据,并用快照补齐实时卡片字段,不依赖发现页投影。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.GetMyRoom(ctx, req))
}
// GetCurrentRoom 代理到用户当前房间恢复探测读模型。
func (s *Server) GetCurrentRoom(ctx context.Context, req *roomv1.GetCurrentRoomRequest) (*roomv1.GetCurrentRoomResponse, error) {
// 恢复探测不修改 Room Cell只在读模型命中后用快照做二次校验。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.GetCurrentRoom(ctx, req))
}
// GetRoomSnapshot 代理到房间页完整快照只读查询。
func (s *Server) GetRoomSnapshot(ctx context.Context, req *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error) {
// 快照查询不刷新 presence不替代 heartbeat也不能让未进房用户读取完整房间态。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardQuery(s, ctx, req.GetMeta().GetAppCode(), req.GetRoomId(), func(callCtx context.Context, client roomv1.RoomQueryServiceClient) (*roomv1.GetRoomSnapshotResponse, error) {
return client.GetRoomSnapshot(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.GetRoomSnapshot(ctx, req))
}
func (s *Server) ListRoomBackgrounds(ctx context.Context, req *roomv1.ListRoomBackgroundsRequest) (*roomv1.ListRoomBackgroundsResponse, error) {
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardQuery(s, ctx, req.GetMeta().GetAppCode(), req.GetRoomId(), func(callCtx context.Context, client roomv1.RoomQueryServiceClient) (*roomv1.ListRoomBackgroundsResponse, error) {
return client.ListRoomBackgrounds(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.ListRoomBackgrounds(ctx, req))
}
// GetRoomTreasure 代理到房间宝箱只读查询。
func (s *Server) GetRoomTreasure(ctx context.Context, req *roomv1.GetRoomTreasureRequest) (*roomv1.GetRoomTreasureResponse, error) {
// 宝箱查询不刷新 presence当前进度仍以 Room Cell 快照和 UTC 日边界为准。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardQuery(s, ctx, req.GetMeta().GetAppCode(), req.GetRoomId(), func(callCtx context.Context, client roomv1.RoomQueryServiceClient) (*roomv1.GetRoomTreasureResponse, error) {
return client.GetRoomTreasure(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.GetRoomTreasure(ctx, req))
}
// ListRoomOnlineUsers 代理到房间在线用户分页读模型。
func (s *Server) ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) {
// 在线人数弹窗只读 room_user_presence 投影,不拉完整 RoomSnapshot 做分页。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.ListRoomOnlineUsers(ctx, req))
}
// ListRoomBannedUsers 代理到房间黑名单分页读模型。
func (s *Server) ListRoomBannedUsers(ctx context.Context, req *roomv1.ListRoomBannedUsersRequest) (*roomv1.ListRoomBannedUsersResponse, error) {
// 黑名单来自 Room Cell必须按 room owner 路由读取最新治理状态。
ctx = contextWithMetaApp(ctx, req.GetMeta())
if resp, forwarded, err := forwardQuery(s, ctx, req.GetMeta().GetAppCode(), req.GetRoomId(), func(callCtx context.Context, client roomv1.RoomQueryServiceClient) (*roomv1.ListRoomBannedUsersResponse, error) {
return client.ListRoomBannedUsers(callCtx, req)
}); forwarded {
return resp, err
}
return mapServiceResult(s.svc.ListRoomBannedUsers(ctx, req))
}
func contextWithMetaApp(ctx context.Context, meta *roomv1.RequestMeta) context.Context {
if meta == nil {
return appcode.WithContext(ctx, "")
}
return appcode.WithContext(ctx, meta.GetAppCode())
}
func forwardCommand[T any](s *Server, ctx context.Context, meta *roomv1.RequestMeta, call func(context.Context, roomv1.RoomCommandServiceClient) (T, error)) (T, bool, error) {
var zero T
if s == nil || s.ownerForwarder == nil || meta == nil {
return zero, false, nil
}
return forwardCommandToOwner(s.ownerForwarder, ctx, meta.GetAppCode(), meta.GetRoomId(), call)
}
func forwardGuard[T any](s *Server, ctx context.Context, appCode string, roomID string, call func(context.Context, roomv1.RoomGuardServiceClient) (T, error)) (T, bool, error) {
var zero T
if s == nil || s.ownerForwarder == nil {
return zero, false, nil
}
return forwardGuardToOwner(s.ownerForwarder, ctx, appCode, roomID, call)
}
func forwardQuery[T any](s *Server, ctx context.Context, appCode string, roomID string, call func(context.Context, roomv1.RoomQueryServiceClient) (T, error)) (T, bool, error) {
var zero T
if s == nil || s.ownerForwarder == nil {
return zero, false, nil
}
return forwardQueryToOwner(s.ownerForwarder, ctx, appCode, roomID, call)
}