package service import ( "context" roomv1 "hyapp.local/api/proto/room/v1" "hyapp/pkg/appcode" ) // CheckSpeakPermission 给腾讯云 IM 回调或 gateway 提供同步发言守卫。 func (s *Service) CheckSpeakPermission(ctx context.Context, req *roomv1.CheckSpeakPermissionRequest) (*roomv1.CheckSpeakPermissionResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) closed, err := s.isRoomClosed(ctx, req.GetRoomId()) if err != nil { return nil, err } if closed { return &roomv1.CheckSpeakPermissionResponse{ Allowed: false, Reason: "room_closed", }, nil } // 守卫查询优先读内存 Cell;没有 Cell 时从快照读取,避免外部 IM 入口越权自判。 snapshot, err := s.currentSnapshot(ctx, req.GetRoomId()) if err != nil { return nil, err } if snapshot == nil { // 没有快照代表 room-service 不认识该房间。 return &roomv1.CheckSpeakPermissionResponse{ Allowed: false, Reason: "room_not_found", }, nil } nowMS := s.clock.Now().UnixMilli() banned := snapshotUserBanned(snapshot, req.GetUserId(), nowMS) // 允许发言必须同时满足未 ban、未 mute、房间开启聊天、用户在业务 presence 内。 if !banned && !containsID(snapshot.GetMuteUserIds(), req.GetUserId()) && snapshot.GetChatEnabled() && findProtoUser(snapshot, req.GetUserId()) != nil { return &roomv1.CheckSpeakPermissionResponse{ Allowed: true, RoomVersion: snapshot.GetVersion(), }, nil } reason := "not_in_room" switch { case banned: // ban 优先级最高,被踢用户即使仍在腾讯云 IM 群内也不能发言。 reason = "user_banned" case containsID(snapshot.GetMuteUserIds(), req.GetUserId()): reason = "user_muted" case !snapshot.GetChatEnabled(): reason = "chat_disabled" } return &roomv1.CheckSpeakPermissionResponse{ Allowed: false, Reason: reason, RoomVersion: snapshot.GetVersion(), }, nil } // VerifyRoomPresence 给外部 IM 入口提供进群前的同步 presence 守卫。 func (s *Service) VerifyRoomPresence(ctx context.Context, req *roomv1.VerifyRoomPresenceRequest) (*roomv1.VerifyRoomPresenceResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) // 腾讯云 IM 进群前必须通过该守卫,不能只依赖客户端先调用过 JoinRoom。 closed, err := s.isRoomClosed(ctx, req.GetRoomId()) if err != nil { return nil, err } if closed { return &roomv1.VerifyRoomPresenceResponse{ Present: false, Reason: "room_closed", }, nil } snapshot, err := s.currentSnapshot(ctx, req.GetRoomId()) if err != nil { return nil, err } if snapshot == nil { // 房间不存在时外部 IM 入口不应把用户加入房间频道。 return &roomv1.VerifyRoomPresenceResponse{ Present: false, Reason: "room_not_found", }, nil } if snapshotUserBanned(snapshot, req.GetUserId(), s.clock.Now().UnixMilli()) { // ban 用户不能订阅,即使快照里还有旧 presence 也以 ban 为准。 return &roomv1.VerifyRoomPresenceResponse{ Present: false, Reason: "user_banned", RoomVersion: snapshot.GetVersion(), }, nil } if findProtoUser(snapshot, req.GetUserId()) == nil { // 没有 room-service presence 的用户不能偷收房间消息。 return &roomv1.VerifyRoomPresenceResponse{ Present: false, Reason: "not_in_room", RoomVersion: snapshot.GetVersion(), }, nil } return &roomv1.VerifyRoomPresenceResponse{ Present: true, RoomVersion: snapshot.GetVersion(), }, nil }