210 lines
9.6 KiB
Go
210 lines
9.6 KiB
Go
// 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.Error,grpc-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())
|
||
}
|