Merge branch 'test'
This commit is contained in:
commit
0ecb394dfc
@ -203,14 +203,15 @@ func (x *RoomCreated) GetRoomDescription() string {
|
|||||||
|
|
||||||
// RoomProfileUpdated 表达房间展示资料或麦位数量变更。
|
// RoomProfileUpdated 表达房间展示资料或麦位数量变更。
|
||||||
type RoomProfileUpdated struct {
|
type RoomProfileUpdated struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"`
|
ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"`
|
||||||
RoomName string `protobuf:"bytes,2,opt,name=room_name,json=roomName,proto3" json:"room_name,omitempty"`
|
RoomName string `protobuf:"bytes,2,opt,name=room_name,json=roomName,proto3" json:"room_name,omitempty"`
|
||||||
RoomAvatar string `protobuf:"bytes,3,opt,name=room_avatar,json=roomAvatar,proto3" json:"room_avatar,omitempty"`
|
RoomAvatar string `protobuf:"bytes,3,opt,name=room_avatar,json=roomAvatar,proto3" json:"room_avatar,omitempty"`
|
||||||
RoomDescription string `protobuf:"bytes,4,opt,name=room_description,json=roomDescription,proto3" json:"room_description,omitempty"`
|
RoomDescription string `protobuf:"bytes,4,opt,name=room_description,json=roomDescription,proto3" json:"room_description,omitempty"`
|
||||||
SeatCount int32 `protobuf:"varint,5,opt,name=seat_count,json=seatCount,proto3" json:"seat_count,omitempty"`
|
SeatCount int32 `protobuf:"varint,5,opt,name=seat_count,json=seatCount,proto3" json:"seat_count,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
RoomBackgroundUrl string `protobuf:"bytes,6,opt,name=room_background_url,json=roomBackgroundUrl,proto3" json:"room_background_url,omitempty"`
|
||||||
sizeCache protoimpl.SizeCache
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomProfileUpdated) Reset() {
|
func (x *RoomProfileUpdated) Reset() {
|
||||||
@ -278,6 +279,74 @@ func (x *RoomProfileUpdated) GetSeatCount() int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *RoomProfileUpdated) GetRoomBackgroundUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.RoomBackgroundUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoomBackgroundChanged 表达房主切换当前房间背景图。
|
||||||
|
type RoomBackgroundChanged struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"`
|
||||||
|
BackgroundId int64 `protobuf:"varint,2,opt,name=background_id,json=backgroundId,proto3" json:"background_id,omitempty"`
|
||||||
|
RoomBackgroundUrl string `protobuf:"bytes,3,opt,name=room_background_url,json=roomBackgroundUrl,proto3" json:"room_background_url,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomBackgroundChanged) Reset() {
|
||||||
|
*x = RoomBackgroundChanged{}
|
||||||
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomBackgroundChanged) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RoomBackgroundChanged) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RoomBackgroundChanged) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RoomBackgroundChanged.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RoomBackgroundChanged) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomBackgroundChanged) GetActorUserId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ActorUserId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomBackgroundChanged) GetBackgroundId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.BackgroundId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomBackgroundChanged) GetRoomBackgroundUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.RoomBackgroundUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// RoomUserJoined 表达用户业务态进房成功。
|
// RoomUserJoined 表达用户业务态进房成功。
|
||||||
type RoomUserJoined struct {
|
type RoomUserJoined struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
@ -291,7 +360,7 @@ type RoomUserJoined struct {
|
|||||||
|
|
||||||
func (x *RoomUserJoined) Reset() {
|
func (x *RoomUserJoined) Reset() {
|
||||||
*x = RoomUserJoined{}
|
*x = RoomUserJoined{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[3]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[4]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -303,7 +372,7 @@ func (x *RoomUserJoined) String() string {
|
|||||||
func (*RoomUserJoined) ProtoMessage() {}
|
func (*RoomUserJoined) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomUserJoined) ProtoReflect() protoreflect.Message {
|
func (x *RoomUserJoined) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[3]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[4]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -316,7 +385,7 @@ func (x *RoomUserJoined) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomUserJoined.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomUserJoined.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomUserJoined) Descriptor() ([]byte, []int) {
|
func (*RoomUserJoined) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{3}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{4}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomUserJoined) GetUserId() int64 {
|
func (x *RoomUserJoined) GetUserId() int64 {
|
||||||
@ -350,7 +419,7 @@ type RoomUserLeft struct {
|
|||||||
|
|
||||||
func (x *RoomUserLeft) Reset() {
|
func (x *RoomUserLeft) Reset() {
|
||||||
*x = RoomUserLeft{}
|
*x = RoomUserLeft{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[4]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[5]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -362,7 +431,7 @@ func (x *RoomUserLeft) String() string {
|
|||||||
func (*RoomUserLeft) ProtoMessage() {}
|
func (*RoomUserLeft) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomUserLeft) ProtoReflect() protoreflect.Message {
|
func (x *RoomUserLeft) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[4]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[5]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -375,7 +444,7 @@ func (x *RoomUserLeft) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomUserLeft.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomUserLeft.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomUserLeft) Descriptor() ([]byte, []int) {
|
func (*RoomUserLeft) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{4}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{5}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomUserLeft) GetUserId() int64 {
|
func (x *RoomUserLeft) GetUserId() int64 {
|
||||||
@ -396,7 +465,7 @@ type RoomClosed struct {
|
|||||||
|
|
||||||
func (x *RoomClosed) Reset() {
|
func (x *RoomClosed) Reset() {
|
||||||
*x = RoomClosed{}
|
*x = RoomClosed{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[5]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[6]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -408,7 +477,7 @@ func (x *RoomClosed) String() string {
|
|||||||
func (*RoomClosed) ProtoMessage() {}
|
func (*RoomClosed) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomClosed) ProtoReflect() protoreflect.Message {
|
func (x *RoomClosed) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[5]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[6]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -421,7 +490,7 @@ func (x *RoomClosed) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomClosed.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomClosed.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomClosed) Descriptor() ([]byte, []int) {
|
func (*RoomClosed) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{5}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{6}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomClosed) GetActorUserId() int64 {
|
func (x *RoomClosed) GetActorUserId() int64 {
|
||||||
@ -462,7 +531,7 @@ type RoomMicChanged struct {
|
|||||||
|
|
||||||
func (x *RoomMicChanged) Reset() {
|
func (x *RoomMicChanged) Reset() {
|
||||||
*x = RoomMicChanged{}
|
*x = RoomMicChanged{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[6]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[7]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -474,7 +543,7 @@ func (x *RoomMicChanged) String() string {
|
|||||||
func (*RoomMicChanged) ProtoMessage() {}
|
func (*RoomMicChanged) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomMicChanged) ProtoReflect() protoreflect.Message {
|
func (x *RoomMicChanged) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[6]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[7]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -487,7 +556,7 @@ func (x *RoomMicChanged) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomMicChanged.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomMicChanged.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomMicChanged) Descriptor() ([]byte, []int) {
|
func (*RoomMicChanged) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{6}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{7}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomMicChanged) GetActorUserId() int64 {
|
func (x *RoomMicChanged) GetActorUserId() int64 {
|
||||||
@ -586,7 +655,7 @@ type RoomMicSeatLocked struct {
|
|||||||
|
|
||||||
func (x *RoomMicSeatLocked) Reset() {
|
func (x *RoomMicSeatLocked) Reset() {
|
||||||
*x = RoomMicSeatLocked{}
|
*x = RoomMicSeatLocked{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[7]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[8]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -598,7 +667,7 @@ func (x *RoomMicSeatLocked) String() string {
|
|||||||
func (*RoomMicSeatLocked) ProtoMessage() {}
|
func (*RoomMicSeatLocked) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomMicSeatLocked) ProtoReflect() protoreflect.Message {
|
func (x *RoomMicSeatLocked) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[7]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[8]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -611,7 +680,7 @@ func (x *RoomMicSeatLocked) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomMicSeatLocked.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomMicSeatLocked.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomMicSeatLocked) Descriptor() ([]byte, []int) {
|
func (*RoomMicSeatLocked) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{7}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{8}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomMicSeatLocked) GetActorUserId() int64 {
|
func (x *RoomMicSeatLocked) GetActorUserId() int64 {
|
||||||
@ -646,7 +715,7 @@ type RoomChatEnabledChanged struct {
|
|||||||
|
|
||||||
func (x *RoomChatEnabledChanged) Reset() {
|
func (x *RoomChatEnabledChanged) Reset() {
|
||||||
*x = RoomChatEnabledChanged{}
|
*x = RoomChatEnabledChanged{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[8]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[9]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -658,7 +727,7 @@ func (x *RoomChatEnabledChanged) String() string {
|
|||||||
func (*RoomChatEnabledChanged) ProtoMessage() {}
|
func (*RoomChatEnabledChanged) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomChatEnabledChanged) ProtoReflect() protoreflect.Message {
|
func (x *RoomChatEnabledChanged) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[8]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[9]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -671,7 +740,7 @@ func (x *RoomChatEnabledChanged) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomChatEnabledChanged.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomChatEnabledChanged.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomChatEnabledChanged) Descriptor() ([]byte, []int) {
|
func (*RoomChatEnabledChanged) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{8}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{9}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomChatEnabledChanged) GetActorUserId() int64 {
|
func (x *RoomChatEnabledChanged) GetActorUserId() int64 {
|
||||||
@ -699,7 +768,7 @@ type RoomPasswordChanged struct {
|
|||||||
|
|
||||||
func (x *RoomPasswordChanged) Reset() {
|
func (x *RoomPasswordChanged) Reset() {
|
||||||
*x = RoomPasswordChanged{}
|
*x = RoomPasswordChanged{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[9]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[10]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -711,7 +780,7 @@ func (x *RoomPasswordChanged) String() string {
|
|||||||
func (*RoomPasswordChanged) ProtoMessage() {}
|
func (*RoomPasswordChanged) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomPasswordChanged) ProtoReflect() protoreflect.Message {
|
func (x *RoomPasswordChanged) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[9]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[10]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -724,7 +793,7 @@ func (x *RoomPasswordChanged) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomPasswordChanged.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomPasswordChanged.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomPasswordChanged) Descriptor() ([]byte, []int) {
|
func (*RoomPasswordChanged) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{9}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{10}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomPasswordChanged) GetActorUserId() int64 {
|
func (x *RoomPasswordChanged) GetActorUserId() int64 {
|
||||||
@ -753,7 +822,7 @@ type RoomAdminChanged struct {
|
|||||||
|
|
||||||
func (x *RoomAdminChanged) Reset() {
|
func (x *RoomAdminChanged) Reset() {
|
||||||
*x = RoomAdminChanged{}
|
*x = RoomAdminChanged{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[10]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[11]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -765,7 +834,7 @@ func (x *RoomAdminChanged) String() string {
|
|||||||
func (*RoomAdminChanged) ProtoMessage() {}
|
func (*RoomAdminChanged) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomAdminChanged) ProtoReflect() protoreflect.Message {
|
func (x *RoomAdminChanged) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[10]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[11]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -778,7 +847,7 @@ func (x *RoomAdminChanged) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomAdminChanged.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomAdminChanged.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomAdminChanged) Descriptor() ([]byte, []int) {
|
func (*RoomAdminChanged) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{10}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{11}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomAdminChanged) GetActorUserId() int64 {
|
func (x *RoomAdminChanged) GetActorUserId() int64 {
|
||||||
@ -814,7 +883,7 @@ type RoomUserMuted struct {
|
|||||||
|
|
||||||
func (x *RoomUserMuted) Reset() {
|
func (x *RoomUserMuted) Reset() {
|
||||||
*x = RoomUserMuted{}
|
*x = RoomUserMuted{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[11]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[12]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -826,7 +895,7 @@ func (x *RoomUserMuted) String() string {
|
|||||||
func (*RoomUserMuted) ProtoMessage() {}
|
func (*RoomUserMuted) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomUserMuted) ProtoReflect() protoreflect.Message {
|
func (x *RoomUserMuted) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[11]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[12]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -839,7 +908,7 @@ func (x *RoomUserMuted) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomUserMuted.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomUserMuted.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomUserMuted) Descriptor() ([]byte, []int) {
|
func (*RoomUserMuted) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{11}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{12}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomUserMuted) GetActorUserId() int64 {
|
func (x *RoomUserMuted) GetActorUserId() int64 {
|
||||||
@ -874,7 +943,7 @@ type RoomUserKicked struct {
|
|||||||
|
|
||||||
func (x *RoomUserKicked) Reset() {
|
func (x *RoomUserKicked) Reset() {
|
||||||
*x = RoomUserKicked{}
|
*x = RoomUserKicked{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[12]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[13]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -886,7 +955,7 @@ func (x *RoomUserKicked) String() string {
|
|||||||
func (*RoomUserKicked) ProtoMessage() {}
|
func (*RoomUserKicked) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomUserKicked) ProtoReflect() protoreflect.Message {
|
func (x *RoomUserKicked) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[12]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[13]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -899,7 +968,7 @@ func (x *RoomUserKicked) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomUserKicked.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomUserKicked.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomUserKicked) Descriptor() ([]byte, []int) {
|
func (*RoomUserKicked) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{12}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{13}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomUserKicked) GetActorUserId() int64 {
|
func (x *RoomUserKicked) GetActorUserId() int64 {
|
||||||
@ -927,7 +996,7 @@ type RoomUserUnbanned struct {
|
|||||||
|
|
||||||
func (x *RoomUserUnbanned) Reset() {
|
func (x *RoomUserUnbanned) Reset() {
|
||||||
*x = RoomUserUnbanned{}
|
*x = RoomUserUnbanned{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[13]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[14]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -939,7 +1008,7 @@ func (x *RoomUserUnbanned) String() string {
|
|||||||
func (*RoomUserUnbanned) ProtoMessage() {}
|
func (*RoomUserUnbanned) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomUserUnbanned) ProtoReflect() protoreflect.Message {
|
func (x *RoomUserUnbanned) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[13]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[14]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -952,7 +1021,7 @@ func (x *RoomUserUnbanned) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomUserUnbanned.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomUserUnbanned.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomUserUnbanned) Descriptor() ([]byte, []int) {
|
func (*RoomUserUnbanned) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{13}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{14}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomUserUnbanned) GetActorUserId() int64 {
|
func (x *RoomUserUnbanned) GetActorUserId() int64 {
|
||||||
@ -987,7 +1056,7 @@ type RoomGiftSent struct {
|
|||||||
|
|
||||||
func (x *RoomGiftSent) Reset() {
|
func (x *RoomGiftSent) Reset() {
|
||||||
*x = RoomGiftSent{}
|
*x = RoomGiftSent{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[14]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[15]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -999,7 +1068,7 @@ func (x *RoomGiftSent) String() string {
|
|||||||
func (*RoomGiftSent) ProtoMessage() {}
|
func (*RoomGiftSent) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomGiftSent) ProtoReflect() protoreflect.Message {
|
func (x *RoomGiftSent) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[14]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[15]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1012,7 +1081,7 @@ func (x *RoomGiftSent) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomGiftSent.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomGiftSent.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomGiftSent) Descriptor() ([]byte, []int) {
|
func (*RoomGiftSent) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{14}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{15}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomGiftSent) GetSenderUserId() int64 {
|
func (x *RoomGiftSent) GetSenderUserId() int64 {
|
||||||
@ -1089,7 +1158,7 @@ type RoomHeatChanged struct {
|
|||||||
|
|
||||||
func (x *RoomHeatChanged) Reset() {
|
func (x *RoomHeatChanged) Reset() {
|
||||||
*x = RoomHeatChanged{}
|
*x = RoomHeatChanged{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[15]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[16]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1101,7 +1170,7 @@ func (x *RoomHeatChanged) String() string {
|
|||||||
func (*RoomHeatChanged) ProtoMessage() {}
|
func (*RoomHeatChanged) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomHeatChanged) ProtoReflect() protoreflect.Message {
|
func (x *RoomHeatChanged) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[15]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[16]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1114,7 +1183,7 @@ func (x *RoomHeatChanged) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomHeatChanged.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomHeatChanged.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomHeatChanged) Descriptor() ([]byte, []int) {
|
func (*RoomHeatChanged) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{15}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{16}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomHeatChanged) GetDelta() int64 {
|
func (x *RoomHeatChanged) GetDelta() int64 {
|
||||||
@ -1143,7 +1212,7 @@ type RoomRankChanged struct {
|
|||||||
|
|
||||||
func (x *RoomRankChanged) Reset() {
|
func (x *RoomRankChanged) Reset() {
|
||||||
*x = RoomRankChanged{}
|
*x = RoomRankChanged{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[16]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[17]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1155,7 +1224,7 @@ func (x *RoomRankChanged) String() string {
|
|||||||
func (*RoomRankChanged) ProtoMessage() {}
|
func (*RoomRankChanged) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomRankChanged) ProtoReflect() protoreflect.Message {
|
func (x *RoomRankChanged) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[16]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[17]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1168,7 +1237,7 @@ func (x *RoomRankChanged) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomRankChanged.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomRankChanged.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomRankChanged) Descriptor() ([]byte, []int) {
|
func (*RoomRankChanged) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{16}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{17}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomRankChanged) GetUserId() int64 {
|
func (x *RoomRankChanged) GetUserId() int64 {
|
||||||
@ -1208,7 +1277,7 @@ type RoomTreasureRewardGrant struct {
|
|||||||
|
|
||||||
func (x *RoomTreasureRewardGrant) Reset() {
|
func (x *RoomTreasureRewardGrant) Reset() {
|
||||||
*x = RoomTreasureRewardGrant{}
|
*x = RoomTreasureRewardGrant{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[17]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[18]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1220,7 +1289,7 @@ func (x *RoomTreasureRewardGrant) String() string {
|
|||||||
func (*RoomTreasureRewardGrant) ProtoMessage() {}
|
func (*RoomTreasureRewardGrant) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomTreasureRewardGrant) ProtoReflect() protoreflect.Message {
|
func (x *RoomTreasureRewardGrant) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[17]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[18]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1233,7 +1302,7 @@ func (x *RoomTreasureRewardGrant) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomTreasureRewardGrant.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomTreasureRewardGrant.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomTreasureRewardGrant) Descriptor() ([]byte, []int) {
|
func (*RoomTreasureRewardGrant) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{17}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{18}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomTreasureRewardGrant) GetRewardRole() string {
|
func (x *RoomTreasureRewardGrant) GetRewardRole() string {
|
||||||
@ -1315,7 +1384,7 @@ type RoomTreasureProgressChanged struct {
|
|||||||
|
|
||||||
func (x *RoomTreasureProgressChanged) Reset() {
|
func (x *RoomTreasureProgressChanged) Reset() {
|
||||||
*x = RoomTreasureProgressChanged{}
|
*x = RoomTreasureProgressChanged{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[18]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[19]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1327,7 +1396,7 @@ func (x *RoomTreasureProgressChanged) String() string {
|
|||||||
func (*RoomTreasureProgressChanged) ProtoMessage() {}
|
func (*RoomTreasureProgressChanged) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomTreasureProgressChanged) ProtoReflect() protoreflect.Message {
|
func (x *RoomTreasureProgressChanged) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[18]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[19]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1340,7 +1409,7 @@ func (x *RoomTreasureProgressChanged) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomTreasureProgressChanged.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomTreasureProgressChanged.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomTreasureProgressChanged) Descriptor() ([]byte, []int) {
|
func (*RoomTreasureProgressChanged) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{18}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{19}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomTreasureProgressChanged) GetBoxId() string {
|
func (x *RoomTreasureProgressChanged) GetBoxId() string {
|
||||||
@ -1461,7 +1530,7 @@ type RoomTreasureCountdownStarted struct {
|
|||||||
|
|
||||||
func (x *RoomTreasureCountdownStarted) Reset() {
|
func (x *RoomTreasureCountdownStarted) Reset() {
|
||||||
*x = RoomTreasureCountdownStarted{}
|
*x = RoomTreasureCountdownStarted{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[19]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[20]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1473,7 +1542,7 @@ func (x *RoomTreasureCountdownStarted) String() string {
|
|||||||
func (*RoomTreasureCountdownStarted) ProtoMessage() {}
|
func (*RoomTreasureCountdownStarted) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomTreasureCountdownStarted) ProtoReflect() protoreflect.Message {
|
func (x *RoomTreasureCountdownStarted) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[19]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[20]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1486,7 +1555,7 @@ func (x *RoomTreasureCountdownStarted) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomTreasureCountdownStarted.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomTreasureCountdownStarted.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomTreasureCountdownStarted) Descriptor() ([]byte, []int) {
|
func (*RoomTreasureCountdownStarted) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{19}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{20}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomTreasureCountdownStarted) GetBoxId() string {
|
func (x *RoomTreasureCountdownStarted) GetBoxId() string {
|
||||||
@ -1584,7 +1653,7 @@ type RoomTreasureOpened struct {
|
|||||||
|
|
||||||
func (x *RoomTreasureOpened) Reset() {
|
func (x *RoomTreasureOpened) Reset() {
|
||||||
*x = RoomTreasureOpened{}
|
*x = RoomTreasureOpened{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[20]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[21]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1596,7 +1665,7 @@ func (x *RoomTreasureOpened) String() string {
|
|||||||
func (*RoomTreasureOpened) ProtoMessage() {}
|
func (*RoomTreasureOpened) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomTreasureOpened) ProtoReflect() protoreflect.Message {
|
func (x *RoomTreasureOpened) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[20]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[21]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1609,7 +1678,7 @@ func (x *RoomTreasureOpened) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomTreasureOpened.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomTreasureOpened.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomTreasureOpened) Descriptor() ([]byte, []int) {
|
func (*RoomTreasureOpened) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{20}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{21}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomTreasureOpened) GetBoxId() string {
|
func (x *RoomTreasureOpened) GetBoxId() string {
|
||||||
@ -1687,7 +1756,7 @@ type RoomTreasureRewardGranted struct {
|
|||||||
|
|
||||||
func (x *RoomTreasureRewardGranted) Reset() {
|
func (x *RoomTreasureRewardGranted) Reset() {
|
||||||
*x = RoomTreasureRewardGranted{}
|
*x = RoomTreasureRewardGranted{}
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[21]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[22]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1699,7 +1768,7 @@ func (x *RoomTreasureRewardGranted) String() string {
|
|||||||
func (*RoomTreasureRewardGranted) ProtoMessage() {}
|
func (*RoomTreasureRewardGranted) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RoomTreasureRewardGranted) ProtoReflect() protoreflect.Message {
|
func (x *RoomTreasureRewardGranted) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[21]
|
mi := &file_proto_events_room_v1_events_proto_msgTypes[22]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1712,7 +1781,7 @@ func (x *RoomTreasureRewardGranted) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RoomTreasureRewardGranted.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RoomTreasureRewardGranted.ProtoReflect.Descriptor instead.
|
||||||
func (*RoomTreasureRewardGranted) Descriptor() ([]byte, []int) {
|
func (*RoomTreasureRewardGranted) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{21}
|
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{22}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomTreasureRewardGranted) GetBoxId() string {
|
func (x *RoomTreasureRewardGranted) GetBoxId() string {
|
||||||
@ -1758,7 +1827,7 @@ const file_proto_events_room_v1_events_proto_rawDesc = "" +
|
|||||||
"\troom_name\x18\x05 \x01(\tR\broomName\x12\x1f\n" +
|
"\troom_name\x18\x05 \x01(\tR\broomName\x12\x1f\n" +
|
||||||
"\vroom_avatar\x18\x06 \x01(\tR\n" +
|
"\vroom_avatar\x18\x06 \x01(\tR\n" +
|
||||||
"roomAvatar\x12)\n" +
|
"roomAvatar\x12)\n" +
|
||||||
"\x10room_description\x18\a \x01(\tR\x0froomDescription\"\xc0\x01\n" +
|
"\x10room_description\x18\a \x01(\tR\x0froomDescription\"\xf0\x01\n" +
|
||||||
"\x12RoomProfileUpdated\x12\"\n" +
|
"\x12RoomProfileUpdated\x12\"\n" +
|
||||||
"\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12\x1b\n" +
|
"\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12\x1b\n" +
|
||||||
"\troom_name\x18\x02 \x01(\tR\broomName\x12\x1f\n" +
|
"\troom_name\x18\x02 \x01(\tR\broomName\x12\x1f\n" +
|
||||||
@ -1766,7 +1835,12 @@ const file_proto_events_room_v1_events_proto_rawDesc = "" +
|
|||||||
"roomAvatar\x12)\n" +
|
"roomAvatar\x12)\n" +
|
||||||
"\x10room_description\x18\x04 \x01(\tR\x0froomDescription\x12\x1d\n" +
|
"\x10room_description\x18\x04 \x01(\tR\x0froomDescription\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"seat_count\x18\x05 \x01(\x05R\tseatCount\"i\n" +
|
"seat_count\x18\x05 \x01(\x05R\tseatCount\x12.\n" +
|
||||||
|
"\x13room_background_url\x18\x06 \x01(\tR\x11roomBackgroundUrl\"\x90\x01\n" +
|
||||||
|
"\x15RoomBackgroundChanged\x12\"\n" +
|
||||||
|
"\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12#\n" +
|
||||||
|
"\rbackground_id\x18\x02 \x01(\x03R\fbackgroundId\x12.\n" +
|
||||||
|
"\x13room_background_url\x18\x03 \x01(\tR\x11roomBackgroundUrl\"i\n" +
|
||||||
"\x0eRoomUserJoined\x12\x17\n" +
|
"\x0eRoomUserJoined\x12\x17\n" +
|
||||||
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x12\n" +
|
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x12\n" +
|
||||||
"\x04role\x18\x02 \x01(\tR\x04role\x12*\n" +
|
"\x04role\x18\x02 \x01(\tR\x04role\x12*\n" +
|
||||||
@ -1911,34 +1985,35 @@ func file_proto_events_room_v1_events_proto_rawDescGZIP() []byte {
|
|||||||
return file_proto_events_room_v1_events_proto_rawDescData
|
return file_proto_events_room_v1_events_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_proto_events_room_v1_events_proto_msgTypes = make([]protoimpl.MessageInfo, 22)
|
var file_proto_events_room_v1_events_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
|
||||||
var file_proto_events_room_v1_events_proto_goTypes = []any{
|
var file_proto_events_room_v1_events_proto_goTypes = []any{
|
||||||
(*EventEnvelope)(nil), // 0: hyapp.events.room.v1.EventEnvelope
|
(*EventEnvelope)(nil), // 0: hyapp.events.room.v1.EventEnvelope
|
||||||
(*RoomCreated)(nil), // 1: hyapp.events.room.v1.RoomCreated
|
(*RoomCreated)(nil), // 1: hyapp.events.room.v1.RoomCreated
|
||||||
(*RoomProfileUpdated)(nil), // 2: hyapp.events.room.v1.RoomProfileUpdated
|
(*RoomProfileUpdated)(nil), // 2: hyapp.events.room.v1.RoomProfileUpdated
|
||||||
(*RoomUserJoined)(nil), // 3: hyapp.events.room.v1.RoomUserJoined
|
(*RoomBackgroundChanged)(nil), // 3: hyapp.events.room.v1.RoomBackgroundChanged
|
||||||
(*RoomUserLeft)(nil), // 4: hyapp.events.room.v1.RoomUserLeft
|
(*RoomUserJoined)(nil), // 4: hyapp.events.room.v1.RoomUserJoined
|
||||||
(*RoomClosed)(nil), // 5: hyapp.events.room.v1.RoomClosed
|
(*RoomUserLeft)(nil), // 5: hyapp.events.room.v1.RoomUserLeft
|
||||||
(*RoomMicChanged)(nil), // 6: hyapp.events.room.v1.RoomMicChanged
|
(*RoomClosed)(nil), // 6: hyapp.events.room.v1.RoomClosed
|
||||||
(*RoomMicSeatLocked)(nil), // 7: hyapp.events.room.v1.RoomMicSeatLocked
|
(*RoomMicChanged)(nil), // 7: hyapp.events.room.v1.RoomMicChanged
|
||||||
(*RoomChatEnabledChanged)(nil), // 8: hyapp.events.room.v1.RoomChatEnabledChanged
|
(*RoomMicSeatLocked)(nil), // 8: hyapp.events.room.v1.RoomMicSeatLocked
|
||||||
(*RoomPasswordChanged)(nil), // 9: hyapp.events.room.v1.RoomPasswordChanged
|
(*RoomChatEnabledChanged)(nil), // 9: hyapp.events.room.v1.RoomChatEnabledChanged
|
||||||
(*RoomAdminChanged)(nil), // 10: hyapp.events.room.v1.RoomAdminChanged
|
(*RoomPasswordChanged)(nil), // 10: hyapp.events.room.v1.RoomPasswordChanged
|
||||||
(*RoomUserMuted)(nil), // 11: hyapp.events.room.v1.RoomUserMuted
|
(*RoomAdminChanged)(nil), // 11: hyapp.events.room.v1.RoomAdminChanged
|
||||||
(*RoomUserKicked)(nil), // 12: hyapp.events.room.v1.RoomUserKicked
|
(*RoomUserMuted)(nil), // 12: hyapp.events.room.v1.RoomUserMuted
|
||||||
(*RoomUserUnbanned)(nil), // 13: hyapp.events.room.v1.RoomUserUnbanned
|
(*RoomUserKicked)(nil), // 13: hyapp.events.room.v1.RoomUserKicked
|
||||||
(*RoomGiftSent)(nil), // 14: hyapp.events.room.v1.RoomGiftSent
|
(*RoomUserUnbanned)(nil), // 14: hyapp.events.room.v1.RoomUserUnbanned
|
||||||
(*RoomHeatChanged)(nil), // 15: hyapp.events.room.v1.RoomHeatChanged
|
(*RoomGiftSent)(nil), // 15: hyapp.events.room.v1.RoomGiftSent
|
||||||
(*RoomRankChanged)(nil), // 16: hyapp.events.room.v1.RoomRankChanged
|
(*RoomHeatChanged)(nil), // 16: hyapp.events.room.v1.RoomHeatChanged
|
||||||
(*RoomTreasureRewardGrant)(nil), // 17: hyapp.events.room.v1.RoomTreasureRewardGrant
|
(*RoomRankChanged)(nil), // 17: hyapp.events.room.v1.RoomRankChanged
|
||||||
(*RoomTreasureProgressChanged)(nil), // 18: hyapp.events.room.v1.RoomTreasureProgressChanged
|
(*RoomTreasureRewardGrant)(nil), // 18: hyapp.events.room.v1.RoomTreasureRewardGrant
|
||||||
(*RoomTreasureCountdownStarted)(nil), // 19: hyapp.events.room.v1.RoomTreasureCountdownStarted
|
(*RoomTreasureProgressChanged)(nil), // 19: hyapp.events.room.v1.RoomTreasureProgressChanged
|
||||||
(*RoomTreasureOpened)(nil), // 20: hyapp.events.room.v1.RoomTreasureOpened
|
(*RoomTreasureCountdownStarted)(nil), // 20: hyapp.events.room.v1.RoomTreasureCountdownStarted
|
||||||
(*RoomTreasureRewardGranted)(nil), // 21: hyapp.events.room.v1.RoomTreasureRewardGranted
|
(*RoomTreasureOpened)(nil), // 21: hyapp.events.room.v1.RoomTreasureOpened
|
||||||
|
(*RoomTreasureRewardGranted)(nil), // 22: hyapp.events.room.v1.RoomTreasureRewardGranted
|
||||||
}
|
}
|
||||||
var file_proto_events_room_v1_events_proto_depIdxs = []int32{
|
var file_proto_events_room_v1_events_proto_depIdxs = []int32{
|
||||||
17, // 0: hyapp.events.room.v1.RoomTreasureOpened.rewards:type_name -> hyapp.events.room.v1.RoomTreasureRewardGrant
|
18, // 0: hyapp.events.room.v1.RoomTreasureOpened.rewards:type_name -> hyapp.events.room.v1.RoomTreasureRewardGrant
|
||||||
17, // 1: hyapp.events.room.v1.RoomTreasureRewardGranted.rewards:type_name -> hyapp.events.room.v1.RoomTreasureRewardGrant
|
18, // 1: hyapp.events.room.v1.RoomTreasureRewardGranted.rewards:type_name -> hyapp.events.room.v1.RoomTreasureRewardGrant
|
||||||
2, // [2:2] is the sub-list for method output_type
|
2, // [2:2] is the sub-list for method output_type
|
||||||
2, // [2:2] is the sub-list for method input_type
|
2, // [2:2] is the sub-list for method input_type
|
||||||
2, // [2:2] is the sub-list for extension type_name
|
2, // [2:2] is the sub-list for extension type_name
|
||||||
@ -1957,7 +2032,7 @@ func file_proto_events_room_v1_events_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_events_room_v1_events_proto_rawDesc), len(file_proto_events_room_v1_events_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_events_room_v1_events_proto_rawDesc), len(file_proto_events_room_v1_events_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 22,
|
NumMessages: 23,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -34,6 +34,14 @@ message RoomProfileUpdated {
|
|||||||
string room_avatar = 3;
|
string room_avatar = 3;
|
||||||
string room_description = 4;
|
string room_description = 4;
|
||||||
int32 seat_count = 5;
|
int32 seat_count = 5;
|
||||||
|
string room_background_url = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoomBackgroundChanged 表达房主切换当前房间背景图。
|
||||||
|
message RoomBackgroundChanged {
|
||||||
|
int64 actor_user_id = 1;
|
||||||
|
int64 background_id = 2;
|
||||||
|
string room_background_url = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomUserJoined 表达用户业务态进房成功。
|
// RoomUserJoined 表达用户业务态进房成功。
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -299,6 +299,15 @@ message AdminCancelRoomPinResponse {
|
|||||||
int64 server_time_ms = 2;
|
int64 server_time_ms = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoomBanState 表达单个房间 ban 的治理状态。
|
||||||
|
message RoomBanState {
|
||||||
|
int64 user_id = 1;
|
||||||
|
int64 actor_user_id = 2;
|
||||||
|
int64 created_at_ms = 3;
|
||||||
|
// expires_at_ms 为 0 表示永久 ban;非 0 表示该 UTC 毫秒后可重新进房。
|
||||||
|
int64 expires_at_ms = 4;
|
||||||
|
}
|
||||||
|
|
||||||
// RoomSnapshot 把 room-service 对外需要暴露的房间投影集中返回。
|
// RoomSnapshot 把 room-service 对外需要暴露的房间投影集中返回。
|
||||||
message RoomSnapshot {
|
message RoomSnapshot {
|
||||||
string room_id = 1;
|
string room_id = 1;
|
||||||
@ -322,6 +331,53 @@ message RoomSnapshot {
|
|||||||
bool locked = 19;
|
bool locked = 19;
|
||||||
// treasure 是语音房宝箱当前状态;配置物料通过 GetRoomTreasure 单独读取,避免快照过大。
|
// treasure 是语音房宝箱当前状态;配置物料通过 GetRoomTreasure 单独读取,避免快照过大。
|
||||||
RoomTreasureState treasure = 20;
|
RoomTreasureState treasure = 20;
|
||||||
|
// ban_states 保存 ban 的过期边界;ban_user_ids 仍只服务简单集合判断。
|
||||||
|
repeated RoomBanState ban_states = 21;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoomBackgroundImage 是房间维度保存过的背景图素材。
|
||||||
|
// 保存列表归属于 room-service,当前生效背景仍写入 RoomSnapshot.room_ext["background_url"]。
|
||||||
|
message RoomBackgroundImage {
|
||||||
|
int64 background_id = 1;
|
||||||
|
string room_id = 2;
|
||||||
|
string image_url = 3;
|
||||||
|
int64 created_by_user_id = 4;
|
||||||
|
int64 created_at_ms = 5;
|
||||||
|
int64 updated_at_ms = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveRoomBackgroundRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string room_id = 2;
|
||||||
|
string image_url = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveRoomBackgroundResponse {
|
||||||
|
RoomBackgroundImage background = 1;
|
||||||
|
int64 server_time_ms = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListRoomBackgroundsRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string room_id = 2;
|
||||||
|
int64 viewer_user_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListRoomBackgroundsResponse {
|
||||||
|
repeated RoomBackgroundImage backgrounds = 1;
|
||||||
|
string selected_background_url = 2;
|
||||||
|
int64 server_time_ms = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetRoomBackgroundRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
int64 background_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetRoomBackgroundResponse {
|
||||||
|
CommandResult result = 1;
|
||||||
|
RoomSnapshot room = 2;
|
||||||
|
RoomBackgroundImage background = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRoomRequest 创建一个新的房间执行单元。
|
// CreateRoomRequest 创建一个新的房间执行单元。
|
||||||
@ -662,6 +718,8 @@ message MuteUserResponse {
|
|||||||
message KickUserRequest {
|
message KickUserRequest {
|
||||||
RequestMeta meta = 1;
|
RequestMeta meta = 1;
|
||||||
int64 target_user_id = 2;
|
int64 target_user_id = 2;
|
||||||
|
// duration_ms 为 0 表示永久 ban;大于 0 表示从服务端当前 UTC 时间起禁止进房的时长。
|
||||||
|
int64 duration_ms = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// KickUserResponse 返回最新房间快照。
|
// KickUserResponse 返回最新房间快照。
|
||||||
@ -927,6 +985,34 @@ message ListRoomOnlineUsersResponse {
|
|||||||
repeated RoomOnlineUser items = 6;
|
repeated RoomOnlineUser items = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoomBannedUser 是房间黑名单列表的轻量治理读模型;用户展示资料由 gateway 批量补齐。
|
||||||
|
message RoomBannedUser {
|
||||||
|
int64 user_id = 1;
|
||||||
|
int64 actor_user_id = 2;
|
||||||
|
int64 created_at_ms = 3;
|
||||||
|
// unban_at_ms 为 0 表示永久 ban;非 0 表示该 UTC 毫秒后自动允许重新进房。
|
||||||
|
int64 unban_at_ms = 4;
|
||||||
|
int64 remaining_ms = 5;
|
||||||
|
bool permanent = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRoomBannedUsersRequest 分页查询房间当前仍有效的黑名单。
|
||||||
|
message ListRoomBannedUsersRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string room_id = 2;
|
||||||
|
int64 viewer_user_id = 3;
|
||||||
|
int32 page = 4;
|
||||||
|
int32 page_size = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListRoomBannedUsersResponse {
|
||||||
|
repeated RoomBannedUser items = 1;
|
||||||
|
int64 total = 2;
|
||||||
|
int32 page = 3;
|
||||||
|
int32 page_size = 4;
|
||||||
|
int64 server_time_ms = 5;
|
||||||
|
}
|
||||||
|
|
||||||
// FollowRoomRequest 建立当前用户对房间的关注关系。
|
// FollowRoomRequest 建立当前用户对房间的关注关系。
|
||||||
// 该关系是 room-service 的用户-房间低频关系事实,不进入 Room Cell command log。
|
// 该关系是 room-service 的用户-房间低频关系事实,不进入 Room Cell command log。
|
||||||
message FollowRoomRequest {
|
message FollowRoomRequest {
|
||||||
@ -959,6 +1045,8 @@ message UnfollowRoomResponse {
|
|||||||
service RoomCommandService {
|
service RoomCommandService {
|
||||||
rpc CreateRoom(CreateRoomRequest) returns (CreateRoomResponse);
|
rpc CreateRoom(CreateRoomRequest) returns (CreateRoomResponse);
|
||||||
rpc UpdateRoomProfile(UpdateRoomProfileRequest) returns (UpdateRoomProfileResponse);
|
rpc UpdateRoomProfile(UpdateRoomProfileRequest) returns (UpdateRoomProfileResponse);
|
||||||
|
rpc SaveRoomBackground(SaveRoomBackgroundRequest) returns (SaveRoomBackgroundResponse);
|
||||||
|
rpc SetRoomBackground(SetRoomBackgroundRequest) returns (SetRoomBackgroundResponse);
|
||||||
rpc JoinRoom(JoinRoomRequest) returns (JoinRoomResponse);
|
rpc JoinRoom(JoinRoomRequest) returns (JoinRoomResponse);
|
||||||
rpc RoomHeartbeat(RoomHeartbeatRequest) returns (RoomHeartbeatResponse);
|
rpc RoomHeartbeat(RoomHeartbeatRequest) returns (RoomHeartbeatResponse);
|
||||||
rpc LeaveRoom(LeaveRoomRequest) returns (LeaveRoomResponse);
|
rpc LeaveRoom(LeaveRoomRequest) returns (LeaveRoomResponse);
|
||||||
@ -1007,6 +1095,8 @@ service RoomQueryService {
|
|||||||
rpc GetMyRoom(GetMyRoomRequest) returns (GetMyRoomResponse);
|
rpc GetMyRoom(GetMyRoomRequest) returns (GetMyRoomResponse);
|
||||||
rpc GetCurrentRoom(GetCurrentRoomRequest) returns (GetCurrentRoomResponse);
|
rpc GetCurrentRoom(GetCurrentRoomRequest) returns (GetCurrentRoomResponse);
|
||||||
rpc GetRoomSnapshot(GetRoomSnapshotRequest) returns (GetRoomSnapshotResponse);
|
rpc GetRoomSnapshot(GetRoomSnapshotRequest) returns (GetRoomSnapshotResponse);
|
||||||
|
rpc ListRoomBackgrounds(ListRoomBackgroundsRequest) returns (ListRoomBackgroundsResponse);
|
||||||
rpc GetRoomTreasure(GetRoomTreasureRequest) returns (GetRoomTreasureResponse);
|
rpc GetRoomTreasure(GetRoomTreasureRequest) returns (GetRoomTreasureResponse);
|
||||||
rpc ListRoomOnlineUsers(ListRoomOnlineUsersRequest) returns (ListRoomOnlineUsersResponse);
|
rpc ListRoomOnlineUsers(ListRoomOnlineUsersRequest) returns (ListRoomOnlineUsersResponse);
|
||||||
|
rpc ListRoomBannedUsers(ListRoomBannedUsersRequest) returns (ListRoomBannedUsersResponse);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,8 @@ const _ = grpc.SupportPackageIsVersion9
|
|||||||
const (
|
const (
|
||||||
RoomCommandService_CreateRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/CreateRoom"
|
RoomCommandService_CreateRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/CreateRoom"
|
||||||
RoomCommandService_UpdateRoomProfile_FullMethodName = "/hyapp.room.v1.RoomCommandService/UpdateRoomProfile"
|
RoomCommandService_UpdateRoomProfile_FullMethodName = "/hyapp.room.v1.RoomCommandService/UpdateRoomProfile"
|
||||||
|
RoomCommandService_SaveRoomBackground_FullMethodName = "/hyapp.room.v1.RoomCommandService/SaveRoomBackground"
|
||||||
|
RoomCommandService_SetRoomBackground_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetRoomBackground"
|
||||||
RoomCommandService_JoinRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/JoinRoom"
|
RoomCommandService_JoinRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/JoinRoom"
|
||||||
RoomCommandService_RoomHeartbeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/RoomHeartbeat"
|
RoomCommandService_RoomHeartbeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/RoomHeartbeat"
|
||||||
RoomCommandService_LeaveRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/LeaveRoom"
|
RoomCommandService_LeaveRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/LeaveRoom"
|
||||||
@ -58,6 +60,8 @@ const (
|
|||||||
type RoomCommandServiceClient interface {
|
type RoomCommandServiceClient interface {
|
||||||
CreateRoom(ctx context.Context, in *CreateRoomRequest, opts ...grpc.CallOption) (*CreateRoomResponse, error)
|
CreateRoom(ctx context.Context, in *CreateRoomRequest, opts ...grpc.CallOption) (*CreateRoomResponse, error)
|
||||||
UpdateRoomProfile(ctx context.Context, in *UpdateRoomProfileRequest, opts ...grpc.CallOption) (*UpdateRoomProfileResponse, error)
|
UpdateRoomProfile(ctx context.Context, in *UpdateRoomProfileRequest, opts ...grpc.CallOption) (*UpdateRoomProfileResponse, error)
|
||||||
|
SaveRoomBackground(ctx context.Context, in *SaveRoomBackgroundRequest, opts ...grpc.CallOption) (*SaveRoomBackgroundResponse, error)
|
||||||
|
SetRoomBackground(ctx context.Context, in *SetRoomBackgroundRequest, opts ...grpc.CallOption) (*SetRoomBackgroundResponse, error)
|
||||||
JoinRoom(ctx context.Context, in *JoinRoomRequest, opts ...grpc.CallOption) (*JoinRoomResponse, error)
|
JoinRoom(ctx context.Context, in *JoinRoomRequest, opts ...grpc.CallOption) (*JoinRoomResponse, error)
|
||||||
RoomHeartbeat(ctx context.Context, in *RoomHeartbeatRequest, opts ...grpc.CallOption) (*RoomHeartbeatResponse, error)
|
RoomHeartbeat(ctx context.Context, in *RoomHeartbeatRequest, opts ...grpc.CallOption) (*RoomHeartbeatResponse, error)
|
||||||
LeaveRoom(ctx context.Context, in *LeaveRoomRequest, opts ...grpc.CallOption) (*LeaveRoomResponse, error)
|
LeaveRoom(ctx context.Context, in *LeaveRoomRequest, opts ...grpc.CallOption) (*LeaveRoomResponse, error)
|
||||||
@ -115,6 +119,26 @@ func (c *roomCommandServiceClient) UpdateRoomProfile(ctx context.Context, in *Up
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *roomCommandServiceClient) SaveRoomBackground(ctx context.Context, in *SaveRoomBackgroundRequest, opts ...grpc.CallOption) (*SaveRoomBackgroundResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(SaveRoomBackgroundResponse)
|
||||||
|
err := c.cc.Invoke(ctx, RoomCommandService_SaveRoomBackground_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *roomCommandServiceClient) SetRoomBackground(ctx context.Context, in *SetRoomBackgroundRequest, opts ...grpc.CallOption) (*SetRoomBackgroundResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(SetRoomBackgroundResponse)
|
||||||
|
err := c.cc.Invoke(ctx, RoomCommandService_SetRoomBackground_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *roomCommandServiceClient) JoinRoom(ctx context.Context, in *JoinRoomRequest, opts ...grpc.CallOption) (*JoinRoomResponse, error) {
|
func (c *roomCommandServiceClient) JoinRoom(ctx context.Context, in *JoinRoomRequest, opts ...grpc.CallOption) (*JoinRoomResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(JoinRoomResponse)
|
out := new(JoinRoomResponse)
|
||||||
@ -393,6 +417,8 @@ func (c *roomCommandServiceClient) UnfollowRoom(ctx context.Context, in *Unfollo
|
|||||||
type RoomCommandServiceServer interface {
|
type RoomCommandServiceServer interface {
|
||||||
CreateRoom(context.Context, *CreateRoomRequest) (*CreateRoomResponse, error)
|
CreateRoom(context.Context, *CreateRoomRequest) (*CreateRoomResponse, error)
|
||||||
UpdateRoomProfile(context.Context, *UpdateRoomProfileRequest) (*UpdateRoomProfileResponse, error)
|
UpdateRoomProfile(context.Context, *UpdateRoomProfileRequest) (*UpdateRoomProfileResponse, error)
|
||||||
|
SaveRoomBackground(context.Context, *SaveRoomBackgroundRequest) (*SaveRoomBackgroundResponse, error)
|
||||||
|
SetRoomBackground(context.Context, *SetRoomBackgroundRequest) (*SetRoomBackgroundResponse, error)
|
||||||
JoinRoom(context.Context, *JoinRoomRequest) (*JoinRoomResponse, error)
|
JoinRoom(context.Context, *JoinRoomRequest) (*JoinRoomResponse, error)
|
||||||
RoomHeartbeat(context.Context, *RoomHeartbeatRequest) (*RoomHeartbeatResponse, error)
|
RoomHeartbeat(context.Context, *RoomHeartbeatRequest) (*RoomHeartbeatResponse, error)
|
||||||
LeaveRoom(context.Context, *LeaveRoomRequest) (*LeaveRoomResponse, error)
|
LeaveRoom(context.Context, *LeaveRoomRequest) (*LeaveRoomResponse, error)
|
||||||
@ -436,6 +462,12 @@ func (UnimplementedRoomCommandServiceServer) CreateRoom(context.Context, *Create
|
|||||||
func (UnimplementedRoomCommandServiceServer) UpdateRoomProfile(context.Context, *UpdateRoomProfileRequest) (*UpdateRoomProfileResponse, error) {
|
func (UnimplementedRoomCommandServiceServer) UpdateRoomProfile(context.Context, *UpdateRoomProfileRequest) (*UpdateRoomProfileResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method UpdateRoomProfile not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateRoomProfile not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedRoomCommandServiceServer) SaveRoomBackground(context.Context, *SaveRoomBackgroundRequest) (*SaveRoomBackgroundResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method SaveRoomBackground not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedRoomCommandServiceServer) SetRoomBackground(context.Context, *SetRoomBackgroundRequest) (*SetRoomBackgroundResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method SetRoomBackground not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedRoomCommandServiceServer) JoinRoom(context.Context, *JoinRoomRequest) (*JoinRoomResponse, error) {
|
func (UnimplementedRoomCommandServiceServer) JoinRoom(context.Context, *JoinRoomRequest) (*JoinRoomResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method JoinRoom not implemented")
|
return nil, status.Error(codes.Unimplemented, "method JoinRoom not implemented")
|
||||||
}
|
}
|
||||||
@ -574,6 +606,42 @@ func _RoomCommandService_UpdateRoomProfile_Handler(srv interface{}, ctx context.
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _RoomCommandService_SaveRoomBackground_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SaveRoomBackgroundRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(RoomCommandServiceServer).SaveRoomBackground(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: RoomCommandService_SaveRoomBackground_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(RoomCommandServiceServer).SaveRoomBackground(ctx, req.(*SaveRoomBackgroundRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _RoomCommandService_SetRoomBackground_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SetRoomBackgroundRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(RoomCommandServiceServer).SetRoomBackground(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: RoomCommandService_SetRoomBackground_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(RoomCommandServiceServer).SetRoomBackground(ctx, req.(*SetRoomBackgroundRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _RoomCommandService_JoinRoom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _RoomCommandService_JoinRoom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(JoinRoomRequest)
|
in := new(JoinRoomRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -1075,6 +1143,14 @@ var RoomCommandService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "UpdateRoomProfile",
|
MethodName: "UpdateRoomProfile",
|
||||||
Handler: _RoomCommandService_UpdateRoomProfile_Handler,
|
Handler: _RoomCommandService_UpdateRoomProfile_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SaveRoomBackground",
|
||||||
|
Handler: _RoomCommandService_SaveRoomBackground_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SetRoomBackground",
|
||||||
|
Handler: _RoomCommandService_SetRoomBackground_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "JoinRoom",
|
MethodName: "JoinRoom",
|
||||||
Handler: _RoomCommandService_JoinRoom_Handler,
|
Handler: _RoomCommandService_JoinRoom_Handler,
|
||||||
@ -1344,8 +1420,10 @@ const (
|
|||||||
RoomQueryService_GetMyRoom_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetMyRoom"
|
RoomQueryService_GetMyRoom_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetMyRoom"
|
||||||
RoomQueryService_GetCurrentRoom_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetCurrentRoom"
|
RoomQueryService_GetCurrentRoom_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetCurrentRoom"
|
||||||
RoomQueryService_GetRoomSnapshot_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetRoomSnapshot"
|
RoomQueryService_GetRoomSnapshot_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetRoomSnapshot"
|
||||||
|
RoomQueryService_ListRoomBackgrounds_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRoomBackgrounds"
|
||||||
RoomQueryService_GetRoomTreasure_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetRoomTreasure"
|
RoomQueryService_GetRoomTreasure_FullMethodName = "/hyapp.room.v1.RoomQueryService/GetRoomTreasure"
|
||||||
RoomQueryService_ListRoomOnlineUsers_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRoomOnlineUsers"
|
RoomQueryService_ListRoomOnlineUsers_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRoomOnlineUsers"
|
||||||
|
RoomQueryService_ListRoomBannedUsers_FullMethodName = "/hyapp.room.v1.RoomQueryService/ListRoomBannedUsers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RoomQueryServiceClient is the client API for RoomQueryService service.
|
// RoomQueryServiceClient is the client API for RoomQueryService service.
|
||||||
@ -1365,8 +1443,10 @@ type RoomQueryServiceClient interface {
|
|||||||
GetMyRoom(ctx context.Context, in *GetMyRoomRequest, opts ...grpc.CallOption) (*GetMyRoomResponse, error)
|
GetMyRoom(ctx context.Context, in *GetMyRoomRequest, opts ...grpc.CallOption) (*GetMyRoomResponse, error)
|
||||||
GetCurrentRoom(ctx context.Context, in *GetCurrentRoomRequest, opts ...grpc.CallOption) (*GetCurrentRoomResponse, error)
|
GetCurrentRoom(ctx context.Context, in *GetCurrentRoomRequest, opts ...grpc.CallOption) (*GetCurrentRoomResponse, error)
|
||||||
GetRoomSnapshot(ctx context.Context, in *GetRoomSnapshotRequest, opts ...grpc.CallOption) (*GetRoomSnapshotResponse, error)
|
GetRoomSnapshot(ctx context.Context, in *GetRoomSnapshotRequest, opts ...grpc.CallOption) (*GetRoomSnapshotResponse, error)
|
||||||
|
ListRoomBackgrounds(ctx context.Context, in *ListRoomBackgroundsRequest, opts ...grpc.CallOption) (*ListRoomBackgroundsResponse, error)
|
||||||
GetRoomTreasure(ctx context.Context, in *GetRoomTreasureRequest, opts ...grpc.CallOption) (*GetRoomTreasureResponse, error)
|
GetRoomTreasure(ctx context.Context, in *GetRoomTreasureRequest, opts ...grpc.CallOption) (*GetRoomTreasureResponse, error)
|
||||||
ListRoomOnlineUsers(ctx context.Context, in *ListRoomOnlineUsersRequest, opts ...grpc.CallOption) (*ListRoomOnlineUsersResponse, error)
|
ListRoomOnlineUsers(ctx context.Context, in *ListRoomOnlineUsersRequest, opts ...grpc.CallOption) (*ListRoomOnlineUsersResponse, error)
|
||||||
|
ListRoomBannedUsers(ctx context.Context, in *ListRoomBannedUsersRequest, opts ...grpc.CallOption) (*ListRoomBannedUsersResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type roomQueryServiceClient struct {
|
type roomQueryServiceClient struct {
|
||||||
@ -1487,6 +1567,16 @@ func (c *roomQueryServiceClient) GetRoomSnapshot(ctx context.Context, in *GetRoo
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *roomQueryServiceClient) ListRoomBackgrounds(ctx context.Context, in *ListRoomBackgroundsRequest, opts ...grpc.CallOption) (*ListRoomBackgroundsResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListRoomBackgroundsResponse)
|
||||||
|
err := c.cc.Invoke(ctx, RoomQueryService_ListRoomBackgrounds_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *roomQueryServiceClient) GetRoomTreasure(ctx context.Context, in *GetRoomTreasureRequest, opts ...grpc.CallOption) (*GetRoomTreasureResponse, error) {
|
func (c *roomQueryServiceClient) GetRoomTreasure(ctx context.Context, in *GetRoomTreasureRequest, opts ...grpc.CallOption) (*GetRoomTreasureResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(GetRoomTreasureResponse)
|
out := new(GetRoomTreasureResponse)
|
||||||
@ -1507,6 +1597,16 @@ func (c *roomQueryServiceClient) ListRoomOnlineUsers(ctx context.Context, in *Li
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *roomQueryServiceClient) ListRoomBannedUsers(ctx context.Context, in *ListRoomBannedUsersRequest, opts ...grpc.CallOption) (*ListRoomBannedUsersResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListRoomBannedUsersResponse)
|
||||||
|
err := c.cc.Invoke(ctx, RoomQueryService_ListRoomBannedUsers_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RoomQueryServiceServer is the server API for RoomQueryService service.
|
// RoomQueryServiceServer is the server API for RoomQueryService service.
|
||||||
// All implementations must embed UnimplementedRoomQueryServiceServer
|
// All implementations must embed UnimplementedRoomQueryServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@ -1524,8 +1624,10 @@ type RoomQueryServiceServer interface {
|
|||||||
GetMyRoom(context.Context, *GetMyRoomRequest) (*GetMyRoomResponse, error)
|
GetMyRoom(context.Context, *GetMyRoomRequest) (*GetMyRoomResponse, error)
|
||||||
GetCurrentRoom(context.Context, *GetCurrentRoomRequest) (*GetCurrentRoomResponse, error)
|
GetCurrentRoom(context.Context, *GetCurrentRoomRequest) (*GetCurrentRoomResponse, error)
|
||||||
GetRoomSnapshot(context.Context, *GetRoomSnapshotRequest) (*GetRoomSnapshotResponse, error)
|
GetRoomSnapshot(context.Context, *GetRoomSnapshotRequest) (*GetRoomSnapshotResponse, error)
|
||||||
|
ListRoomBackgrounds(context.Context, *ListRoomBackgroundsRequest) (*ListRoomBackgroundsResponse, error)
|
||||||
GetRoomTreasure(context.Context, *GetRoomTreasureRequest) (*GetRoomTreasureResponse, error)
|
GetRoomTreasure(context.Context, *GetRoomTreasureRequest) (*GetRoomTreasureResponse, error)
|
||||||
ListRoomOnlineUsers(context.Context, *ListRoomOnlineUsersRequest) (*ListRoomOnlineUsersResponse, error)
|
ListRoomOnlineUsers(context.Context, *ListRoomOnlineUsersRequest) (*ListRoomOnlineUsersResponse, error)
|
||||||
|
ListRoomBannedUsers(context.Context, *ListRoomBannedUsersRequest) (*ListRoomBannedUsersResponse, error)
|
||||||
mustEmbedUnimplementedRoomQueryServiceServer()
|
mustEmbedUnimplementedRoomQueryServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1569,12 +1671,18 @@ func (UnimplementedRoomQueryServiceServer) GetCurrentRoom(context.Context, *GetC
|
|||||||
func (UnimplementedRoomQueryServiceServer) GetRoomSnapshot(context.Context, *GetRoomSnapshotRequest) (*GetRoomSnapshotResponse, error) {
|
func (UnimplementedRoomQueryServiceServer) GetRoomSnapshot(context.Context, *GetRoomSnapshotRequest) (*GetRoomSnapshotResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method GetRoomSnapshot not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetRoomSnapshot not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedRoomQueryServiceServer) ListRoomBackgrounds(context.Context, *ListRoomBackgroundsRequest) (*ListRoomBackgroundsResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ListRoomBackgrounds not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedRoomQueryServiceServer) GetRoomTreasure(context.Context, *GetRoomTreasureRequest) (*GetRoomTreasureResponse, error) {
|
func (UnimplementedRoomQueryServiceServer) GetRoomTreasure(context.Context, *GetRoomTreasureRequest) (*GetRoomTreasureResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method GetRoomTreasure not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetRoomTreasure not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedRoomQueryServiceServer) ListRoomOnlineUsers(context.Context, *ListRoomOnlineUsersRequest) (*ListRoomOnlineUsersResponse, error) {
|
func (UnimplementedRoomQueryServiceServer) ListRoomOnlineUsers(context.Context, *ListRoomOnlineUsersRequest) (*ListRoomOnlineUsersResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method ListRoomOnlineUsers not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListRoomOnlineUsers not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedRoomQueryServiceServer) ListRoomBannedUsers(context.Context, *ListRoomBannedUsersRequest) (*ListRoomBannedUsersResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ListRoomBannedUsers not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedRoomQueryServiceServer) mustEmbedUnimplementedRoomQueryServiceServer() {}
|
func (UnimplementedRoomQueryServiceServer) mustEmbedUnimplementedRoomQueryServiceServer() {}
|
||||||
func (UnimplementedRoomQueryServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedRoomQueryServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@ -1794,6 +1902,24 @@ func _RoomQueryService_GetRoomSnapshot_Handler(srv interface{}, ctx context.Cont
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _RoomQueryService_ListRoomBackgrounds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListRoomBackgroundsRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(RoomQueryServiceServer).ListRoomBackgrounds(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: RoomQueryService_ListRoomBackgrounds_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(RoomQueryServiceServer).ListRoomBackgrounds(ctx, req.(*ListRoomBackgroundsRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _RoomQueryService_GetRoomTreasure_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _RoomQueryService_GetRoomTreasure_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(GetRoomTreasureRequest)
|
in := new(GetRoomTreasureRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -1830,6 +1956,24 @@ func _RoomQueryService_ListRoomOnlineUsers_Handler(srv interface{}, ctx context.
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _RoomQueryService_ListRoomBannedUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListRoomBannedUsersRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(RoomQueryServiceServer).ListRoomBannedUsers(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: RoomQueryService_ListRoomBannedUsers_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(RoomQueryServiceServer).ListRoomBannedUsers(ctx, req.(*ListRoomBannedUsersRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// RoomQueryService_ServiceDesc is the grpc.ServiceDesc for RoomQueryService service.
|
// RoomQueryService_ServiceDesc is the grpc.ServiceDesc for RoomQueryService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@ -1881,6 +2025,10 @@ var RoomQueryService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetRoomSnapshot",
|
MethodName: "GetRoomSnapshot",
|
||||||
Handler: _RoomQueryService_GetRoomSnapshot_Handler,
|
Handler: _RoomQueryService_GetRoomSnapshot_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListRoomBackgrounds",
|
||||||
|
Handler: _RoomQueryService_ListRoomBackgrounds_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "GetRoomTreasure",
|
MethodName: "GetRoomTreasure",
|
||||||
Handler: _RoomQueryService_GetRoomTreasure_Handler,
|
Handler: _RoomQueryService_GetRoomTreasure_Handler,
|
||||||
@ -1889,6 +2037,10 @@ var RoomQueryService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "ListRoomOnlineUsers",
|
MethodName: "ListRoomOnlineUsers",
|
||||||
Handler: _RoomQueryService_ListRoomOnlineUsers_Handler,
|
Handler: _RoomQueryService_ListRoomOnlineUsers_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListRoomBannedUsers",
|
||||||
|
Handler: _RoomQueryService_ListRoomBannedUsers_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "proto/room/v1/room.proto",
|
Metadata: "proto/room/v1/room.proto",
|
||||||
|
|||||||
385
docs/flutter对接/房间背景图Flutter对接.md
Normal file
385
docs/flutter对接/房间背景图Flutter对接.md
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
# 房间背景图 Flutter 对接
|
||||||
|
|
||||||
|
本文描述 Flutter App 对接房间背景图的 HTTP 接口和腾讯云 IM 房间消息。房间背景图是房间维度素材:上传文件只拿 URL,保存接口把 URL 归档到房间素材列表,设置接口才会改变当前房间背景并广播房间 IM 消息。
|
||||||
|
|
||||||
|
## 基础地址
|
||||||
|
|
||||||
|
本地开发地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://127.0.0.1:13000
|
||||||
|
```
|
||||||
|
|
||||||
|
线上环境使用当前 App 配置里的 gateway base URL。下面所有路径都拼在 gateway base URL 后。
|
||||||
|
|
||||||
|
## 请求头
|
||||||
|
|
||||||
|
| Header | 必填 | 示例 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `Authorization` | 是 | `Bearer <access_token>` | 登录 access token;服务端从 token 读取当前用户 ID。 |
|
||||||
|
| `X-App-Code` | 否 | `lalu` | App 编码;登录态接口最终以 token 内的 `app_code` 为准。 |
|
||||||
|
| `Content-Type` | POST JSON 必填 | `application/json` | 保存和设置背景使用 JSON。 |
|
||||||
|
| `Content-Type` | 上传必填 | `multipart/form-data` | 上传文件使用 multipart。 |
|
||||||
|
|
||||||
|
统一响应 envelope:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "OK",
|
||||||
|
"message": "ok",
|
||||||
|
"request_id": "req_abc",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
客户端分支判断只看 `code`,`message` 只用于兜底展示,`request_id` 要写入日志。
|
||||||
|
|
||||||
|
## 调用顺序
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 房主选择本地图片
|
||||||
|
2. POST /api/v1/files/upload 上传图片,拿 data.url
|
||||||
|
3. POST /api/v1/rooms/backgrounds/save 保存到房间背景素材列表
|
||||||
|
4. GET /api/v1/rooms/{room_id}/backgrounds 刷新背景列表
|
||||||
|
5. POST /api/v1/rooms/backgrounds/set 设置当前背景
|
||||||
|
6. 当前房间内所有用户通过 IM 收到 room_background_changed
|
||||||
|
7. 房间页用 data.room.background_url 或 IM attributes.room_background_url 刷新背景
|
||||||
|
```
|
||||||
|
|
||||||
|
## 上传图片
|
||||||
|
|
||||||
|
背景图先走通用文件上传。该接口不改变房间状态,只返回对象存储 URL。
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/files/upload
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
X-App-Code: lalu
|
||||||
|
Content-Type: multipart/form-data
|
||||||
|
|
||||||
|
file=<binary>
|
||||||
|
```
|
||||||
|
|
||||||
|
请求字段:
|
||||||
|
|
||||||
|
| 字段 | 必填 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `file` | 是 | multipart file | 单文件最大 20MB。背景建议传图片文件;服务端通用入口不绑定房间语义。 |
|
||||||
|
|
||||||
|
成功响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "OK",
|
||||||
|
"message": "ok",
|
||||||
|
"request_id": "req_upload_bg",
|
||||||
|
"data": {
|
||||||
|
"url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png",
|
||||||
|
"object_key": "app/files/10001/1778000000000_xxx.png",
|
||||||
|
"content_type": "image/png",
|
||||||
|
"size_bytes": 345678
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
客户端只需要把 `data.url` 传给保存背景接口。
|
||||||
|
|
||||||
|
## 保存背景图
|
||||||
|
|
||||||
|
保存背景图把 URL 写入当前房间的背景素材列表。保存成功不会改变当前房间背景,也不会发房间 IM 消息。
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/rooms/backgrounds/save
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
X-App-Code: lalu
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"room_id": "lalu_abc123",
|
||||||
|
"image_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
请求字段:
|
||||||
|
|
||||||
|
| 字段 | 必填 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `room_id` | 是 | string | 房间 ID。 |
|
||||||
|
| `image_url` | 是 | string | 上传接口返回的 `data.url`;服务端 trim 后最长 1024 字符。 |
|
||||||
|
|
||||||
|
成功响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "OK",
|
||||||
|
"message": "ok",
|
||||||
|
"request_id": "req_bg_save",
|
||||||
|
"data": {
|
||||||
|
"background": {
|
||||||
|
"background_id": 101,
|
||||||
|
"room_id": "lalu_abc123",
|
||||||
|
"image_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png",
|
||||||
|
"created_by_user_id": "10001",
|
||||||
|
"created_at_ms": 1778000000123,
|
||||||
|
"updated_at_ms": 1778000000123
|
||||||
|
},
|
||||||
|
"server_time_ms": 1778000000123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `background.background_id` | int64 | 设置当前背景时使用。Flutter 如果统一按字符串保存 ID,也可以转成字符串保存;提交时服务端支持数字或数字字符串。 |
|
||||||
|
| `background.image_url` | string | 背景图展示 URL。 |
|
||||||
|
| `background.created_by_user_id` | string | 保存素材的用户 ID。 |
|
||||||
|
| `server_time_ms` | int64 | 服务端时间,Unix epoch milliseconds。 |
|
||||||
|
|
||||||
|
## 获取背景图列表
|
||||||
|
|
||||||
|
房主打开房间背景编辑面板时调用。
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/rooms/{room_id}/backgrounds
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
X-App-Code: lalu
|
||||||
|
```
|
||||||
|
|
||||||
|
路径参数:
|
||||||
|
|
||||||
|
| 参数 | 必填 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `room_id` | 是 | string | 房间 ID。 |
|
||||||
|
|
||||||
|
成功响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "OK",
|
||||||
|
"message": "ok",
|
||||||
|
"request_id": "req_bg_list",
|
||||||
|
"data": {
|
||||||
|
"backgrounds": [
|
||||||
|
{
|
||||||
|
"background_id": 101,
|
||||||
|
"room_id": "lalu_abc123",
|
||||||
|
"image_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png",
|
||||||
|
"created_by_user_id": "10001",
|
||||||
|
"created_at_ms": 1778000000123,
|
||||||
|
"updated_at_ms": 1778000000123
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selected_background_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png",
|
||||||
|
"server_time_ms": 1778000000456
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `backgrounds` | array | 房间保存过的背景图,按最近保存优先。 |
|
||||||
|
| `selected_background_url` | string | 当前生效背景;没有设置过时为空或缺省。 |
|
||||||
|
| `server_time_ms` | int64 | 服务端时间。 |
|
||||||
|
|
||||||
|
## 设置当前背景
|
||||||
|
|
||||||
|
设置当前背景会改变 Room Cell 快照,返回最新房间数据,并向房间 IM 群广播 `room_background_changed`。
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/rooms/backgrounds/set
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
X-App-Code: lalu
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"room_id": "lalu_abc123",
|
||||||
|
"command_id": "cmd_background_lalu_abc123_10001_1778000000000",
|
||||||
|
"background_id": "101"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
请求字段:
|
||||||
|
|
||||||
|
| 字段 | 必填 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `room_id` | 是 | string | 房间 ID。 |
|
||||||
|
| `command_id` | 是 | string | 房间命令幂等键;同一次点击超时重试必须复用。 |
|
||||||
|
| `background_id` | 是 | int64/string | 背景素材 ID,必须来自当前房间的背景列表。 |
|
||||||
|
|
||||||
|
成功响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "OK",
|
||||||
|
"message": "ok",
|
||||||
|
"request_id": "req_bg_set",
|
||||||
|
"data": {
|
||||||
|
"result": {
|
||||||
|
"applied": true,
|
||||||
|
"room_version": 18,
|
||||||
|
"server_time_ms": 1778000000789
|
||||||
|
},
|
||||||
|
"room": {
|
||||||
|
"room_id": "lalu_abc123",
|
||||||
|
"im_group_id": "lalu_abc123",
|
||||||
|
"room_short_id": "100001",
|
||||||
|
"title": "Live Room",
|
||||||
|
"cover_url": "https://cdn.example.com/room.png",
|
||||||
|
"background_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png",
|
||||||
|
"description": "welcome",
|
||||||
|
"owner_user_id": "10001",
|
||||||
|
"mode": "voice",
|
||||||
|
"status": "active",
|
||||||
|
"chat_enabled": true,
|
||||||
|
"locked": false,
|
||||||
|
"heat": 1000,
|
||||||
|
"online_count": 12,
|
||||||
|
"seat_count": 10,
|
||||||
|
"occupied_seat_count": 2,
|
||||||
|
"visible_region_id": 1001,
|
||||||
|
"version": 18
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"background_id": 101,
|
||||||
|
"room_id": "lalu_abc123",
|
||||||
|
"image_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png",
|
||||||
|
"created_by_user_id": "10001",
|
||||||
|
"created_at_ms": 1778000000123,
|
||||||
|
"updated_at_ms": 1778000000123
|
||||||
|
},
|
||||||
|
"server_time_ms": 1778000000789
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `result.applied` | bool | `true` 表示本次命令产生状态变更;同一 `command_id` 重试可能为 `false`。 |
|
||||||
|
| `result.room_version` | int64 | 命令完成后的房间版本。 |
|
||||||
|
| `room.background_url` | string | 当前生效背景图。客户端设置成功后直接用它刷新 UI。 |
|
||||||
|
| `background` | object | 被设置的背景素材。 |
|
||||||
|
|
||||||
|
## 房间详情返回
|
||||||
|
|
||||||
|
进房或详情页重建时不需要额外查背景列表。当前生效背景在房间详情里返回:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/rooms/{room_id}/detail
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
X-App-Code: lalu
|
||||||
|
```
|
||||||
|
|
||||||
|
关键字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"room": {
|
||||||
|
"room_id": "lalu_abc123",
|
||||||
|
"background_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png",
|
||||||
|
"version": 18
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
客户端进入房间页时用 `data.room.background_url` 初始化背景。收到 IM 后如果 `room_version` 更大,再覆盖本地背景。
|
||||||
|
|
||||||
|
## 腾讯 IM 消息
|
||||||
|
|
||||||
|
设置背景成功后,room-service 会向房间 IM 群发送 TIMCustomElem。Flutter 需要监听群自定义消息并解析 JSON。
|
||||||
|
|
||||||
|
消息负载是 `tencentim.RoomEvent` JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event_id": "evt_xxx",
|
||||||
|
"room_id": "lalu_abc123",
|
||||||
|
"event_type": "room_background_changed",
|
||||||
|
"actor_user_id": 10001,
|
||||||
|
"room_version": 18,
|
||||||
|
"attributes": {
|
||||||
|
"background_id": "101",
|
||||||
|
"room_background_url": "https://cdn.example.com/app/files/10001/1778000000000_xxx.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
腾讯 IM 自定义消息字段:
|
||||||
|
|
||||||
|
| 字段 | 值 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `TIMCustomElem.data` | 上面的 JSON 字符串 | 业务负载。 |
|
||||||
|
| `TIMCustomElem.desc` | `room_background_changed` | 事件类型。 |
|
||||||
|
| `TIMCustomElem.extension` | `room_system_message` | 房间系统消息标记。 |
|
||||||
|
|
||||||
|
客户端处理规则:
|
||||||
|
|
||||||
|
| 条件 | 处理 |
|
||||||
|
| --- | --- |
|
||||||
|
| `event_type != room_background_changed` | 交给其他房间消息处理器。 |
|
||||||
|
| `room_id` 不是当前房间 | 忽略。 |
|
||||||
|
| `event_id` 已处理过 | 忽略,避免重放或 SDK 重复投递。 |
|
||||||
|
| `room_version` 小于本地房间版本 | 忽略旧消息。 |
|
||||||
|
| `room_version` 大于等于本地房间版本 | 设置房间背景为 `attributes.room_background_url`,并更新本地 `room_version`。 |
|
||||||
|
|
||||||
|
Flutter 伪代码:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void onRoomCustomMessage(String data) {
|
||||||
|
final event = jsonDecode(data) as Map<String, dynamic>;
|
||||||
|
if (event['event_type'] != 'room_background_changed') return;
|
||||||
|
if (event['room_id'] != currentRoomId) return;
|
||||||
|
|
||||||
|
final eventId = event['event_id'] as String? ?? '';
|
||||||
|
if (handledEventIds.contains(eventId)) return;
|
||||||
|
|
||||||
|
final version = (event['room_version'] as num?)?.toInt() ?? 0;
|
||||||
|
if (version < localRoomVersion) return;
|
||||||
|
|
||||||
|
final attrs = (event['attributes'] as Map?)?.cast<String, dynamic>() ?? {};
|
||||||
|
final backgroundUrl = attrs['room_background_url'] as String? ?? '';
|
||||||
|
handledEventIds.add(eventId);
|
||||||
|
localRoomVersion = version;
|
||||||
|
setRoomBackground(backgroundUrl);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
| code | HTTP | 常见场景 | Flutter 处理 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `INVALID_ARGUMENT` | 400 | `room_id` 格式不对、`image_url` 为空、`background_id` 为空、上传文件过大或格式不合法。 | 停止当前动作,提示重新选择图片或刷新页面后重试。 |
|
||||||
|
| `UNAUTHORIZED` | 401 | access token 缺失或过期。 | 回登录或刷新 token。 |
|
||||||
|
| `PROFILE_REQUIRED` | 403 | 用户未完成资料。 | 引导补全资料。 |
|
||||||
|
| `PERMISSION_DENIED` | 403 | 当前用户不是房主。 | 关闭编辑入口或提示无权限;不要重试。 |
|
||||||
|
| `NOT_FOUND` | 404 | 房间不存在,或设置的 `background_id` 不属于该房间。 | 刷新我的房间和背景列表。 |
|
||||||
|
| `CONFLICT` | 409 | 房间不是 active,或同一 `command_id` 携带了不同 payload。 | 刷新房间状态;如果是超时重试,确保复用同一个请求体。 |
|
||||||
|
| `UPSTREAM_ERROR` | 502 | gateway、room-service、对象存储或 IM 投递依赖临时不可用。 | 保留用户选择,提示稍后重试。 |
|
||||||
|
|
||||||
|
失败响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "PERMISSION_DENIED",
|
||||||
|
"message": "permission denied",
|
||||||
|
"request_id": "req_bg_set_denied"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI 状态建议
|
||||||
|
|
||||||
|
| 场景 | 建议 |
|
||||||
|
| --- | --- |
|
||||||
|
| 房主打开背景设置面板 | 调 `GET /rooms/{room_id}/backgrounds`,用 `selected_background_url` 标记当前背景。 |
|
||||||
|
| 用户选择新图片 | 先本地预览,再上传,上传成功后调用保存接口。 |
|
||||||
|
| 保存成功 | 把返回的 `background` 插入列表顶部,但不切换房间背景。 |
|
||||||
|
| 点击应用背景 | 生成新的 `command_id` 调设置接口;按钮进入 loading,防止重复点击。 |
|
||||||
|
| 设置成功 | 用响应里的 `room.background_url` 立即刷新当前设备 UI。 |
|
||||||
|
| 其他用户在线 | 通过 `room_background_changed` IM 消息刷新背景。 |
|
||||||
|
| 断线重进 | 通过 `/rooms/{room_id}/detail` 的 `room.background_url` 恢复当前背景,不依赖历史 IM。 |
|
||||||
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
`POST /api/v1/rooms/user/kick`
|
`POST /api/v1/rooms/user/kick`
|
||||||
|
|
||||||
|
`GET /api/v1/rooms/{room_id}/banned-users`
|
||||||
|
|
||||||
`POST /api/v1/rooms/user/unban`
|
`POST /api/v1/rooms/user/unban`
|
||||||
|
|
||||||
本地开发地址:
|
本地开发地址:
|
||||||
@ -19,6 +21,7 @@ http://127.0.0.1:13000
|
|||||||
| 方法 | 接口 | 说明 |
|
| 方法 | 接口 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `POST` | `/api/v1/rooms/user/kick` | 踢出房间用户,并写入房间 ban 集合。 |
|
| `POST` | `/api/v1/rooms/user/kick` | 踢出房间用户,并写入房间 ban 集合。 |
|
||||||
|
| `GET` | `/api/v1/rooms/{room_id}/banned-users` | 查询房间维度当前仍有效的踢人列表,也就是房间黑名单列表。 |
|
||||||
| `POST` | `/api/v1/rooms/user/unban` | 解除房间 ban,允许用户后续重新 JoinRoom。 |
|
| `POST` | `/api/v1/rooms/user/unban` | 解除房间 ban,允许用户后续重新 JoinRoom。 |
|
||||||
|
|
||||||
## 请求头
|
## 请求头
|
||||||
@ -39,6 +42,7 @@ http://127.0.0.1:13000
|
|||||||
| `room_id` | 是 | string | `room_10001` | 房间 ID。 |
|
| `room_id` | 是 | string | `room_10001` | 房间 ID。 |
|
||||||
| `command_id` | 是 | string | `cmd_kick_room_10001_20001_1710000000` | 房间命令幂等键;同一次踢人重试必须复用。 |
|
| `command_id` | 是 | string | `cmd_kick_room_10001_20001_1710000000` | 房间命令幂等键;同一次踢人重试必须复用。 |
|
||||||
| `target_user_id` | 是 | int64 | `20001` | 被踢用户长 ID。 |
|
| `target_user_id` | 是 | int64 | `20001` | 被踢用户长 ID。 |
|
||||||
|
| `duration_ms` | 否 | int64 | `600000` | 禁止重新进房的毫秒时长;`0` 或不传表示永久,直到房间 owner 调用解封。 |
|
||||||
|
|
||||||
请求示例:
|
请求示例:
|
||||||
|
|
||||||
@ -51,7 +55,8 @@ Content-Type: application/json
|
|||||||
{
|
{
|
||||||
"room_id": "room_10001",
|
"room_id": "room_10001",
|
||||||
"command_id": "cmd_kick_room_10001_20001_1710000000",
|
"command_id": "cmd_kick_room_10001_20001_1710000000",
|
||||||
"target_user_id": 20001
|
"target_user_id": 20001,
|
||||||
|
"duration_ms": 600000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -72,6 +77,14 @@ Content-Type: application/json
|
|||||||
"room_id": "room_10001",
|
"room_id": "room_10001",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"ban_user_ids": [20001],
|
"ban_user_ids": [20001],
|
||||||
|
"ban_states": [
|
||||||
|
{
|
||||||
|
"user_id": 20001,
|
||||||
|
"actor_user_id": 10001,
|
||||||
|
"created_at_ms": 1710000000456,
|
||||||
|
"expires_at_ms": 1710000600456
|
||||||
|
}
|
||||||
|
],
|
||||||
"online_users": [],
|
"online_users": [],
|
||||||
"mic_seats": [],
|
"mic_seats": [],
|
||||||
"version": 18
|
"version": 18
|
||||||
@ -86,17 +99,106 @@ Content-Type: application/json
|
|||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
| 字段 | 类型 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `data.result.applied` | bool | 本次命令是否产生状态变更;重复踢已 ban 且无残留状态的用户可能为 `false`。 |
|
| `data.result.applied` | bool | 本次命令是否产生状态变更;同一个 `command_id` 重试返回同一命令结果,新的 `command_id` 再踢同一用户会刷新 ban 状态。 |
|
||||||
| `data.room.ban_user_ids` | int64[] | 当前房间 ban 用户集合;被踢用户会进入该集合。 |
|
| `data.room.ban_user_ids` | int64[] | 当前房间 ban 用户集合;被踢用户会进入该集合。 |
|
||||||
|
| `data.room.ban_states` | array | 当前房间 ban 的时间边界;`expires_at_ms=0` 表示永久,非 0 表示该 UTC 毫秒后可重新进房。 |
|
||||||
| `data.room.online_users` | array | 当前业务 presence;被踢用户会被移除。 |
|
| `data.room.online_users` | array | 当前业务 presence;被踢用户会被移除。 |
|
||||||
| `data.room.mic_seats` | array | 如果被踢用户在麦上,对应麦位会被释放。 |
|
| `data.room.mic_seats` | array | 如果被踢用户在麦上,对应麦位会被释放。 |
|
||||||
| `data.rtc_kicked` | bool | 服务端是否已调用 TRTC `RemoveUserByStrRoomId` 移除目标用户。 |
|
| `data.rtc_kicked` | bool | 服务端是否已调用 TRTC `RemoveUserByStrRoomId` 移除目标用户。 |
|
||||||
| `data.rtc_kick_error` | string | TRTC 服务端踢人失败原因;踢人事实已生效,客户端仍按被踢处理。 |
|
| `data.rtc_kick_error` | string | TRTC 服务端踢人失败原因;踢人事实已生效,客户端仍按被踢处理。 |
|
||||||
|
|
||||||
|
## 房间踢人列表
|
||||||
|
|
||||||
|
该接口用于房主或房管查看当前房间仍有效的踢人列表。列表只返回当前仍会阻止用户进房的记录;限时踢人已过期后不再返回。被解除的用户也不再返回。
|
||||||
|
|
||||||
|
请求示例:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/rooms/room_10001/banned-users?page=1&page_size=20
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
X-App-Code: lalu
|
||||||
|
```
|
||||||
|
|
||||||
|
路径参数:
|
||||||
|
|
||||||
|
| 字段 | 必填 | 类型 | 模拟值 | 说明 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `room_id` | 是 | string | `room_10001` | 房间 ID。 |
|
||||||
|
|
||||||
|
Query 参数:
|
||||||
|
|
||||||
|
| 字段 | 必填 | 类型 | 默认值 | 说明 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `page` | 否 | int32 | `1` | 页码,从 1 开始。 |
|
||||||
|
| `page_size` | 否 | int32 | `20` | 每页数量;建议 App 使用 20,后端最大值按实现收敛。 |
|
||||||
|
|
||||||
|
成功响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "OK",
|
||||||
|
"message": "ok",
|
||||||
|
"request_id": "req_abc",
|
||||||
|
"data": {
|
||||||
|
"room_id": "room_10001",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"user_id": "20001",
|
||||||
|
"display_user_id": "90020001",
|
||||||
|
"username": "Alice",
|
||||||
|
"avatar": "https://example.com/avatar.png",
|
||||||
|
"country": "US",
|
||||||
|
"country_name": "United States",
|
||||||
|
"country_display_name": "United States",
|
||||||
|
"country_flag": "🇺🇸",
|
||||||
|
"actor_user_id": "10001",
|
||||||
|
"created_at_ms": 1710000000456,
|
||||||
|
"unban_at_ms": 1710000600456,
|
||||||
|
"remaining_ms": 599000,
|
||||||
|
"permanent": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"page": 1,
|
||||||
|
"page_size": 20,
|
||||||
|
"server_time_ms": 1710000001456
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `data.room_id` | string | 当前查询的房间 ID。 |
|
||||||
|
| `data.items[].user_id` | string | 被踢用户长 ID;Flutter 侧按字符串保存,避免大整数精度问题。 |
|
||||||
|
| `data.items[].display_user_id` | string | 用户展示 ID;可能为空,端上需要兜底展示 `user_id`。 |
|
||||||
|
| `data.items[].username` | string | 用户昵称;可能为空,端上可兜底展示 `display_user_id` 或 `user_id`。 |
|
||||||
|
| `data.items[].avatar` | string | 用户头像 URL;可能为空,端上展示默认头像。 |
|
||||||
|
| `data.items[].country` | string | 用户国家二位码,例如 `US`。 |
|
||||||
|
| `data.items[].country_name` | string | 用户国家标准名。 |
|
||||||
|
| `data.items[].country_display_name` | string | App 展示优先使用的国家名。 |
|
||||||
|
| `data.items[].country_flag` | string | 国家国旗 emoji;为空时端上可按 `country` 本地兜底。 |
|
||||||
|
| `data.items[].actor_user_id` | string | 执行踢人的用户 ID;系统驱逐时可能为 `"0"`。 |
|
||||||
|
| `data.items[].created_at_ms` | int64 | 踢人状态写入时间,UTC epoch milliseconds。 |
|
||||||
|
| `data.items[].unban_at_ms` | int64 | 自动解封时间;`0` 表示永久,需要手动解除。 |
|
||||||
|
| `data.items[].remaining_ms` | int64 | 距离自动解除的剩余毫秒;永久踢人为 `0`。 |
|
||||||
|
| `data.items[].permanent` | bool | 是否永久禁止进房。 |
|
||||||
|
| `data.total` | int64 | 当前房间有效黑名单总数。 |
|
||||||
|
| `data.server_time_ms` | int64 | 服务端当前 UTC 毫秒;倒计时 UI 以它校正本地时间。 |
|
||||||
|
|
||||||
## 解除房间 ban
|
## 解除房间 ban
|
||||||
|
|
||||||
解除 ban 只恢复用户后续重新进房资格,不恢复历史 presence、麦位、管理员身份或 RTC 连接。
|
解除 ban 只恢复用户后续重新进房资格,不恢复历史 presence、麦位、管理员身份或 RTC 连接。
|
||||||
|
|
||||||
|
请求体:
|
||||||
|
|
||||||
|
| 字段 | 必填 | 类型 | 模拟值 | 说明 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `room_id` | 是 | string | `room_10001` | 房间 ID。 |
|
||||||
|
| `command_id` | 是 | string | `cmd_unban_room_10001_20001_1710000100` | 房间命令幂等键;同一次解除操作重试必须复用。 |
|
||||||
|
| `target_user_id` | 是 | int64 | `20001` | 要解除踢人状态的用户长 ID。 |
|
||||||
|
|
||||||
请求示例:
|
请求示例:
|
||||||
|
|
||||||
```http
|
```http
|
||||||
@ -112,7 +214,37 @@ Content-Type: application/json
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
成功响应字段和踢人一致,`room.ban_user_ids` 中不再包含目标用户。
|
成功响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "OK",
|
||||||
|
"message": "ok",
|
||||||
|
"request_id": "req_abc",
|
||||||
|
"data": {
|
||||||
|
"result": {
|
||||||
|
"applied": true,
|
||||||
|
"room_version": 19,
|
||||||
|
"server_time_ms": 1710000100456
|
||||||
|
},
|
||||||
|
"room": {
|
||||||
|
"room_id": "room_10001",
|
||||||
|
"status": "active",
|
||||||
|
"ban_user_ids": [],
|
||||||
|
"ban_states": [],
|
||||||
|
"version": 19
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `data.result.applied` | bool | 是否实际删除了 ban 状态;目标用户本来不在黑名单时为 `false`。 |
|
||||||
|
| `data.room.ban_user_ids` | int64[] | 当前房间仍有效的 ban 用户集合;成功解除后不再包含目标用户。 |
|
||||||
|
| `data.room.ban_states` | array | 当前房间仍有效的 ban 状态;成功解除后不再包含目标用户对应状态。 |
|
||||||
|
|
||||||
## IM 和 RTC 交互
|
## IM 和 RTC 交互
|
||||||
|
|
||||||
@ -211,12 +343,22 @@ C2C 通知和房间群消息使用同一个 `event_id`。Flutter 侧把 `room_us
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 同一个 `command_id` 重试同一次踢人 | 返回同一命令结果,不重复变更房间。 |
|
| 同一个 `command_id` 重试同一次踢人 | 返回同一命令结果,不重复变更房间。 |
|
||||||
| 同一个 `command_id` 换另一个 `target_user_id` | `409 CONFLICT`。 |
|
| 同一个 `command_id` 换另一个 `target_user_id` | `409 CONFLICT`。 |
|
||||||
| 重复踢已 ban 且无残留状态的用户 | `200 OK`,`applied=false`。 |
|
| 新的 `command_id` 再次踢同一用户 | `200 OK`,刷新 ban 状态和 `duration_ms`。 |
|
||||||
| 解封未 ban 用户 | `200 OK`,`applied=false`。 |
|
| 解封未 ban 用户 | `200 OK`,`applied=false`。 |
|
||||||
|
|
||||||
## Flutter 解析示例
|
## Flutter 解析示例
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
|
int parseIntLike(dynamic value) {
|
||||||
|
if (value is int) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value is num) {
|
||||||
|
return value.toInt();
|
||||||
|
}
|
||||||
|
return int.tryParse(value?.toString() ?? '') ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
class RoomCommandResult {
|
class RoomCommandResult {
|
||||||
RoomCommandResult({
|
RoomCommandResult({
|
||||||
required this.applied,
|
required this.applied,
|
||||||
@ -243,12 +385,14 @@ class KickUserResult {
|
|||||||
required this.rtcKicked,
|
required this.rtcKicked,
|
||||||
required this.rtcKickError,
|
required this.rtcKickError,
|
||||||
required this.banUserIds,
|
required this.banUserIds,
|
||||||
|
required this.banStates,
|
||||||
});
|
});
|
||||||
|
|
||||||
final RoomCommandResult result;
|
final RoomCommandResult result;
|
||||||
final bool rtcKicked;
|
final bool rtcKicked;
|
||||||
final String rtcKickError;
|
final String rtcKickError;
|
||||||
final List<int> banUserIds;
|
final List<int> banUserIds;
|
||||||
|
final List<RoomBanState> banStates;
|
||||||
|
|
||||||
factory KickUserResult.fromJson(Map<String, dynamic> json) {
|
factory KickUserResult.fromJson(Map<String, dynamic> json) {
|
||||||
final room = json['room'] as Map<String, dynamic>? ?? const {};
|
final room = json['room'] as Map<String, dynamic>? ?? const {};
|
||||||
@ -259,8 +403,119 @@ class KickUserResult {
|
|||||||
rtcKicked: json['rtc_kicked'] as bool? ?? false,
|
rtcKicked: json['rtc_kicked'] as bool? ?? false,
|
||||||
rtcKickError: json['rtc_kick_error'] as String? ?? '',
|
rtcKickError: json['rtc_kick_error'] as String? ?? '',
|
||||||
banUserIds: (room['ban_user_ids'] as List<dynamic>? ?? const [])
|
banUserIds: (room['ban_user_ids'] as List<dynamic>? ?? const [])
|
||||||
.map((value) => (value as num).toInt())
|
.map(parseIntLike)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
banStates: (room['ban_states'] as List<dynamic>? ?? const [])
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(RoomBanState.fromJson)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomBanState {
|
||||||
|
RoomBanState({
|
||||||
|
required this.userId,
|
||||||
|
required this.actorUserId,
|
||||||
|
required this.createdAtMs,
|
||||||
|
required this.expiresAtMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int userId;
|
||||||
|
final int actorUserId;
|
||||||
|
final int createdAtMs;
|
||||||
|
final int expiresAtMs;
|
||||||
|
|
||||||
|
bool get permanent => expiresAtMs == 0;
|
||||||
|
|
||||||
|
factory RoomBanState.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RoomBanState(
|
||||||
|
userId: parseIntLike(json['user_id']),
|
||||||
|
actorUserId: parseIntLike(json['actor_user_id']),
|
||||||
|
createdAtMs: parseIntLike(json['created_at_ms']),
|
||||||
|
expiresAtMs: parseIntLike(json['expires_at_ms']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomBannedUsersResult {
|
||||||
|
RoomBannedUsersResult({
|
||||||
|
required this.roomId,
|
||||||
|
required this.items,
|
||||||
|
required this.total,
|
||||||
|
required this.page,
|
||||||
|
required this.pageSize,
|
||||||
|
required this.serverTimeMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String roomId;
|
||||||
|
final List<RoomBannedUserItem> items;
|
||||||
|
final int total;
|
||||||
|
final int page;
|
||||||
|
final int pageSize;
|
||||||
|
final int serverTimeMs;
|
||||||
|
|
||||||
|
factory RoomBannedUsersResult.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RoomBannedUsersResult(
|
||||||
|
roomId: json['room_id'] as String? ?? '',
|
||||||
|
items: (json['items'] as List<dynamic>? ?? const [])
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(RoomBannedUserItem.fromJson)
|
||||||
|
.toList(),
|
||||||
|
total: parseIntLike(json['total']),
|
||||||
|
page: parseIntLike(json['page']),
|
||||||
|
pageSize: parseIntLike(json['page_size']),
|
||||||
|
serverTimeMs: parseIntLike(json['server_time_ms']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomBannedUserItem {
|
||||||
|
RoomBannedUserItem({
|
||||||
|
required this.userId,
|
||||||
|
required this.displayUserId,
|
||||||
|
required this.username,
|
||||||
|
required this.avatar,
|
||||||
|
required this.country,
|
||||||
|
required this.countryName,
|
||||||
|
required this.countryDisplayName,
|
||||||
|
required this.countryFlag,
|
||||||
|
required this.actorUserId,
|
||||||
|
required this.createdAtMs,
|
||||||
|
required this.unbanAtMs,
|
||||||
|
required this.remainingMs,
|
||||||
|
required this.permanent,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int userId;
|
||||||
|
final String displayUserId;
|
||||||
|
final String username;
|
||||||
|
final String avatar;
|
||||||
|
final String country;
|
||||||
|
final String countryName;
|
||||||
|
final String countryDisplayName;
|
||||||
|
final String countryFlag;
|
||||||
|
final int actorUserId;
|
||||||
|
final int createdAtMs;
|
||||||
|
final int unbanAtMs;
|
||||||
|
final int remainingMs;
|
||||||
|
final bool permanent;
|
||||||
|
|
||||||
|
factory RoomBannedUserItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RoomBannedUserItem(
|
||||||
|
userId: parseIntLike(json['user_id']),
|
||||||
|
displayUserId: json['display_user_id'] as String? ?? '',
|
||||||
|
username: json['username'] as String? ?? '',
|
||||||
|
avatar: json['avatar'] as String? ?? '',
|
||||||
|
country: json['country'] as String? ?? '',
|
||||||
|
countryName: json['country_name'] as String? ?? '',
|
||||||
|
countryDisplayName: json['country_display_name'] as String? ?? '',
|
||||||
|
countryFlag: json['country_flag'] as String? ?? '',
|
||||||
|
actorUserId: parseIntLike(json['actor_user_id']),
|
||||||
|
createdAtMs: parseIntLike(json['created_at_ms']),
|
||||||
|
unbanAtMs: parseIntLike(json['unban_at_ms']),
|
||||||
|
remainingMs: parseIntLike(json['remaining_ms']),
|
||||||
|
permanent: json['permanent'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1069,6 +1069,51 @@ paths:
|
|||||||
$ref: "#/responses/Conflict"
|
$ref: "#/responses/Conflict"
|
||||||
"502":
|
"502":
|
||||||
$ref: "#/responses/UpstreamError"
|
$ref: "#/responses/UpstreamError"
|
||||||
|
/api/v1/rooms/{room_id}/banned-users:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- rooms
|
||||||
|
summary: 查询房间黑名单列表
|
||||||
|
operationId: listRoomBannedUsers
|
||||||
|
description: 房主或房管分页查看当前仍有效的房间踢人列表;已过期和已解封用户不返回。
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: room_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
pattern: "^[A-Za-z0-9_-]{1,48}$"
|
||||||
|
maxLength: 48
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
default: 1
|
||||||
|
- name: page_size
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
default: 20
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 返回房间当前仍有效的黑名单用户及展示资料。
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/RoomBannedUsersEnvelope"
|
||||||
|
"400":
|
||||||
|
$ref: "#/responses/BadRequest"
|
||||||
|
"401":
|
||||||
|
$ref: "#/responses/Unauthorized"
|
||||||
|
"403":
|
||||||
|
$ref: "#/responses/Forbidden"
|
||||||
|
"404":
|
||||||
|
$ref: "#/responses/NotFound"
|
||||||
|
"409":
|
||||||
|
$ref: "#/responses/Conflict"
|
||||||
|
"502":
|
||||||
|
$ref: "#/responses/UpstreamError"
|
||||||
/api/v1/rooms/{room_id}/follow:
|
/api/v1/rooms/{room_id}/follow:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -4208,6 +4253,10 @@ definitions:
|
|||||||
target_user_id:
|
target_user_id:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
duration_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: 0 表示永久禁止进房;大于 0 表示从服务端当前 UTC 时间起禁止进房的毫秒时长。
|
||||||
UnbanUserRequest:
|
UnbanUserRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -4342,6 +4391,11 @@ definitions:
|
|||||||
items:
|
items:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
ban_states:
|
||||||
|
type: array
|
||||||
|
description: 房间 ban 的时间边界;expires_at_ms 为 0 表示永久。
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/RoomBanState"
|
||||||
mute_user_ids:
|
mute_user_ids:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
@ -4362,6 +4416,76 @@ definitions:
|
|||||||
version:
|
version:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
RoomBanState:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
user_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
actor_user_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
created_at_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
expires_at_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: 0 表示永久;非 0 表示该 UTC 毫秒后可重新进房。
|
||||||
|
RoomBannedUserData:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
display_user_id:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
avatar:
|
||||||
|
type: string
|
||||||
|
country:
|
||||||
|
type: string
|
||||||
|
country_name:
|
||||||
|
type: string
|
||||||
|
country_display_name:
|
||||||
|
type: string
|
||||||
|
country_flag:
|
||||||
|
type: string
|
||||||
|
actor_user_id:
|
||||||
|
type: string
|
||||||
|
created_at_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
unban_at_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: 0 表示永久,需要手动解除;非 0 表示自动解封 UTC 毫秒。
|
||||||
|
remaining_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
permanent:
|
||||||
|
type: boolean
|
||||||
|
RoomBannedUsersData:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
room_id:
|
||||||
|
type: string
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/RoomBannedUserData"
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
page:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
page_size:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
server_time_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
RoomListItemData:
|
RoomListItemData:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@ -5201,6 +5325,13 @@ definitions:
|
|||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: "#/definitions/UnbanUserResponse"
|
$ref: "#/definitions/UnbanUserResponse"
|
||||||
|
RoomBannedUsersEnvelope:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/definitions/GatewayOKEnvelopeBase"
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: "#/definitions/RoomBannedUsersData"
|
||||||
SendGiftEnvelope:
|
SendGiftEnvelope:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: "#/definitions/GatewayOKEnvelopeBase"
|
- $ref: "#/definitions/GatewayOKEnvelopeBase"
|
||||||
|
|||||||
@ -411,6 +411,27 @@ paths:
|
|||||||
$ref: "#/definitions/GetRoomSnapshotResponse"
|
$ref: "#/definitions/GetRoomSnapshotResponse"
|
||||||
default:
|
default:
|
||||||
$ref: "#/responses/GRPCError"
|
$ref: "#/responses/GRPCError"
|
||||||
|
/hyapp.room.v1.RoomQueryService/ListRoomBannedUsers:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- room-query
|
||||||
|
summary: 查询房间黑名单列表
|
||||||
|
operationId: roomListRoomBannedUsers
|
||||||
|
description: 读取 Room Cell 当前仍有效的 ban 状态;viewer 必须是当前房间 owner/admin。
|
||||||
|
x-grpc-full-method: /hyapp.room.v1.RoomQueryService/ListRoomBannedUsers
|
||||||
|
parameters:
|
||||||
|
- name: body
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ListRoomBannedUsersRequest"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 返回当前仍有效的房间黑名单。
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ListRoomBannedUsersResponse"
|
||||||
|
default:
|
||||||
|
$ref: "#/responses/GRPCError"
|
||||||
/grpc.health.v1.Health/Check:
|
/grpc.health.v1.Health/Check:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -587,6 +608,11 @@ definitions:
|
|||||||
items:
|
items:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
ban_states:
|
||||||
|
type: array
|
||||||
|
description: 房间 ban 的时间边界;expires_at_ms 为 0 表示永久。
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/RoomBanState"
|
||||||
mute_user_ids:
|
mute_user_ids:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
@ -607,6 +633,22 @@ definitions:
|
|||||||
version:
|
version:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
RoomBanState:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
user_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
actor_user_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
created_at_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
expires_at_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: 0 表示永久;非 0 表示该 UTC 毫秒后可重新进房。
|
||||||
CreateRoomRequest:
|
CreateRoomRequest:
|
||||||
type: object
|
type: object
|
||||||
description: 同一个 `meta.actor_user_id` 只能作为 owner 创建一个房间;owner/host 不接受外部字段自报。
|
description: 同一个 `meta.actor_user_id` 只能作为 owner 创建一个房间;owner/host 不接受外部字段自报。
|
||||||
@ -882,6 +924,10 @@ definitions:
|
|||||||
target_user_id:
|
target_user_id:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
duration_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: 0 表示永久禁止进房;大于 0 表示从服务端当前 UTC 时间起禁止进房的毫秒时长。
|
||||||
KickUserResponse:
|
KickUserResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -1052,6 +1098,63 @@ definitions:
|
|||||||
server_time_ms:
|
server_time_ms:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
RoomBannedUser:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
user_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
actor_user_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
created_at_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
unban_at_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: 0 表示永久;非 0 表示自动解封 UTC 毫秒。
|
||||||
|
remaining_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
permanent:
|
||||||
|
type: boolean
|
||||||
|
ListRoomBannedUsersRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
meta:
|
||||||
|
$ref: "#/definitions/RequestMeta"
|
||||||
|
room_id:
|
||||||
|
type: string
|
||||||
|
viewer_user_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: 由 gateway 从 token user_id 注入,客户端不能自报。
|
||||||
|
page:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
page_size:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
ListRoomBannedUsersResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/RoomBannedUser"
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
page:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
page_size:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
server_time_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
RoomSystemMessage:
|
RoomSystemMessage:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@ -60,10 +60,13 @@
|
|||||||
| GET | `/api/v1/rooms/{room_id}/detail` | rooms | `getRoomDetail` | 查询房间详情页首屏聚合包 |
|
| GET | `/api/v1/rooms/{room_id}/detail` | rooms | `getRoomDetail` | 查询房间详情页首屏聚合包 |
|
||||||
| GET | `/api/v1/rooms/{room_id}/online-users` | rooms | `listRoomOnlineUsers` | 分页查询房间在线用户 |
|
| GET | `/api/v1/rooms/{room_id}/online-users` | rooms | `listRoomOnlineUsers` | 分页查询房间在线用户 |
|
||||||
| GET | `/api/v1/rooms/{room_id}/gift-panel` | rooms | `getRoomGiftPanel` | 查询房间礼物面板初始化数据 |
|
| GET | `/api/v1/rooms/{room_id}/gift-panel` | rooms | `getRoomGiftPanel` | 查询房间礼物面板初始化数据 |
|
||||||
|
| GET | `/api/v1/rooms/{room_id}/backgrounds` | rooms | `listRoomBackgrounds` | 房主查询房间已保存背景图列表和当前背景 |
|
||||||
| POST | `/api/v1/rooms/{room_id}/follow` | rooms | `followRoom` | 关注房间 |
|
| POST | `/api/v1/rooms/{room_id}/follow` | rooms | `followRoom` | 关注房间 |
|
||||||
| DELETE | `/api/v1/rooms/{room_id}/follow` | rooms | `unfollowRoom` | 取消关注房间 |
|
| DELETE | `/api/v1/rooms/{room_id}/follow` | rooms | `unfollowRoom` | 取消关注房间 |
|
||||||
| POST | `/api/v1/rooms/create` | rooms | `createRoom` | 创建房间 |
|
| POST | `/api/v1/rooms/create` | rooms | `createRoom` | 创建房间 |
|
||||||
| POST | `/api/v1/rooms/profile/update` | rooms | `updateRoomProfile` | 修改房间资料和座位数 |
|
| POST | `/api/v1/rooms/profile/update` | rooms | `updateRoomProfile` | 修改房间资料和座位数 |
|
||||||
|
| POST | `/api/v1/rooms/backgrounds/save` | rooms | `saveRoomBackground` | 房主保存房间背景图 URL 到素材列表 |
|
||||||
|
| POST | `/api/v1/rooms/backgrounds/set` | rooms | `setRoomBackground` | 房主设置当前房间背景图 |
|
||||||
| POST | `/api/v1/rooms/join` | rooms | `joinRoom` | 加入房间并返回首屏数据和 RTC token |
|
| POST | `/api/v1/rooms/join` | rooms | `joinRoom` | 加入房间并返回首屏数据和 RTC token |
|
||||||
| POST | `/api/v1/rooms/heartbeat` | rooms | `roomHeartbeat` | 刷新房间业务 presence |
|
| POST | `/api/v1/rooms/heartbeat` | rooms | `roomHeartbeat` | 刷新房间业务 presence |
|
||||||
| POST | `/api/v1/rooms/leave` | rooms | `leaveRoom` | 离开房间 |
|
| POST | `/api/v1/rooms/leave` | rooms | `leaveRoom` | 离开房间 |
|
||||||
|
|||||||
@ -513,6 +513,54 @@ Rules:
|
|||||||
- 成功响应返回最新 `room` 和 `seats`,客户端直接刷新房间标题、头像、简介和麦位列表。
|
- 成功响应返回最新 `room` 和 `seats`,客户端直接刷新房间标题、头像、简介和麦位列表。
|
||||||
- 成功后房间系统消息事件名为 `room_profile_updated`。
|
- 成功后房间系统消息事件名为 `room_profile_updated`。
|
||||||
|
|
||||||
|
### Room Backgrounds
|
||||||
|
|
||||||
|
房间背景图是房间维度素材;保存素材不立即改变当前房间背景,设置背景会写入 Room Cell 快照,`GET /api/v1/rooms/{room_id}/detail` 的 `data.room.background_url` 返回当前生效背景。
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/rooms/backgrounds/save
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"room_id": "lalu_xxx",
|
||||||
|
"image_url": "https://cdn.example/room-bg.png"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/rooms/{room_id}/backgrounds
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
```
|
||||||
|
|
||||||
|
Response `data.backgrounds[]` 返回 `background_id`、`image_url`、创建人和时间;`data.selected_background_url` 是当前生效背景。
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/rooms/backgrounds/set
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"room_id": "lalu_xxx",
|
||||||
|
"command_id": "cmd_background_<room_id>_<user_id>_<nonce>",
|
||||||
|
"background_id": "101"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- 只有房主可以保存、查询和设置自己房间的背景图。
|
||||||
|
- `image_url` 应先通过上传接口拿到对象存储 URL,再传给保存接口。
|
||||||
|
- 设置成功后返回最新 `room.background_url`,并通过 `room_background_changed` 房间系统消息同步;消息 `attributes` 包含 `background_id` 和 `room_background_url`。
|
||||||
|
|
||||||
### Heartbeat
|
### Heartbeat
|
||||||
|
|
||||||
```http
|
```http
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import (
|
|||||||
type RoomClient interface {
|
type RoomClient interface {
|
||||||
CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) (*roomv1.CreateRoomResponse, error)
|
CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) (*roomv1.CreateRoomResponse, error)
|
||||||
UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomProfileRequest) (*roomv1.UpdateRoomProfileResponse, error)
|
UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomProfileRequest) (*roomv1.UpdateRoomProfileResponse, error)
|
||||||
|
SaveRoomBackground(ctx context.Context, req *roomv1.SaveRoomBackgroundRequest) (*roomv1.SaveRoomBackgroundResponse, error)
|
||||||
|
SetRoomBackground(ctx context.Context, req *roomv1.SetRoomBackgroundRequest) (*roomv1.SetRoomBackgroundResponse, error)
|
||||||
JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error)
|
JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error)
|
||||||
RoomHeartbeat(ctx context.Context, req *roomv1.RoomHeartbeatRequest) (*roomv1.RoomHeartbeatResponse, error)
|
RoomHeartbeat(ctx context.Context, req *roomv1.RoomHeartbeatRequest) (*roomv1.RoomHeartbeatResponse, error)
|
||||||
LeaveRoom(ctx context.Context, req *roomv1.LeaveRoomRequest) (*roomv1.LeaveRoomResponse, error)
|
LeaveRoom(ctx context.Context, req *roomv1.LeaveRoomRequest) (*roomv1.LeaveRoomResponse, error)
|
||||||
@ -47,8 +49,10 @@ type RoomQueryClient interface {
|
|||||||
GetMyRoom(ctx context.Context, req *roomv1.GetMyRoomRequest) (*roomv1.GetMyRoomResponse, error)
|
GetMyRoom(ctx context.Context, req *roomv1.GetMyRoomRequest) (*roomv1.GetMyRoomResponse, error)
|
||||||
GetCurrentRoom(ctx context.Context, req *roomv1.GetCurrentRoomRequest) (*roomv1.GetCurrentRoomResponse, error)
|
GetCurrentRoom(ctx context.Context, req *roomv1.GetCurrentRoomRequest) (*roomv1.GetCurrentRoomResponse, error)
|
||||||
GetRoomSnapshot(ctx context.Context, req *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error)
|
GetRoomSnapshot(ctx context.Context, req *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error)
|
||||||
|
ListRoomBackgrounds(ctx context.Context, req *roomv1.ListRoomBackgroundsRequest) (*roomv1.ListRoomBackgroundsResponse, error)
|
||||||
GetRoomTreasure(ctx context.Context, req *roomv1.GetRoomTreasureRequest) (*roomv1.GetRoomTreasureResponse, error)
|
GetRoomTreasure(ctx context.Context, req *roomv1.GetRoomTreasureRequest) (*roomv1.GetRoomTreasureResponse, error)
|
||||||
ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error)
|
ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error)
|
||||||
|
ListRoomBannedUsers(ctx context.Context, req *roomv1.ListRoomBannedUsersRequest) (*roomv1.ListRoomBannedUsersResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type grpcRoomClient struct {
|
type grpcRoomClient struct {
|
||||||
@ -92,6 +96,14 @@ func (c *grpcRoomClient) UpdateRoomProfile(ctx context.Context, req *roomv1.Upda
|
|||||||
return c.client.UpdateRoomProfile(ctx, req)
|
return c.client.UpdateRoomProfile(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *grpcRoomClient) SaveRoomBackground(ctx context.Context, req *roomv1.SaveRoomBackgroundRequest) (*roomv1.SaveRoomBackgroundResponse, error) {
|
||||||
|
return c.client.SaveRoomBackground(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *grpcRoomClient) SetRoomBackground(ctx context.Context, req *roomv1.SetRoomBackgroundRequest) (*roomv1.SetRoomBackgroundResponse, error) {
|
||||||
|
return c.client.SetRoomBackground(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *grpcRoomClient) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error) {
|
func (c *grpcRoomClient) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error) {
|
||||||
return c.client.JoinRoom(ctx, req)
|
return c.client.JoinRoom(ctx, req)
|
||||||
}
|
}
|
||||||
@ -200,6 +212,10 @@ func (c *grpcRoomQueryClient) GetRoomSnapshot(ctx context.Context, req *roomv1.G
|
|||||||
return c.client.GetRoomSnapshot(ctx, req)
|
return c.client.GetRoomSnapshot(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *grpcRoomQueryClient) ListRoomBackgrounds(ctx context.Context, req *roomv1.ListRoomBackgroundsRequest) (*roomv1.ListRoomBackgroundsResponse, error) {
|
||||||
|
return c.client.ListRoomBackgrounds(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *grpcRoomQueryClient) GetRoomTreasure(ctx context.Context, req *roomv1.GetRoomTreasureRequest) (*roomv1.GetRoomTreasureResponse, error) {
|
func (c *grpcRoomQueryClient) GetRoomTreasure(ctx context.Context, req *roomv1.GetRoomTreasureRequest) (*roomv1.GetRoomTreasureResponse, error) {
|
||||||
return c.client.GetRoomTreasure(ctx, req)
|
return c.client.GetRoomTreasure(ctx, req)
|
||||||
}
|
}
|
||||||
@ -207,3 +223,7 @@ func (c *grpcRoomQueryClient) GetRoomTreasure(ctx context.Context, req *roomv1.G
|
|||||||
func (c *grpcRoomQueryClient) ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) {
|
func (c *grpcRoomQueryClient) ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) {
|
||||||
return c.client.ListRoomOnlineUsers(ctx, req)
|
return c.client.ListRoomOnlineUsers(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *grpcRoomQueryClient) ListRoomBannedUsers(ctx context.Context, req *roomv1.ListRoomBannedUsersRequest) (*roomv1.ListRoomBannedUsersResponse, error) {
|
||||||
|
return c.client.ListRoomBannedUsers(ctx, req)
|
||||||
|
}
|
||||||
|
|||||||
@ -120,6 +120,14 @@ func (f *fakeUserLeaderboardRoomQueryClient) GetRoomTreasure(context.Context, *r
|
|||||||
return &roomv1.GetRoomTreasureResponse{}, nil
|
return &roomv1.GetRoomTreasureResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeUserLeaderboardRoomQueryClient) ListRoomBackgrounds(context.Context, *roomv1.ListRoomBackgroundsRequest) (*roomv1.ListRoomBackgroundsResponse, error) {
|
||||||
|
return &roomv1.ListRoomBackgroundsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *fakeUserLeaderboardRoomQueryClient) ListRoomOnlineUsers(context.Context, *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) {
|
func (f *fakeUserLeaderboardRoomQueryClient) ListRoomOnlineUsers(context.Context, *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) {
|
||||||
return &roomv1.ListRoomOnlineUsersResponse{}, nil
|
return &roomv1.ListRoomOnlineUsersResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeUserLeaderboardRoomQueryClient) ListRoomBannedUsers(context.Context, *roomv1.ListRoomBannedUsersRequest) (*roomv1.ListRoomBannedUsersResponse, error) {
|
||||||
|
return &roomv1.ListRoomBannedUsersResponse{}, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -100,11 +100,15 @@ type RoomHandlers struct {
|
|||||||
GetRoomSnapshot http.HandlerFunc
|
GetRoomSnapshot http.HandlerFunc
|
||||||
GetRoomDetail http.HandlerFunc
|
GetRoomDetail http.HandlerFunc
|
||||||
ListRoomOnlineUsers http.HandlerFunc
|
ListRoomOnlineUsers http.HandlerFunc
|
||||||
|
ListRoomBannedUsers http.HandlerFunc
|
||||||
GetRoomGiftPanel http.HandlerFunc
|
GetRoomGiftPanel http.HandlerFunc
|
||||||
GetRoomTreasure http.HandlerFunc
|
GetRoomTreasure http.HandlerFunc
|
||||||
FollowRoom http.HandlerFunc
|
FollowRoom http.HandlerFunc
|
||||||
CreateRoom http.HandlerFunc
|
CreateRoom http.HandlerFunc
|
||||||
UpdateRoomProfile http.HandlerFunc
|
UpdateRoomProfile http.HandlerFunc
|
||||||
|
SaveRoomBackground http.HandlerFunc
|
||||||
|
ListRoomBackgrounds http.HandlerFunc
|
||||||
|
SetRoomBackground http.HandlerFunc
|
||||||
JoinRoom http.HandlerFunc
|
JoinRoom http.HandlerFunc
|
||||||
RoomHeartbeat http.HandlerFunc
|
RoomHeartbeat http.HandlerFunc
|
||||||
LeaveRoom http.HandlerFunc
|
LeaveRoom http.HandlerFunc
|
||||||
@ -309,11 +313,15 @@ func (r routes) registerRoomRoutes() {
|
|||||||
r.profile("/rooms/snapshot", http.MethodGet, h.GetRoomSnapshot)
|
r.profile("/rooms/snapshot", http.MethodGet, h.GetRoomSnapshot)
|
||||||
r.profile("/rooms/{room_id}/detail", http.MethodGet, h.GetRoomDetail)
|
r.profile("/rooms/{room_id}/detail", http.MethodGet, h.GetRoomDetail)
|
||||||
r.profile("/rooms/{room_id}/online-users", http.MethodGet, h.ListRoomOnlineUsers)
|
r.profile("/rooms/{room_id}/online-users", http.MethodGet, h.ListRoomOnlineUsers)
|
||||||
|
r.profile("/rooms/{room_id}/banned-users", http.MethodGet, h.ListRoomBannedUsers)
|
||||||
r.profile("/rooms/{room_id}/gift-panel", http.MethodGet, h.GetRoomGiftPanel)
|
r.profile("/rooms/{room_id}/gift-panel", http.MethodGet, h.GetRoomGiftPanel)
|
||||||
r.profile("/rooms/{room_id}/treasure", http.MethodGet, h.GetRoomTreasure)
|
r.profile("/rooms/{room_id}/treasure", http.MethodGet, h.GetRoomTreasure)
|
||||||
r.profile("/rooms/{room_id}/follow", "", h.FollowRoom)
|
r.profile("/rooms/{room_id}/follow", "", h.FollowRoom)
|
||||||
|
r.profile("/rooms/{room_id}/backgrounds", http.MethodGet, h.ListRoomBackgrounds)
|
||||||
r.profile("/rooms/create", http.MethodPost, h.CreateRoom)
|
r.profile("/rooms/create", http.MethodPost, h.CreateRoom)
|
||||||
r.profile("/rooms/profile/update", http.MethodPost, h.UpdateRoomProfile)
|
r.profile("/rooms/profile/update", http.MethodPost, h.UpdateRoomProfile)
|
||||||
|
r.profile("/rooms/backgrounds/save", http.MethodPost, h.SaveRoomBackground)
|
||||||
|
r.profile("/rooms/backgrounds/set", http.MethodPost, h.SetRoomBackground)
|
||||||
r.profile("/rooms/join", http.MethodPost, h.JoinRoom)
|
r.profile("/rooms/join", http.MethodPost, h.JoinRoom)
|
||||||
r.profile("/rooms/heartbeat", http.MethodPost, h.RoomHeartbeat)
|
r.profile("/rooms/heartbeat", http.MethodPost, h.RoomHeartbeat)
|
||||||
r.profile("/rooms/leave", http.MethodPost, h.LeaveRoom)
|
r.profile("/rooms/leave", http.MethodPost, h.LeaveRoom)
|
||||||
|
|||||||
@ -33,32 +33,34 @@ import (
|
|||||||
// fakeRoomClient 只承接 gateway transport 测试需要观察的 gRPC 入参。
|
// fakeRoomClient 只承接 gateway transport 测试需要观察的 gRPC 入参。
|
||||||
// 测试目标是 HTTP envelope 和 RequestMeta 传递,不验证 room-service 业务行为。
|
// 测试目标是 HTTP envelope 和 RequestMeta 传递,不验证 room-service 业务行为。
|
||||||
type fakeRoomClient struct {
|
type fakeRoomClient struct {
|
||||||
lastCreate *roomv1.CreateRoomRequest
|
lastCreate *roomv1.CreateRoomRequest
|
||||||
lastUpdateProfile *roomv1.UpdateRoomProfileRequest
|
lastUpdateProfile *roomv1.UpdateRoomProfileRequest
|
||||||
lastJoin *roomv1.JoinRoomRequest
|
lastSaveBackground *roomv1.SaveRoomBackgroundRequest
|
||||||
lastHeartbeat *roomv1.RoomHeartbeatRequest
|
lastSetBackground *roomv1.SetRoomBackgroundRequest
|
||||||
lastLeave *roomv1.LeaveRoomRequest
|
lastJoin *roomv1.JoinRoomRequest
|
||||||
lastMicUp *roomv1.MicUpRequest
|
lastHeartbeat *roomv1.RoomHeartbeatRequest
|
||||||
lastMicDown *roomv1.MicDownRequest
|
lastLeave *roomv1.LeaveRoomRequest
|
||||||
lastChangeMicSeat *roomv1.ChangeMicSeatRequest
|
lastMicUp *roomv1.MicUpRequest
|
||||||
lastConfirmMic *roomv1.ConfirmMicPublishingRequest
|
lastMicDown *roomv1.MicDownRequest
|
||||||
lastSetMicMute *roomv1.SetMicMuteRequest
|
lastChangeMicSeat *roomv1.ChangeMicSeatRequest
|
||||||
lastRTCEvent *roomv1.ApplyRTCEventRequest
|
lastConfirmMic *roomv1.ConfirmMicPublishingRequest
|
||||||
lastMicSeatLock *roomv1.SetMicSeatLockRequest
|
lastSetMicMute *roomv1.SetMicMuteRequest
|
||||||
lastChatEnabled *roomv1.SetChatEnabledRequest
|
lastRTCEvent *roomv1.ApplyRTCEventRequest
|
||||||
lastRoomPassword *roomv1.SetRoomPasswordRequest
|
lastMicSeatLock *roomv1.SetMicSeatLockRequest
|
||||||
lastSetAdmin *roomv1.SetRoomAdminRequest
|
lastChatEnabled *roomv1.SetChatEnabledRequest
|
||||||
lastMute *roomv1.MuteUserRequest
|
lastRoomPassword *roomv1.SetRoomPasswordRequest
|
||||||
lastKick *roomv1.KickUserRequest
|
lastSetAdmin *roomv1.SetRoomAdminRequest
|
||||||
lastUnban *roomv1.UnbanUserRequest
|
lastMute *roomv1.MuteUserRequest
|
||||||
lastGift *roomv1.SendGiftRequest
|
lastKick *roomv1.KickUserRequest
|
||||||
sendGiftResp *roomv1.SendGiftResponse
|
lastUnban *roomv1.UnbanUserRequest
|
||||||
lastFollowRoom *roomv1.FollowRoomRequest
|
lastGift *roomv1.SendGiftRequest
|
||||||
lastUnfollowRoom *roomv1.UnfollowRoomRequest
|
sendGiftResp *roomv1.SendGiftResponse
|
||||||
createErr error
|
lastFollowRoom *roomv1.FollowRoomRequest
|
||||||
updateProfileErr error
|
lastUnfollowRoom *roomv1.UnfollowRoomRequest
|
||||||
joinErr error
|
createErr error
|
||||||
joinResp *roomv1.JoinRoomResponse
|
updateProfileErr error
|
||||||
|
joinErr error
|
||||||
|
joinResp *roomv1.JoinRoomResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeRoomClient) CreateRoom(_ context.Context, req *roomv1.CreateRoomRequest) (*roomv1.CreateRoomResponse, error) {
|
func (f *fakeRoomClient) CreateRoom(_ context.Context, req *roomv1.CreateRoomRequest) (*roomv1.CreateRoomResponse, error) {
|
||||||
@ -97,6 +99,47 @@ func (f *fakeRoomClient) UpdateRoomProfile(_ context.Context, req *roomv1.Update
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeRoomClient) SaveRoomBackground(_ context.Context, req *roomv1.SaveRoomBackgroundRequest) (*roomv1.SaveRoomBackgroundResponse, error) {
|
||||||
|
f.lastSaveBackground = req
|
||||||
|
return &roomv1.SaveRoomBackgroundResponse{
|
||||||
|
Background: &roomv1.RoomBackgroundImage{
|
||||||
|
BackgroundId: 101,
|
||||||
|
RoomId: req.GetRoomId(),
|
||||||
|
ImageUrl: req.GetImageUrl(),
|
||||||
|
CreatedByUserId: req.GetMeta().GetActorUserId(),
|
||||||
|
CreatedAtMs: 1_700_000_000_003,
|
||||||
|
UpdatedAtMs: 1_700_000_000_003,
|
||||||
|
},
|
||||||
|
ServerTimeMs: 1_700_000_000_003,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeRoomClient) SetRoomBackground(_ context.Context, req *roomv1.SetRoomBackgroundRequest) (*roomv1.SetRoomBackgroundResponse, error) {
|
||||||
|
f.lastSetBackground = req
|
||||||
|
return &roomv1.SetRoomBackgroundResponse{
|
||||||
|
Result: &roomv1.CommandResult{Applied: true, RoomVersion: 4, ServerTimeMs: 1_700_000_000_004},
|
||||||
|
Room: &roomv1.RoomSnapshot{
|
||||||
|
RoomId: req.GetMeta().GetRoomId(),
|
||||||
|
OwnerUserId: req.GetMeta().GetActorUserId(),
|
||||||
|
Mode: "voice",
|
||||||
|
Status: "active",
|
||||||
|
ChatEnabled: true,
|
||||||
|
RoomExt: map[string]string{
|
||||||
|
"background_url": "https://cdn.example.com/bg.png",
|
||||||
|
},
|
||||||
|
Version: 4,
|
||||||
|
},
|
||||||
|
Background: &roomv1.RoomBackgroundImage{
|
||||||
|
BackgroundId: req.GetBackgroundId(),
|
||||||
|
RoomId: req.GetMeta().GetRoomId(),
|
||||||
|
ImageUrl: "https://cdn.example.com/bg.png",
|
||||||
|
CreatedByUserId: req.GetMeta().GetActorUserId(),
|
||||||
|
CreatedAtMs: 1_700_000_000_003,
|
||||||
|
UpdatedAtMs: 1_700_000_000_003,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *fakeRoomClient) JoinRoom(_ context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error) {
|
func (f *fakeRoomClient) JoinRoom(_ context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error) {
|
||||||
f.lastJoin = req
|
f.lastJoin = req
|
||||||
if f.joinErr != nil {
|
if f.joinErr != nil {
|
||||||
@ -293,27 +336,33 @@ type fakeUserIdentityClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fakeRoomQueryClient struct {
|
type fakeRoomQueryClient struct {
|
||||||
lastList *roomv1.ListRoomsRequest
|
lastList *roomv1.ListRoomsRequest
|
||||||
lastFeeds *roomv1.ListRoomFeedsRequest
|
lastFeeds *roomv1.ListRoomFeedsRequest
|
||||||
lastMyRoom *roomv1.GetMyRoomRequest
|
lastMyRoom *roomv1.GetMyRoomRequest
|
||||||
lastCurrent *roomv1.GetCurrentRoomRequest
|
lastCurrent *roomv1.GetCurrentRoomRequest
|
||||||
lastSnapshot *roomv1.GetRoomSnapshotRequest
|
lastSnapshot *roomv1.GetRoomSnapshotRequest
|
||||||
lastTreasure *roomv1.GetRoomTreasureRequest
|
lastBackgrounds *roomv1.ListRoomBackgroundsRequest
|
||||||
lastOnline *roomv1.ListRoomOnlineUsersRequest
|
lastTreasure *roomv1.GetRoomTreasureRequest
|
||||||
resp *roomv1.ListRoomsResponse
|
lastOnline *roomv1.ListRoomOnlineUsersRequest
|
||||||
feedsResp *roomv1.ListRoomsResponse
|
lastBannedUsers *roomv1.ListRoomBannedUsersRequest
|
||||||
myRoomResp *roomv1.GetMyRoomResponse
|
resp *roomv1.ListRoomsResponse
|
||||||
currentResp *roomv1.GetCurrentRoomResponse
|
feedsResp *roomv1.ListRoomsResponse
|
||||||
snapshotResp *roomv1.GetRoomSnapshotResponse
|
myRoomResp *roomv1.GetMyRoomResponse
|
||||||
treasureResp *roomv1.GetRoomTreasureResponse
|
currentResp *roomv1.GetCurrentRoomResponse
|
||||||
onlineResp *roomv1.ListRoomOnlineUsersResponse
|
snapshotResp *roomv1.GetRoomSnapshotResponse
|
||||||
err error
|
backgroundsResp *roomv1.ListRoomBackgroundsResponse
|
||||||
feedsErr error
|
treasureResp *roomv1.GetRoomTreasureResponse
|
||||||
myRoomErr error
|
onlineResp *roomv1.ListRoomOnlineUsersResponse
|
||||||
currentErr error
|
bannedUsersResp *roomv1.ListRoomBannedUsersResponse
|
||||||
snapshotErr error
|
err error
|
||||||
treasureErr error
|
feedsErr error
|
||||||
onlineErr error
|
myRoomErr error
|
||||||
|
currentErr error
|
||||||
|
snapshotErr error
|
||||||
|
backgroundsErr error
|
||||||
|
treasureErr error
|
||||||
|
onlineErr error
|
||||||
|
bannedUsersErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeUserDeviceClient struct {
|
type fakeUserDeviceClient struct {
|
||||||
@ -761,6 +810,17 @@ func (f *fakeRoomQueryClient) GetRoomSnapshot(_ context.Context, req *roomv1.Get
|
|||||||
return &roomv1.GetRoomSnapshotResponse{}, nil
|
return &roomv1.GetRoomSnapshotResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeRoomQueryClient) ListRoomBackgrounds(_ context.Context, req *roomv1.ListRoomBackgroundsRequest) (*roomv1.ListRoomBackgroundsResponse, error) {
|
||||||
|
f.lastBackgrounds = req
|
||||||
|
if f.backgroundsErr != nil {
|
||||||
|
return nil, f.backgroundsErr
|
||||||
|
}
|
||||||
|
if f.backgroundsResp != nil {
|
||||||
|
return f.backgroundsResp, nil
|
||||||
|
}
|
||||||
|
return &roomv1.ListRoomBackgroundsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *fakeRoomQueryClient) GetRoomTreasure(_ context.Context, req *roomv1.GetRoomTreasureRequest) (*roomv1.GetRoomTreasureResponse, error) {
|
func (f *fakeRoomQueryClient) GetRoomTreasure(_ context.Context, req *roomv1.GetRoomTreasureRequest) (*roomv1.GetRoomTreasureResponse, error) {
|
||||||
f.lastTreasure = req
|
f.lastTreasure = req
|
||||||
if f.treasureErr != nil {
|
if f.treasureErr != nil {
|
||||||
@ -783,6 +843,17 @@ func (f *fakeRoomQueryClient) ListRoomOnlineUsers(_ context.Context, req *roomv1
|
|||||||
return &roomv1.ListRoomOnlineUsersResponse{Page: req.GetPage(), PageSize: req.GetPageSize()}, nil
|
return &roomv1.ListRoomOnlineUsersResponse{Page: req.GetPage(), PageSize: req.GetPageSize()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeRoomQueryClient) ListRoomBannedUsers(_ context.Context, req *roomv1.ListRoomBannedUsersRequest) (*roomv1.ListRoomBannedUsersResponse, error) {
|
||||||
|
f.lastBannedUsers = req
|
||||||
|
if f.bannedUsersErr != nil {
|
||||||
|
return nil, f.bannedUsersErr
|
||||||
|
}
|
||||||
|
if f.bannedUsersResp != nil {
|
||||||
|
return f.bannedUsersResp, nil
|
||||||
|
}
|
||||||
|
return &roomv1.ListRoomBannedUsersResponse{Page: req.GetPage(), PageSize: req.GetPageSize()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *fakeUserDeviceClient) BindPushToken(_ context.Context, req *userv1.BindPushTokenRequest) (*userv1.BindPushTokenResponse, error) {
|
func (f *fakeUserDeviceClient) BindPushToken(_ context.Context, req *userv1.BindPushTokenRequest) (*userv1.BindPushTokenResponse, error) {
|
||||||
f.lastBind = req
|
f.lastBind = req
|
||||||
if f.bindErr != nil {
|
if f.bindErr != nil {
|
||||||
@ -1562,7 +1633,7 @@ func TestRoomCommandsPropagateClientCommandID(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "kick",
|
name: "kick",
|
||||||
path: "/api/v1/rooms/user/kick",
|
path: "/api/v1/rooms/user/kick",
|
||||||
body: `{"room_id":"room-1","command_id":"cmd-kick-1","target_user_id":43}`,
|
body: `{"room_id":"room-1","command_id":"cmd-kick-1","target_user_id":43,"duration_ms":600000}`,
|
||||||
commandID: "cmd-kick-1",
|
commandID: "cmd-kick-1",
|
||||||
extractMeta: func(client *fakeRoomClient) *roomv1.RequestMeta {
|
extractMeta: func(client *fakeRoomClient) *roomv1.RequestMeta {
|
||||||
return client.lastKick.GetMeta()
|
return client.lastKick.GetMeta()
|
||||||
@ -1617,6 +1688,9 @@ func TestRoomCommandsPropagateClientCommandID(t *testing.T) {
|
|||||||
if meta.GetActorUserId() != 42 {
|
if meta.GetActorUserId() != 42 {
|
||||||
t.Fatalf("actor user mismatch: %+v", meta)
|
t.Fatalf("actor user mismatch: %+v", meta)
|
||||||
}
|
}
|
||||||
|
if test.name == "kick" && client.lastKick.GetDurationMs() != 600000 {
|
||||||
|
t.Fatalf("kick duration_ms was not forwarded: %+v", client.lastKick)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2290,6 +2364,57 @@ func TestListRoomOnlineUsersIncludesProfileGenderAndAge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListRoomBannedUsersIncludesProfileCountryAndUnbanTime(t *testing.T) {
|
||||||
|
queryClient := &fakeRoomQueryClient{bannedUsersResp: &roomv1.ListRoomBannedUsersResponse{
|
||||||
|
Items: []*roomv1.RoomBannedUser{
|
||||||
|
{
|
||||||
|
UserId: 43,
|
||||||
|
ActorUserId: 42,
|
||||||
|
CreatedAtMs: 1000,
|
||||||
|
UnbanAtMs: 7000,
|
||||||
|
RemainingMs: 6000,
|
||||||
|
Permanent: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Total: 1,
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 20,
|
||||||
|
ServerTimeMs: 2000,
|
||||||
|
}}
|
||||||
|
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
|
||||||
|
handler.SetRoomQueryClient(queryClient)
|
||||||
|
router := handler.Routes(auth.NewVerifier("secret"))
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/room-banned/banned-users?page=1&page_size=20", nil)
|
||||||
|
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
|
||||||
|
request.Header.Set("X-Request-ID", "req-room-banned-users")
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
if queryClient.lastBannedUsers == nil || queryClient.lastBannedUsers.GetRoomId() != "room-banned" || queryClient.lastBannedUsers.GetViewerUserId() != 42 || queryClient.lastBannedUsers.GetPageSize() != 20 {
|
||||||
|
t.Fatalf("banned users request mismatch: %+v", queryClient.lastBannedUsers)
|
||||||
|
}
|
||||||
|
var response httpkit.ResponseEnvelope
|
||||||
|
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
|
||||||
|
t.Fatalf("decode banned users response failed: %v", err)
|
||||||
|
}
|
||||||
|
data, ok := response.Data.(map[string]any)
|
||||||
|
items, itemsOK := data["items"].([]any)
|
||||||
|
if response.Code != httpkit.CodeOK || !ok || !itemsOK || len(items) != 1 {
|
||||||
|
t.Fatalf("banned users response mismatch: %+v", response)
|
||||||
|
}
|
||||||
|
first, ok := items[0].(map[string]any)
|
||||||
|
if !ok || first["user_id"] != "43" || first["actor_user_id"] != "42" || first["unban_at_ms"] != float64(7000) || first["remaining_ms"] != float64(6000) {
|
||||||
|
t.Fatalf("banned user item mismatch: %+v", first)
|
||||||
|
}
|
||||||
|
if first["username"] != "user-43" || first["avatar"] != "https://cdn.example/avatar.png" || first["country"] != "US" || first["country_flag"] != "🇺🇸" {
|
||||||
|
t.Fatalf("banned user profile fields mismatch: %+v", first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetRoomSnapshotRejectsInvalidRoomIDBeforeGRPC(t *testing.T) {
|
func TestGetRoomSnapshotRejectsInvalidRoomIDBeforeGRPC(t *testing.T) {
|
||||||
queryClient := &fakeRoomQueryClient{}
|
queryClient := &fakeRoomQueryClient{}
|
||||||
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
|
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
|
||||||
@ -4679,6 +4804,74 @@ func TestUpdateRoomProfileRPCErrorWritesEnvelope(t *testing.T) {
|
|||||||
assertEnvelope(t, recorder, http.StatusConflict, string(xerr.Conflict), "req-profile-conflict")
|
assertEnvelope(t, recorder, http.StatusConflict, string(xerr.Conflict), "req-profile-conflict")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRoomBackgroundRoutesForwardOwnerRequests(t *testing.T) {
|
||||||
|
client := &fakeRoomClient{}
|
||||||
|
queryClient := &fakeRoomQueryClient{backgroundsResp: &roomv1.ListRoomBackgroundsResponse{
|
||||||
|
Backgrounds: []*roomv1.RoomBackgroundImage{{
|
||||||
|
BackgroundId: 101,
|
||||||
|
RoomId: "room-1",
|
||||||
|
ImageUrl: "https://cdn.example.com/bg.png",
|
||||||
|
CreatedByUserId: 42,
|
||||||
|
CreatedAtMs: 1_700_000_000_003,
|
||||||
|
UpdatedAtMs: 1_700_000_000_003,
|
||||||
|
}},
|
||||||
|
SelectedBackgroundUrl: "https://cdn.example.com/bg.png",
|
||||||
|
ServerTimeMs: 1_700_000_000_004,
|
||||||
|
}}
|
||||||
|
handler := NewHandler(client)
|
||||||
|
handler.SetRoomQueryClient(queryClient)
|
||||||
|
router := handler.Routes(auth.NewVerifier("secret"))
|
||||||
|
|
||||||
|
saveReq := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/backgrounds/save", bytes.NewReader([]byte(`{"room_id":"room-1","image_url":" https://cdn.example.com/bg.png "}`)))
|
||||||
|
saveReq.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
|
||||||
|
saveReq.Header.Set("X-Request-ID", "req-bg-save")
|
||||||
|
saveRecorder := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(saveRecorder, saveReq)
|
||||||
|
if saveRecorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("save status mismatch: got %d body=%s", saveRecorder.Code, saveRecorder.Body.String())
|
||||||
|
}
|
||||||
|
if client.lastSaveBackground == nil || client.lastSaveBackground.GetRoomId() != "room-1" || client.lastSaveBackground.GetImageUrl() != "https://cdn.example.com/bg.png" || client.lastSaveBackground.GetMeta().GetActorUserId() != 42 {
|
||||||
|
t.Fatalf("SaveRoomBackground forwarding mismatch: %+v", client.lastSaveBackground)
|
||||||
|
}
|
||||||
|
|
||||||
|
listReq := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/room-1/backgrounds", nil)
|
||||||
|
listReq.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
|
||||||
|
listReq.Header.Set("X-Request-ID", "req-bg-list")
|
||||||
|
listRecorder := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(listRecorder, listReq)
|
||||||
|
if listRecorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("list status mismatch: got %d body=%s", listRecorder.Code, listRecorder.Body.String())
|
||||||
|
}
|
||||||
|
if queryClient.lastBackgrounds == nil || queryClient.lastBackgrounds.GetRoomId() != "room-1" || queryClient.lastBackgrounds.GetViewerUserId() != 42 {
|
||||||
|
t.Fatalf("ListRoomBackgrounds forwarding mismatch: %+v", queryClient.lastBackgrounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
setReq := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/backgrounds/set", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-bg-set","background_id":"101"}`)))
|
||||||
|
setReq.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
|
||||||
|
setReq.Header.Set("X-Request-ID", "req-bg-set")
|
||||||
|
setRecorder := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(setRecorder, setReq)
|
||||||
|
if setRecorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("set status mismatch: got %d body=%s", setRecorder.Code, setRecorder.Body.String())
|
||||||
|
}
|
||||||
|
if client.lastSetBackground == nil || client.lastSetBackground.GetMeta().GetRoomId() != "room-1" || client.lastSetBackground.GetMeta().GetCommandId() != "cmd-bg-set" || client.lastSetBackground.GetBackgroundId() != 101 {
|
||||||
|
t.Fatalf("SetRoomBackground forwarding mismatch: %+v", client.lastSetBackground)
|
||||||
|
}
|
||||||
|
|
||||||
|
var envelope httpkit.ResponseEnvelope
|
||||||
|
if err := json.NewDecoder(setRecorder.Body).Decode(&envelope); err != nil {
|
||||||
|
t.Fatalf("decode set response failed: %v", err)
|
||||||
|
}
|
||||||
|
data, ok := envelope.Data.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("set response data mismatch: %+v", envelope.Data)
|
||||||
|
}
|
||||||
|
room, ok := data["room"].(map[string]any)
|
||||||
|
if !ok || room["background_url"] != "https://cdn.example.com/bg.png" {
|
||||||
|
t.Fatalf("set response must expose selected background_url: %+v", data["room"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateRoomRejectsMissingRequiredFieldsBeforeGRPC(t *testing.T) {
|
func TestCreateRoomRejectsMissingRequiredFieldsBeforeGRPC(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@ -51,11 +51,15 @@ func (h *Handler) RoomHandlers() httproutes.RoomHandlers {
|
|||||||
GetRoomSnapshot: h.getRoomSnapshot,
|
GetRoomSnapshot: h.getRoomSnapshot,
|
||||||
GetRoomDetail: h.getRoomDetail,
|
GetRoomDetail: h.getRoomDetail,
|
||||||
ListRoomOnlineUsers: h.listRoomOnlineUsers,
|
ListRoomOnlineUsers: h.listRoomOnlineUsers,
|
||||||
|
ListRoomBannedUsers: h.listRoomBannedUsers,
|
||||||
GetRoomGiftPanel: h.getRoomGiftPanel,
|
GetRoomGiftPanel: h.getRoomGiftPanel,
|
||||||
GetRoomTreasure: h.getRoomTreasure,
|
GetRoomTreasure: h.getRoomTreasure,
|
||||||
FollowRoom: h.handleRoomFollow,
|
FollowRoom: h.handleRoomFollow,
|
||||||
CreateRoom: h.createRoom,
|
CreateRoom: h.createRoom,
|
||||||
UpdateRoomProfile: h.updateRoomProfile,
|
UpdateRoomProfile: h.updateRoomProfile,
|
||||||
|
SaveRoomBackground: h.saveRoomBackground,
|
||||||
|
ListRoomBackgrounds: h.listRoomBackgrounds,
|
||||||
|
SetRoomBackground: h.setRoomBackground,
|
||||||
JoinRoom: h.joinRoom,
|
JoinRoom: h.joinRoom,
|
||||||
RoomHeartbeat: h.roomHeartbeat,
|
RoomHeartbeat: h.roomHeartbeat,
|
||||||
LeaveRoom: h.leaveRoom,
|
LeaveRoom: h.leaveRoom,
|
||||||
|
|||||||
@ -475,6 +475,46 @@ func (h *Handler) listRoomOnlineUsers(writer http.ResponseWriter, request *http.
|
|||||||
httpkit.WriteOK(writer, request, roomOnlineUserDataFromProto(resp, h.roomDisplayProfileMap(request, userIDs)))
|
httpkit.WriteOK(writer, request, roomOnlineUserDataFromProto(resp, h.roomDisplayProfileMap(request, userIDs)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// listRoomBannedUsers 分页返回房间治理面板使用的当前有效黑名单。
|
||||||
|
func (h *Handler) listRoomBannedUsers(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
if h.roomQueryClient == nil {
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roomID := strings.TrimSpace(request.PathValue("room_id"))
|
||||||
|
if !roomid.ValidStringID(roomID) {
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
page, ok := httpkit.PositiveInt32Query(request, "page", 1)
|
||||||
|
if !ok {
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pageSize, ok := httpkit.PositiveInt32Query(request, "page_size", 20)
|
||||||
|
if !ok {
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.roomQueryClient.ListRoomBannedUsers(request.Context(), &roomv1.ListRoomBannedUsersRequest{
|
||||||
|
Meta: httpkit.RoomMeta(request, roomID, ""),
|
||||||
|
RoomId: roomID,
|
||||||
|
ViewerUserId: auth.UserIDFromContext(request.Context()),
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
httpkit.WriteRPCError(writer, request, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userIDs := make([]int64, 0, len(resp.GetItems()))
|
||||||
|
for _, item := range resp.GetItems() {
|
||||||
|
userIDs = append(userIDs, item.GetUserId())
|
||||||
|
}
|
||||||
|
httpkit.WriteOK(writer, request, roomBannedUsersDataFromProto(roomID, resp, h.roomDisplayProfileMap(request, userIDs)))
|
||||||
|
}
|
||||||
|
|
||||||
// getRoomGiftPanel 返回房间送礼面板初始化数据。
|
// getRoomGiftPanel 返回房间送礼面板初始化数据。
|
||||||
func (h *Handler) getRoomGiftPanel(writer http.ResponseWriter, request *http.Request) {
|
func (h *Handler) getRoomGiftPanel(writer http.ResponseWriter, request *http.Request) {
|
||||||
if h.roomQueryClient == nil || h.walletClient == nil {
|
if h.roomQueryClient == nil || h.walletClient == nil {
|
||||||
@ -705,6 +745,76 @@ func (h *Handler) updateRoomProfile(writer http.ResponseWriter, request *http.Re
|
|||||||
httpkit.WriteOK(writer, request, updateRoomProfileDataFromProto(resp))
|
httpkit.WriteOK(writer, request, updateRoomProfileDataFromProto(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// saveRoomBackground 保存房主上传后的背景图 URL,素材归 room-service 按房间维度管理。
|
||||||
|
func (h *Handler) saveRoomBackground(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var body struct {
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
ImageURL string `json:"image_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if !httpkit.Decode(writer, request, &body) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body.RoomID = strings.TrimSpace(body.RoomID)
|
||||||
|
body.ImageURL = strings.TrimSpace(body.ImageURL)
|
||||||
|
if !roomid.ValidStringID(body.RoomID) || body.ImageURL == "" {
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.roomClient.SaveRoomBackground(request.Context(), &roomv1.SaveRoomBackgroundRequest{
|
||||||
|
Meta: httpkit.RoomMeta(request, body.RoomID, ""),
|
||||||
|
RoomId: body.RoomID,
|
||||||
|
ImageUrl: body.ImageURL,
|
||||||
|
})
|
||||||
|
httpkit.Write(writer, request, saveRoomBackgroundDataFromProto(resp), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// listRoomBackgrounds 返回房主保存过的背景图素材,以及 Room Cell 快照里的当前生效背景。
|
||||||
|
func (h *Handler) listRoomBackgrounds(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
if h.roomQueryClient == nil {
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roomID := strings.TrimSpace(request.PathValue("room_id"))
|
||||||
|
if !roomid.ValidStringID(roomID) {
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.roomQueryClient.ListRoomBackgrounds(request.Context(), &roomv1.ListRoomBackgroundsRequest{
|
||||||
|
Meta: httpkit.RoomMeta(request, roomID, ""),
|
||||||
|
RoomId: roomID,
|
||||||
|
ViewerUserId: auth.UserIDFromContext(request.Context()),
|
||||||
|
})
|
||||||
|
httpkit.Write(writer, request, listRoomBackgroundsDataFromProto(resp), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setRoomBackground 把已保存素材设为当前房间背景;权限和状态写入仍由 Room Cell 串行裁决。
|
||||||
|
func (h *Handler) setRoomBackground(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var body struct {
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
CommandID string `json:"command_id"`
|
||||||
|
BackgroundID flexibleInt64 `json:"background_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if !httpkit.Decode(writer, request, &body) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body.RoomID = strings.TrimSpace(body.RoomID)
|
||||||
|
body.CommandID = strings.TrimSpace(body.CommandID)
|
||||||
|
if !roomid.ValidStringID(body.RoomID) || body.CommandID == "" || int64(body.BackgroundID) <= 0 {
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.roomClient.SetRoomBackground(request.Context(), &roomv1.SetRoomBackgroundRequest{
|
||||||
|
Meta: httpkit.RoomMeta(request, body.RoomID, body.CommandID),
|
||||||
|
BackgroundId: int64(body.BackgroundID),
|
||||||
|
})
|
||||||
|
httpkit.Write(writer, request, setRoomBackgroundDataFromProto(resp), err)
|
||||||
|
}
|
||||||
|
|
||||||
func trimOptionalString(value *string) {
|
func trimOptionalString(value *string) {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
return
|
return
|
||||||
@ -1089,6 +1199,7 @@ func (h *Handler) kickUser(writer http.ResponseWriter, request *http.Request) {
|
|||||||
RoomID string `json:"room_id"`
|
RoomID string `json:"room_id"`
|
||||||
CommandID string `json:"command_id"`
|
CommandID string `json:"command_id"`
|
||||||
TargetUserID int64 `json:"target_user_id"`
|
TargetUserID int64 `json:"target_user_id"`
|
||||||
|
DurationMS int64 `json:"duration_ms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if !httpkit.Decode(writer, request, &body) {
|
if !httpkit.Decode(writer, request, &body) {
|
||||||
@ -1098,6 +1209,7 @@ func (h *Handler) kickUser(writer http.ResponseWriter, request *http.Request) {
|
|||||||
resp, err := h.roomClient.KickUser(request.Context(), &roomv1.KickUserRequest{
|
resp, err := h.roomClient.KickUser(request.Context(), &roomv1.KickUserRequest{
|
||||||
Meta: httpkit.RoomMeta(request, body.RoomID, body.CommandID),
|
Meta: httpkit.RoomMeta(request, body.RoomID, body.CommandID),
|
||||||
TargetUserId: body.TargetUserID,
|
TargetUserId: body.TargetUserID,
|
||||||
|
DurationMs: body.DurationMS,
|
||||||
})
|
})
|
||||||
httpkit.Write(writer, request, resp, err)
|
httpkit.Write(writer, request, resp, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,6 +92,31 @@ type roomOnlineUserData struct {
|
|||||||
Profile *roomDisplayProfileData `json:"profile,omitempty"`
|
Profile *roomDisplayProfileData `json:"profile,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type roomBannedUsersData struct {
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
Items []roomBannedUserData `json:"items"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Page int32 `json:"page"`
|
||||||
|
PageSize int32 `json:"page_size"`
|
||||||
|
ServerTimeMS int64 `json:"server_time_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type roomBannedUserData struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
DisplayUserID string `json:"display_user_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
CountryName string `json:"country_name"`
|
||||||
|
CountryDisplayName string `json:"country_display_name"`
|
||||||
|
CountryFlag string `json:"country_flag"`
|
||||||
|
ActorUserID string `json:"actor_user_id"`
|
||||||
|
CreatedAtMS int64 `json:"created_at_ms"`
|
||||||
|
UnbanAtMS int64 `json:"unban_at_ms"`
|
||||||
|
RemainingMS int64 `json:"remaining_ms"`
|
||||||
|
Permanent bool `json:"permanent"`
|
||||||
|
}
|
||||||
|
|
||||||
type setRoomAdminData struct {
|
type setRoomAdminData struct {
|
||||||
Result roomCommandResultData `json:"result"`
|
Result roomCommandResultData `json:"result"`
|
||||||
TargetUserID string `json:"target_user_id"`
|
TargetUserID string `json:"target_user_id"`
|
||||||
@ -220,6 +245,33 @@ type updateRoomProfileData struct {
|
|||||||
ServerTimeMS int64 `json:"server_time_ms"`
|
ServerTimeMS int64 `json:"server_time_ms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type roomBackgroundData struct {
|
||||||
|
BackgroundID int64 `json:"background_id"`
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
ImageURL string `json:"image_url"`
|
||||||
|
CreatedByUserID string `json:"created_by_user_id,omitempty"`
|
||||||
|
CreatedAtMS int64 `json:"created_at_ms"`
|
||||||
|
UpdatedAtMS int64 `json:"updated_at_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type saveRoomBackgroundData struct {
|
||||||
|
Background roomBackgroundData `json:"background"`
|
||||||
|
ServerTimeMS int64 `json:"server_time_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type listRoomBackgroundsData struct {
|
||||||
|
Backgrounds []roomBackgroundData `json:"backgrounds"`
|
||||||
|
SelectedBackgroundURL string `json:"selected_background_url,omitempty"`
|
||||||
|
ServerTimeMS int64 `json:"server_time_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type setRoomBackgroundData struct {
|
||||||
|
Result roomCommandResultData `json:"result"`
|
||||||
|
Room roomInitialData `json:"room"`
|
||||||
|
Background roomBackgroundData `json:"background"`
|
||||||
|
ServerTimeMS int64 `json:"server_time_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
type roomSnapshotData struct {
|
type roomSnapshotData struct {
|
||||||
Room roomInitialData `json:"room"`
|
Room roomInitialData `json:"room"`
|
||||||
Seats []roomSeatData `json:"seats"`
|
Seats []roomSeatData `json:"seats"`
|
||||||
@ -247,6 +299,7 @@ type roomInitialData struct {
|
|||||||
RoomShortID string `json:"room_short_id,omitempty"`
|
RoomShortID string `json:"room_short_id,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
CoverURL string `json:"cover_url,omitempty"`
|
CoverURL string `json:"cover_url,omitempty"`
|
||||||
|
BackgroundURL string `json:"background_url,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
OwnerUserID string `json:"owner_user_id,omitempty"`
|
OwnerUserID string `json:"owner_user_id,omitempty"`
|
||||||
Mode string `json:"mode,omitempty"`
|
Mode string `json:"mode,omitempty"`
|
||||||
@ -478,6 +531,58 @@ func updateRoomProfileDataFromProto(resp *roomv1.UpdateRoomProfileResponse) upda
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func saveRoomBackgroundDataFromProto(resp *roomv1.SaveRoomBackgroundResponse) saveRoomBackgroundData {
|
||||||
|
if resp == nil {
|
||||||
|
return saveRoomBackgroundData{}
|
||||||
|
}
|
||||||
|
return saveRoomBackgroundData{
|
||||||
|
Background: roomBackgroundDataFromProto(resp.GetBackground()),
|
||||||
|
ServerTimeMS: resp.GetServerTimeMs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listRoomBackgroundsDataFromProto(resp *roomv1.ListRoomBackgroundsResponse) listRoomBackgroundsData {
|
||||||
|
if resp == nil {
|
||||||
|
return listRoomBackgroundsData{}
|
||||||
|
}
|
||||||
|
backgrounds := make([]roomBackgroundData, 0, len(resp.GetBackgrounds()))
|
||||||
|
for _, background := range resp.GetBackgrounds() {
|
||||||
|
backgrounds = append(backgrounds, roomBackgroundDataFromProto(background))
|
||||||
|
}
|
||||||
|
return listRoomBackgroundsData{
|
||||||
|
Backgrounds: backgrounds,
|
||||||
|
SelectedBackgroundURL: resp.GetSelectedBackgroundUrl(),
|
||||||
|
ServerTimeMS: resp.GetServerTimeMs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRoomBackgroundDataFromProto(resp *roomv1.SetRoomBackgroundResponse) setRoomBackgroundData {
|
||||||
|
if resp == nil {
|
||||||
|
return setRoomBackgroundData{}
|
||||||
|
}
|
||||||
|
snapshot := resp.GetRoom()
|
||||||
|
return setRoomBackgroundData{
|
||||||
|
Result: commandResultDataFromProto(resp.GetResult()),
|
||||||
|
Room: roomInitialRoomDataFromSnapshot(snapshot, snapshot.GetRoomId()),
|
||||||
|
Background: roomBackgroundDataFromProto(resp.GetBackground()),
|
||||||
|
ServerTimeMS: resp.GetResult().GetServerTimeMs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func roomBackgroundDataFromProto(background *roomv1.RoomBackgroundImage) roomBackgroundData {
|
||||||
|
if background == nil {
|
||||||
|
return roomBackgroundData{}
|
||||||
|
}
|
||||||
|
return roomBackgroundData{
|
||||||
|
BackgroundID: background.GetBackgroundId(),
|
||||||
|
RoomID: background.GetRoomId(),
|
||||||
|
ImageURL: background.GetImageUrl(),
|
||||||
|
CreatedByUserID: formatOptionalUserID(background.GetCreatedByUserId()),
|
||||||
|
CreatedAtMS: background.GetCreatedAtMs(),
|
||||||
|
UpdatedAtMS: background.GetUpdatedAtMs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func roomSnapshotDataFromProto(resp *roomv1.GetRoomSnapshotResponse) roomSnapshotData {
|
func roomSnapshotDataFromProto(resp *roomv1.GetRoomSnapshotResponse) roomSnapshotData {
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
return roomSnapshotData{}
|
return roomSnapshotData{}
|
||||||
@ -511,6 +616,7 @@ func roomInitialRoomDataFromSnapshot(snapshot *roomv1.RoomSnapshot, roomID strin
|
|||||||
RoomShortID: roomShortID,
|
RoomShortID: roomShortID,
|
||||||
Title: ext["title"],
|
Title: ext["title"],
|
||||||
CoverURL: ext["cover_url"],
|
CoverURL: ext["cover_url"],
|
||||||
|
BackgroundURL: ext["background_url"],
|
||||||
Description: ext["description"],
|
Description: ext["description"],
|
||||||
OwnerUserID: formatOptionalUserID(snapshot.GetOwnerUserId()),
|
OwnerUserID: formatOptionalUserID(snapshot.GetOwnerUserId()),
|
||||||
Mode: snapshot.GetMode(),
|
Mode: snapshot.GetMode(),
|
||||||
@ -702,6 +808,41 @@ func roomOnlineUserDataFromProto(resp *roomv1.ListRoomOnlineUsersResponse, profi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func roomBannedUsersDataFromProto(roomID string, resp *roomv1.ListRoomBannedUsersResponse, profiles map[int64]roomDisplayProfileData) roomBannedUsersData {
|
||||||
|
if resp == nil {
|
||||||
|
return roomBannedUsersData{RoomID: roomID}
|
||||||
|
}
|
||||||
|
items := make([]roomBannedUserData, 0, len(resp.GetItems()))
|
||||||
|
for _, user := range resp.GetItems() {
|
||||||
|
item := roomBannedUserData{
|
||||||
|
UserID: formatOptionalUserID(user.GetUserId()),
|
||||||
|
ActorUserID: formatOptionalUserID(user.GetActorUserId()),
|
||||||
|
CreatedAtMS: user.GetCreatedAtMs(),
|
||||||
|
UnbanAtMS: user.GetUnbanAtMs(),
|
||||||
|
RemainingMS: user.GetRemainingMs(),
|
||||||
|
Permanent: user.GetPermanent(),
|
||||||
|
}
|
||||||
|
if profile, ok := profiles[user.GetUserId()]; ok {
|
||||||
|
item.DisplayUserID = profile.DisplayUserID
|
||||||
|
item.Username = profile.Username
|
||||||
|
item.Avatar = profile.Avatar
|
||||||
|
item.Country = profile.Country
|
||||||
|
item.CountryName = profile.CountryName
|
||||||
|
item.CountryDisplayName = profile.CountryDisplayName
|
||||||
|
item.CountryFlag = profile.CountryFlag
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
return roomBannedUsersData{
|
||||||
|
RoomID: roomID,
|
||||||
|
Items: items,
|
||||||
|
Total: resp.GetTotal(),
|
||||||
|
Page: resp.GetPage(),
|
||||||
|
PageSize: resp.GetPageSize(),
|
||||||
|
ServerTimeMS: resp.GetServerTimeMs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func roomOnlineUsersFromLegacyUsers(users []*roomv1.RoomUser) []*roomv1.RoomOnlineUser {
|
func roomOnlineUsersFromLegacyUsers(users []*roomv1.RoomUser) []*roomv1.RoomOnlineUser {
|
||||||
items := make([]*roomv1.RoomOnlineUser, 0, len(users))
|
items := make([]*roomv1.RoomOnlineUser, 0, len(users))
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
|
|||||||
@ -88,6 +88,19 @@ CREATE TABLE IF NOT EXISTS room_user_gift_stats (
|
|||||||
KEY idx_room_user_gift_stats_room_value (app_code, room_id, gift_value DESC, last_gift_at_ms DESC, user_id)
|
KEY idx_room_user_gift_stats_room_value (app_code, room_id, gift_value DESC, last_gift_at_ms DESC, user_id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='房间用户送礼价值统计表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='房间用户送礼价值统计表';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS room_background_images (
|
||||||
|
app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离',
|
||||||
|
background_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '背景图 ID',
|
||||||
|
room_id VARCHAR(64) NOT NULL COMMENT '房间 ID',
|
||||||
|
image_url VARCHAR(256) NOT NULL COMMENT '背景图 URL',
|
||||||
|
created_by_user_id BIGINT NOT NULL COMMENT '保存背景图的用户 ID',
|
||||||
|
created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms',
|
||||||
|
updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms',
|
||||||
|
PRIMARY KEY (background_id),
|
||||||
|
UNIQUE KEY uk_room_background_url (app_code, room_id, image_url),
|
||||||
|
KEY idx_room_background_list (app_code, room_id, created_at_ms DESC, background_id DESC)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='房间背景图素材表';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS room_snapshots (
|
CREATE TABLE IF NOT EXISTS room_snapshots (
|
||||||
app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离',
|
app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离',
|
||||||
room_id VARCHAR(64) NOT NULL COMMENT '房间 ID',
|
room_id VARCHAR(64) NOT NULL COMMENT '房间 ID',
|
||||||
|
|||||||
@ -181,10 +181,23 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room
|
|||||||
}
|
}
|
||||||
base.ActorUserID = body.GetActorUserId()
|
base.ActorUserID = body.GetActorUserId()
|
||||||
base.Attributes = map[string]string{
|
base.Attributes = map[string]string{
|
||||||
"room_name": body.GetRoomName(),
|
"room_name": body.GetRoomName(),
|
||||||
"room_avatar": body.GetRoomAvatar(),
|
"room_avatar": body.GetRoomAvatar(),
|
||||||
"room_description": body.GetRoomDescription(),
|
"room_description": body.GetRoomDescription(),
|
||||||
"seat_count": fmt.Sprintf("%d", body.GetSeatCount()),
|
"seat_count": fmt.Sprintf("%d", body.GetSeatCount()),
|
||||||
|
"room_background_url": body.GetRoomBackgroundUrl(),
|
||||||
|
}
|
||||||
|
return base, true, nil
|
||||||
|
case "RoomBackgroundChanged":
|
||||||
|
// 背景切换是独立的房间 UI 状态变更,客户端无需从通用资料更新里反推。
|
||||||
|
var body roomeventsv1.RoomBackgroundChanged
|
||||||
|
if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
|
||||||
|
return tencentim.RoomEvent{}, false, err
|
||||||
|
}
|
||||||
|
base.ActorUserID = body.GetActorUserId()
|
||||||
|
base.Attributes = map[string]string{
|
||||||
|
"background_id": fmt.Sprintf("%d", body.GetBackgroundId()),
|
||||||
|
"room_background_url": body.GetRoomBackgroundUrl(),
|
||||||
}
|
}
|
||||||
return base, true, nil
|
return base, true, nil
|
||||||
case "RoomChatEnabledChanged":
|
case "RoomChatEnabledChanged":
|
||||||
@ -358,6 +371,8 @@ func eventTypeForClient(eventType string) string {
|
|||||||
return "room_mic_seat_locked"
|
return "room_mic_seat_locked"
|
||||||
case "RoomProfileUpdated":
|
case "RoomProfileUpdated":
|
||||||
return "room_profile_updated"
|
return "room_profile_updated"
|
||||||
|
case "RoomBackgroundChanged":
|
||||||
|
return "room_background_changed"
|
||||||
case "RoomChatEnabledChanged":
|
case "RoomChatEnabledChanged":
|
||||||
return "room_chat_enabled_changed"
|
return "room_chat_enabled_changed"
|
||||||
case "RoomPasswordChanged":
|
case "RoomPasswordChanged":
|
||||||
|
|||||||
@ -32,6 +32,28 @@ func TestRoomEventFromEnvelopeSkipsNonIMEvents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRoomBackgroundChangedPublishesDedicatedIMEvent(t *testing.T) {
|
||||||
|
record, err := outbox.Build("room-bg", "RoomBackgroundChanged", 7, time.Now(), &roomeventsv1.RoomBackgroundChanged{
|
||||||
|
ActorUserId: 42,
|
||||||
|
BackgroundId: 101,
|
||||||
|
RoomBackgroundUrl: "https://cdn.example.com/bg.png",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Build RoomBackgroundChanged envelope failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
event, publish, err := roomEventFromEnvelope(record.Envelope)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RoomBackgroundChanged should decode: %v", err)
|
||||||
|
}
|
||||||
|
if !publish {
|
||||||
|
t.Fatal("RoomBackgroundChanged must publish Tencent IM message")
|
||||||
|
}
|
||||||
|
if event.EventType != "room_background_changed" || event.Attributes["room_background_url"] != "https://cdn.example.com/bg.png" || event.Attributes["background_id"] != "101" {
|
||||||
|
t.Fatalf("background IM event mismatch: %+v", event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTencentIMPublisherRemovesGroupMemberBeforeKickMessage(t *testing.T) {
|
func TestTencentIMPublisherRemovesGroupMemberBeforeKickMessage(t *testing.T) {
|
||||||
paths := make([]string, 0, 2)
|
paths := make([]string, 0, 2)
|
||||||
client, err := tencentim.NewRESTClient(tencentim.RESTConfig{
|
client, err := tencentim.NewRESTClient(tencentim.RESTConfig{
|
||||||
|
|||||||
@ -93,6 +93,17 @@ type UpdateRoomProfile struct {
|
|||||||
// Type 返回命令类型。
|
// Type 返回命令类型。
|
||||||
func (UpdateRoomProfile) Type() string { return "update_room_profile" }
|
func (UpdateRoomProfile) Type() string { return "update_room_profile" }
|
||||||
|
|
||||||
|
// SetRoomBackground 定义房主把已保存背景图设置为当前房间背景的请求。
|
||||||
|
type SetRoomBackground struct {
|
||||||
|
Base
|
||||||
|
// BackgroundID 必须归属于当前房间,避免房主把其他房间的素材串用到自己的房间状态。
|
||||||
|
BackgroundID int64 `json:"background_id"`
|
||||||
|
// BackgroundURL 是提交前从房间背景素材表解析出的确定性快照,恢复时不再回查列表表。
|
||||||
|
BackgroundURL string `json:"background_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SetRoomBackground) Type() string { return "set_room_background" }
|
||||||
|
|
||||||
// JoinRoom 定义业务进房请求。
|
// JoinRoom 定义业务进房请求。
|
||||||
type JoinRoom struct {
|
type JoinRoom struct {
|
||||||
Base
|
Base
|
||||||
@ -309,6 +320,8 @@ type KickUser struct {
|
|||||||
Base
|
Base
|
||||||
// TargetUserID 是被踢出并加入 ban 集合的用户。
|
// TargetUserID 是被踢出并加入 ban 集合的用户。
|
||||||
TargetUserID int64 `json:"target_user_id"`
|
TargetUserID int64 `json:"target_user_id"`
|
||||||
|
// DurationMS 为 0 表示永久 ban;大于 0 表示从命令提交时间起禁止进房的时长。
|
||||||
|
DurationMS int64 `json:"duration_ms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type 返回命令类型。
|
// Type 返回命令类型。
|
||||||
@ -502,6 +515,8 @@ func Deserialize(commandType string, payload []byte) (Command, error) {
|
|||||||
cmd = &CreateRoom{}
|
cmd = &CreateRoom{}
|
||||||
case UpdateRoomProfile{}.Type():
|
case UpdateRoomProfile{}.Type():
|
||||||
cmd = &UpdateRoomProfile{}
|
cmd = &UpdateRoomProfile{}
|
||||||
|
case SetRoomBackground{}.Type():
|
||||||
|
cmd = &SetRoomBackground{}
|
||||||
case JoinRoom{}.Type():
|
case JoinRoom{}.Type():
|
||||||
cmd = &JoinRoom{}
|
cmd = &JoinRoom{}
|
||||||
case RoomHeartbeat{}.Type():
|
case RoomHeartbeat{}.Type():
|
||||||
|
|||||||
251
services/room-service/internal/room/service/background.go
Normal file
251
services/room-service/internal/room/service/background.go
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
||||||
|
roomv1 "hyapp.local/api/proto/room/v1"
|
||||||
|
"hyapp/pkg/roomid"
|
||||||
|
"hyapp/pkg/tencentim"
|
||||||
|
"hyapp/pkg/xerr"
|
||||||
|
"hyapp/services/room-service/internal/room/command"
|
||||||
|
"hyapp/services/room-service/internal/room/outbox"
|
||||||
|
"hyapp/services/room-service/internal/room/rank"
|
||||||
|
"hyapp/services/room-service/internal/room/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SaveRoomBackground 把房主上传后的背景图 URL 保存到房间维度素材列表。
|
||||||
|
// 该动作只维护低频素材表,不改变 Room Cell 当前背景;真正生效必须调用 SetRoomBackground。
|
||||||
|
func (s *Service) SaveRoomBackground(ctx context.Context, req *roomv1.SaveRoomBackgroundRequest) (*roomv1.SaveRoomBackgroundResponse, error) {
|
||||||
|
ctx = contextFromMeta(ctx, req.GetMeta())
|
||||||
|
roomID, actorUserID, imageURL, err := normalizeSaveRoomBackground(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.requireRoomOwnerMeta(ctx, roomID, actorUserID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nowMS := s.clock.Now().UnixMilli()
|
||||||
|
background, err := s.repository.SaveRoomBackground(ctx, RoomBackgroundImage{
|
||||||
|
AppCode: req.GetMeta().GetAppCode(),
|
||||||
|
RoomID: roomID,
|
||||||
|
ImageURL: imageURL,
|
||||||
|
CreatedByUserID: actorUserID,
|
||||||
|
CreatedAtMS: nowMS,
|
||||||
|
UpdatedAtMS: nowMS,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &roomv1.SaveRoomBackgroundResponse{
|
||||||
|
Background: roomBackgroundToProto(background),
|
||||||
|
ServerTimeMs: s.clock.Now().UnixMilli(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRoomBackgrounds 返回房主保存过的背景图列表和当前生效背景。
|
||||||
|
// 列表属于房间编辑面板数据,因此只允许房主读取。
|
||||||
|
func (s *Service) ListRoomBackgrounds(ctx context.Context, req *roomv1.ListRoomBackgroundsRequest) (*roomv1.ListRoomBackgroundsResponse, error) {
|
||||||
|
ctx = contextFromMeta(ctx, req.GetMeta())
|
||||||
|
roomID, viewerUserID, err := normalizeListRoomBackgrounds(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.requireRoomOwnerMeta(ctx, roomID, viewerUserID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
backgrounds, err := s.repository.ListRoomBackgrounds(ctx, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
snapshot, err := s.currentSnapshot(ctx, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &roomv1.ListRoomBackgroundsResponse{
|
||||||
|
Backgrounds: roomBackgroundsToProto(backgrounds),
|
||||||
|
SelectedBackgroundUrl: snapshot.GetRoomExt()[roomExtBackgroundKey],
|
||||||
|
ServerTimeMs: s.clock.Now().UnixMilli(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRoomBackground 把已保存的背景图设置为当前房间背景。
|
||||||
|
// 当前背景属于房间展示状态,必须经 Room Cell 命令链路、command log 和快照恢复。
|
||||||
|
func (s *Service) SetRoomBackground(ctx context.Context, req *roomv1.SetRoomBackgroundRequest) (*roomv1.SetRoomBackgroundResponse, error) {
|
||||||
|
ctx = contextFromMeta(ctx, req.GetMeta())
|
||||||
|
roomID, actorUserID, backgroundID, err := normalizeSetRoomBackground(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.requireRoomOwnerMeta(ctx, roomID, actorUserID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
background, exists, err := s.repository.GetRoomBackground(ctx, roomID, backgroundID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, xerr.New(xerr.NotFound, "room background not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := command.SetRoomBackground{
|
||||||
|
Base: baseFromMeta(req.GetMeta()),
|
||||||
|
BackgroundID: background.BackgroundID,
|
||||||
|
BackgroundURL: background.ImageURL,
|
||||||
|
}
|
||||||
|
result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
|
||||||
|
if err := requireActiveRoom(current); err != nil {
|
||||||
|
return mutationResult{}, nil, err
|
||||||
|
}
|
||||||
|
if current.OwnerUserID != cmd.ActorUserID() {
|
||||||
|
return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "permission denied")
|
||||||
|
}
|
||||||
|
if current.RoomExt == nil {
|
||||||
|
current.RoomExt = make(map[string]string)
|
||||||
|
}
|
||||||
|
if current.RoomExt[roomExtBackgroundKey] == cmd.BackgroundURL {
|
||||||
|
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
current.RoomExt[roomExtBackgroundKey] = cmd.BackgroundURL
|
||||||
|
current.Version++
|
||||||
|
backgroundEvent, err := outbox.Build(current.RoomID, "RoomBackgroundChanged", current.Version, now, &roomeventsv1.RoomBackgroundChanged{
|
||||||
|
ActorUserId: cmd.ActorUserID(),
|
||||||
|
BackgroundId: cmd.BackgroundID,
|
||||||
|
RoomBackgroundUrl: current.RoomExt[roomExtBackgroundKey],
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return mutationResult{}, nil, err
|
||||||
|
}
|
||||||
|
return mutationResult{
|
||||||
|
snapshot: current.ToProto(),
|
||||||
|
syncEvent: &tencentim.RoomEvent{
|
||||||
|
EventID: backgroundEvent.EventID,
|
||||||
|
RoomID: current.RoomID,
|
||||||
|
EventType: "room_background_changed",
|
||||||
|
ActorUserID: cmd.ActorUserID(),
|
||||||
|
RoomVersion: current.Version,
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"background_id": int64String(cmd.BackgroundID),
|
||||||
|
"room_background_url": current.RoomExt[roomExtBackgroundKey],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []outbox.Record{backgroundEvent}, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &roomv1.SetRoomBackgroundResponse{
|
||||||
|
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
||||||
|
Room: result.snapshot,
|
||||||
|
Background: roomBackgroundToProto(background),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeSaveRoomBackground(req *roomv1.SaveRoomBackgroundRequest) (string, int64, string, error) {
|
||||||
|
if req == nil || req.GetMeta() == nil {
|
||||||
|
return "", 0, "", xerr.New(xerr.InvalidArgument, "request is required")
|
||||||
|
}
|
||||||
|
roomID := strings.TrimSpace(req.GetRoomId())
|
||||||
|
if roomID == "" {
|
||||||
|
roomID = strings.TrimSpace(req.GetMeta().GetRoomId())
|
||||||
|
}
|
||||||
|
if !roomid.ValidStringID(roomID) {
|
||||||
|
return "", 0, "", xerr.New(xerr.InvalidArgument, "room_id is invalid")
|
||||||
|
}
|
||||||
|
actorUserID := req.GetMeta().GetActorUserId()
|
||||||
|
if actorUserID <= 0 {
|
||||||
|
return "", 0, "", xerr.New(xerr.InvalidArgument, "actor_user_id is required")
|
||||||
|
}
|
||||||
|
imageURL := strings.TrimSpace(req.GetImageUrl())
|
||||||
|
if imageURL == "" {
|
||||||
|
return "", 0, "", xerr.New(xerr.InvalidArgument, "image_url is required")
|
||||||
|
}
|
||||||
|
if utf8.RuneCountInString(imageURL) > maxRoomBackgroundRunes {
|
||||||
|
return "", 0, "", xerr.New(xerr.InvalidArgument, "image_url is too long")
|
||||||
|
}
|
||||||
|
return roomID, actorUserID, imageURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeListRoomBackgrounds(req *roomv1.ListRoomBackgroundsRequest) (string, int64, error) {
|
||||||
|
if req == nil || req.GetMeta() == nil {
|
||||||
|
return "", 0, xerr.New(xerr.InvalidArgument, "request is required")
|
||||||
|
}
|
||||||
|
roomID := strings.TrimSpace(req.GetRoomId())
|
||||||
|
if !roomid.ValidStringID(roomID) {
|
||||||
|
return "", 0, xerr.New(xerr.InvalidArgument, "room_id is invalid")
|
||||||
|
}
|
||||||
|
viewerUserID := req.GetViewerUserId()
|
||||||
|
if viewerUserID <= 0 {
|
||||||
|
return "", 0, xerr.New(xerr.InvalidArgument, "viewer_user_id is required")
|
||||||
|
}
|
||||||
|
return roomID, viewerUserID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeSetRoomBackground(req *roomv1.SetRoomBackgroundRequest) (string, int64, int64, error) {
|
||||||
|
if req == nil || req.GetMeta() == nil {
|
||||||
|
return "", 0, 0, xerr.New(xerr.InvalidArgument, "request is required")
|
||||||
|
}
|
||||||
|
roomID := strings.TrimSpace(req.GetMeta().GetRoomId())
|
||||||
|
if !roomid.ValidStringID(roomID) {
|
||||||
|
return "", 0, 0, xerr.New(xerr.InvalidArgument, "room_id is invalid")
|
||||||
|
}
|
||||||
|
actorUserID := req.GetMeta().GetActorUserId()
|
||||||
|
if actorUserID <= 0 {
|
||||||
|
return "", 0, 0, xerr.New(xerr.InvalidArgument, "actor_user_id is required")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(req.GetMeta().GetCommandId()) == "" {
|
||||||
|
return "", 0, 0, xerr.New(xerr.InvalidArgument, "command_id is required")
|
||||||
|
}
|
||||||
|
if req.GetBackgroundId() <= 0 {
|
||||||
|
return "", 0, 0, xerr.New(xerr.InvalidArgument, "background_id is required")
|
||||||
|
}
|
||||||
|
return roomID, actorUserID, req.GetBackgroundId(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) requireRoomOwnerMeta(ctx context.Context, roomID string, actorUserID int64) error {
|
||||||
|
meta, exists, err := s.repository.GetRoomMeta(ctx, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return xerr.New(xerr.NotFound, "room not found")
|
||||||
|
}
|
||||||
|
if meta.OwnerUserID != actorUserID {
|
||||||
|
return xerr.New(xerr.PermissionDenied, "permission denied")
|
||||||
|
}
|
||||||
|
if meta.Status != state.RoomStatusActive {
|
||||||
|
return xerr.New(xerr.Conflict, "room is not active")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func roomBackgroundToProto(background RoomBackgroundImage) *roomv1.RoomBackgroundImage {
|
||||||
|
if background.BackgroundID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &roomv1.RoomBackgroundImage{
|
||||||
|
BackgroundId: background.BackgroundID,
|
||||||
|
RoomId: background.RoomID,
|
||||||
|
ImageUrl: background.ImageURL,
|
||||||
|
CreatedByUserId: background.CreatedByUserID,
|
||||||
|
CreatedAtMs: background.CreatedAtMS,
|
||||||
|
UpdatedAtMs: background.UpdatedAtMS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func roomBackgroundsToProto(backgrounds []RoomBackgroundImage) []*roomv1.RoomBackgroundImage {
|
||||||
|
out := make([]*roomv1.RoomBackgroundImage, 0, len(backgrounds))
|
||||||
|
for _, background := range backgrounds {
|
||||||
|
out = append(out, roomBackgroundToProto(background))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
135
services/room-service/internal/room/service/background_test.go
Normal file
135
services/room-service/internal/room/service/background_test.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package service_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
roomv1 "hyapp.local/api/proto/room/v1"
|
||||||
|
"hyapp/pkg/appcode"
|
||||||
|
"hyapp/pkg/xerr"
|
||||||
|
"hyapp/services/room-service/internal/integration"
|
||||||
|
roomservice "hyapp/services/room-service/internal/room/service"
|
||||||
|
"hyapp/services/room-service/internal/router"
|
||||||
|
"hyapp/services/room-service/internal/testutil/mysqltest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRoomBackgroundSaveListSetFlow(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
repository := mysqltest.NewRepository(t)
|
||||||
|
svc := roomservice.New(roomservice.Config{
|
||||||
|
NodeID: "node-room-background-test",
|
||||||
|
LeaseTTL: 10 * time.Second,
|
||||||
|
RankLimit: 20,
|
||||||
|
SnapshotEveryN: 1,
|
||||||
|
}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
|
||||||
|
|
||||||
|
roomID := "room-background-flow"
|
||||||
|
ownerID := int64(7101)
|
||||||
|
if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, ownerID),
|
||||||
|
SeatCount: 10,
|
||||||
|
Mode: "voice",
|
||||||
|
VisibleRegionId: 1001,
|
||||||
|
RoomName: "Background Flow",
|
||||||
|
RoomShortId: "bg-flow",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("create room failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveResp, err := svc.SaveRoomBackground(ctx, &roomv1.SaveRoomBackgroundRequest{
|
||||||
|
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID, ActorUserId: ownerID},
|
||||||
|
RoomId: roomID,
|
||||||
|
ImageUrl: " https://cdn.example.com/room-bg.png ",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("save background failed: %v", err)
|
||||||
|
}
|
||||||
|
if saveResp.GetBackground().GetBackgroundId() <= 0 || saveResp.GetBackground().GetImageUrl() != "https://cdn.example.com/room-bg.png" {
|
||||||
|
t.Fatalf("save response mismatch: %+v", saveResp.GetBackground())
|
||||||
|
}
|
||||||
|
|
||||||
|
listResp, err := svc.ListRoomBackgrounds(ctx, &roomv1.ListRoomBackgroundsRequest{
|
||||||
|
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
|
||||||
|
RoomId: roomID,
|
||||||
|
ViewerUserId: ownerID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("list backgrounds failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(listResp.GetBackgrounds()) != 1 || listResp.GetSelectedBackgroundUrl() != "" {
|
||||||
|
t.Fatalf("list response before set mismatch: %+v", listResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
setResp, err := svc.SetRoomBackground(ctx, &roomv1.SetRoomBackgroundRequest{
|
||||||
|
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID, ActorUserId: ownerID, CommandId: "cmd-room-bg-set"},
|
||||||
|
BackgroundId: saveResp.GetBackground().GetBackgroundId(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("set background failed: %v", err)
|
||||||
|
}
|
||||||
|
if !setResp.GetResult().GetApplied() || setResp.GetRoom().GetRoomExt()["background_url"] != "https://cdn.example.com/room-bg.png" {
|
||||||
|
t.Fatalf("set response mismatch: %+v", setResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotResp, err := svc.GetRoomSnapshot(ctx, &roomv1.GetRoomSnapshotRequest{
|
||||||
|
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
|
||||||
|
RoomId: roomID,
|
||||||
|
ViewerUserId: ownerID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("snapshot after set failed: %v", err)
|
||||||
|
}
|
||||||
|
if snapshotResp.GetRoom().GetRoomExt()["background_url"] != "https://cdn.example.com/room-bg.png" {
|
||||||
|
t.Fatalf("snapshot must expose selected background_url: %+v", snapshotResp.GetRoom().GetRoomExt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoomBackgroundOnlyOwnerCanEdit(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
repository := mysqltest.NewRepository(t)
|
||||||
|
svc := roomservice.New(roomservice.Config{
|
||||||
|
NodeID: "node-room-background-owner-test",
|
||||||
|
LeaseTTL: 10 * time.Second,
|
||||||
|
RankLimit: 20,
|
||||||
|
SnapshotEveryN: 1,
|
||||||
|
}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
|
||||||
|
|
||||||
|
roomID := "room-background-owner"
|
||||||
|
ownerID := int64(7201)
|
||||||
|
otherID := int64(7202)
|
||||||
|
if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, ownerID),
|
||||||
|
SeatCount: 10,
|
||||||
|
Mode: "voice",
|
||||||
|
VisibleRegionId: 1001,
|
||||||
|
RoomName: "Background Owner",
|
||||||
|
RoomShortId: "bg-owner",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("create room failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := svc.SaveRoomBackground(ctx, &roomv1.SaveRoomBackgroundRequest{
|
||||||
|
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID, ActorUserId: otherID},
|
||||||
|
RoomId: roomID,
|
||||||
|
ImageUrl: "https://cdn.example.com/other-bg.png",
|
||||||
|
}); !xerr.IsCode(err, xerr.PermissionDenied) {
|
||||||
|
t.Fatalf("non-owner save must be denied: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveResp, err := svc.SaveRoomBackground(ctx, &roomv1.SaveRoomBackgroundRequest{
|
||||||
|
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID, ActorUserId: ownerID},
|
||||||
|
RoomId: roomID,
|
||||||
|
ImageUrl: "https://cdn.example.com/owner-bg.png",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("owner save background failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := svc.SetRoomBackground(ctx, &roomv1.SetRoomBackgroundRequest{
|
||||||
|
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID, ActorUserId: otherID, CommandId: "cmd-room-bg-other"},
|
||||||
|
BackgroundId: saveResp.GetBackground().GetBackgroundId(),
|
||||||
|
}); !xerr.IsCode(err, xerr.PermissionDenied) {
|
||||||
|
t.Fatalf("non-owner set must be denied: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
roomv1 "hyapp.local/api/proto/room/v1"
|
||||||
|
"hyapp/pkg/appcode"
|
||||||
|
"hyapp/pkg/roomid"
|
||||||
|
"hyapp/pkg/xerr"
|
||||||
|
"hyapp/services/room-service/internal/room/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// defaultRoomBannedUsersPageSize 是房间黑名单面板默认页大小。
|
||||||
|
defaultRoomBannedUsersPageSize = 20
|
||||||
|
// maxRoomBannedUsersPageSize 限制单次查询量,避免管理面板一次拉完整治理状态。
|
||||||
|
maxRoomBannedUsersPageSize = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListRoomBannedUsers 分页读取房间当前仍有效的黑名单。
|
||||||
|
// 黑名单状态属于 Room Cell,用户头像、昵称、国家等展示资料由 gateway 批量查询 user-service。
|
||||||
|
func (s *Service) ListRoomBannedUsers(ctx context.Context, req *roomv1.ListRoomBannedUsersRequest) (*roomv1.ListRoomBannedUsersResponse, error) {
|
||||||
|
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||||||
|
roomID := req.GetRoomId()
|
||||||
|
viewerUserID := req.GetViewerUserId()
|
||||||
|
if !roomid.ValidStringID(roomID) {
|
||||||
|
return nil, xerr.New(xerr.InvalidArgument, "room_id is invalid")
|
||||||
|
}
|
||||||
|
if viewerUserID <= 0 {
|
||||||
|
return nil, xerr.New(xerr.InvalidArgument, "viewer_user_id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot, err := s.currentSnapshot(ctx, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if snapshot == nil || snapshot.GetRoomId() == "" {
|
||||||
|
return nil, xerr.New(xerr.NotFound, "room not found")
|
||||||
|
}
|
||||||
|
if snapshot.GetStatus() != state.RoomStatusActive {
|
||||||
|
return nil, xerr.New(xerr.RoomClosed, "room closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
current := state.FromProto(snapshot)
|
||||||
|
if err := requireManagerPresent(current, viewerUserID); err != nil {
|
||||||
|
// 黑名单是房间治理数据,只允许当前仍在房间内的 owner/admin 查看。
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nowMS := s.clock.Now().UnixMilli()
|
||||||
|
items := activeBannedUsersFromState(current, nowMS)
|
||||||
|
page, pageSize := normalizeRoomBannedUsersPage(req.GetPage(), req.GetPageSize())
|
||||||
|
total := int64(len(items))
|
||||||
|
start := (page - 1) * pageSize
|
||||||
|
if start >= len(items) {
|
||||||
|
items = nil
|
||||||
|
} else {
|
||||||
|
end := start + pageSize
|
||||||
|
if end > len(items) {
|
||||||
|
end = len(items)
|
||||||
|
}
|
||||||
|
items = items[start:end]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &roomv1.ListRoomBannedUsersResponse{
|
||||||
|
Items: items,
|
||||||
|
Total: total,
|
||||||
|
Page: int32(page),
|
||||||
|
PageSize: int32(pageSize),
|
||||||
|
ServerTimeMs: nowMS,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func activeBannedUsersFromState(current *state.RoomState, nowMS int64) []*roomv1.RoomBannedUser {
|
||||||
|
if current == nil || len(current.BanUsers) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
items := make([]*roomv1.RoomBannedUser, 0, len(current.BanUsers))
|
||||||
|
for _, ban := range current.BanUsers {
|
||||||
|
if !ban.ActiveAt(nowMS) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
remainingMS := int64(0)
|
||||||
|
if ban.ExpiresAtMS > 0 {
|
||||||
|
remainingMS = ban.ExpiresAtMS - nowMS
|
||||||
|
}
|
||||||
|
items = append(items, &roomv1.RoomBannedUser{
|
||||||
|
UserId: ban.UserID,
|
||||||
|
ActorUserId: ban.ActorUserID,
|
||||||
|
CreatedAtMs: ban.CreatedAtMS,
|
||||||
|
UnbanAtMs: ban.ExpiresAtMS,
|
||||||
|
RemainingMs: remainingMS,
|
||||||
|
Permanent: ban.ExpiresAtMS == 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
if items[i].GetCreatedAtMs() != items[j].GetCreatedAtMs() {
|
||||||
|
return items[i].GetCreatedAtMs() > items[j].GetCreatedAtMs()
|
||||||
|
}
|
||||||
|
return items[i].GetUserId() < items[j].GetUserId()
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeRoomBannedUsersPage(rawPage int32, rawPageSize int32) (int, int) {
|
||||||
|
page := int(rawPage)
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pageSize := int(rawPageSize)
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = defaultRoomBannedUsersPageSize
|
||||||
|
}
|
||||||
|
if pageSize > maxRoomBannedUsersPageSize {
|
||||||
|
pageSize = maxRoomBannedUsersPageSize
|
||||||
|
}
|
||||||
|
return page, pageSize
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ const (
|
|||||||
roomExtCoverURLKey = "cover_url"
|
roomExtCoverURLKey = "cover_url"
|
||||||
roomExtDescriptionKey = "description"
|
roomExtDescriptionKey = "description"
|
||||||
roomExtShortIDKey = "room_short_id"
|
roomExtShortIDKey = "room_short_id"
|
||||||
|
roomExtBackgroundKey = "background_url"
|
||||||
|
|
||||||
// defaultRoomAvatar 是房间头像缺省值,语义上代表系统默认资源,不绑定外部 CDN 地址。
|
// defaultRoomAvatar 是房间头像缺省值,语义上代表系统默认资源,不绑定外部 CDN 地址。
|
||||||
defaultRoomAvatar = "hyapp://room/default-avatar"
|
defaultRoomAvatar = "hyapp://room/default-avatar"
|
||||||
@ -33,6 +34,7 @@ const (
|
|||||||
maxRoomNameRunes = 128
|
maxRoomNameRunes = 128
|
||||||
maxRoomAvatarRunes = 512
|
maxRoomAvatarRunes = 512
|
||||||
maxRoomDescriptionRunes = 512
|
maxRoomDescriptionRunes = 512
|
||||||
|
maxRoomBackgroundRunes = 256
|
||||||
)
|
)
|
||||||
|
|
||||||
// roomProfileInput 是创建房间时需要落到 RoomExt 的展示资料集合。
|
// roomProfileInput 是创建房间时需要落到 RoomExt 的展示资料集合。
|
||||||
|
|||||||
@ -41,7 +41,7 @@ func (s *Service) GetCurrentRoom(ctx context.Context, req *roomv1.GetCurrentRoom
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !currentRoomSnapshotRestorable(snapshot, userID) {
|
if !currentRoomSnapshotRestorable(snapshot, userID, serverTimeMS) {
|
||||||
// 查询语义必须保持只读;读模型滞后由后续命令投影或专项补偿修复。
|
// 查询语义必须保持只读;读模型滞后由后续命令投影或专项补偿修复。
|
||||||
return emptyCurrentRoomResponse(serverTimeMS), nil
|
return emptyCurrentRoomResponse(serverTimeMS), nil
|
||||||
}
|
}
|
||||||
@ -81,12 +81,12 @@ func emptyCurrentRoomResponse(serverTimeMS int64) *roomv1.GetCurrentRoomResponse
|
|||||||
}
|
}
|
||||||
|
|
||||||
// currentRoomSnapshotRestorable 判定读模型指向的房间是否仍可被客户端恢复。
|
// currentRoomSnapshotRestorable 判定读模型指向的房间是否仍可被客户端恢复。
|
||||||
func currentRoomSnapshotRestorable(snapshot *roomv1.RoomSnapshot, userID int64) bool {
|
func currentRoomSnapshotRestorable(snapshot *roomv1.RoomSnapshot, userID int64, nowMS int64) bool {
|
||||||
if snapshot == nil || snapshot.GetRoomId() == "" || snapshot.GetStatus() != state.RoomStatusActive {
|
if snapshot == nil || snapshot.GetRoomId() == "" || snapshot.GetStatus() != state.RoomStatusActive {
|
||||||
// 非 active 房间不能恢复,即使用户级 presence 投影还没被清理。
|
// 非 active 房间不能恢复,即使用户级 presence 投影还没被清理。
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if containsUserID(snapshot.GetBanUserIds(), userID) {
|
if snapshotUserBanned(snapshot, userID, nowMS) {
|
||||||
// ban 优先于旧 presence,避免被踢用户通过本地恢复入口重新收消息。
|
// ban 优先于旧 presence,避免被踢用户通过本地恢复入口重新收消息。
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,8 +35,10 @@ func (s *Service) CheckSpeakPermission(ctx context.Context, req *roomv1.CheckSpe
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nowMS := s.clock.Now().UnixMilli()
|
||||||
|
banned := snapshotUserBanned(snapshot, req.GetUserId(), nowMS)
|
||||||
// 允许发言必须同时满足未 ban、未 mute、房间开启聊天、用户在业务 presence 内。
|
// 允许发言必须同时满足未 ban、未 mute、房间开启聊天、用户在业务 presence 内。
|
||||||
if !containsID(snapshot.GetBanUserIds(), req.GetUserId()) && !containsID(snapshot.GetMuteUserIds(), req.GetUserId()) && snapshot.GetChatEnabled() && findProtoUser(snapshot, req.GetUserId()) != nil {
|
if !banned && !containsID(snapshot.GetMuteUserIds(), req.GetUserId()) && snapshot.GetChatEnabled() && findProtoUser(snapshot, req.GetUserId()) != nil {
|
||||||
return &roomv1.CheckSpeakPermissionResponse{
|
return &roomv1.CheckSpeakPermissionResponse{
|
||||||
Allowed: true,
|
Allowed: true,
|
||||||
RoomVersion: snapshot.GetVersion(),
|
RoomVersion: snapshot.GetVersion(),
|
||||||
@ -45,7 +47,7 @@ func (s *Service) CheckSpeakPermission(ctx context.Context, req *roomv1.CheckSpe
|
|||||||
|
|
||||||
reason := "not_in_room"
|
reason := "not_in_room"
|
||||||
switch {
|
switch {
|
||||||
case containsID(snapshot.GetBanUserIds(), req.GetUserId()):
|
case banned:
|
||||||
// ban 优先级最高,被踢用户即使仍在腾讯云 IM 群内也不能发言。
|
// ban 优先级最高,被踢用户即使仍在腾讯云 IM 群内也不能发言。
|
||||||
reason = "user_banned"
|
reason = "user_banned"
|
||||||
case containsID(snapshot.GetMuteUserIds(), req.GetUserId()):
|
case containsID(snapshot.GetMuteUserIds(), req.GetUserId()):
|
||||||
@ -89,7 +91,7 @@ func (s *Service) VerifyRoomPresence(ctx context.Context, req *roomv1.VerifyRoom
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if containsID(snapshot.GetBanUserIds(), req.GetUserId()) {
|
if snapshotUserBanned(snapshot, req.GetUserId(), s.clock.Now().UnixMilli()) {
|
||||||
// ban 用户不能订阅,即使快照里还有旧 presence 也以 ban 为准。
|
// ban 用户不能订阅,即使快照里还有旧 presence 也以 ban 为准。
|
||||||
return &roomv1.VerifyRoomPresenceResponse{
|
return &roomv1.VerifyRoomPresenceResponse{
|
||||||
Present: false,
|
Present: false,
|
||||||
|
|||||||
@ -154,6 +154,21 @@ func containsID(values []int64, target int64) bool {
|
|||||||
return slices.Contains(values, target)
|
return slices.Contains(values, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func snapshotUserBanned(snapshot *roomv1.RoomSnapshot, userID int64, nowMS int64) bool {
|
||||||
|
if snapshot == nil || userID <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, ban := range snapshot.GetBanStates() {
|
||||||
|
if ban.GetUserId() != userID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// ban_states 是带时间边界的权威状态;过期后即使 ban_user_ids 还没刷新也不能继续拦截。
|
||||||
|
return ban.GetExpiresAtMs() == 0 || ban.GetExpiresAtMs() > nowMS
|
||||||
|
}
|
||||||
|
// 旧快照没有 ban_states 时只能按永久 ban 解释 ban_user_ids,避免恢复路径放宽权限。
|
||||||
|
return containsID(snapshot.GetBanUserIds(), userID)
|
||||||
|
}
|
||||||
|
|
||||||
func cloneProtoRank(items []state.RankItem) []*roomv1.RankItem {
|
func cloneProtoRank(items []state.RankItem) []*roomv1.RankItem {
|
||||||
// SendGift 响应需要 protobuf 榜单项,不能直接暴露内部 state.RankItem。
|
// SendGift 响应需要 protobuf 榜单项,不能直接暴露内部 state.RankItem。
|
||||||
result := make([]*roomv1.RankItem, 0, len(items))
|
result := make([]*roomv1.RankItem, 0, len(items))
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
||||||
roomv1 "hyapp.local/api/proto/room/v1"
|
roomv1 "hyapp.local/api/proto/room/v1"
|
||||||
"hyapp/pkg/appcode"
|
"hyapp/pkg/appcode"
|
||||||
|
"hyapp/pkg/xerr"
|
||||||
"hyapp/services/room-service/internal/integration"
|
"hyapp/services/room-service/internal/integration"
|
||||||
roomoutbox "hyapp/services/room-service/internal/room/outbox"
|
roomoutbox "hyapp/services/room-service/internal/room/outbox"
|
||||||
roomservice "hyapp/services/room-service/internal/room/service"
|
roomservice "hyapp/services/room-service/internal/room/service"
|
||||||
@ -100,6 +101,160 @@ func TestKickUserRemovesPresenceBanAndRTCConnection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKickUserDurationExpiresAndAllowsJoin(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
repository := mysqltest.NewRepository(t)
|
||||||
|
now := &fixedRoomTreasureClock{now: time.Date(2026, 5, 28, 8, 0, 0, 0, time.UTC)}
|
||||||
|
svc := roomservice.New(roomservice.Config{
|
||||||
|
NodeID: "node-kick-duration-test",
|
||||||
|
LeaseTTL: 10 * time.Second,
|
||||||
|
RankLimit: 20,
|
||||||
|
SnapshotEveryN: 1,
|
||||||
|
Clock: now,
|
||||||
|
}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
|
||||||
|
|
||||||
|
roomID := "room-kick-duration"
|
||||||
|
ownerID := int64(7301)
|
||||||
|
viewerID := int64(7401)
|
||||||
|
if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, ownerID),
|
||||||
|
SeatCount: 10,
|
||||||
|
Mode: "voice",
|
||||||
|
VisibleRegionId: 8101,
|
||||||
|
RoomName: "Kick Duration",
|
||||||
|
RoomShortId: "kick-duration",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("create duration room fixture failed: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, viewerID),
|
||||||
|
Role: "audience",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("join duration target failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kickResp, err := svc.KickUser(ctx, &roomv1.KickUserRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, ownerID),
|
||||||
|
TargetUserId: viewerID,
|
||||||
|
DurationMs: 1000,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("duration kick failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(kickResp.GetRoom().GetBanStates()) != 1 || kickResp.GetRoom().GetBanStates()[0].GetExpiresAtMs() != now.Now().Add(time.Second).UnixMilli() {
|
||||||
|
t.Fatalf("duration kick must persist ban expiration: %+v", kickResp.GetRoom().GetBanStates())
|
||||||
|
}
|
||||||
|
if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, viewerID),
|
||||||
|
Role: "audience",
|
||||||
|
}); err == nil {
|
||||||
|
t.Fatalf("target must not join before kick duration expires")
|
||||||
|
}
|
||||||
|
|
||||||
|
presenceResp, err := svc.VerifyRoomPresence(ctx, &roomv1.VerifyRoomPresenceRequest{
|
||||||
|
AppCode: appcode.Default,
|
||||||
|
RoomId: roomID,
|
||||||
|
UserId: viewerID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("verify presence before expiration failed: %v", err)
|
||||||
|
}
|
||||||
|
if presenceResp.GetPresent() || presenceResp.GetReason() != "user_banned" {
|
||||||
|
t.Fatalf("active duration ban must reject IM guard: %+v", presenceResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
now.now = now.now.Add(2 * time.Second)
|
||||||
|
if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, viewerID),
|
||||||
|
Role: "audience",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("target must join after kick duration expires: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListRoomBannedUsersReturnsActiveOnlyForManagers(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
repository := mysqltest.NewRepository(t)
|
||||||
|
now := &fixedRoomTreasureClock{now: time.Date(2026, 5, 29, 8, 0, 0, 0, time.UTC)}
|
||||||
|
svc := roomservice.New(roomservice.Config{
|
||||||
|
NodeID: "node-banned-users-test",
|
||||||
|
LeaseTTL: 10 * time.Second,
|
||||||
|
RankLimit: 20,
|
||||||
|
SnapshotEveryN: 1,
|
||||||
|
Clock: now,
|
||||||
|
}, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
|
||||||
|
|
||||||
|
roomID := "room-banned-users"
|
||||||
|
ownerID := int64(7501)
|
||||||
|
expiredID := int64(7502)
|
||||||
|
activeID := int64(7503)
|
||||||
|
normalID := int64(7504)
|
||||||
|
if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, ownerID),
|
||||||
|
SeatCount: 10,
|
||||||
|
Mode: "voice",
|
||||||
|
VisibleRegionId: 8101,
|
||||||
|
RoomName: "Banned Users",
|
||||||
|
RoomShortId: "banned-users",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("create banned users room failed: %v", err)
|
||||||
|
}
|
||||||
|
for _, userID := range []int64{expiredID, activeID, normalID} {
|
||||||
|
if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, userID),
|
||||||
|
Role: "audience",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("join fixture user %d failed: %v", userID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := svc.KickUser(ctx, &roomv1.KickUserRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, ownerID),
|
||||||
|
TargetUserId: expiredID,
|
||||||
|
DurationMs: 1000,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("kick expired fixture user failed: %v", err)
|
||||||
|
}
|
||||||
|
now.now = now.now.Add(2 * time.Second)
|
||||||
|
activeCreatedAt := now.Now().UnixMilli()
|
||||||
|
activeUnbanAt := now.Now().Add(10 * time.Second).UnixMilli()
|
||||||
|
if _, err := svc.KickUser(ctx, &roomv1.KickUserRequest{
|
||||||
|
Meta: roomservice.NewRequestMeta(roomID, ownerID),
|
||||||
|
TargetUserId: activeID,
|
||||||
|
DurationMs: 10_000,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("kick active fixture user failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := svc.ListRoomBannedUsers(ctx, &roomv1.ListRoomBannedUsersRequest{
|
||||||
|
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
|
||||||
|
RoomId: roomID,
|
||||||
|
ViewerUserId: normalID,
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 10,
|
||||||
|
}); !xerr.IsCode(err, xerr.PermissionDenied) {
|
||||||
|
t.Fatalf("non-manager list banned users must be denied: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := svc.ListRoomBannedUsers(ctx, &roomv1.ListRoomBannedUsersRequest{
|
||||||
|
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
|
||||||
|
RoomId: roomID,
|
||||||
|
ViewerUserId: ownerID,
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 10,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("owner list banned users failed: %v", err)
|
||||||
|
}
|
||||||
|
if resp.GetTotal() != 1 || len(resp.GetItems()) != 1 {
|
||||||
|
t.Fatalf("only active ban must be returned: %+v", resp)
|
||||||
|
}
|
||||||
|
item := resp.GetItems()[0]
|
||||||
|
if item.GetUserId() != activeID || item.GetActorUserId() != ownerID || item.GetCreatedAtMs() != activeCreatedAt || item.GetUnbanAtMs() != activeUnbanAt || item.GetRemainingMs() != 10_000 || item.GetPermanent() {
|
||||||
|
t.Fatalf("active ban item mismatch: %+v", item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCloseRoomByAdminEvictsCurrentUsersThroughOutboxAndRTC(t *testing.T) {
|
func TestCloseRoomByAdminEvictsCurrentUsersThroughOutboxAndRTC(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
repository := mysqltest.NewRepository(t)
|
repository := mysqltest.NewRepository(t)
|
||||||
|
|||||||
@ -94,6 +94,7 @@ func (s *Service) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*r
|
|||||||
cmd := command.KickUser{
|
cmd := command.KickUser{
|
||||||
Base: baseFromMeta(req.GetMeta()),
|
Base: baseFromMeta(req.GetMeta()),
|
||||||
TargetUserID: req.GetTargetUserId(),
|
TargetUserID: req.GetTargetUserId(),
|
||||||
|
DurationMS: req.GetDurationMs(),
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldKickRTC := false
|
shouldKickRTC := false
|
||||||
@ -107,6 +108,9 @@ func (s *Service) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*r
|
|||||||
if cmd.TargetUserID == cmd.ActorUserID() {
|
if cmd.TargetUserID == cmd.ActorUserID() {
|
||||||
return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "cannot kick self")
|
return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "cannot kick self")
|
||||||
}
|
}
|
||||||
|
if cmd.DurationMS < 0 {
|
||||||
|
return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "duration_ms must be non-negative")
|
||||||
|
}
|
||||||
if err := requireActorPresent(current, cmd.ActorUserID()); err != nil {
|
if err := requireActorPresent(current, cmd.ActorUserID()); err != nil {
|
||||||
return mutationResult{}, nil, err
|
return mutationResult{}, nil, err
|
||||||
}
|
}
|
||||||
@ -116,10 +120,6 @@ func (s *Service) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*r
|
|||||||
|
|
||||||
_, inRoom := current.OnlineUsers[cmd.TargetUserID]
|
_, inRoom := current.OnlineUsers[cmd.TargetUserID]
|
||||||
_, onSeat := current.SeatByUser(cmd.TargetUserID)
|
_, onSeat := current.SeatByUser(cmd.TargetUserID)
|
||||||
if current.BanUsers[cmd.TargetUserID] && !inRoom && !onSeat && !current.AdminUsers[cmd.TargetUserID] {
|
|
||||||
// 已处于 ban 且没有残留 presence、麦位或管理员身份时,重复 KickUser 不产生新事件。
|
|
||||||
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
|
||||||
}
|
|
||||||
// 只有目标确实还在业务房间或麦位上,才需要请求 TRTC 服务端移除实时音频连接。
|
// 只有目标确实还在业务房间或麦位上,才需要请求 TRTC 服务端移除实时音频连接。
|
||||||
shouldKickRTC = inRoom || onSeat
|
shouldKickRTC = inRoom || onSeat
|
||||||
|
|
||||||
@ -136,7 +136,20 @@ func (s *Service) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*r
|
|||||||
// ban 集合会让后续 JoinRoom 和 VerifyRoomPresence 都失败。
|
// ban 集合会让后续 JoinRoom 和 VerifyRoomPresence 都失败。
|
||||||
delete(current.OnlineUsers, cmd.TargetUserID)
|
delete(current.OnlineUsers, cmd.TargetUserID)
|
||||||
delete(current.AdminUsers, cmd.TargetUserID)
|
delete(current.AdminUsers, cmd.TargetUserID)
|
||||||
current.BanUsers[cmd.TargetUserID] = true
|
expiresAtMS := int64(0)
|
||||||
|
nowMS := now.UnixMilli()
|
||||||
|
if cmd.DurationMS > 0 {
|
||||||
|
expiresAtMS = nowMS + cmd.DurationMS
|
||||||
|
if expiresAtMS <= nowMS {
|
||||||
|
return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "duration_ms is too large")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current.BanUsers[cmd.TargetUserID] = state.UserModerationState{
|
||||||
|
UserID: cmd.TargetUserID,
|
||||||
|
ActorUserID: cmd.ActorUserID(),
|
||||||
|
CreatedAtMS: nowMS,
|
||||||
|
ExpiresAtMS: expiresAtMS,
|
||||||
|
}
|
||||||
current.Version++
|
current.Version++
|
||||||
|
|
||||||
kickEvent, err := outbox.Build(current.RoomID, "RoomUserKicked", current.Version, now, &roomeventsv1.RoomUserKicked{
|
kickEvent, err := outbox.Build(current.RoomID, "RoomUserKicked", current.Version, now, &roomeventsv1.RoomUserKicked{
|
||||||
@ -207,7 +220,7 @@ func (s *Service) UnbanUser(ctx context.Context, req *roomv1.UnbanUserRequest) (
|
|||||||
if err := requireOwnerPresent(current, cmd.ActorUserID()); err != nil {
|
if err := requireOwnerPresent(current, cmd.ActorUserID()); err != nil {
|
||||||
return mutationResult{}, nil, err
|
return mutationResult{}, nil, err
|
||||||
}
|
}
|
||||||
if !current.BanUsers[cmd.TargetUserID] {
|
if _, exists := current.BanUsers[cmd.TargetUserID]; !exists {
|
||||||
// 重复 unban 是 no-op,仍写 command log 确保 command_id 幂等可判定。
|
// 重复 unban 是 no-op,仍写 command log 确保 command_id 幂等可判定。
|
||||||
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,9 +38,13 @@ func (s *Service) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*r
|
|||||||
}
|
}
|
||||||
|
|
||||||
result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
|
result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
|
||||||
if current.BanUsers[cmd.ActorUserID()] {
|
if ban, exists := current.BanUsers[cmd.ActorUserID()]; exists {
|
||||||
// 被踢或封禁用户不能重新进入业务 presence。
|
if ban.ActiveAt(now.UnixMilli()) {
|
||||||
return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "user is banned")
|
// 被踢或封禁用户在 ban 未过期前不能重新进入业务 presence。
|
||||||
|
return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "user is banned")
|
||||||
|
}
|
||||||
|
// 限时 ban 过期后由下一次进房命令清理,避免定时任务介入 Room Cell owner 状态。
|
||||||
|
delete(current.BanUsers, cmd.ActorUserID())
|
||||||
}
|
}
|
||||||
|
|
||||||
if existing, exists := current.OnlineUsers[cmd.ActorUserID()]; exists {
|
if existing, exists := current.OnlineUsers[cmd.ActorUserID()]; exists {
|
||||||
@ -138,9 +142,13 @@ func (s *Service) RoomHeartbeat(ctx context.Context, req *roomv1.RoomHeartbeatRe
|
|||||||
if err := requireActiveRoom(current); err != nil {
|
if err := requireActiveRoom(current); err != nil {
|
||||||
return mutationResult{}, nil, err
|
return mutationResult{}, nil, err
|
||||||
}
|
}
|
||||||
if current.BanUsers[cmd.ActorUserID()] {
|
if ban, exists := current.BanUsers[cmd.ActorUserID()]; exists {
|
||||||
// ban 用户不能通过心跳续住业务 presence;KickUser 已经清理 presence,这里是防御脏状态。
|
if ban.ActiveAt(now.UnixMilli()) {
|
||||||
return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "user is banned")
|
// ban 用户不能通过心跳续住业务 presence;KickUser 已经清理 presence,这里是防御脏状态。
|
||||||
|
return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "user is banned")
|
||||||
|
}
|
||||||
|
// 过期 ban 不再阻断心跳;保守清理后继续按 presence 是否存在判断。
|
||||||
|
delete(current.BanUsers, cmd.ActorUserID())
|
||||||
}
|
}
|
||||||
|
|
||||||
existing, exists := current.OnlineUsers[cmd.ActorUserID()]
|
existing, exists := current.OnlineUsers[cmd.ActorUserID()]
|
||||||
|
|||||||
@ -52,11 +52,12 @@ func (s *Service) UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomP
|
|||||||
current.Version++
|
current.Version++
|
||||||
seatCount := int32(len(current.MicSeats))
|
seatCount := int32(len(current.MicSeats))
|
||||||
profileEvent, err := outbox.Build(current.RoomID, "RoomProfileUpdated", current.Version, now, &roomeventsv1.RoomProfileUpdated{
|
profileEvent, err := outbox.Build(current.RoomID, "RoomProfileUpdated", current.Version, now, &roomeventsv1.RoomProfileUpdated{
|
||||||
ActorUserId: cmd.ActorUserID(),
|
ActorUserId: cmd.ActorUserID(),
|
||||||
RoomName: current.RoomExt[roomExtTitleKey],
|
RoomName: current.RoomExt[roomExtTitleKey],
|
||||||
RoomAvatar: current.RoomExt[roomExtCoverURLKey],
|
RoomAvatar: current.RoomExt[roomExtCoverURLKey],
|
||||||
RoomDescription: current.RoomExt[roomExtDescriptionKey],
|
RoomDescription: current.RoomExt[roomExtDescriptionKey],
|
||||||
SeatCount: seatCount,
|
SeatCount: seatCount,
|
||||||
|
RoomBackgroundUrl: current.RoomExt[roomExtBackgroundKey],
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mutationResult{}, nil, err
|
return mutationResult{}, nil, err
|
||||||
@ -71,10 +72,11 @@ func (s *Service) UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomP
|
|||||||
ActorUserID: cmd.ActorUserID(),
|
ActorUserID: cmd.ActorUserID(),
|
||||||
RoomVersion: current.Version,
|
RoomVersion: current.Version,
|
||||||
Attributes: map[string]string{
|
Attributes: map[string]string{
|
||||||
"room_name": current.RoomExt[roomExtTitleKey],
|
"room_name": current.RoomExt[roomExtTitleKey],
|
||||||
"room_avatar": current.RoomExt[roomExtCoverURLKey],
|
"room_avatar": current.RoomExt[roomExtCoverURLKey],
|
||||||
"room_description": current.RoomExt[roomExtDescriptionKey],
|
"room_description": current.RoomExt[roomExtDescriptionKey],
|
||||||
"seat_count": int32String(seatCount),
|
"seat_count": int32String(seatCount),
|
||||||
|
"room_background_url": current.RoomExt[roomExtBackgroundKey],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,7 +108,7 @@ func (s *Service) recoverRoom(ctx context.Context, roomMeta RoomMeta) (*state.Ro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := replay(recovered, cmd); err != nil {
|
if err := replay(recovered, cmd, record.CreatedAtMS); err != nil {
|
||||||
// replay 失败说明命令日志无法被当前状态机解释,不能继续装载错误 Cell。
|
// replay 失败说明命令日志无法被当前状态机解释,不能继续装载错误 Cell。
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ func (s *Service) recoverRoom(ctx context.Context, roomMeta RoomMeta) (*state.Ro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// replay 只重放房间内确定性状态变更,不重复执行钱包扣费或腾讯云 IM 投递。
|
// replay 只重放房间内确定性状态变更,不重复执行钱包扣费或腾讯云 IM 投递。
|
||||||
func replay(current *state.RoomState, cmd command.Command) error {
|
func replay(current *state.RoomState, cmd command.Command, committedAtMS int64) error {
|
||||||
// replay 只重建房间内状态,不重复写 outbox、不重复调用 wallet、不重复同步腾讯云 IM。
|
// replay 只重建房间内状态,不重复写 outbox、不重复调用 wallet、不重复同步腾讯云 IM。
|
||||||
switch typed := cmd.(type) {
|
switch typed := cmd.(type) {
|
||||||
case *command.CreateRoom:
|
case *command.CreateRoom:
|
||||||
@ -139,6 +139,12 @@ func replay(current *state.RoomState, cmd command.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
current.Version++
|
current.Version++
|
||||||
|
case *command.SetRoomBackground:
|
||||||
|
if current.RoomExt == nil {
|
||||||
|
current.RoomExt = make(map[string]string)
|
||||||
|
}
|
||||||
|
current.RoomExt[roomExtBackgroundKey] = typed.BackgroundURL
|
||||||
|
current.Version++
|
||||||
case *command.JoinRoom:
|
case *command.JoinRoom:
|
||||||
if existing, exists := current.OnlineUsers[typed.ActorUserID()]; exists {
|
if existing, exists := current.OnlineUsers[typed.ActorUserID()]; exists {
|
||||||
// 重复 JoinRoom 回放只刷新 last_seen,不能重置首次进房时间或降级 owner 角色。
|
// 重复 JoinRoom 回放只刷新 last_seen,不能重置首次进房时间或降级 owner 角色。
|
||||||
@ -334,7 +340,20 @@ func replay(current *state.RoomState, cmd command.Command) error {
|
|||||||
}
|
}
|
||||||
delete(current.OnlineUsers, typed.TargetUserID)
|
delete(current.OnlineUsers, typed.TargetUserID)
|
||||||
delete(current.AdminUsers, typed.TargetUserID)
|
delete(current.AdminUsers, typed.TargetUserID)
|
||||||
current.BanUsers[typed.TargetUserID] = true
|
createdAtMS := committedAtMS
|
||||||
|
if createdAtMS <= 0 {
|
||||||
|
createdAtMS = typed.SentAtMS
|
||||||
|
}
|
||||||
|
expiresAtMS := int64(0)
|
||||||
|
if typed.DurationMS > 0 && createdAtMS > 0 {
|
||||||
|
expiresAtMS = createdAtMS + typed.DurationMS
|
||||||
|
}
|
||||||
|
current.BanUsers[typed.TargetUserID] = state.UserModerationState{
|
||||||
|
UserID: typed.TargetUserID,
|
||||||
|
ActorUserID: typed.ActorUserID(),
|
||||||
|
CreatedAtMS: createdAtMS,
|
||||||
|
ExpiresAtMS: expiresAtMS,
|
||||||
|
}
|
||||||
current.Version++
|
current.Version++
|
||||||
case *command.UnbanUser:
|
case *command.UnbanUser:
|
||||||
// Unban 只解除 ban,不恢复被 KickUser 删除的 presence、麦位或管理员身份。
|
// Unban 只解除 ban,不恢复被 KickUser 删除的 presence、麦位或管理员身份。
|
||||||
|
|||||||
@ -92,6 +92,18 @@ type RoomCloseInfo struct {
|
|||||||
ClearOnOpen bool
|
ClearOnOpen bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoomBackgroundImage 是房间 owner 保存过的背景图素材。
|
||||||
|
// 当前生效背景仍属于 Room Cell 快照;该表只保存可选素材列表。
|
||||||
|
type RoomBackgroundImage struct {
|
||||||
|
AppCode string
|
||||||
|
BackgroundID int64
|
||||||
|
RoomID string
|
||||||
|
ImageURL string
|
||||||
|
CreatedByUserID int64
|
||||||
|
CreatedAtMS int64
|
||||||
|
UpdatedAtMS int64
|
||||||
|
}
|
||||||
|
|
||||||
// SnapshotRecord 对应 room_snapshots 中的一条最新快照。
|
// SnapshotRecord 对应 room_snapshots 中的一条最新快照。
|
||||||
type SnapshotRecord struct {
|
type SnapshotRecord struct {
|
||||||
// AppCode 是快照所属 App,恢复时必须和房间 meta 保持一致。
|
// AppCode 是快照所属 App,恢复时必须和房间 meta 保持一致。
|
||||||
@ -480,6 +492,12 @@ type Repository interface {
|
|||||||
GetRoomMeta(ctx context.Context, roomID string) (RoomMeta, bool, error)
|
GetRoomMeta(ctx context.Context, roomID string) (RoomMeta, bool, error)
|
||||||
// GetRoomMetaByOwner 查询 owner 已创建的房间;产品规则要求一个用户只能创建一个房间。
|
// GetRoomMetaByOwner 查询 owner 已创建的房间;产品规则要求一个用户只能创建一个房间。
|
||||||
GetRoomMetaByOwner(ctx context.Context, ownerUserID int64) (RoomMeta, bool, error)
|
GetRoomMetaByOwner(ctx context.Context, ownerUserID int64) (RoomMeta, bool, error)
|
||||||
|
// SaveRoomBackground 保存房间 owner 上传过的背景图 URL;相同房间相同 URL 幂等返回同一条素材。
|
||||||
|
SaveRoomBackground(ctx context.Context, background RoomBackgroundImage) (RoomBackgroundImage, error)
|
||||||
|
// GetRoomBackground 精确读取一个背景图素材,用于 SetRoomBackground 校验归属。
|
||||||
|
GetRoomBackground(ctx context.Context, roomID string, backgroundID int64) (RoomBackgroundImage, bool, error)
|
||||||
|
// ListRoomBackgrounds 按房间读取背景图素材列表,不读取 Room Cell 状态。
|
||||||
|
ListRoomBackgrounds(ctx context.Context, roomID string) ([]RoomBackgroundImage, error)
|
||||||
// GetCommand 读取已提交命令,用于幂等重试和 command_id 冲突判定。
|
// GetCommand 读取已提交命令,用于幂等重试和 command_id 冲突判定。
|
||||||
GetCommand(ctx context.Context, roomID string, commandID string) (CommandRecord, bool, error)
|
GetCommand(ctx context.Context, roomID string, commandID string) (CommandRecord, bool, error)
|
||||||
// SaveCommand 追加成功命令日志,关键命令恢复必须依赖它。
|
// SaveCommand 追加成功命令日志,关键命令恢复必须依赖它。
|
||||||
|
|||||||
@ -71,7 +71,7 @@ func (s *Service) GetRoomTreasure(ctx context.Context, req *roomv1.GetRoomTreasu
|
|||||||
if snapshot.GetStatus() != state.RoomStatusActive {
|
if snapshot.GetStatus() != state.RoomStatusActive {
|
||||||
return nil, xerr.New(xerr.RoomClosed, "room closed")
|
return nil, xerr.New(xerr.RoomClosed, "room closed")
|
||||||
}
|
}
|
||||||
if containsUserID(snapshot.GetBanUserIds(), viewerUserID) || findProtoUser(snapshot, viewerUserID) == nil {
|
if snapshotUserBanned(snapshot, viewerUserID, s.clock.Now().UnixMilli()) || findProtoUser(snapshot, viewerUserID) == nil {
|
||||||
// 宝箱状态属于房间内活动信息,只允许仍在业务 presence 内的用户读取。
|
// 宝箱状态属于房间内活动信息,只允许仍在业务 presence 内的用户读取。
|
||||||
return nil, xerr.New(xerr.PermissionDenied, "viewer is not in room")
|
return nil, xerr.New(xerr.PermissionDenied, "viewer is not in room")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ func (s *Service) GetRoomSnapshot(ctx context.Context, req *roomv1.GetRoomSnapsh
|
|||||||
if snapshot.GetStatus() != state.RoomStatusActive {
|
if snapshot.GetStatus() != state.RoomStatusActive {
|
||||||
return nil, xerr.New(xerr.RoomClosed, "room closed")
|
return nil, xerr.New(xerr.RoomClosed, "room closed")
|
||||||
}
|
}
|
||||||
if containsUserID(snapshot.GetBanUserIds(), viewerUserID) || findProtoUser(snapshot, viewerUserID) == nil {
|
if snapshotUserBanned(snapshot, viewerUserID, s.clock.Now().UnixMilli()) || findProtoUser(snapshot, viewerUserID) == nil {
|
||||||
// 完整快照包含在线用户、麦位和房管集合,必须要求 viewer 仍在业务 presence 内。
|
// 完整快照包含在线用户、麦位和房管集合,必须要求 viewer 仍在业务 presence 内。
|
||||||
return nil, xerr.New(xerr.PermissionDenied, "viewer is not in room")
|
return nil, xerr.New(xerr.PermissionDenied, "viewer is not in room")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,7 +67,7 @@ func (s *Service) SystemEvictUser(ctx context.Context, req *roomv1.SystemEvictUs
|
|||||||
|
|
||||||
_, inRoom := current.OnlineUsers[cmd.TargetUserID]
|
_, inRoom := current.OnlineUsers[cmd.TargetUserID]
|
||||||
_, onSeat := current.SeatByUser(cmd.TargetUserID)
|
_, onSeat := current.SeatByUser(cmd.TargetUserID)
|
||||||
alreadyBanned := current.BanUsers[cmd.TargetUserID]
|
_, alreadyBanned := current.BanUsers[cmd.TargetUserID]
|
||||||
if !inRoom && !onSeat && !current.AdminUsers[cmd.TargetUserID] && (!cmd.BanFromRoom || alreadyBanned) {
|
if !inRoom && !onSeat && !current.AdminUsers[cmd.TargetUserID] && (!cmd.BanFromRoom || alreadyBanned) {
|
||||||
// 重复系统驱逐保持幂等;没有残留状态时只返回当前快照。
|
// 重复系统驱逐保持幂等;没有残留状态时只返回当前快照。
|
||||||
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
||||||
@ -89,7 +89,11 @@ func (s *Service) SystemEvictUser(ctx context.Context, req *roomv1.SystemEvictUs
|
|||||||
delete(current.AdminUsers, cmd.TargetUserID)
|
delete(current.AdminUsers, cmd.TargetUserID)
|
||||||
if cmd.BanFromRoom {
|
if cmd.BanFromRoom {
|
||||||
// BanFromRoom 由治理入口决定;只驱逐不封房间时,用户后续可按产品策略重新进房。
|
// BanFromRoom 由治理入口决定;只驱逐不封房间时,用户后续可按产品策略重新进房。
|
||||||
current.BanUsers[cmd.TargetUserID] = true
|
current.BanUsers[cmd.TargetUserID] = state.UserModerationState{
|
||||||
|
UserID: cmd.TargetUserID,
|
||||||
|
ActorUserID: 0,
|
||||||
|
CreatedAtMS: now.UnixMilli(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
current.Version++
|
current.Version++
|
||||||
|
|
||||||
|
|||||||
@ -98,6 +98,23 @@ type RoomUserState struct {
|
|||||||
LastSeenAtMS int64
|
LastSeenAtMS int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserModerationState 保存房间内 ban/mute 这类治理状态的时间边界。
|
||||||
|
type UserModerationState struct {
|
||||||
|
// UserID 是被治理用户。
|
||||||
|
UserID int64
|
||||||
|
// ActorUserID 是触发治理命令的房间内操作者;系统治理入口使用 0。
|
||||||
|
ActorUserID int64
|
||||||
|
// CreatedAtMS 是治理状态写入 Room Cell 的 UTC epoch milliseconds。
|
||||||
|
CreatedAtMS int64
|
||||||
|
// ExpiresAtMS 为 0 表示永久有效;非 0 表示该 UTC 毫秒后自动失效。
|
||||||
|
ExpiresAtMS int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActiveAt 判断治理状态在指定 UTC 毫秒是否仍然生效。
|
||||||
|
func (m UserModerationState) ActiveAt(nowMS int64) bool {
|
||||||
|
return m.UserID > 0 && (m.ExpiresAtMS == 0 || m.ExpiresAtMS > nowMS)
|
||||||
|
}
|
||||||
|
|
||||||
// RankItem 表达房间内本地礼物榜的单个用户累计值。
|
// RankItem 表达房间内本地礼物榜的单个用户累计值。
|
||||||
type RankItem struct {
|
type RankItem struct {
|
||||||
// UserID 是榜单归属用户。
|
// UserID 是榜单归属用户。
|
||||||
@ -189,8 +206,8 @@ type RoomState struct {
|
|||||||
OnlineUsers map[int64]*RoomUserState
|
OnlineUsers map[int64]*RoomUserState
|
||||||
// AdminUsers 是房间管理权限集合。
|
// AdminUsers 是房间管理权限集合。
|
||||||
AdminUsers map[int64]bool
|
AdminUsers map[int64]bool
|
||||||
// BanUsers 是被踢或封禁的房间用户集合。
|
// BanUsers 是被踢或封禁的房间用户状态,过期时间由 Room Cell 持久化和恢复。
|
||||||
BanUsers map[int64]bool
|
BanUsers map[int64]UserModerationState
|
||||||
// MuteUsers 是被禁言用户集合。
|
// MuteUsers 是被禁言用户集合。
|
||||||
MuteUsers map[int64]bool
|
MuteUsers map[int64]bool
|
||||||
// Heat 是房间热度,当前主要由送礼累计。
|
// Heat 是房间热度,当前主要由送礼累计。
|
||||||
@ -223,7 +240,7 @@ func NewRoomState(roomID string, ownerUserID int64, seatCount int32, mode string
|
|||||||
MicSeats: seats,
|
MicSeats: seats,
|
||||||
OnlineUsers: make(map[int64]*RoomUserState),
|
OnlineUsers: make(map[int64]*RoomUserState),
|
||||||
AdminUsers: make(map[int64]bool),
|
AdminUsers: make(map[int64]bool),
|
||||||
BanUsers: make(map[int64]bool),
|
BanUsers: make(map[int64]UserModerationState),
|
||||||
MuteUsers: make(map[int64]bool),
|
MuteUsers: make(map[int64]bool),
|
||||||
RoomExt: make(map[string]string),
|
RoomExt: make(map[string]string),
|
||||||
}
|
}
|
||||||
@ -248,7 +265,7 @@ func (s *RoomState) Clone() *RoomState {
|
|||||||
MicSeats: append([]MicSeat(nil), s.MicSeats...),
|
MicSeats: append([]MicSeat(nil), s.MicSeats...),
|
||||||
OnlineUsers: make(map[int64]*RoomUserState, len(s.OnlineUsers)),
|
OnlineUsers: make(map[int64]*RoomUserState, len(s.OnlineUsers)),
|
||||||
AdminUsers: make(map[int64]bool, len(s.AdminUsers)),
|
AdminUsers: make(map[int64]bool, len(s.AdminUsers)),
|
||||||
BanUsers: make(map[int64]bool, len(s.BanUsers)),
|
BanUsers: make(map[int64]UserModerationState, len(s.BanUsers)),
|
||||||
MuteUsers: make(map[int64]bool, len(s.MuteUsers)),
|
MuteUsers: make(map[int64]bool, len(s.MuteUsers)),
|
||||||
Heat: s.Heat,
|
Heat: s.Heat,
|
||||||
GiftRank: append([]RankItem(nil), s.GiftRank...),
|
GiftRank: append([]RankItem(nil), s.GiftRank...),
|
||||||
@ -387,7 +404,7 @@ func (s *RoomState) ToProto() *roomv1.RoomSnapshot {
|
|||||||
MicSeats: seats,
|
MicSeats: seats,
|
||||||
OnlineUsers: users,
|
OnlineUsers: users,
|
||||||
AdminUserIds: sortedSetIDsExcept(s.AdminUsers, s.OwnerUserID),
|
AdminUserIds: sortedSetIDsExcept(s.AdminUsers, s.OwnerUserID),
|
||||||
BanUserIds: sortedSetIDs(s.BanUsers),
|
BanUserIds: sortedModerationIDs(s.BanUsers),
|
||||||
MuteUserIds: sortedSetIDs(s.MuteUsers),
|
MuteUserIds: sortedSetIDs(s.MuteUsers),
|
||||||
GiftRank: rankItems,
|
GiftRank: rankItems,
|
||||||
RoomExt: cloneStringMap(s.RoomExt),
|
RoomExt: cloneStringMap(s.RoomExt),
|
||||||
@ -396,6 +413,7 @@ func (s *RoomState) ToProto() *roomv1.RoomSnapshot {
|
|||||||
RoomShortId: s.RoomExt["room_short_id"],
|
RoomShortId: s.RoomExt["room_short_id"],
|
||||||
Locked: s.RoomPasswordHash != "",
|
Locked: s.RoomPasswordHash != "",
|
||||||
Treasure: treasureStateToProto(s.Treasure),
|
Treasure: treasureStateToProto(s.Treasure),
|
||||||
|
BanStates: sortedModerationStates(s.BanUsers),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +435,7 @@ func FromProto(snapshot *roomv1.RoomSnapshot) *RoomState {
|
|||||||
MicSeats: make([]MicSeat, 0, len(snapshot.GetMicSeats())),
|
MicSeats: make([]MicSeat, 0, len(snapshot.GetMicSeats())),
|
||||||
OnlineUsers: make(map[int64]*RoomUserState, len(snapshot.GetOnlineUsers())),
|
OnlineUsers: make(map[int64]*RoomUserState, len(snapshot.GetOnlineUsers())),
|
||||||
AdminUsers: make(map[int64]bool, len(snapshot.GetAdminUserIds())),
|
AdminUsers: make(map[int64]bool, len(snapshot.GetAdminUserIds())),
|
||||||
BanUsers: make(map[int64]bool, len(snapshot.GetBanUserIds())),
|
BanUsers: make(map[int64]UserModerationState, len(snapshot.GetBanStates())+len(snapshot.GetBanUserIds())),
|
||||||
MuteUsers: make(map[int64]bool, len(snapshot.GetMuteUserIds())),
|
MuteUsers: make(map[int64]bool, len(snapshot.GetMuteUserIds())),
|
||||||
GiftRank: make([]RankItem, 0, len(snapshot.GetGiftRank())),
|
GiftRank: make([]RankItem, 0, len(snapshot.GetGiftRank())),
|
||||||
RoomExt: cloneStringMap(snapshot.GetRoomExt()),
|
RoomExt: cloneStringMap(snapshot.GetRoomExt()),
|
||||||
@ -460,8 +478,26 @@ func FromProto(snapshot *roomv1.RoomSnapshot) *RoomState {
|
|||||||
restored.AdminUsers[userID] = true
|
restored.AdminUsers[userID] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ban := range snapshot.GetBanStates() {
|
||||||
|
if ban.GetUserId() <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
restored.BanUsers[ban.GetUserId()] = UserModerationState{
|
||||||
|
UserID: ban.GetUserId(),
|
||||||
|
ActorUserID: ban.GetActorUserId(),
|
||||||
|
CreatedAtMS: ban.GetCreatedAtMs(),
|
||||||
|
ExpiresAtMS: ban.GetExpiresAtMs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, userID := range snapshot.GetBanUserIds() {
|
for _, userID := range snapshot.GetBanUserIds() {
|
||||||
restored.BanUsers[userID] = true
|
if userID <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := restored.BanUsers[userID]; !exists {
|
||||||
|
// 旧快照只有 ban_user_ids 时按永久 ban 恢复,避免恢复后放宽权限。
|
||||||
|
restored.BanUsers[userID] = UserModerationState{UserID: userID}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, userID := range snapshot.GetMuteUserIds() {
|
for _, userID := range snapshot.GetMuteUserIds() {
|
||||||
@ -495,6 +531,32 @@ func sortedSetIDs(values map[int64]bool) []int64 {
|
|||||||
return ids
|
return ids
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortedModerationIDs(values map[int64]UserModerationState) []int64 {
|
||||||
|
ids := make([]int64, 0, len(values))
|
||||||
|
for userID, moderation := range values {
|
||||||
|
if moderation.UserID > 0 {
|
||||||
|
ids = append(ids, userID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(ids)
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedModerationStates(values map[int64]UserModerationState) []*roomv1.RoomBanState {
|
||||||
|
ids := sortedModerationIDs(values)
|
||||||
|
states := make([]*roomv1.RoomBanState, 0, len(ids))
|
||||||
|
for _, userID := range ids {
|
||||||
|
moderation := values[userID]
|
||||||
|
states = append(states, &roomv1.RoomBanState{
|
||||||
|
UserId: moderation.UserID,
|
||||||
|
ActorUserId: moderation.ActorUserID,
|
||||||
|
CreatedAtMs: moderation.CreatedAtMS,
|
||||||
|
ExpiresAtMs: moderation.ExpiresAtMS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return states
|
||||||
|
}
|
||||||
|
|
||||||
func sortedSetIDsExcept(values map[int64]bool, excluded int64) []int64 {
|
func sortedSetIDsExcept(values map[int64]bool, excluded int64) []int64 {
|
||||||
ids := make([]int64, 0, len(values))
|
ids := make([]int64, 0, len(values))
|
||||||
for userID, enabled := range values {
|
for userID, enabled := range values {
|
||||||
|
|||||||
@ -147,6 +147,18 @@ func (r *Repository) Migrate(ctx context.Context) error {
|
|||||||
PRIMARY KEY (app_code, room_id, user_id),
|
PRIMARY KEY (app_code, room_id, user_id),
|
||||||
KEY idx_room_user_gift_stats_room_value (app_code, room_id, gift_value DESC, last_gift_at_ms DESC, user_id)
|
KEY idx_room_user_gift_stats_room_value (app_code, room_id, gift_value DESC, last_gift_at_ms DESC, user_id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS room_background_images (
|
||||||
|
app_code VARCHAR(32) NOT NULL,
|
||||||
|
background_id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
room_id VARCHAR(64) NOT NULL,
|
||||||
|
image_url VARCHAR(256) NOT NULL,
|
||||||
|
created_by_user_id BIGINT NOT NULL,
|
||||||
|
created_at_ms BIGINT NOT NULL,
|
||||||
|
updated_at_ms BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (background_id),
|
||||||
|
UNIQUE KEY uk_room_background_url (app_code, room_id, image_url),
|
||||||
|
KEY idx_room_background_list (app_code, room_id, created_at_ms DESC, background_id DESC)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
||||||
`CREATE TABLE IF NOT EXISTS room_snapshots (
|
`CREATE TABLE IF NOT EXISTS room_snapshots (
|
||||||
app_code VARCHAR(32) NOT NULL,
|
app_code VARCHAR(32) NOT NULL,
|
||||||
room_id VARCHAR(64) NOT NULL,
|
room_id VARCHAR(64) NOT NULL,
|
||||||
@ -517,6 +529,92 @@ func (r *Repository) GetRoomMetaByOwner(ctx context.Context, ownerUserID int64)
|
|||||||
return meta, true, nil
|
return meta, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveRoomBackground 保存房间背景图素材;相同房间相同 URL 按幂等返回原记录。
|
||||||
|
func (r *Repository) SaveRoomBackground(ctx context.Context, background roomservice.RoomBackgroundImage) (roomservice.RoomBackgroundImage, error) {
|
||||||
|
appCode := normalizedRecordAppCode(ctx, background.AppCode)
|
||||||
|
roomID := strings.TrimSpace(background.RoomID)
|
||||||
|
imageURL := strings.TrimSpace(background.ImageURL)
|
||||||
|
nowMS := background.CreatedAtMS
|
||||||
|
if nowMS <= 0 {
|
||||||
|
nowMS = time.Now().UTC().UnixMilli()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := r.db.ExecContext(ctx, `
|
||||||
|
INSERT INTO room_background_images (
|
||||||
|
app_code, room_id, image_url, created_by_user_id, created_at_ms, updated_at_ms
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE updated_at_ms = VALUES(updated_at_ms)
|
||||||
|
`, appCode, roomID, imageURL, background.CreatedByUserID, nowMS, nowMS)
|
||||||
|
if err != nil {
|
||||||
|
return roomservice.RoomBackgroundImage{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
row := r.db.QueryRowContext(ctx, `
|
||||||
|
SELECT app_code, background_id, room_id, image_url, created_by_user_id, created_at_ms, updated_at_ms
|
||||||
|
FROM room_background_images
|
||||||
|
WHERE app_code = ? AND room_id = ? AND image_url = ?
|
||||||
|
LIMIT 1
|
||||||
|
`, appCode, roomID, imageURL)
|
||||||
|
return scanRoomBackground(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomBackground 精确读取一个房间背景图素材。
|
||||||
|
func (r *Repository) GetRoomBackground(ctx context.Context, roomID string, backgroundID int64) (roomservice.RoomBackgroundImage, bool, error) {
|
||||||
|
row := r.db.QueryRowContext(ctx, `
|
||||||
|
SELECT app_code, background_id, room_id, image_url, created_by_user_id, created_at_ms, updated_at_ms
|
||||||
|
FROM room_background_images
|
||||||
|
WHERE app_code = ? AND room_id = ? AND background_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
`, appcode.FromContext(ctx), strings.TrimSpace(roomID), backgroundID)
|
||||||
|
|
||||||
|
background, err := scanRoomBackground(row)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return roomservice.RoomBackgroundImage{}, false, nil
|
||||||
|
}
|
||||||
|
return roomservice.RoomBackgroundImage{}, false, err
|
||||||
|
}
|
||||||
|
return background, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRoomBackgrounds 返回某个房间保存过的背景图素材,按最近保存优先。
|
||||||
|
func (r *Repository) ListRoomBackgrounds(ctx context.Context, roomID string) ([]roomservice.RoomBackgroundImage, error) {
|
||||||
|
rows, err := r.db.QueryContext(ctx, `
|
||||||
|
SELECT app_code, background_id, room_id, image_url, created_by_user_id, created_at_ms, updated_at_ms
|
||||||
|
FROM room_background_images
|
||||||
|
WHERE app_code = ? AND room_id = ?
|
||||||
|
ORDER BY created_at_ms DESC, background_id DESC
|
||||||
|
`, appcode.FromContext(ctx), strings.TrimSpace(roomID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
backgrounds := []roomservice.RoomBackgroundImage{}
|
||||||
|
for rows.Next() {
|
||||||
|
background, err := scanRoomBackground(rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
backgrounds = append(backgrounds, background)
|
||||||
|
}
|
||||||
|
return backgrounds, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanRoomBackground(scanner interface{ Scan(dest ...any) error }) (roomservice.RoomBackgroundImage, error) {
|
||||||
|
var background roomservice.RoomBackgroundImage
|
||||||
|
err := scanner.Scan(
|
||||||
|
&background.AppCode,
|
||||||
|
&background.BackgroundID,
|
||||||
|
&background.RoomID,
|
||||||
|
&background.ImageURL,
|
||||||
|
&background.CreatedByUserID,
|
||||||
|
&background.CreatedAtMS,
|
||||||
|
&background.UpdatedAtMS,
|
||||||
|
)
|
||||||
|
return background, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetCommand 读取已提交命令,用于幂等重试和 command_id 冲突判定。
|
// GetCommand 读取已提交命令,用于幂等重试和 command_id 冲突判定。
|
||||||
func (r *Repository) GetCommand(ctx context.Context, roomID string, commandID string) (roomservice.CommandRecord, bool, error) {
|
func (r *Repository) GetCommand(ctx context.Context, roomID string, commandID string) (roomservice.CommandRecord, bool, error) {
|
||||||
// 幂等检查只看 command log,只有成功提交过的命令才算 seen。
|
// 幂等检查只看 command log,只有成功提交过的命令才算 seen。
|
||||||
|
|||||||
@ -84,6 +84,21 @@ func (s *Server) UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomPr
|
|||||||
return mapServiceResult(s.svc.UpdateRoomProfile(ctx, req))
|
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 代理到领域服务。
|
// JoinRoom 代理到领域服务。
|
||||||
func (s *Server) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error) {
|
func (s *Server) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error) {
|
||||||
// JoinRoom 只维护 room-service presence,真实消息连接由腾讯云 IM SDK 处理。
|
// JoinRoom 只维护 room-service presence,真实消息连接由腾讯云 IM SDK 处理。
|
||||||
@ -463,6 +478,16 @@ func (s *Server) GetRoomSnapshot(ctx context.Context, req *roomv1.GetRoomSnapsho
|
|||||||
return mapServiceResult(s.svc.GetRoomSnapshot(ctx, req))
|
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 代理到房间宝箱只读查询。
|
// GetRoomTreasure 代理到房间宝箱只读查询。
|
||||||
func (s *Server) GetRoomTreasure(ctx context.Context, req *roomv1.GetRoomTreasureRequest) (*roomv1.GetRoomTreasureResponse, error) {
|
func (s *Server) GetRoomTreasure(ctx context.Context, req *roomv1.GetRoomTreasureRequest) (*roomv1.GetRoomTreasureResponse, error) {
|
||||||
// 宝箱查询不刷新 presence;当前进度仍以 Room Cell 快照和 UTC 日边界为准。
|
// 宝箱查询不刷新 presence;当前进度仍以 Room Cell 快照和 UTC 日边界为准。
|
||||||
@ -482,6 +507,18 @@ func (s *Server) ListRoomOnlineUsers(ctx context.Context, req *roomv1.ListRoomOn
|
|||||||
return mapServiceResult(s.svc.ListRoomOnlineUsers(ctx, req))
|
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 {
|
func contextWithMetaApp(ctx context.Context, meta *roomv1.RequestMeta) context.Context {
|
||||||
if meta == nil {
|
if meta == nil {
|
||||||
return appcode.WithContext(ctx, "")
|
return appcode.WithContext(ctx, "")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user