2026-05-06 21:22:39 +08:00

210 lines
9.6 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"
roomv1 "hyapp.local/api/proto/room/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
roomservice "hyapp/services/room-service/internal/room/service"
)
// 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
}
// NewServer 创建一个可注册到 gRPC 的 room server。
func NewServer(svc *roomservice.Service) *Server {
return &Server{svc: svc}
}
// 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())
return mapServiceResult(s.svc.CreateRoom(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())
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())
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())
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())
return mapServiceResult(s.svc.CloseRoom(ctx, req))
}
// MicUp 代理到领域服务。
func (s *Server) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1.MicUpResponse, error) {
// 麦位修改由 Room Cell 串行执行gRPC 层不做状态判断。
ctx = contextWithMetaApp(ctx, req.GetMeta())
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())
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())
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())
return mapServiceResult(s.svc.ConfirmMicPublishing(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())
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())
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())
return mapServiceResult(s.svc.SetChatEnabled(ctx, req))
}
// SetRoomAdmin 代理到领域服务。
func (s *Server) SetRoomAdmin(ctx context.Context, req *roomv1.SetRoomAdminRequest) (*roomv1.SetRoomAdminResponse, error) {
// 管理员集合只能由 room-service 权限矩阵修改。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.SetRoomAdmin(ctx, req))
}
// TransferRoomHost 代理到领域服务。
func (s *Server) TransferRoomHost(ctx context.Context, req *roomv1.TransferRoomHostRequest) (*roomv1.TransferRoomHostResponse, error) {
// 主持人转移由 Room Cell 串行提交,并进入 command log/outbox。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.TransferRoomHost(ctx, req))
}
// MuteUser 代理到领域服务。
func (s *Server) MuteUser(ctx context.Context, req *roomv1.MuteUserRequest) (*roomv1.MuteUserResponse, error) {
// 禁言状态是腾讯云 IM 发言回调的权威来源。
ctx = contextWithMetaApp(ctx, req.GetMeta())
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())
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())
return mapServiceResult(s.svc.UnbanUser(ctx, req))
}
// SendGift 代理到领域服务。
func (s *Server) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) {
// SendGift 先同步扣费,成功后才更新房间热度和本地榜。
ctx = contextWithMetaApp(ctx, req.GetMeta())
return mapServiceResult(s.svc.SendGift(ctx, req))
}
// CheckSpeakPermission 代理到领域服务。
func (s *Server) CheckSpeakPermission(ctx context.Context, req *roomv1.CheckSpeakPermissionRequest) (*roomv1.CheckSpeakPermissionResponse, error) {
// 外部 IM 发公屏前调用该守卫,避免复制禁言和房间状态规则。
ctx = appcode.WithContext(ctx, req.GetAppCode())
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())
return mapServiceResult(s.svc.VerifyRoomPresence(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))
}
// 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())
return mapServiceResult(s.svc.GetRoomSnapshot(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())
}