完善房间
This commit is contained in:
parent
8fcf491b74
commit
d1fbeb54a8
@ -210,6 +210,84 @@ func (x *RoomCreated) GetRoomDescription() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// RoomProfileUpdated 表达房间展示资料或麦位数量变更。
|
||||
type RoomProfileUpdated struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
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"`
|
||||
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"`
|
||||
SeatCount int32 `protobuf:"varint,5,opt,name=seat_count,json=seatCount,proto3" json:"seat_count,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RoomProfileUpdated) Reset() {
|
||||
*x = RoomProfileUpdated{}
|
||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RoomProfileUpdated) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RoomProfileUpdated) ProtoMessage() {}
|
||||
|
||||
func (x *RoomProfileUpdated) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[2]
|
||||
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 RoomProfileUpdated.ProtoReflect.Descriptor instead.
|
||||
func (*RoomProfileUpdated) Descriptor() ([]byte, []int) {
|
||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *RoomProfileUpdated) GetActorUserId() int64 {
|
||||
if x != nil {
|
||||
return x.ActorUserId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RoomProfileUpdated) GetRoomName() string {
|
||||
if x != nil {
|
||||
return x.RoomName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoomProfileUpdated) GetRoomAvatar() string {
|
||||
if x != nil {
|
||||
return x.RoomAvatar
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoomProfileUpdated) GetRoomDescription() string {
|
||||
if x != nil {
|
||||
return x.RoomDescription
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoomProfileUpdated) GetSeatCount() int32 {
|
||||
if x != nil {
|
||||
return x.SeatCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// RoomUserJoined 表达用户业务态进房成功。
|
||||
type RoomUserJoined struct {
|
||||
state protoimpl.MessageState
|
||||
@ -222,7 +300,7 @@ type RoomUserJoined struct {
|
||||
|
||||
func (x *RoomUserJoined) Reset() {
|
||||
*x = RoomUserJoined{}
|
||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[2]
|
||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -234,7 +312,7 @@ func (x *RoomUserJoined) String() string {
|
||||
func (*RoomUserJoined) ProtoMessage() {}
|
||||
|
||||
func (x *RoomUserJoined) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[2]
|
||||
mi := &file_proto_events_room_v1_events_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -247,7 +325,7 @@ func (x *RoomUserJoined) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomUserJoined.ProtoReflect.Descriptor instead.
|
||||
func (*RoomUserJoined) Descriptor() ([]byte, []int) {
|
||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{2}
|
||||
return file_proto_events_room_v1_events_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *RoomUserJoined) GetUserId() int64 {
|
||||
@ -275,7 +353,7 @@ type RoomUserLeft struct {
|
||||
|
||||
func (x *RoomUserLeft) Reset() {
|
||||
*x = RoomUserLeft{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -287,7 +365,7 @@ func (x *RoomUserLeft) String() string {
|
||||
func (*RoomUserLeft) ProtoMessage() {}
|
||||
|
||||
func (x *RoomUserLeft) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -300,7 +378,7 @@ func (x *RoomUserLeft) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomUserLeft.ProtoReflect.Descriptor instead.
|
||||
func (*RoomUserLeft) 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 *RoomUserLeft) GetUserId() int64 {
|
||||
@ -322,7 +400,7 @@ type RoomClosed struct {
|
||||
|
||||
func (x *RoomClosed) Reset() {
|
||||
*x = RoomClosed{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -334,7 +412,7 @@ func (x *RoomClosed) String() string {
|
||||
func (*RoomClosed) ProtoMessage() {}
|
||||
|
||||
func (x *RoomClosed) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -347,7 +425,7 @@ func (x *RoomClosed) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomClosed.ProtoReflect.Descriptor instead.
|
||||
func (*RoomClosed) 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 *RoomClosed) GetActorUserId() int64 {
|
||||
@ -387,7 +465,7 @@ type RoomMicChanged struct {
|
||||
|
||||
func (x *RoomMicChanged) Reset() {
|
||||
*x = RoomMicChanged{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -399,7 +477,7 @@ func (x *RoomMicChanged) String() string {
|
||||
func (*RoomMicChanged) ProtoMessage() {}
|
||||
|
||||
func (x *RoomMicChanged) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -412,7 +490,7 @@ func (x *RoomMicChanged) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomMicChanged.ProtoReflect.Descriptor instead.
|
||||
func (*RoomMicChanged) 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 *RoomMicChanged) GetActorUserId() int64 {
|
||||
@ -498,7 +576,7 @@ type RoomMicSeatLocked struct {
|
||||
|
||||
func (x *RoomMicSeatLocked) Reset() {
|
||||
*x = RoomMicSeatLocked{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -510,7 +588,7 @@ func (x *RoomMicSeatLocked) String() string {
|
||||
func (*RoomMicSeatLocked) ProtoMessage() {}
|
||||
|
||||
func (x *RoomMicSeatLocked) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -523,7 +601,7 @@ func (x *RoomMicSeatLocked) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomMicSeatLocked.ProtoReflect.Descriptor instead.
|
||||
func (*RoomMicSeatLocked) 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 *RoomMicSeatLocked) GetActorUserId() int64 {
|
||||
@ -559,7 +637,7 @@ type RoomChatEnabledChanged struct {
|
||||
|
||||
func (x *RoomChatEnabledChanged) Reset() {
|
||||
*x = RoomChatEnabledChanged{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -571,7 +649,7 @@ func (x *RoomChatEnabledChanged) String() string {
|
||||
func (*RoomChatEnabledChanged) ProtoMessage() {}
|
||||
|
||||
func (x *RoomChatEnabledChanged) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -584,7 +662,7 @@ func (x *RoomChatEnabledChanged) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomChatEnabledChanged.ProtoReflect.Descriptor instead.
|
||||
func (*RoomChatEnabledChanged) 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 *RoomChatEnabledChanged) GetActorUserId() int64 {
|
||||
@ -614,7 +692,7 @@ type RoomAdminChanged struct {
|
||||
|
||||
func (x *RoomAdminChanged) Reset() {
|
||||
*x = RoomAdminChanged{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -626,7 +704,7 @@ func (x *RoomAdminChanged) String() string {
|
||||
func (*RoomAdminChanged) ProtoMessage() {}
|
||||
|
||||
func (x *RoomAdminChanged) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -639,7 +717,7 @@ func (x *RoomAdminChanged) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomAdminChanged.ProtoReflect.Descriptor instead.
|
||||
func (*RoomAdminChanged) 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 *RoomAdminChanged) GetActorUserId() int64 {
|
||||
@ -676,7 +754,7 @@ type RoomHostTransferred struct {
|
||||
|
||||
func (x *RoomHostTransferred) Reset() {
|
||||
*x = RoomHostTransferred{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -688,7 +766,7 @@ func (x *RoomHostTransferred) String() string {
|
||||
func (*RoomHostTransferred) ProtoMessage() {}
|
||||
|
||||
func (x *RoomHostTransferred) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -701,7 +779,7 @@ func (x *RoomHostTransferred) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomHostTransferred.ProtoReflect.Descriptor instead.
|
||||
func (*RoomHostTransferred) 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 *RoomHostTransferred) GetActorUserId() int64 {
|
||||
@ -738,7 +816,7 @@ type RoomUserMuted struct {
|
||||
|
||||
func (x *RoomUserMuted) Reset() {
|
||||
*x = RoomUserMuted{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -750,7 +828,7 @@ func (x *RoomUserMuted) String() string {
|
||||
func (*RoomUserMuted) ProtoMessage() {}
|
||||
|
||||
func (x *RoomUserMuted) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -763,7 +841,7 @@ func (x *RoomUserMuted) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomUserMuted.ProtoReflect.Descriptor instead.
|
||||
func (*RoomUserMuted) 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 *RoomUserMuted) GetActorUserId() int64 {
|
||||
@ -799,7 +877,7 @@ type RoomUserKicked struct {
|
||||
|
||||
func (x *RoomUserKicked) Reset() {
|
||||
*x = RoomUserKicked{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -811,7 +889,7 @@ func (x *RoomUserKicked) String() string {
|
||||
func (*RoomUserKicked) ProtoMessage() {}
|
||||
|
||||
func (x *RoomUserKicked) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -824,7 +902,7 @@ func (x *RoomUserKicked) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomUserKicked.ProtoReflect.Descriptor instead.
|
||||
func (*RoomUserKicked) 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 *RoomUserKicked) GetActorUserId() int64 {
|
||||
@ -853,7 +931,7 @@ type RoomUserUnbanned struct {
|
||||
|
||||
func (x *RoomUserUnbanned) Reset() {
|
||||
*x = RoomUserUnbanned{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -865,7 +943,7 @@ func (x *RoomUserUnbanned) String() string {
|
||||
func (*RoomUserUnbanned) ProtoMessage() {}
|
||||
|
||||
func (x *RoomUserUnbanned) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -878,7 +956,7 @@ func (x *RoomUserUnbanned) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomUserUnbanned.ProtoReflect.Descriptor instead.
|
||||
func (*RoomUserUnbanned) 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 *RoomUserUnbanned) GetActorUserId() int64 {
|
||||
@ -913,7 +991,7 @@ type RoomGiftSent struct {
|
||||
|
||||
func (x *RoomGiftSent) Reset() {
|
||||
*x = RoomGiftSent{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -925,7 +1003,7 @@ func (x *RoomGiftSent) String() string {
|
||||
func (*RoomGiftSent) ProtoMessage() {}
|
||||
|
||||
func (x *RoomGiftSent) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -938,7 +1016,7 @@ func (x *RoomGiftSent) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomGiftSent.ProtoReflect.Descriptor instead.
|
||||
func (*RoomGiftSent) 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 *RoomGiftSent) GetSenderUserId() int64 {
|
||||
@ -1009,7 +1087,7 @@ type RoomHeatChanged struct {
|
||||
|
||||
func (x *RoomHeatChanged) Reset() {
|
||||
*x = RoomHeatChanged{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1021,7 +1099,7 @@ func (x *RoomHeatChanged) String() string {
|
||||
func (*RoomHeatChanged) ProtoMessage() {}
|
||||
|
||||
func (x *RoomHeatChanged) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1034,7 +1112,7 @@ func (x *RoomHeatChanged) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomHeatChanged.ProtoReflect.Descriptor instead.
|
||||
func (*RoomHeatChanged) 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 *RoomHeatChanged) GetDelta() int64 {
|
||||
@ -1064,7 +1142,7 @@ type RoomRankChanged struct {
|
||||
|
||||
func (x *RoomRankChanged) Reset() {
|
||||
*x = RoomRankChanged{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -1076,7 +1154,7 @@ func (x *RoomRankChanged) String() string {
|
||||
func (*RoomRankChanged) ProtoMessage() {}
|
||||
|
||||
func (x *RoomRankChanged) 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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -1089,7 +1167,7 @@ func (x *RoomRankChanged) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoomRankChanged.ProtoReflect.Descriptor instead.
|
||||
func (*RoomRankChanged) 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 *RoomRankChanged) GetUserId() int64 {
|
||||
@ -1148,122 +1226,134 @@ var file_proto_events_room_v1_events_proto_rawDesc = []byte{
|
||||
0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x29, 0x0a,
|
||||
0x10, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x6f, 0x6f, 0x6d, 0x44, 0x65, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3d, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d,
|
||||
0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,
|
||||
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65,
|
||||
0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x27, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x55,
|
||||
0x73, 0x65, 0x72, 0x4c, 0x65, 0x66, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64,
|
||||
0x22, 0x48, 0x0a, 0x0a, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x12, 0x22,
|
||||
0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72,
|
||||
0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xee, 0x02, 0x0a, 0x0e, 0x52,
|
||||
0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a,
|
||||
0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49,
|
||||
0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65,
|
||||
0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f,
|
||||
0x73, 0x65, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d,
|
||||
0x53, 0x65, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x74, 0x6f, 0x53, 0x65, 0x61, 0x74, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61,
|
||||
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d,
|
||||
0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70,
|
||||
0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||
0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x75, 0x62, 0x6c,
|
||||
0x69, 0x73, 0x68, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x73, 0x18,
|
||||
0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x65,
|
||||
0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x70, 0x75, 0x62, 0x6c,
|
||||
0x69, 0x73, 0x68, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d,
|
||||
0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68,
|
||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x68, 0x0a, 0x11, 0x52,
|
||||
0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x65, 0x64,
|
||||
0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73,
|
||||
0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c,
|
||||
0x6f, 0x63, 0x6b, 0x65, 0x64, 0x22, 0x56, 0x0a, 0x16, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x68, 0x61,
|
||||
0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xc0, 0x01, 0x0a, 0x12, 0x52, 0x6f, 0x6f,
|
||||
0x6d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12,
|
||||
0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65,
|
||||
0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x76, 0x0a,
|
||||
0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65,
|
||||
0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x41, 0x76, 0x61, 0x74, 0x61,
|
||||
0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x6f, 0x6f,
|
||||
0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a,
|
||||
0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05,
|
||||
0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x3d, 0x0a, 0x0e, 0x52,
|
||||
0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x17, 0x0a,
|
||||
0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
|
||||
0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x27, 0x0a, 0x0c, 0x52, 0x6f,
|
||||
0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x66, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,
|
||||
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65,
|
||||
0x72, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6c, 0x6f, 0x73, 0x65,
|
||||
0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55,
|
||||
0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f,
|
||||
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74,
|
||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65,
|
||||
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e,
|
||||
0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x8b, 0x01, 0x0a, 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x6f,
|
||||
0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x12, 0x22, 0x0a,
|
||||
0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49,
|
||||
0x64, 0x12, 0x27, 0x0a, 0x10, 0x6f, 0x6c, 0x64, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73,
|
||||
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6f, 0x6c, 0x64,
|
||||
0x48, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x10, 0x6e, 0x65,
|
||||
0x77, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6e, 0x65, 0x77, 0x48, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65,
|
||||
0x72, 0x49, 0x64, 0x22, 0x6f, 0x0a, 0x0d, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4d,
|
||||
0x75, 0x74, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73,
|
||||
0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xee, 0x02,
|
||||
0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64,
|
||||
0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73,
|
||||
0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75,
|
||||
0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61,
|
||||
0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72,
|
||||
0x6f, 0x6d, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66,
|
||||
0x72, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x73, 0x65,
|
||||
0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x74, 0x6f, 0x53, 0x65, 0x61, 0x74,
|
||||
0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x63, 0x5f,
|
||||
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23,
|
||||
0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18,
|
||||
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x70,
|
||||
0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f,
|
||||
0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73,
|
||||
0x68, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x70,
|
||||
0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d,
|
||||
0x65, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x70, 0x75, 0x62, 0x6c,
|
||||
0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x68,
|
||||
0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63,
|
||||
0x6b, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65,
|
||||
0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f,
|
||||
0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f,
|
||||
0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f,
|
||||
0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x22, 0x56, 0x0a, 0x16, 0x52, 0x6f, 0x6f, 0x6d,
|
||||
0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67,
|
||||
0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72,
|
||||
0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
|
||||
0x22, 0x76, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61,
|
||||
0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73,
|
||||
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74,
|
||||
0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67,
|
||||
0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14,
|
||||
0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d,
|
||||
0x75, 0x74, 0x65, 0x64, 0x22, 0x5a, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72,
|
||||
0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f,
|
||||
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61,
|
||||
0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61,
|
||||
0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64,
|
||||
0x22, 0x5c, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x55, 0x6e, 0x62, 0x61,
|
||||
0x6e, 0x6e, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73,
|
||||
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74,
|
||||
0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67,
|
||||
0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18,
|
||||
0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x8b, 0x01, 0x0a, 0x13, 0x52, 0x6f, 0x6f,
|
||||
0x6d, 0x48, 0x6f, 0x73, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64,
|
||||
0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73,
|
||||
0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x10, 0x6f, 0x6c, 0x64, 0x5f, 0x68, 0x6f, 0x73, 0x74,
|
||||
0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d,
|
||||
0x6f, 0x6c, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a,
|
||||
0x10, 0x6e, 0x65, 0x77, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6e, 0x65, 0x77, 0x48, 0x6f, 0x73, 0x74,
|
||||
0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x6f, 0x0a, 0x0d, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73,
|
||||
0x65, 0x72, 0x4d, 0x75, 0x74, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72,
|
||||
0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b,
|
||||
0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74,
|
||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49,
|
||||
0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x22, 0x5a, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x55,
|
||||
0x73, 0x65, 0x72, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74,
|
||||
0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a,
|
||||
0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65,
|
||||
0x72, 0x49, 0x64, 0x22, 0x5c, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x55,
|
||||
0x6e, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72,
|
||||
0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b,
|
||||
0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74,
|
||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49,
|
||||
0x64, 0x22, 0xaa, 0x02, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65,
|
||||
0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65,
|
||||
0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64,
|
||||
0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67,
|
||||
0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xaa,
|
||||
0x02, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x6e, 0x74, 0x12,
|
||||
0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55,
|
||||
0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f,
|
||||
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74,
|
||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67,
|
||||
0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69,
|
||||
0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65,
|
||||
0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,
|
||||
0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64,
|
||||
0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69,
|
||||
0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73,
|
||||
0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a,
|
||||
0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x22, 0x4a, 0x0a, 0x0f, 0x52,
|
||||
0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x14,
|
||||
0x0a, 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x64,
|
||||
0x65, 0x6c, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f,
|
||||
0x68, 0x65, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72,
|
||||
0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x74, 0x22, 0x5f, 0x0a, 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x52,
|
||||
0x61, 0x6e, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,
|
||||
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65,
|
||||
0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66,
|
||||
0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67,
|
||||
0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x68, 0x79, 0x61, 0x70,
|
||||
0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31,
|
||||
0x3b, 0x72, 0x6f, 0x6f, 0x6d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17,
|
||||
0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66,
|
||||
0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67,
|
||||
0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70,
|
||||
0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72,
|
||||
0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f,
|
||||
0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12,
|
||||
0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x22, 0x4a,
|
||||
0x0a, 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65,
|
||||
0x64, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65,
|
||||
0x6e, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63,
|
||||
0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x74, 0x22, 0x5f, 0x0a, 0x0f, 0x52, 0x6f,
|
||||
0x6f, 0x6d, 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x17, 0x0a,
|
||||
0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
|
||||
0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a,
|
||||
0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x68,
|
||||
0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x6f, 0x6f, 0x6d,
|
||||
0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x6f, 0x6d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x76, 0x31,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@ -1278,24 +1368,25 @@ func file_proto_events_room_v1_events_proto_rawDescGZIP() []byte {
|
||||
return file_proto_events_room_v1_events_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_events_room_v1_events_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
|
||||
var file_proto_events_room_v1_events_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
|
||||
var file_proto_events_room_v1_events_proto_goTypes = []any{
|
||||
(*EventEnvelope)(nil), // 0: hyapp.events.room.v1.EventEnvelope
|
||||
(*RoomCreated)(nil), // 1: hyapp.events.room.v1.RoomCreated
|
||||
(*RoomUserJoined)(nil), // 2: hyapp.events.room.v1.RoomUserJoined
|
||||
(*RoomUserLeft)(nil), // 3: hyapp.events.room.v1.RoomUserLeft
|
||||
(*RoomClosed)(nil), // 4: hyapp.events.room.v1.RoomClosed
|
||||
(*RoomMicChanged)(nil), // 5: hyapp.events.room.v1.RoomMicChanged
|
||||
(*RoomMicSeatLocked)(nil), // 6: hyapp.events.room.v1.RoomMicSeatLocked
|
||||
(*RoomChatEnabledChanged)(nil), // 7: hyapp.events.room.v1.RoomChatEnabledChanged
|
||||
(*RoomAdminChanged)(nil), // 8: hyapp.events.room.v1.RoomAdminChanged
|
||||
(*RoomHostTransferred)(nil), // 9: hyapp.events.room.v1.RoomHostTransferred
|
||||
(*RoomUserMuted)(nil), // 10: hyapp.events.room.v1.RoomUserMuted
|
||||
(*RoomUserKicked)(nil), // 11: hyapp.events.room.v1.RoomUserKicked
|
||||
(*RoomUserUnbanned)(nil), // 12: hyapp.events.room.v1.RoomUserUnbanned
|
||||
(*RoomGiftSent)(nil), // 13: hyapp.events.room.v1.RoomGiftSent
|
||||
(*RoomHeatChanged)(nil), // 14: hyapp.events.room.v1.RoomHeatChanged
|
||||
(*RoomRankChanged)(nil), // 15: hyapp.events.room.v1.RoomRankChanged
|
||||
(*RoomProfileUpdated)(nil), // 2: hyapp.events.room.v1.RoomProfileUpdated
|
||||
(*RoomUserJoined)(nil), // 3: hyapp.events.room.v1.RoomUserJoined
|
||||
(*RoomUserLeft)(nil), // 4: hyapp.events.room.v1.RoomUserLeft
|
||||
(*RoomClosed)(nil), // 5: hyapp.events.room.v1.RoomClosed
|
||||
(*RoomMicChanged)(nil), // 6: hyapp.events.room.v1.RoomMicChanged
|
||||
(*RoomMicSeatLocked)(nil), // 7: hyapp.events.room.v1.RoomMicSeatLocked
|
||||
(*RoomChatEnabledChanged)(nil), // 8: hyapp.events.room.v1.RoomChatEnabledChanged
|
||||
(*RoomAdminChanged)(nil), // 9: hyapp.events.room.v1.RoomAdminChanged
|
||||
(*RoomHostTransferred)(nil), // 10: hyapp.events.room.v1.RoomHostTransferred
|
||||
(*RoomUserMuted)(nil), // 11: hyapp.events.room.v1.RoomUserMuted
|
||||
(*RoomUserKicked)(nil), // 12: hyapp.events.room.v1.RoomUserKicked
|
||||
(*RoomUserUnbanned)(nil), // 13: hyapp.events.room.v1.RoomUserUnbanned
|
||||
(*RoomGiftSent)(nil), // 14: hyapp.events.room.v1.RoomGiftSent
|
||||
(*RoomHeatChanged)(nil), // 15: hyapp.events.room.v1.RoomHeatChanged
|
||||
(*RoomRankChanged)(nil), // 16: hyapp.events.room.v1.RoomRankChanged
|
||||
}
|
||||
var file_proto_events_room_v1_events_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
@ -1316,7 +1407,7 @@ func file_proto_events_room_v1_events_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_proto_events_room_v1_events_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 16,
|
||||
NumMessages: 17,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@ -28,6 +28,15 @@ message RoomCreated {
|
||||
string room_description = 7;
|
||||
}
|
||||
|
||||
// RoomProfileUpdated 表达房间展示资料或麦位数量变更。
|
||||
message RoomProfileUpdated {
|
||||
int64 actor_user_id = 1;
|
||||
string room_name = 2;
|
||||
string room_avatar = 3;
|
||||
string room_description = 4;
|
||||
int32 seat_count = 5;
|
||||
}
|
||||
|
||||
// RoomUserJoined 表达用户业务态进房成功。
|
||||
message RoomUserJoined {
|
||||
int64 user_id = 1;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -81,11 +81,11 @@ message RoomSnapshot {
|
||||
}
|
||||
|
||||
// CreateRoomRequest 创建一个新的房间执行单元。
|
||||
// 必填语义:meta.room_id、meta.command_id、meta.actor_user_id、seat_count、mode 和 room_name。
|
||||
// 必填语义:meta.room_id、meta.command_id、meta.actor_user_id、mode 和 room_name。
|
||||
// 同一个 meta.actor_user_id 只能作为 owner 创建一个房间;重复创建返回 Conflict。
|
||||
message CreateRoomRequest {
|
||||
RequestMeta meta = 1;
|
||||
// seat_count 必须大于 0;没有麦位的语音房不进入 Room Cell。
|
||||
// seat_count 为空或 0 时使用后台房间配置默认值;正数必须命中后台启用座位数。
|
||||
int32 seat_count = 2;
|
||||
// mode 必须非空;当前只作为房间状态字段保存和透出。
|
||||
string mode = 3;
|
||||
@ -107,6 +107,22 @@ message CreateRoomResponse {
|
||||
RoomSnapshot room = 2;
|
||||
}
|
||||
|
||||
// UpdateRoomProfileRequest 修改房间展示资料和麦位数量。
|
||||
// 该命令只允许当前在房间 presence 内的 owner 执行,座位数必须命中后台启用配置。
|
||||
message UpdateRoomProfileRequest {
|
||||
RequestMeta meta = 1;
|
||||
optional string room_name = 2;
|
||||
optional string room_avatar = 3;
|
||||
optional string room_description = 4;
|
||||
optional int32 seat_count = 5;
|
||||
}
|
||||
|
||||
// UpdateRoomProfileResponse 返回修改后的最新房间快照。
|
||||
message UpdateRoomProfileResponse {
|
||||
CommandResult result = 1;
|
||||
RoomSnapshot room = 2;
|
||||
}
|
||||
|
||||
// JoinRoomRequest 把用户 presence 接入房间业务态。
|
||||
message JoinRoomRequest {
|
||||
RequestMeta meta = 1;
|
||||
@ -480,6 +496,7 @@ message GetRoomSnapshotResponse {
|
||||
// RoomCommandService 承载所有会改变房间状态的命令。
|
||||
service RoomCommandService {
|
||||
rpc CreateRoom(CreateRoomRequest) returns (CreateRoomResponse);
|
||||
rpc UpdateRoomProfile(UpdateRoomProfileRequest) returns (UpdateRoomProfileResponse);
|
||||
rpc JoinRoom(JoinRoomRequest) returns (JoinRoomResponse);
|
||||
rpc RoomHeartbeat(RoomHeartbeatRequest) returns (RoomHeartbeatResponse);
|
||||
rpc LeaveRoom(LeaveRoomRequest) returns (LeaveRoomResponse);
|
||||
|
||||
@ -20,6 +20,7 @@ const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
RoomCommandService_CreateRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/CreateRoom"
|
||||
RoomCommandService_UpdateRoomProfile_FullMethodName = "/hyapp.room.v1.RoomCommandService/UpdateRoomProfile"
|
||||
RoomCommandService_JoinRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/JoinRoom"
|
||||
RoomCommandService_RoomHeartbeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/RoomHeartbeat"
|
||||
RoomCommandService_LeaveRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/LeaveRoom"
|
||||
@ -46,6 +47,7 @@ const (
|
||||
// RoomCommandService 承载所有会改变房间状态的命令。
|
||||
type RoomCommandServiceClient interface {
|
||||
CreateRoom(ctx context.Context, in *CreateRoomRequest, opts ...grpc.CallOption) (*CreateRoomResponse, error)
|
||||
UpdateRoomProfile(ctx context.Context, in *UpdateRoomProfileRequest, opts ...grpc.CallOption) (*UpdateRoomProfileResponse, error)
|
||||
JoinRoom(ctx context.Context, in *JoinRoomRequest, opts ...grpc.CallOption) (*JoinRoomResponse, error)
|
||||
RoomHeartbeat(ctx context.Context, in *RoomHeartbeatRequest, opts ...grpc.CallOption) (*RoomHeartbeatResponse, error)
|
||||
LeaveRoom(ctx context.Context, in *LeaveRoomRequest, opts ...grpc.CallOption) (*LeaveRoomResponse, error)
|
||||
@ -83,6 +85,16 @@ func (c *roomCommandServiceClient) CreateRoom(ctx context.Context, in *CreateRoo
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *roomCommandServiceClient) UpdateRoomProfile(ctx context.Context, in *UpdateRoomProfileRequest, opts ...grpc.CallOption) (*UpdateRoomProfileResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(UpdateRoomProfileResponse)
|
||||
err := c.cc.Invoke(ctx, RoomCommandService_UpdateRoomProfile_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) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(JoinRoomResponse)
|
||||
@ -260,6 +272,7 @@ func (c *roomCommandServiceClient) SendGift(ctx context.Context, in *SendGiftReq
|
||||
// RoomCommandService 承载所有会改变房间状态的命令。
|
||||
type RoomCommandServiceServer interface {
|
||||
CreateRoom(context.Context, *CreateRoomRequest) (*CreateRoomResponse, error)
|
||||
UpdateRoomProfile(context.Context, *UpdateRoomProfileRequest) (*UpdateRoomProfileResponse, error)
|
||||
JoinRoom(context.Context, *JoinRoomRequest) (*JoinRoomResponse, error)
|
||||
RoomHeartbeat(context.Context, *RoomHeartbeatRequest) (*RoomHeartbeatResponse, error)
|
||||
LeaveRoom(context.Context, *LeaveRoomRequest) (*LeaveRoomResponse, error)
|
||||
@ -290,6 +303,9 @@ type UnimplementedRoomCommandServiceServer struct{}
|
||||
func (UnimplementedRoomCommandServiceServer) CreateRoom(context.Context, *CreateRoomRequest) (*CreateRoomResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateRoom not implemented")
|
||||
}
|
||||
func (UnimplementedRoomCommandServiceServer) UpdateRoomProfile(context.Context, *UpdateRoomProfileRequest) (*UpdateRoomProfileResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateRoomProfile not implemented")
|
||||
}
|
||||
func (UnimplementedRoomCommandServiceServer) JoinRoom(context.Context, *JoinRoomRequest) (*JoinRoomResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method JoinRoom not implemented")
|
||||
}
|
||||
@ -380,6 +396,24 @@ func _RoomCommandService_CreateRoom_Handler(srv interface{}, ctx context.Context
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _RoomCommandService_UpdateRoomProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateRoomProfileRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(RoomCommandServiceServer).UpdateRoomProfile(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: RoomCommandService_UpdateRoomProfile_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(RoomCommandServiceServer).UpdateRoomProfile(ctx, req.(*UpdateRoomProfileRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _RoomCommandService_JoinRoom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(JoinRoomRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@ -697,6 +731,10 @@ var RoomCommandService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "CreateRoom",
|
||||
Handler: _RoomCommandService_CreateRoom_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateRoomProfile",
|
||||
Handler: _RoomCommandService_UpdateRoomProfile_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "JoinRoom",
|
||||
Handler: _RoomCommandService_JoinRoom_Handler,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -251,6 +251,17 @@ message GetUserRoleSummaryResponse {
|
||||
UserRoleSummary summary = 1;
|
||||
}
|
||||
|
||||
message CheckBusinessCapabilityRequest {
|
||||
RequestMeta meta = 1;
|
||||
int64 actor_user_id = 2;
|
||||
string capability = 3;
|
||||
}
|
||||
|
||||
message CheckBusinessCapabilityResponse {
|
||||
bool allowed = 1;
|
||||
string reason = 2;
|
||||
}
|
||||
|
||||
message GetAgencyMembersRequest {
|
||||
RequestMeta meta = 1;
|
||||
int64 agency_id = 2;
|
||||
@ -391,6 +402,7 @@ service UserHostService {
|
||||
rpc GetBDProfile(GetBDProfileRequest) returns (GetBDProfileResponse);
|
||||
rpc GetCoinSellerProfile(GetCoinSellerProfileRequest) returns (GetCoinSellerProfileResponse);
|
||||
rpc GetUserRoleSummary(GetUserRoleSummaryRequest) returns (GetUserRoleSummaryResponse);
|
||||
rpc CheckBusinessCapability(CheckBusinessCapabilityRequest) returns (CheckBusinessCapabilityResponse);
|
||||
rpc GetAgencyMembers(GetAgencyMembersRequest) returns (GetAgencyMembersResponse);
|
||||
rpc GetAgencyApplications(GetAgencyApplicationsRequest) returns (GetAgencyApplicationsResponse);
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ const (
|
||||
UserHostService_GetBDProfile_FullMethodName = "/hyapp.user.v1.UserHostService/GetBDProfile"
|
||||
UserHostService_GetCoinSellerProfile_FullMethodName = "/hyapp.user.v1.UserHostService/GetCoinSellerProfile"
|
||||
UserHostService_GetUserRoleSummary_FullMethodName = "/hyapp.user.v1.UserHostService/GetUserRoleSummary"
|
||||
UserHostService_CheckBusinessCapability_FullMethodName = "/hyapp.user.v1.UserHostService/CheckBusinessCapability"
|
||||
UserHostService_GetAgencyMembers_FullMethodName = "/hyapp.user.v1.UserHostService/GetAgencyMembers"
|
||||
UserHostService_GetAgencyApplications_FullMethodName = "/hyapp.user.v1.UserHostService/GetAgencyApplications"
|
||||
)
|
||||
@ -51,6 +52,7 @@ type UserHostServiceClient interface {
|
||||
GetBDProfile(ctx context.Context, in *GetBDProfileRequest, opts ...grpc.CallOption) (*GetBDProfileResponse, error)
|
||||
GetCoinSellerProfile(ctx context.Context, in *GetCoinSellerProfileRequest, opts ...grpc.CallOption) (*GetCoinSellerProfileResponse, error)
|
||||
GetUserRoleSummary(ctx context.Context, in *GetUserRoleSummaryRequest, opts ...grpc.CallOption) (*GetUserRoleSummaryResponse, error)
|
||||
CheckBusinessCapability(ctx context.Context, in *CheckBusinessCapabilityRequest, opts ...grpc.CallOption) (*CheckBusinessCapabilityResponse, error)
|
||||
GetAgencyMembers(ctx context.Context, in *GetAgencyMembersRequest, opts ...grpc.CallOption) (*GetAgencyMembersResponse, error)
|
||||
GetAgencyApplications(ctx context.Context, in *GetAgencyApplicationsRequest, opts ...grpc.CallOption) (*GetAgencyApplicationsResponse, error)
|
||||
}
|
||||
@ -173,6 +175,16 @@ func (c *userHostServiceClient) GetUserRoleSummary(ctx context.Context, in *GetU
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *userHostServiceClient) CheckBusinessCapability(ctx context.Context, in *CheckBusinessCapabilityRequest, opts ...grpc.CallOption) (*CheckBusinessCapabilityResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(CheckBusinessCapabilityResponse)
|
||||
err := c.cc.Invoke(ctx, UserHostService_CheckBusinessCapability_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *userHostServiceClient) GetAgencyMembers(ctx context.Context, in *GetAgencyMembersRequest, opts ...grpc.CallOption) (*GetAgencyMembersResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetAgencyMembersResponse)
|
||||
@ -210,6 +222,7 @@ type UserHostServiceServer interface {
|
||||
GetBDProfile(context.Context, *GetBDProfileRequest) (*GetBDProfileResponse, error)
|
||||
GetCoinSellerProfile(context.Context, *GetCoinSellerProfileRequest) (*GetCoinSellerProfileResponse, error)
|
||||
GetUserRoleSummary(context.Context, *GetUserRoleSummaryRequest) (*GetUserRoleSummaryResponse, error)
|
||||
CheckBusinessCapability(context.Context, *CheckBusinessCapabilityRequest) (*CheckBusinessCapabilityResponse, error)
|
||||
GetAgencyMembers(context.Context, *GetAgencyMembersRequest) (*GetAgencyMembersResponse, error)
|
||||
GetAgencyApplications(context.Context, *GetAgencyApplicationsRequest) (*GetAgencyApplicationsResponse, error)
|
||||
mustEmbedUnimplementedUserHostServiceServer()
|
||||
@ -255,6 +268,9 @@ func (UnimplementedUserHostServiceServer) GetCoinSellerProfile(context.Context,
|
||||
func (UnimplementedUserHostServiceServer) GetUserRoleSummary(context.Context, *GetUserRoleSummaryRequest) (*GetUserRoleSummaryResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetUserRoleSummary not implemented")
|
||||
}
|
||||
func (UnimplementedUserHostServiceServer) CheckBusinessCapability(context.Context, *CheckBusinessCapabilityRequest) (*CheckBusinessCapabilityResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CheckBusinessCapability not implemented")
|
||||
}
|
||||
func (UnimplementedUserHostServiceServer) GetAgencyMembers(context.Context, *GetAgencyMembersRequest) (*GetAgencyMembersResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAgencyMembers not implemented")
|
||||
}
|
||||
@ -480,6 +496,24 @@ func _UserHostService_GetUserRoleSummary_Handler(srv interface{}, ctx context.Co
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _UserHostService_CheckBusinessCapability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CheckBusinessCapabilityRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(UserHostServiceServer).CheckBusinessCapability(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: UserHostService_CheckBusinessCapability_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(UserHostServiceServer).CheckBusinessCapability(ctx, req.(*CheckBusinessCapabilityRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _UserHostService_GetAgencyMembers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetAgencyMembersRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@ -567,6 +601,10 @@ var UserHostService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetUserRoleSummary",
|
||||
Handler: _UserHostService_GetUserRoleSummary_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CheckBusinessCapability",
|
||||
Handler: _UserHostService_CheckBusinessCapability_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetAgencyMembers",
|
||||
Handler: _UserHostService_GetAgencyMembers_Handler,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -171,6 +171,29 @@ message GetUserResponse {
|
||||
User user = 1;
|
||||
}
|
||||
|
||||
message BusinessUserLookupRequest {
|
||||
RequestMeta meta = 1;
|
||||
int64 actor_user_id = 2;
|
||||
string scene = 3;
|
||||
string keyword = 4;
|
||||
int32 page_size = 5;
|
||||
}
|
||||
|
||||
message BusinessUserLookupItem {
|
||||
int64 user_id = 1;
|
||||
string display_user_id = 2;
|
||||
string username = 3;
|
||||
string avatar = 4;
|
||||
UserStatus status = 5;
|
||||
int64 region_id = 6;
|
||||
string region_code = 7;
|
||||
string region_name = 8;
|
||||
}
|
||||
|
||||
message BusinessUserLookupResponse {
|
||||
repeated BusinessUserLookupItem users = 1;
|
||||
}
|
||||
|
||||
// UserProfileStats 是我的页顶部统计 read model;不得在查询时实时扫描关系链或礼物流水。
|
||||
message UserProfileStats {
|
||||
int64 user_id = 1;
|
||||
@ -627,6 +650,7 @@ message ExpirePrettyDisplayUserIDResponse {
|
||||
// UserService 提供用户主状态、基础资料和注册资料读写接口。
|
||||
service UserService {
|
||||
rpc GetUser(GetUserRequest) returns (GetUserResponse);
|
||||
rpc BusinessUserLookup(BusinessUserLookupRequest) returns (BusinessUserLookupResponse);
|
||||
rpc GetMyProfileStats(GetMyProfileStatsRequest) returns (GetMyProfileStatsResponse);
|
||||
rpc BatchGetUsers(BatchGetUsersRequest) returns (BatchGetUsersResponse);
|
||||
rpc ListUserIDs(ListUserIDsRequest) returns (ListUserIDsResponse);
|
||||
|
||||
@ -20,6 +20,7 @@ const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
UserService_GetUser_FullMethodName = "/hyapp.user.v1.UserService/GetUser"
|
||||
UserService_BusinessUserLookup_FullMethodName = "/hyapp.user.v1.UserService/BusinessUserLookup"
|
||||
UserService_GetMyProfileStats_FullMethodName = "/hyapp.user.v1.UserService/GetMyProfileStats"
|
||||
UserService_BatchGetUsers_FullMethodName = "/hyapp.user.v1.UserService/BatchGetUsers"
|
||||
UserService_ListUserIDs_FullMethodName = "/hyapp.user.v1.UserService/ListUserIDs"
|
||||
@ -36,6 +37,7 @@ const (
|
||||
// UserService 提供用户主状态、基础资料和注册资料读写接口。
|
||||
type UserServiceClient interface {
|
||||
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error)
|
||||
BusinessUserLookup(ctx context.Context, in *BusinessUserLookupRequest, opts ...grpc.CallOption) (*BusinessUserLookupResponse, error)
|
||||
GetMyProfileStats(ctx context.Context, in *GetMyProfileStatsRequest, opts ...grpc.CallOption) (*GetMyProfileStatsResponse, error)
|
||||
BatchGetUsers(ctx context.Context, in *BatchGetUsersRequest, opts ...grpc.CallOption) (*BatchGetUsersResponse, error)
|
||||
ListUserIDs(ctx context.Context, in *ListUserIDsRequest, opts ...grpc.CallOption) (*ListUserIDsResponse, error)
|
||||
@ -63,6 +65,16 @@ func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opt
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *userServiceClient) BusinessUserLookup(ctx context.Context, in *BusinessUserLookupRequest, opts ...grpc.CallOption) (*BusinessUserLookupResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(BusinessUserLookupResponse)
|
||||
err := c.cc.Invoke(ctx, UserService_BusinessUserLookup_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *userServiceClient) GetMyProfileStats(ctx context.Context, in *GetMyProfileStatsRequest, opts ...grpc.CallOption) (*GetMyProfileStatsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetMyProfileStatsResponse)
|
||||
@ -140,6 +152,7 @@ func (c *userServiceClient) CompleteOnboarding(ctx context.Context, in *Complete
|
||||
// UserService 提供用户主状态、基础资料和注册资料读写接口。
|
||||
type UserServiceServer interface {
|
||||
GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error)
|
||||
BusinessUserLookup(context.Context, *BusinessUserLookupRequest) (*BusinessUserLookupResponse, error)
|
||||
GetMyProfileStats(context.Context, *GetMyProfileStatsRequest) (*GetMyProfileStatsResponse, error)
|
||||
BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error)
|
||||
ListUserIDs(context.Context, *ListUserIDsRequest) (*ListUserIDsResponse, error)
|
||||
@ -160,6 +173,9 @@ type UnimplementedUserServiceServer struct{}
|
||||
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
|
||||
}
|
||||
func (UnimplementedUserServiceServer) BusinessUserLookup(context.Context, *BusinessUserLookupRequest) (*BusinessUserLookupResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method BusinessUserLookup not implemented")
|
||||
}
|
||||
func (UnimplementedUserServiceServer) GetMyProfileStats(context.Context, *GetMyProfileStatsRequest) (*GetMyProfileStatsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetMyProfileStats not implemented")
|
||||
}
|
||||
@ -220,6 +236,24 @@ func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _UserService_BusinessUserLookup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(BusinessUserLookupRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(UserServiceServer).BusinessUserLookup(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: UserService_BusinessUserLookup_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(UserServiceServer).BusinessUserLookup(ctx, req.(*BusinessUserLookupRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _UserService_GetMyProfileStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetMyProfileStatsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@ -357,6 +391,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetUser",
|
||||
Handler: _UserService_GetUser_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "BusinessUserLookup",
|
||||
Handler: _UserService_BusinessUserLookup_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetMyProfileStats",
|
||||
Handler: _UserService_GetMyProfileStats_Handler,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -147,6 +147,7 @@ message Resource {
|
||||
int64 updated_by_user_id = 18;
|
||||
int64 created_at_ms = 19;
|
||||
int64 updated_at_ms = 20;
|
||||
bool manager_grant_enabled = 21;
|
||||
}
|
||||
|
||||
message ResourceGroupItem {
|
||||
@ -268,6 +269,7 @@ message ListResourcesRequest {
|
||||
int32 page = 6;
|
||||
int32 page_size = 7;
|
||||
bool active_only = 8;
|
||||
bool manager_grant_only = 9;
|
||||
}
|
||||
|
||||
message ListResourcesResponse {
|
||||
@ -303,6 +305,7 @@ message CreateResourceRequest {
|
||||
string metadata_json = 15;
|
||||
int32 sort_order = 16;
|
||||
int64 operator_user_id = 17;
|
||||
optional bool manager_grant_enabled = 18;
|
||||
}
|
||||
|
||||
message UpdateResourceRequest {
|
||||
@ -324,6 +327,7 @@ message UpdateResourceRequest {
|
||||
string metadata_json = 16;
|
||||
int32 sort_order = 17;
|
||||
int64 operator_user_id = 18;
|
||||
optional bool manager_grant_enabled = 19;
|
||||
}
|
||||
|
||||
message SetResourceStatusRequest {
|
||||
|
||||
@ -50,7 +50,7 @@ stateDiagram-v2
|
||||
|
||||
- `RoomHeartbeat` 只能刷新已有 presence,不能创建 presence。
|
||||
- `LeaveRoom` 要同步释放用户占用的麦位。
|
||||
- RTC `room_exited` 可以清理 presence,但如果用户已经在事件时间之后重新刷新 presence,旧事件必须忽略。
|
||||
- RTC `room_exited` 只释放当前麦位,不清理业务 presence;用户可能仍停留在房间页等待重连。
|
||||
- `KickUser` 写入 ban 集合,用户必须先 `UnbanUser` 才能重新 `JoinRoom`。
|
||||
|
||||
## Mic
|
||||
|
||||
314
docs/经理中心资源赠送架构.md
Normal file
314
docs/经理中心资源赠送架构.md
Normal file
@ -0,0 +1,314 @@
|
||||
# Manager Center Resource Grant Architecture
|
||||
|
||||
本文定义后台资源列表的“赠送权限”开关、H5 经理中心资源赠送、以及可复用用户查询接口的服务边界。资源 catalog、用户资源权益和赠送事实仍按 [Resource And Resource Group Architecture](./资源组与礼物架构.md) 归属 `wallet-service`;经理、Agency、BD 等业务身份仍按 [Host, Agency And BD App Architecture](./主播公会BD架构.md) 归属 `user-service`。
|
||||
|
||||
## Goals
|
||||
|
||||
- 后台资源列表增加“赠送权限” switch,新建资源默认打开;关闭后资源仍可存在、可用于其他业务,但不能出现在经理中心赠送列表,也不能被经理中心赠送。
|
||||
- `server/admin` 和后台前端支持读取、创建、编辑和切换该字段,但不成为资源配置事实 owner。
|
||||
- H5 经理中心提供资源赠送列表、赠送给用户接口和通用用户查询入口。
|
||||
- 经理身份必须由服务端校验,客户端不能自报 `is_manager`。
|
||||
- 用户查询接口按业务场景复用,首个场景是经理中心赠送;后续代理中心、申请成为主播、BD 中心可以复用同一个 user-service 查询能力,但必须传入明确 scene 并执行各自权限和字段裁剪。
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- 不新增独立 `manager-service` 或 `resource-service`。
|
||||
- 不把用户资源余额、背包、赠送记录写入 `server/admin` 数据库。
|
||||
- 不允许 H5 客户端提交资源价格、金币数量、权益有效期或赠送策略。
|
||||
- 不支持经理中心赠送资源组;资源组仍只允许后台或明确的系统命令赠送。
|
||||
- 不在 room-service 中校验经理身份或资源赠送权限。
|
||||
- 不把通用用户查询做成无权限全局搜索;所有查询必须带 scene,且返回字段按 scene 收敛。
|
||||
|
||||
## Service Ownership
|
||||
|
||||
| Service | Owns | Does Not Own |
|
||||
| --- | --- | --- |
|
||||
| `wallet-service` | `resources.manager_grant_enabled`、资源赠送白名单判断、资源赠送事务、钱包入账、用户资源权益、resource outbox | 用户是否经理、用户资料搜索、后台账号权限 |
|
||||
| `user-service` | 用户主数据、短号解析、通用用户查询、经理/Agency/BD/主播/币商等业务身份能力判定 | 资源 catalog、钱包余额、用户资源权益 |
|
||||
| `gateway-service` | H5 HTTP `/api/v1` envelope、App 鉴权、scene 参数、调用 user-service 校验能力和目标用户、调用 wallet-service 赠送命令 | 资源事实、用户身份事实、钱包/背包落账 |
|
||||
| `server/admin` | 后台鉴权、资源列表/编辑表单、操作审计、调用 wallet-service 管理 RPC | 资源事实、H5 经理身份、用户资源背包 |
|
||||
| H5 经理中心 | 展示资源列表、选择目标用户、提交赠送命令 | 权限判断、目标用户真实性、赠送数量和有效期计算 |
|
||||
|
||||
## Core Concepts
|
||||
|
||||
| Concept | Meaning |
|
||||
| --- | --- |
|
||||
| `manager_grant_enabled` | 资源是否允许由经理中心赠送。它不是后台能否赠送的开关;后台仍使用现有 `grantable` 和后台权限控制。 |
|
||||
| `manager_center` | App H5 业务入口,由 gateway 鉴权后进入。首版经理身份映射为 active Agency owner,后续如果引入 Agency manager 表,只扩展 user-service 能力判定。 |
|
||||
| `business user lookup` | 通用用户查询能力。调用方必须传 `scene`,user-service 按 scene 校验 actor 能力并裁剪返回字段。 |
|
||||
| `manager resource grant` | 经理中心发起的单资源赠送命令。落库仍是 `resource_grants`,`grant_source=manager_center`,`operator_user_id=actor_user_id`。 |
|
||||
|
||||
## Admin Resource Switch
|
||||
|
||||
`resources` 增加字段:
|
||||
|
||||
```sql
|
||||
ALTER TABLE resources
|
||||
ADD COLUMN manager_grant_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
ADD KEY idx_resources_manager_grant (app_code, manager_grant_enabled, status, sort_order);
|
||||
```
|
||||
|
||||
字段语义:
|
||||
|
||||
- 默认值为 `TRUE`,满足“后台资源列表赠送权限 switch 默认打开”。
|
||||
- 关闭 switch 只影响经理中心赠送列表和经理中心赠送命令。
|
||||
- `status=disabled` 的资源即使 `manager_grant_enabled=TRUE`,也不能出现在 H5 赠送列表。
|
||||
- `grantable=FALSE` 的资源即使 `manager_grant_enabled=TRUE`,也不能被任何赠送命令发放。
|
||||
- 历史已经赠送出去的权益不受该字段变更影响。
|
||||
|
||||
后台接口保持当前 `server/admin -> wallet-service` 边界:
|
||||
|
||||
```http
|
||||
GET /api/v1/admin/resources
|
||||
POST /api/v1/admin/resources
|
||||
PUT /api/v1/admin/resources/{resource_id}
|
||||
POST /api/v1/admin/resources/{resource_id}/enable
|
||||
POST /api/v1/admin/resources/{resource_id}/disable
|
||||
```
|
||||
|
||||
DTO/RPC 需要补充:
|
||||
|
||||
- `wallet.v1.Resource.manager_grant_enabled`
|
||||
- `CreateResourceRequest.manager_grant_enabled`
|
||||
- `UpdateResourceRequest.manager_grant_enabled`
|
||||
- `ListResourcesRequest.manager_grant_only`
|
||||
|
||||
`server/admin` 只做字段透传和操作审计。新增或编辑资源时,如果请求体不传 `managerGrantEnabled`,admin-server 必须向 wallet-service 传 `true`,避免前端漏字段导致默认关闭。
|
||||
|
||||
## Manager Capability Boundary
|
||||
|
||||
首版经理身份定义为:
|
||||
|
||||
- 当前用户拥有 active `Agency`,且 `agencies.owner_user_id = actor_user_id`。
|
||||
- `Agency.status=active`。
|
||||
- 该能力由 `user-service` host domain 判断,gateway 不能只读 access token 或客户端传参。
|
||||
|
||||
建议在 user-service 增加通用能力接口:
|
||||
|
||||
```proto
|
||||
message CheckBusinessCapabilityRequest {
|
||||
RequestMeta meta = 1;
|
||||
int64 actor_user_id = 2;
|
||||
string capability = 3;
|
||||
}
|
||||
|
||||
message CheckBusinessCapabilityResponse {
|
||||
bool allowed = 1;
|
||||
string reason = 2;
|
||||
}
|
||||
```
|
||||
|
||||
首版 capability:
|
||||
|
||||
| Capability | Allowed When |
|
||||
| --- | --- |
|
||||
| `manager_center` | active Agency owner |
|
||||
| `manager_resource_grant` | active Agency owner |
|
||||
|
||||
后续如果出现 Agency 授权管理员,不改 gateway 和 wallet-service,只在 user-service host domain 增加 `agency_managers` 或对应 read model,并扩展 capability 判定。
|
||||
|
||||
## H5 App APIs
|
||||
|
||||
所有接口走 gateway `/api/v1` envelope。access token 解析、`RequestMeta.request_id`、`app_code`、`client_ip`、`user_agent` 由 gateway 透传到内部 gRPC。
|
||||
|
||||
### 赠送列表
|
||||
|
||||
```http
|
||||
GET /api/v1/manager-center/resource-grants/resources?resource_type=badge&page=1&page_size=20
|
||||
```
|
||||
|
||||
处理规则:
|
||||
|
||||
1. gateway 从 token 取 `actor_user_id`。
|
||||
2. gateway 调 user-service `CheckBusinessCapability(capability=manager_resource_grant)`。
|
||||
3. gateway 调 wallet-service `ListResources(active_only=true, manager_grant_only=true, resource_type, page, page_size)`。
|
||||
4. wallet-service 只返回 `status=active AND grantable=TRUE AND manager_grant_enabled=TRUE` 的资源。
|
||||
|
||||
响应字段只返回 H5 展示和提交所需字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"resource_id": "101",
|
||||
"resource_type": "badge",
|
||||
"name": "VIP Badge",
|
||||
"asset_url": "https://...",
|
||||
"preview_url": "https://...",
|
||||
"wallet_asset_type": "",
|
||||
"wallet_asset_amount": 0,
|
||||
"sort_order": 10
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
```
|
||||
|
||||
H5 不能从该接口获得后台审计字段、`created_by_user_id`、`updated_by_user_id` 或完整 `metadata_json`。如果某类资源需要额外展示配置,必须在 wallet-service 先定义稳定的展示 DTO,不能把原始后台 metadata 透出。
|
||||
|
||||
### 赠送给用户
|
||||
|
||||
```http
|
||||
POST /api/v1/manager-center/resource-grants
|
||||
```
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"command_id": "mgr-grant-20260511-0001",
|
||||
"target_user_id": "900001",
|
||||
"resource_id": "101",
|
||||
"reason": "manager_center"
|
||||
}
|
||||
```
|
||||
|
||||
处理规则:
|
||||
|
||||
1. gateway 校验当前用户拥有 `manager_resource_grant` capability。
|
||||
2. gateway 调 user-service `GetUser(target_user_id)`,确认目标用户属于同一 `app_code`、存在、未禁用、未封禁。
|
||||
3. gateway 不接受客户端提交 `quantity`、`duration_ms`、`wallet_asset_amount`。
|
||||
4. gateway 调 wallet-service `GrantResource`:
|
||||
- `grant_source=manager_center`
|
||||
- `operator_user_id=actor_user_id`
|
||||
- `target_user_id=resolved target`
|
||||
- `quantity=1`
|
||||
- `duration_ms=0`
|
||||
- `reason=manager_center`
|
||||
5. wallet-service 在同一事务中校验资源 `status=active`、`grantable=TRUE`、`manager_grant_enabled=TRUE`,再按现有资源策略落 `resource_grants`、`resource_grant_items`、钱包账本或用户资源权益。
|
||||
|
||||
wallet-service 不能只依赖赠送列表过滤。`GrantResource` 必须在写事务内根据 `grant_source=manager_center` 重新锁定资源并校验 `manager_grant_enabled`,防止客户端绕过列表直接提交资源 ID。
|
||||
|
||||
幂等规则沿用现有 `resource_grants.command_id`:
|
||||
|
||||
- 同一 `app_code + command_id`、同一 payload 重试返回原 grant。
|
||||
- 同一 `command_id` 换目标用户或资源返回冲突。
|
||||
- `request_id` 只做链路追踪,不能当幂等键。
|
||||
|
||||
## Common User Lookup
|
||||
|
||||
通用用户查询不要挂在经理中心专属模块里。建议 gateway 暴露统一入口:
|
||||
|
||||
```http
|
||||
GET /api/v1/business/users/lookup?scene=manager_resource_grant&keyword=123456&page_size=20
|
||||
```
|
||||
|
||||
user-service 新增或扩展查询 RPC:
|
||||
|
||||
```proto
|
||||
message BusinessUserLookupRequest {
|
||||
RequestMeta meta = 1;
|
||||
int64 actor_user_id = 2;
|
||||
string scene = 3;
|
||||
string keyword = 4;
|
||||
int32 page_size = 5;
|
||||
}
|
||||
|
||||
message BusinessUserLookupItem {
|
||||
int64 user_id = 1;
|
||||
string display_user_id = 2;
|
||||
string username = 3;
|
||||
string avatar = 4;
|
||||
UserStatus status = 5;
|
||||
int64 region_id = 6;
|
||||
string region_code = 7;
|
||||
string region_name = 8;
|
||||
}
|
||||
|
||||
message BusinessUserLookupResponse {
|
||||
repeated BusinessUserLookupItem users = 1;
|
||||
}
|
||||
```
|
||||
|
||||
scene 权限矩阵:
|
||||
|
||||
| Scene | Actor Requirement | Query Scope |
|
||||
| --- | --- | --- |
|
||||
| `manager_resource_grant` | `manager_resource_grant` capability | active users in same `app_code` |
|
||||
| `agency_application` | logged-in active user | active agencies or agency owner candidates,按后续申请流程定义 |
|
||||
| `bd_invite_agency` | active BD or BD Leader | same-region active users,不能已有 active Agency owner |
|
||||
| `bd_invite_bd` | active BD Leader | same-region active users,不能已有 active BD |
|
||||
|
||||
首版只实现 `manager_resource_grant`。未实现 scene 必须返回 `INVALID_ARGUMENT`,不能默认降级成全局用户搜索。
|
||||
|
||||
查询安全规则:
|
||||
|
||||
- `keyword` 必须是短号、用户 ID 或至少 2 个字符的昵称片段。
|
||||
- 数字 keyword 优先按 current display_user_id 精确匹配,再按 user_id 精确匹配。
|
||||
- `page_size` 最大 20。
|
||||
- 只返回必要展示字段,不返回手机号、登录方式、设备、邀请码绑定、钱包余额或三方身份。
|
||||
- 被禁用、封禁、跨 `app_code` 用户不返回。
|
||||
- gateway 对 lookup 和 grant 都要做限流;lookup 不能成为撞库枚举入口。
|
||||
|
||||
## Sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant H5 as H5 Manager Center
|
||||
participant G as gateway-service
|
||||
participant U as user-service
|
||||
participant W as wallet-service
|
||||
|
||||
H5->>G: GET /manager-center/resource-grants/resources
|
||||
G->>U: CheckBusinessCapability(actor, manager_resource_grant)
|
||||
U-->>G: allowed
|
||||
G->>W: ListResources(active_only, manager_grant_only)
|
||||
W-->>G: grantable resources
|
||||
G-->>H5: resources
|
||||
|
||||
H5->>G: GET /business/users/lookup?scene=manager_resource_grant
|
||||
G->>U: BusinessUserLookup(actor, scene, keyword)
|
||||
U-->>G: minimal users
|
||||
G-->>H5: users
|
||||
|
||||
H5->>G: POST /manager-center/resource-grants
|
||||
G->>U: CheckBusinessCapability(actor, manager_resource_grant)
|
||||
U-->>G: allowed
|
||||
G->>U: GetUser(target_user_id)
|
||||
U-->>G: active target
|
||||
G->>W: GrantResource(source=manager_center)
|
||||
W-->>G: resource grant
|
||||
G-->>H5: grant result
|
||||
```
|
||||
|
||||
## Validation Rules
|
||||
|
||||
| Rule | Owner | Error |
|
||||
| --- | --- | --- |
|
||||
| actor is not active manager | user-service | `PERMISSION_DENIED` |
|
||||
| lookup scene unknown | user-service | `INVALID_ARGUMENT` |
|
||||
| target user not found, disabled, banned, or different app | user-service/gateway | `NOT_FOUND` or `PERMISSION_DENIED` |
|
||||
| resource disabled | wallet-service | `CONFLICT` |
|
||||
| resource `grantable=FALSE` | wallet-service | `CONFLICT` |
|
||||
| resource `manager_grant_enabled=FALSE` and source is `manager_center` | wallet-service | `PERMISSION_DENIED` |
|
||||
| H5 submits quantity or duration | gateway | ignore or reject;首版建议 reject `INVALID_ARGUMENT` |
|
||||
| duplicate command_id with changed payload | wallet-service | `LEDGER_CONFLICT` |
|
||||
|
||||
## Event And Audit
|
||||
|
||||
- 后台切换 `manager_grant_enabled` 产出 `ResourceChanged` outbox,便于 H5 资源列表缓存刷新。
|
||||
- 经理中心赠送成功产出 `ResourceGranted` 和必要的 `UserResourceChanged`/`WalletBalanceChanged` 事件。
|
||||
- `resource_grants.grant_source='manager_center'` 是 App 赠送审计来源。
|
||||
- `operator_user_id` 记录发起赠送的经理用户 ID,不记录后台 admin 用户。
|
||||
- gateway 应在结构化日志记录 `request_id`、`actor_user_id`、`target_user_id`、`resource_id`、`grant_id` 和错误 reason。
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. `api/proto/wallet/v1/wallet.proto` 增加 `manager_grant_enabled` 和 `manager_grant_only`,运行 `make proto`。
|
||||
2. `wallet-service` MySQL 初始化脚本增加 `resources.manager_grant_enabled`,repository list/create/update/status/GrantResource 路径全部带字段和校验。
|
||||
3. `server/admin` resource DTO/request 增加 `managerGrantEnabled`;后台资源列表 switch 默认打开,编辑时透传。
|
||||
4. `user-service` 增加 `CheckBusinessCapability` 和 `BusinessUserLookup`,首版只开放 `manager_resource_grant` scene。
|
||||
5. `gateway-service` 增加 H5 经理中心三个入口:赠送资源列表、通用用户查询、赠送资源。
|
||||
6. 补 OpenAPI、前端生成代码和 H5 页面联调。
|
||||
7. 补测试:wallet manager switch、admin 透传、user lookup 权限、gateway H5 流程和幂等冲突。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- 新建资源不传 `managerGrantEnabled` 时,后台列表 switch 显示打开。
|
||||
- 后台关闭某资源赠送权限后,H5 经理中心赠送列表不返回该资源。
|
||||
- H5 绕过列表直接提交关闭权限的 `resource_id`,wallet-service 拒绝赠送。
|
||||
- 非经理用户调用赠送列表、用户查询或赠送接口均返回权限错误。
|
||||
- 经理用户可以查询目标用户并完成单资源赠送,`resource_grants.grant_source=manager_center`,`operator_user_id` 为经理用户 ID。
|
||||
- 同一 `command_id` 重试不重复发放;换目标或资源返回冲突。
|
||||
@ -208,7 +208,7 @@ Header: Sign: base64(hmacsha256(callback_sign_key, raw_body))
|
||||
| --- | --- | --- |
|
||||
| `EventGroupId=2, EventType=203` | `audio_started` | 当前麦位为 `pending_publish` 时确认成 `publishing` |
|
||||
| `EventGroupId=2, EventType=204` | `audio_stopped` | 释放当前用户麦位 |
|
||||
| `EventGroupId=1, EventType=104` | `room_exited` | 移除业务 presence,并释放麦位 |
|
||||
| `EventGroupId=1, EventType=104` | `room_exited` | 释放当前用户麦位,不移除业务 presence |
|
||||
|
||||
回调边界:
|
||||
|
||||
@ -216,7 +216,8 @@ Header: Sign: base64(hmacsha256(callback_sign_key, raw_body))
|
||||
- 房间状态变更只调用 room-service `ApplyRTCEvent`,仍进入 Room Cell、command log 和 outbox。
|
||||
- 未知事件在验签成功后返回 `{"code":0}` 并忽略,避免腾讯新增事件导致无意义重试。
|
||||
- room-service 暂时不可用时返回非 200,让腾讯 RTC 按平台策略重试。
|
||||
- 如果一个旧 `room_exited` 回调晚于新的业务心跳到达,room-service 会保留较新的业务 presence,避免旧回调清掉重连用户。
|
||||
- `room_exited` 只代表 RTC 媒体频道退出,用户仍可能停留在房间页重连;业务 presence 只能由 `LeaveRoom`、踢人、关房或 stale worker 清理。
|
||||
- 如果一个旧 `room_exited` 回调晚于新的业务心跳到达,room-service 会保留较新的业务 presence 和新麦位会话,避免旧回调清掉重连用户。
|
||||
|
||||
## Internal Flow
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ type resourceDTO struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Grantable bool `json:"grantable"`
|
||||
ManagerGrantEnabled bool `json:"managerGrantEnabled"`
|
||||
GrantStrategy string `json:"grantStrategy"`
|
||||
WalletAssetType string `json:"walletAssetType"`
|
||||
WalletAssetAmount int64 `json:"walletAssetAmount"`
|
||||
@ -120,6 +121,7 @@ func resourceFromProto(item *walletv1.Resource) resourceDTO {
|
||||
Name: item.GetName(),
|
||||
Status: item.GetStatus(),
|
||||
Grantable: item.GetGrantable(),
|
||||
ManagerGrantEnabled: item.GetManagerGrantEnabled(),
|
||||
GrantStrategy: item.GetGrantStrategy(),
|
||||
WalletAssetType: item.GetWalletAssetType(),
|
||||
WalletAssetAmount: item.GetWalletAssetAmount(),
|
||||
|
||||
@ -18,6 +18,7 @@ type resourceRequest struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Amount int64 `json:"amount"`
|
||||
ManagerGrantEnabled *bool `json:"managerGrantEnabled"`
|
||||
AssetURL string `json:"assetUrl"`
|
||||
PreviewURL string `json:"previewUrl"`
|
||||
SortOrder int32 `json:"sortOrder"`
|
||||
@ -88,6 +89,7 @@ func (r resourceRequest) createProto(c *gin.Context) *walletv1.CreateResourceReq
|
||||
Name: strings.TrimSpace(r.Name),
|
||||
Status: strings.TrimSpace(r.Status),
|
||||
Grantable: true,
|
||||
ManagerGrantEnabled: managerGrantEnabledOrDefault(r.ManagerGrantEnabled),
|
||||
GrantStrategy: resourceGrantStrategy(resourceType),
|
||||
WalletAssetType: walletAssetType,
|
||||
WalletAssetAmount: walletAssetAmount,
|
||||
@ -113,6 +115,7 @@ func (r resourceRequest) updateProto(c *gin.Context, resourceID int64) *walletv1
|
||||
Name: strings.TrimSpace(r.Name),
|
||||
Status: strings.TrimSpace(r.Status),
|
||||
Grantable: true,
|
||||
ManagerGrantEnabled: managerGrantEnabledOrDefault(r.ManagerGrantEnabled),
|
||||
GrantStrategy: resourceGrantStrategy(resourceType),
|
||||
WalletAssetType: walletAssetType,
|
||||
WalletAssetAmount: walletAssetAmount,
|
||||
@ -205,6 +208,14 @@ func (r giftRequest) updateProto(c *gin.Context, giftID string) *walletv1.Update
|
||||
}
|
||||
}
|
||||
|
||||
func managerGrantEnabledOrDefault(value *bool) *bool {
|
||||
enabled := true
|
||||
if value != nil {
|
||||
enabled = *value
|
||||
}
|
||||
return &enabled
|
||||
}
|
||||
|
||||
func groupItemInputs(items []resourceGroupItemRequest) []*walletv1.ResourceGroupItemInput {
|
||||
out := make([]*walletv1.ResourceGroupItemInput, 0, len(items))
|
||||
for _, item := range items {
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
// RoomClient 抽象 gateway 对 room-service 的 gRPC 编排依赖。
|
||||
type RoomClient interface {
|
||||
CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) (*roomv1.CreateRoomResponse, error)
|
||||
UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomProfileRequest) (*roomv1.UpdateRoomProfileResponse, error)
|
||||
JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error)
|
||||
RoomHeartbeat(ctx context.Context, req *roomv1.RoomHeartbeatRequest) (*roomv1.RoomHeartbeatResponse, error)
|
||||
LeaveRoom(ctx context.Context, req *roomv1.LeaveRoomRequest) (*roomv1.LeaveRoomResponse, error)
|
||||
@ -81,6 +82,10 @@ func (c *grpcRoomClient) CreateRoom(ctx context.Context, req *roomv1.CreateRoomR
|
||||
return c.client.CreateRoom(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcRoomClient) UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomProfileRequest) (*roomv1.UpdateRoomProfileResponse, error) {
|
||||
return c.client.UpdateRoomProfile(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcRoomClient) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error) {
|
||||
return c.client.JoinRoom(ctx, req)
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ type UserIdentityClient interface {
|
||||
// UserProfileClient 抽象 gateway 对 user-service 用户资料能力的依赖。
|
||||
type UserProfileClient interface {
|
||||
GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error)
|
||||
BusinessUserLookup(ctx context.Context, req *userv1.BusinessUserLookupRequest) (*userv1.BusinessUserLookupResponse, error)
|
||||
GetMyProfileStats(ctx context.Context, req *userv1.GetMyProfileStatsRequest) (*userv1.GetMyProfileStatsResponse, error)
|
||||
BatchGetUsers(ctx context.Context, req *userv1.BatchGetUsersRequest) (*userv1.BatchGetUsersResponse, error)
|
||||
CompleteOnboarding(ctx context.Context, req *userv1.CompleteOnboardingRequest) (*userv1.CompleteOnboardingResponse, error)
|
||||
@ -67,6 +68,7 @@ type UserHostClient interface {
|
||||
GetBDProfile(ctx context.Context, req *userv1.GetBDProfileRequest) (*userv1.GetBDProfileResponse, error)
|
||||
GetCoinSellerProfile(ctx context.Context, req *userv1.GetCoinSellerProfileRequest) (*userv1.GetCoinSellerProfileResponse, error)
|
||||
GetUserRoleSummary(ctx context.Context, req *userv1.GetUserRoleSummaryRequest) (*userv1.GetUserRoleSummaryResponse, error)
|
||||
CheckBusinessCapability(ctx context.Context, req *userv1.CheckBusinessCapabilityRequest) (*userv1.CheckBusinessCapabilityResponse, error)
|
||||
}
|
||||
|
||||
// AppRegistryClient 抽象 gateway 对 user-service App 注册表的解析依赖。
|
||||
@ -206,6 +208,10 @@ func (c *grpcUserProfileClient) GetUser(ctx context.Context, req *userv1.GetUser
|
||||
return c.client.GetUser(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcUserProfileClient) BusinessUserLookup(ctx context.Context, req *userv1.BusinessUserLookupRequest) (*userv1.BusinessUserLookupResponse, error) {
|
||||
return c.client.BusinessUserLookup(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcUserProfileClient) GetMyProfileStats(ctx context.Context, req *userv1.GetMyProfileStatsRequest) (*userv1.GetMyProfileStatsResponse, error) {
|
||||
return c.client.GetMyProfileStats(ctx, req)
|
||||
}
|
||||
@ -294,6 +300,10 @@ func (c *grpcUserHostClient) GetUserRoleSummary(ctx context.Context, req *userv1
|
||||
return c.client.GetUserRoleSummary(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcUserHostClient) CheckBusinessCapability(ctx context.Context, req *userv1.CheckBusinessCapabilityRequest) (*userv1.CheckBusinessCapabilityResponse, error) {
|
||||
return c.client.CheckBusinessCapability(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcAppRegistryClient) ResolveApp(ctx context.Context, req *userv1.ResolveAppRequest) (*userv1.ResolveAppResponse, error) {
|
||||
return c.client.ResolveApp(ctx, req)
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ type WalletClient interface {
|
||||
ListResources(ctx context.Context, req *walletv1.ListResourcesRequest) (*walletv1.ListResourcesResponse, error)
|
||||
GetResourceGroup(ctx context.Context, req *walletv1.GetResourceGroupRequest) (*walletv1.GetResourceGroupResponse, error)
|
||||
ListGiftConfigs(ctx context.Context, req *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error)
|
||||
GrantResource(ctx context.Context, req *walletv1.GrantResourceRequest) (*walletv1.ResourceGrantResponse, error)
|
||||
ListUserResources(ctx context.Context, req *walletv1.ListUserResourcesRequest) (*walletv1.ListUserResourcesResponse, error)
|
||||
EquipUserResource(ctx context.Context, req *walletv1.EquipUserResourceRequest) (*walletv1.EquipUserResourceResponse, error)
|
||||
}
|
||||
@ -93,6 +94,10 @@ func (c *grpcWalletClient) ListGiftConfigs(ctx context.Context, req *walletv1.Li
|
||||
return c.client.ListGiftConfigs(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcWalletClient) GrantResource(ctx context.Context, req *walletv1.GrantResourceRequest) (*walletv1.ResourceGrantResponse, error) {
|
||||
return c.client.GrantResource(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcWalletClient) ListUserResources(ctx context.Context, req *walletv1.ListUserResourcesRequest) (*walletv1.ListUserResourcesResponse, error) {
|
||||
return c.client.ListUserResources(ctx, req)
|
||||
}
|
||||
|
||||
@ -385,8 +385,8 @@ func (h *Handler) createRoom(writer http.ResponseWriter, request *http.Request)
|
||||
body.RoomName = strings.TrimSpace(body.RoomName)
|
||||
body.RoomAvatar = strings.TrimSpace(body.RoomAvatar)
|
||||
body.RoomDescription = strings.TrimSpace(body.RoomDescription)
|
||||
if body.SeatCount <= 0 || body.Mode == "" || body.RoomName == "" {
|
||||
// gateway mirrors room-service's hard requirements so client errors stay INVALID_ARGUMENT.
|
||||
if body.SeatCount < 0 || body.Mode == "" || body.RoomName == "" {
|
||||
// seat_count 可以省略走后台默认值;mode 和 room_name 仍是创建页的最小必填信息。
|
||||
writeError(writer, request, http.StatusBadRequest, codeInvalidArgument, "invalid argument")
|
||||
return
|
||||
}
|
||||
@ -419,6 +419,47 @@ func (h *Handler) createRoom(writer http.ResponseWriter, request *http.Request)
|
||||
write(writer, request, resp, err)
|
||||
}
|
||||
|
||||
// updateRoomProfile 把 App 房间资料编辑请求转换为 room-service UpdateRoomProfile 命令。
|
||||
func (h *Handler) updateRoomProfile(writer http.ResponseWriter, request *http.Request) {
|
||||
var body struct {
|
||||
RoomID string `json:"room_id"`
|
||||
CommandID string `json:"command_id"`
|
||||
RoomName *string `json:"room_name"`
|
||||
RoomAvatar *string `json:"room_avatar"`
|
||||
RoomDescription *string `json:"room_description"`
|
||||
SeatCount *int32 `json:"seat_count"`
|
||||
}
|
||||
|
||||
if !decode(writer, request, &body) {
|
||||
return
|
||||
}
|
||||
body.RoomID = strings.TrimSpace(body.RoomID)
|
||||
body.CommandID = strings.TrimSpace(body.CommandID)
|
||||
trimOptionalString(body.RoomName)
|
||||
trimOptionalString(body.RoomAvatar)
|
||||
trimOptionalString(body.RoomDescription)
|
||||
|
||||
resp, err := h.roomClient.UpdateRoomProfile(request.Context(), &roomv1.UpdateRoomProfileRequest{
|
||||
Meta: meta(request, body.RoomID, body.CommandID),
|
||||
RoomName: body.RoomName,
|
||||
RoomAvatar: body.RoomAvatar,
|
||||
RoomDescription: body.RoomDescription,
|
||||
SeatCount: body.SeatCount,
|
||||
})
|
||||
if err != nil {
|
||||
writeRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
writeOK(writer, request, updateRoomProfileDataFromProto(resp))
|
||||
}
|
||||
|
||||
func trimOptionalString(value *string) {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
*value = strings.TrimSpace(*value)
|
||||
}
|
||||
|
||||
// joinRoom 把进房 HTTP JSON 请求转换为 room-service JoinRoom 命令,并组装房间首屏初始化包。
|
||||
func (h *Handler) joinRoom(writer http.ResponseWriter, request *http.Request) {
|
||||
var body struct {
|
||||
|
||||
@ -51,6 +51,13 @@ type joinRoomData struct {
|
||||
ServerTimeMS int64 `json:"server_time_ms"`
|
||||
}
|
||||
|
||||
type updateRoomProfileData struct {
|
||||
Result roomCommandResultData `json:"result"`
|
||||
Room roomInitialData `json:"room"`
|
||||
Seats []roomSeatData `json:"seats"`
|
||||
ServerTimeMS int64 `json:"server_time_ms"`
|
||||
}
|
||||
|
||||
type roomCommandResultData struct {
|
||||
Applied bool `json:"applied"`
|
||||
RoomVersion int64 `json:"room_version"`
|
||||
@ -176,6 +183,19 @@ func commandResultDataFromProto(result *roomv1.CommandResult) roomCommandResultD
|
||||
}
|
||||
}
|
||||
|
||||
func updateRoomProfileDataFromProto(resp *roomv1.UpdateRoomProfileResponse) updateRoomProfileData {
|
||||
if resp == nil {
|
||||
return updateRoomProfileData{}
|
||||
}
|
||||
snapshot := resp.GetRoom()
|
||||
return updateRoomProfileData{
|
||||
Result: commandResultDataFromProto(resp.GetResult()),
|
||||
Room: roomInitialRoomDataFromSnapshot(snapshot, snapshot.GetRoomId()),
|
||||
Seats: roomSeatDataFromSnapshot(snapshot),
|
||||
ServerTimeMS: resp.GetResult().GetServerTimeMs(),
|
||||
}
|
||||
}
|
||||
|
||||
func roomInitialRoomDataFromSnapshot(snapshot *roomv1.RoomSnapshot, roomID string) roomInitialData {
|
||||
if snapshot == nil {
|
||||
return roomInitialData{RoomID: roomID, IMGroupID: roomIMGroupID(roomID)}
|
||||
|
||||
@ -59,6 +59,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler {
|
||||
mux.Handle(apiV1Prefix+"/rooms/current", h.profileAPIHandler(jwtVerifier, requireMethod(http.MethodGet, h.getCurrentRoom)))
|
||||
mux.Handle(apiV1Prefix+"/rooms/snapshot", h.profileAPIHandler(jwtVerifier, requireMethod(http.MethodGet, h.getRoomSnapshot)))
|
||||
mux.Handle(apiV1Prefix+"/rooms/create", h.profileAPIHandler(jwtVerifier, requireMethod(http.MethodPost, h.createRoom)))
|
||||
mux.Handle(apiV1Prefix+"/rooms/profile/update", h.profileAPIHandler(jwtVerifier, requireMethod(http.MethodPost, h.updateRoomProfile)))
|
||||
mux.Handle(apiV1Prefix+"/rooms/join", h.profileAPIHandler(jwtVerifier, requireMethod(http.MethodPost, h.joinRoom)))
|
||||
mux.Handle(apiV1Prefix+"/rooms/heartbeat", h.profileAPIHandler(jwtVerifier, requireMethod(http.MethodPost, h.roomHeartbeat)))
|
||||
mux.Handle(apiV1Prefix+"/rooms/leave", h.profileAPIHandler(jwtVerifier, requireMethod(http.MethodPost, h.leaveRoom)))
|
||||
|
||||
@ -120,3 +120,26 @@ CREATE TABLE IF NOT EXISTS room_user_feed_entries (
|
||||
PRIMARY KEY (app_code, user_id, feed_type, room_id),
|
||||
KEY idx_room_user_feed_list (app_code, user_id, feed_type, updated_at_ms DESC, room_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS room_seat_configs (
|
||||
app_code VARCHAR(32) NOT NULL,
|
||||
allowed_seat_counts VARCHAR(128) NOT NULL,
|
||||
default_seat_count INT NOT NULL,
|
||||
created_at_ms BIGINT NOT NULL,
|
||||
updated_at_ms BIGINT NOT NULL,
|
||||
PRIMARY KEY (app_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
INSERT IGNORE INTO room_seat_configs (
|
||||
app_code,
|
||||
allowed_seat_counts,
|
||||
default_seat_count,
|
||||
created_at_ms,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
'lalu',
|
||||
'[10,15,20,25,30]',
|
||||
15,
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
@ -153,6 +153,20 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room
|
||||
base.SeatNo = body.GetSeatNo()
|
||||
base.Attributes = map[string]string{"locked": fmt.Sprintf("%t", body.GetLocked())}
|
||||
return base, true, nil
|
||||
case "RoomProfileUpdated":
|
||||
// 房间资料变更会影响标题、头像、简介和麦位布局,客户端收到后应以 room_version 更新本地 UI。
|
||||
var body roomeventsv1.RoomProfileUpdated
|
||||
if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
|
||||
return tencentim.RoomEvent{}, false, err
|
||||
}
|
||||
base.ActorUserID = body.GetActorUserId()
|
||||
base.Attributes = map[string]string{
|
||||
"room_name": body.GetRoomName(),
|
||||
"room_avatar": body.GetRoomAvatar(),
|
||||
"room_description": body.GetRoomDescription(),
|
||||
"seat_count": fmt.Sprintf("%d", body.GetSeatCount()),
|
||||
}
|
||||
return base, true, nil
|
||||
case "RoomChatEnabledChanged":
|
||||
// 公屏开关事件提示客户端更新输入态,真正发言仍由 CheckSpeakPermission 拦截。
|
||||
var body roomeventsv1.RoomChatEnabledChanged
|
||||
@ -245,6 +259,8 @@ func eventTypeForClient(eventType string) string {
|
||||
return "room_mic_changed"
|
||||
case "RoomMicSeatLocked":
|
||||
return "room_mic_seat_locked"
|
||||
case "RoomProfileUpdated":
|
||||
return "room_profile_updated"
|
||||
case "RoomChatEnabledChanged":
|
||||
return "room_chat_enabled_changed"
|
||||
case "RoomAdminChanged":
|
||||
|
||||
@ -79,6 +79,22 @@ type CreateRoom struct {
|
||||
// Type 返回命令类型。
|
||||
func (CreateRoom) Type() string { return "create_room" }
|
||||
|
||||
// UpdateRoomProfile 定义房主修改房间展示资料和麦位数量的请求。
|
||||
type UpdateRoomProfile struct {
|
||||
Base
|
||||
// RoomName 非 nil 表示更新房间展示名称;空字符串由服务层校验拒绝。
|
||||
RoomName *string `json:"room_name,omitempty"`
|
||||
// RoomAvatar 非 nil 表示更新房间头像;空字符串会恢复为系统默认头像。
|
||||
RoomAvatar *string `json:"room_avatar,omitempty"`
|
||||
// RoomDescription 非 nil 表示更新房间简介,允许清空。
|
||||
RoomDescription *string `json:"room_description,omitempty"`
|
||||
// SeatCount 非 nil 表示调整麦位总数,值必须来自后台启用配置。
|
||||
SeatCount *int32 `json:"seat_count,omitempty"`
|
||||
}
|
||||
|
||||
// Type 返回命令类型。
|
||||
func (UpdateRoomProfile) Type() string { return "update_room_profile" }
|
||||
|
||||
// JoinRoom 定义业务进房请求。
|
||||
type JoinRoom struct {
|
||||
Base
|
||||
@ -338,6 +354,8 @@ func Deserialize(commandType string, payload []byte) (Command, error) {
|
||||
switch commandType {
|
||||
case CreateRoom{}.Type():
|
||||
cmd = &CreateRoom{}
|
||||
case UpdateRoomProfile{}.Type():
|
||||
cmd = &UpdateRoomProfile{}
|
||||
case JoinRoom{}.Type():
|
||||
cmd = &JoinRoom{}
|
||||
case RoomHeartbeat{}.Type():
|
||||
|
||||
@ -60,6 +60,10 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seatCount, err := s.resolveRoomSeatCount(ctx, req.GetSeatCount())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actorUserID := req.GetMeta().GetActorUserId()
|
||||
if actorUserID == 0 {
|
||||
// CreateRoom 的 owner/host 默认来自已鉴权调用方,不允许匿名内部命令造房间。
|
||||
@ -74,7 +78,7 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest)
|
||||
Base: baseFromMeta(req.GetMeta()),
|
||||
OwnerUserID: actorUserID,
|
||||
HostUserID: actorUserID,
|
||||
SeatCount: req.GetSeatCount(),
|
||||
SeatCount: seatCount,
|
||||
Mode: mode,
|
||||
VisibleRegionID: normalizeVisibleRegionID(req.GetVisibleRegionId()),
|
||||
RoomName: profile.Name,
|
||||
@ -83,11 +87,6 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest)
|
||||
RoomShortID: normalizeRoomShortID(req.GetRoomShortId(), actorUserID),
|
||||
}
|
||||
|
||||
if cmd.SeatCount <= 0 {
|
||||
// 没有麦位的房间不符合语音房 v1 的状态模型。
|
||||
return nil, xerr.New(xerr.InvalidArgument, "seat_count must be positive")
|
||||
}
|
||||
|
||||
payload, seen, err := s.commandPayloadForIdempotency(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -14,3 +14,8 @@ func int64String(value int64) string {
|
||||
// 用户 ID 等数值进入 attributes 时保持十进制字符串,和 IM identifier 映射一致。
|
||||
return strconv.FormatInt(value, 10)
|
||||
}
|
||||
|
||||
func int32String(value int32) string {
|
||||
// 麦位编号和座位总数同样使用十进制字符串,避免客户端解析多种数字格式。
|
||||
return strconv.FormatInt(int64(value), 10)
|
||||
}
|
||||
|
||||
@ -84,6 +84,7 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl
|
||||
},
|
||||
OutboxRecords: outboxRecords,
|
||||
RoomStatus: result.roomStatus,
|
||||
RoomSeatCount: result.roomSeatCount,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
195
services/room-service/internal/room/service/profile.go
Normal file
195
services/room-service/internal/room/service/profile.go
Normal file
@ -0,0 +1,195 @@
|
||||
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/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"
|
||||
)
|
||||
|
||||
// UpdateRoomProfile 修改房间展示资料和麦位数量,所有状态变化仍由 Room Cell 串行提交。
|
||||
func (s *Service) UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomProfileRequest) (*roomv1.UpdateRoomProfileResponse, error) {
|
||||
ctx = contextFromMeta(ctx, req.GetMeta())
|
||||
patch, err := s.normalizeUpdateRoomProfile(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := command.UpdateRoomProfile{
|
||||
Base: baseFromMeta(req.GetMeta()),
|
||||
RoomName: patch.Name,
|
||||
RoomAvatar: patch.Avatar,
|
||||
RoomDescription: patch.Description,
|
||||
SeatCount: patch.SeatCount,
|
||||
}
|
||||
|
||||
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 err := requireOwnerPresent(current, cmd.ActorUserID()); err != nil {
|
||||
return mutationResult{}, nil, err
|
||||
}
|
||||
|
||||
profileChanged := applyProfilePatch(current, cmd)
|
||||
seatChanged, err := applySeatCountPatch(current, cmd.SeatCount)
|
||||
if err != nil {
|
||||
return mutationResult{}, nil, err
|
||||
}
|
||||
if !profileChanged && !seatChanged {
|
||||
// 未提交任何字段变化时仍通过 command log 保护 command_id 幂等,但不广播系统消息。
|
||||
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
||||
}
|
||||
|
||||
current.Version++
|
||||
seatCount := int32(len(current.MicSeats))
|
||||
profileEvent, err := outbox.Build(current.RoomID, "RoomProfileUpdated", current.Version, now, &roomeventsv1.RoomProfileUpdated{
|
||||
ActorUserId: cmd.ActorUserID(),
|
||||
RoomName: current.RoomExt[roomExtTitleKey],
|
||||
RoomAvatar: current.RoomExt[roomExtCoverURLKey],
|
||||
RoomDescription: current.RoomExt[roomExtDescriptionKey],
|
||||
SeatCount: seatCount,
|
||||
})
|
||||
if err != nil {
|
||||
return mutationResult{}, nil, err
|
||||
}
|
||||
|
||||
result := mutationResult{
|
||||
snapshot: current.ToProto(),
|
||||
syncEvent: &tencentim.RoomEvent{
|
||||
EventID: profileEvent.EventID,
|
||||
RoomID: current.RoomID,
|
||||
EventType: "room_profile_updated",
|
||||
ActorUserID: cmd.ActorUserID(),
|
||||
RoomVersion: current.Version,
|
||||
Attributes: map[string]string{
|
||||
"room_name": current.RoomExt[roomExtTitleKey],
|
||||
"room_avatar": current.RoomExt[roomExtCoverURLKey],
|
||||
"room_description": current.RoomExt[roomExtDescriptionKey],
|
||||
"seat_count": int32String(seatCount),
|
||||
},
|
||||
},
|
||||
}
|
||||
if seatChanged {
|
||||
result.roomSeatCount = &seatCount
|
||||
}
|
||||
return result, []outbox.Record{profileEvent}, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &roomv1.UpdateRoomProfileResponse{
|
||||
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
||||
Room: result.snapshot,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type updateRoomProfilePatch struct {
|
||||
Name *string
|
||||
Avatar *string
|
||||
Description *string
|
||||
SeatCount *int32
|
||||
}
|
||||
|
||||
func (s *Service) normalizeUpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomProfileRequest) (updateRoomProfilePatch, error) {
|
||||
var patch updateRoomProfilePatch
|
||||
if req == nil {
|
||||
return patch, xerr.New(xerr.InvalidArgument, "request is required")
|
||||
}
|
||||
if req.RoomName != nil {
|
||||
value := strings.TrimSpace(req.GetRoomName())
|
||||
if value == "" {
|
||||
return patch, xerr.New(xerr.InvalidArgument, "room_name is required")
|
||||
}
|
||||
if utf8.RuneCountInString(value) > maxRoomNameRunes {
|
||||
return patch, xerr.New(xerr.InvalidArgument, "room_name is too long")
|
||||
}
|
||||
patch.Name = &value
|
||||
}
|
||||
if req.RoomAvatar != nil {
|
||||
value := strings.TrimSpace(req.GetRoomAvatar())
|
||||
if value == "" {
|
||||
// 房主清空头像时恢复系统默认资源,避免列表卡片落空。
|
||||
value = defaultRoomAvatar
|
||||
}
|
||||
if utf8.RuneCountInString(value) > maxRoomAvatarRunes {
|
||||
return patch, xerr.New(xerr.InvalidArgument, "room_avatar is too long")
|
||||
}
|
||||
patch.Avatar = &value
|
||||
}
|
||||
if req.RoomDescription != nil {
|
||||
value := strings.TrimSpace(req.GetRoomDescription())
|
||||
if utf8.RuneCountInString(value) > maxRoomDescriptionRunes {
|
||||
return patch, xerr.New(xerr.InvalidArgument, "room_description is too long")
|
||||
}
|
||||
patch.Description = &value
|
||||
}
|
||||
if req.SeatCount != nil {
|
||||
value, err := s.validateRoomSeatCount(ctx, req.GetSeatCount())
|
||||
if err != nil {
|
||||
return patch, err
|
||||
}
|
||||
patch.SeatCount = &value
|
||||
}
|
||||
return patch, nil
|
||||
}
|
||||
|
||||
func applyProfilePatch(current *state.RoomState, cmd command.UpdateRoomProfile) bool {
|
||||
if current.RoomExt == nil {
|
||||
current.RoomExt = make(map[string]string)
|
||||
}
|
||||
changed := false
|
||||
if cmd.RoomName != nil && current.RoomExt[roomExtTitleKey] != *cmd.RoomName {
|
||||
current.RoomExt[roomExtTitleKey] = *cmd.RoomName
|
||||
changed = true
|
||||
}
|
||||
if cmd.RoomAvatar != nil && current.RoomExt[roomExtCoverURLKey] != *cmd.RoomAvatar {
|
||||
current.RoomExt[roomExtCoverURLKey] = *cmd.RoomAvatar
|
||||
changed = true
|
||||
}
|
||||
if cmd.RoomDescription != nil && current.RoomExt[roomExtDescriptionKey] != *cmd.RoomDescription {
|
||||
current.RoomExt[roomExtDescriptionKey] = *cmd.RoomDescription
|
||||
changed = true
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
func applySeatCountPatch(current *state.RoomState, seatCount *int32) (bool, error) {
|
||||
if seatCount == nil {
|
||||
return false, nil
|
||||
}
|
||||
target := *seatCount
|
||||
currentCount := int32(len(current.MicSeats))
|
||||
if target == currentCount {
|
||||
return false, nil
|
||||
}
|
||||
if target > currentCount {
|
||||
for seatNo := currentCount + 1; seatNo <= target; seatNo++ {
|
||||
current.MicSeats = append(current.MicSeats, state.MicSeat{SeatNo: seatNo})
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
nextSeats := make([]state.MicSeat, 0, target)
|
||||
for _, seat := range current.MicSeats {
|
||||
if seat.SeatNo <= target {
|
||||
nextSeats = append(nextSeats, seat)
|
||||
continue
|
||||
}
|
||||
if seat.UserID != 0 || seat.Locked || seat.PublishState != "" || seat.MicSessionID != "" {
|
||||
// 缩容不能隐式踢人、清理发流会话或解锁麦位;房主需要先把高编号麦位处理干净。
|
||||
return false, xerr.New(xerr.Conflict, "removed seats must be empty and unlocked")
|
||||
}
|
||||
}
|
||||
current.MicSeats = nextSeats
|
||||
return true, nil
|
||||
}
|
||||
@ -126,6 +126,13 @@ func replay(current *state.RoomState, cmd command.Command) error {
|
||||
})
|
||||
// 创建命令的 owner/mode 等基础 meta 已由 RoomMeta 或快照恢复,这里只保证版本进入初始提交态。
|
||||
current.Version = 1
|
||||
case *command.UpdateRoomProfile:
|
||||
// 房间资料和麦位总数都属于 Room Cell 状态;恢复时只重放已提交的确定性结果。
|
||||
applyProfilePatch(current, *typed)
|
||||
if _, err := applySeatCountPatch(current, typed.SeatCount); err != nil {
|
||||
return err
|
||||
}
|
||||
current.Version++
|
||||
case *command.JoinRoom:
|
||||
if existing, exists := current.OnlineUsers[typed.ActorUserID()]; exists {
|
||||
// 重复 JoinRoom 回放只刷新 last_seen,不能重置首次进房时间或降级 owner 角色。
|
||||
@ -214,7 +221,9 @@ func replay(current *state.RoomState, cmd command.Command) error {
|
||||
current.ClearMicSession(index)
|
||||
current.Version++
|
||||
case rtcEventRoomExited:
|
||||
delete(current.OnlineUsers, typed.TargetUserID)
|
||||
// room_exited 是 RTC 媒体频道退出事实,不是 App 业务离房事实。
|
||||
// 实时路径只释放麦位,业务 presence 只能由 LeaveRoom、KickUser、CloseRoom 或 stale worker 清理;
|
||||
// replay 必须保持相同语义,否则无最新 snapshot 的恢复会把仍在房间页的用户误删。
|
||||
if seat, exists := current.SeatByUser(typed.TargetUserID); exists {
|
||||
index := current.SeatIndex(seat.SeatNo)
|
||||
current.ClearMicSession(index)
|
||||
|
||||
@ -61,6 +61,8 @@ type MutationCommit struct {
|
||||
OutboxRecords []outbox.Record
|
||||
// RoomStatus 非空时同步更新 rooms 和 room_list_entries 的生命周期状态。
|
||||
RoomStatus string
|
||||
// RoomSeatCount 非 nil 时同步更新 rooms 元数据中的麦位总数。
|
||||
RoomSeatCount *int32
|
||||
}
|
||||
|
||||
// SnapshotRecord 对应 room_snapshots 中的一条最新快照。
|
||||
@ -117,6 +119,18 @@ type RoomListEntry struct {
|
||||
FeedSubjectUserID int64
|
||||
}
|
||||
|
||||
// RoomSeatConfig 是后台房间配置里控制创建/修改房间座位数的低频配置。
|
||||
type RoomSeatConfig struct {
|
||||
// AppCode 是配置所属 App。
|
||||
AppCode string
|
||||
// AllowedSeatCounts 是当前 App 允许客户端使用的麦位总数。
|
||||
AllowedSeatCounts []int32
|
||||
// DefaultSeatCount 是 CreateRoom 未传 seat_count 时使用的默认麦位总数。
|
||||
DefaultSeatCount int32
|
||||
// UpdatedAtMS 是配置最后更新时间,主要用于后台展示和排障。
|
||||
UpdatedAtMS int64
|
||||
}
|
||||
|
||||
// RoomPresence 是当前用户可恢复房间的轻量读模型。
|
||||
// 它从 RoomSnapshot 投影而来,只用于 App 启动/回前台探测,不替代 Room Cell 权威状态。
|
||||
type RoomPresence struct {
|
||||
@ -246,6 +260,10 @@ type Repository interface {
|
||||
SaveCommand(ctx context.Context, record CommandRecord) error
|
||||
// SaveMutation 在同一事务中提交命令日志、outbox 事件和可选房间生命周期状态。
|
||||
SaveMutation(ctx context.Context, commit MutationCommit) error
|
||||
// GetRoomSeatConfig 读取后台配置的房间座位数规则;不存在时 service 层使用内置默认值。
|
||||
GetRoomSeatConfig(ctx context.Context) (RoomSeatConfig, bool, error)
|
||||
// UpsertRoomSeatConfig 写入房间座位数配置,供本地验证和后台配置共享同一张表。
|
||||
UpsertRoomSeatConfig(ctx context.Context, config RoomSeatConfig) error
|
||||
// ListCommandsAfter 读取快照版本之后的命令,恢复时按版本顺序回放。
|
||||
ListCommandsAfter(ctx context.Context, roomID string, roomVersion int64) ([]CommandRecord, error)
|
||||
// SaveSnapshot 保存最新房间快照,允许覆盖较低版本号快照但不能倒退。
|
||||
|
||||
110
services/room-service/internal/room/service/seat_config.go
Normal file
110
services/room-service/internal/room/service/seat_config.go
Normal file
@ -0,0 +1,110 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"hyapp/pkg/appcode"
|
||||
"hyapp/pkg/xerr"
|
||||
)
|
||||
|
||||
const defaultRoomSeatCount int32 = 15
|
||||
|
||||
var roomSeatCountCandidates = []int32{10, 15, 20, 25, 30}
|
||||
|
||||
func defaultRoomSeatConfig(appCode string) RoomSeatConfig {
|
||||
allowed := append([]int32(nil), roomSeatCountCandidates...)
|
||||
return RoomSeatConfig{
|
||||
AppCode: appcode.Normalize(appCode),
|
||||
AllowedSeatCounts: allowed,
|
||||
DefaultSeatCount: defaultRoomSeatCount,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) resolveRoomSeatCount(ctx context.Context, requested int32) (int32, error) {
|
||||
if requested < 0 {
|
||||
// 负数没有业务语义,不能让它落到默认值分支掩盖客户端错误。
|
||||
return 0, xerr.New(xerr.InvalidArgument, "seat_count is invalid")
|
||||
}
|
||||
|
||||
config, err := s.currentRoomSeatConfig(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if requested == 0 {
|
||||
return config.DefaultSeatCount, nil
|
||||
}
|
||||
if !seatCountAllowed(config.AllowedSeatCounts, requested) {
|
||||
return 0, xerr.New(xerr.InvalidArgument, fmt.Sprintf("seat_count must be one of %v", config.AllowedSeatCounts))
|
||||
}
|
||||
|
||||
return requested, nil
|
||||
}
|
||||
|
||||
func (s *Service) validateRoomSeatCount(ctx context.Context, requested int32) (int32, error) {
|
||||
if requested <= 0 {
|
||||
return 0, xerr.New(xerr.InvalidArgument, "seat_count is invalid")
|
||||
}
|
||||
config, err := s.currentRoomSeatConfig(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !seatCountAllowed(config.AllowedSeatCounts, requested) {
|
||||
return 0, xerr.New(xerr.InvalidArgument, fmt.Sprintf("seat_count must be one of %v", config.AllowedSeatCounts))
|
||||
}
|
||||
return requested, nil
|
||||
}
|
||||
|
||||
func (s *Service) currentRoomSeatConfig(ctx context.Context) (RoomSeatConfig, error) {
|
||||
config, exists, err := s.repository.GetRoomSeatConfig(ctx)
|
||||
if err != nil {
|
||||
return RoomSeatConfig{}, err
|
||||
}
|
||||
if !exists {
|
||||
// 本地新库会通过 initdb/Migrate 写默认行;该兜底保护测试或手动库缺行时仍能按产品默认创建房间。
|
||||
return defaultRoomSeatConfig(appcode.FromContext(ctx)), nil
|
||||
}
|
||||
normalized, err := normalizeRoomSeatConfig(config)
|
||||
if err != nil {
|
||||
return RoomSeatConfig{}, xerr.New(xerr.Unavailable, "room seat config is invalid")
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func normalizeRoomSeatConfig(config RoomSeatConfig) (RoomSeatConfig, error) {
|
||||
seen := make(map[int32]bool, len(config.AllowedSeatCounts))
|
||||
allowed := make([]int32, 0, len(config.AllowedSeatCounts))
|
||||
for _, value := range config.AllowedSeatCounts {
|
||||
if seen[value] {
|
||||
continue
|
||||
}
|
||||
if !seatCountAllowed(roomSeatCountCandidates, value) {
|
||||
return RoomSeatConfig{}, fmt.Errorf("seat count candidate is invalid: %d", value)
|
||||
}
|
||||
seen[value] = true
|
||||
allowed = append(allowed, value)
|
||||
}
|
||||
sort.Slice(allowed, func(i int, j int) bool {
|
||||
return allowed[i] < allowed[j]
|
||||
})
|
||||
if len(allowed) == 0 {
|
||||
return RoomSeatConfig{}, fmt.Errorf("allowed seat counts are empty")
|
||||
}
|
||||
if !seatCountAllowed(allowed, config.DefaultSeatCount) {
|
||||
return RoomSeatConfig{}, fmt.Errorf("default seat count is not allowed: %d", config.DefaultSeatCount)
|
||||
}
|
||||
|
||||
config.AllowedSeatCounts = allowed
|
||||
config.AppCode = appcode.Normalize(config.AppCode)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func seatCountAllowed(values []int32, target int32) bool {
|
||||
for _, value := range values {
|
||||
if value == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -99,6 +99,8 @@ type mutationResult struct {
|
||||
outboxRecords []outbox.Record
|
||||
// roomStatus 非空时代表命令提交时需要同步更新 rooms 和列表投影生命周期状态。
|
||||
roomStatus string
|
||||
// roomSeatCount 非 nil 时代表命令提交时需要同步更新 rooms 元数据座位数。
|
||||
roomSeatCount *int32
|
||||
}
|
||||
|
||||
// New 初始化 room-service 领域服务。
|
||||
|
||||
@ -1111,6 +1111,58 @@ func TestOldRTCRoomExitedDoesNotClearRejoinedUserOrNewMicSession(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRTCRoomExitedRecoveryKeepsBusinessPresence(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repository := mysqltest.NewRepository(t)
|
||||
directory := router.NewMemoryDirectory()
|
||||
usedClock := &mutableClock{now: time.UnixMilli(1_700_000_000_000)}
|
||||
serviceA := newRoomServiceWithClock("node-a", 100, directory, repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{}, usedClock, time.Minute)
|
||||
roomID := "room-rtc-exit-recovery-presence"
|
||||
|
||||
if _, err := serviceA.CreateRoom(ctx, &roomv1.CreateRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatCount: 4, Mode: "voice", RoomName: "Test Room"}); err != nil {
|
||||
t.Fatalf("CreateRoom failed: %v", err)
|
||||
}
|
||||
if _, err := serviceA.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); err != nil {
|
||||
t.Fatalf("JoinRoom user2 failed: %v", err)
|
||||
}
|
||||
if _, err := serviceA.MicUp(ctx, &roomv1.MicUpRequest{Meta: roomservice.NewRequestMeta(roomID, 2), SeatNo: 1}); err != nil {
|
||||
t.Fatalf("MicUp user2 failed: %v", err)
|
||||
}
|
||||
|
||||
usedClock.now = usedClock.now.Add(time.Second)
|
||||
exitResp, err := serviceA.ApplyRTCEvent(ctx, &roomv1.ApplyRTCEventRequest{
|
||||
Meta: roomservice.NewRequestMeta(roomID, 2),
|
||||
TargetUserId: 2,
|
||||
EventType: "room_exited",
|
||||
EventTimeMs: usedClock.now.UnixMilli(),
|
||||
Reason: "rtc_room_exited:recovery",
|
||||
Source: "tencent_rtc_callback",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyRTCEvent room_exited failed: %v", err)
|
||||
}
|
||||
if user := findRoomUser(exitResp.GetRoom(), 2); user == nil {
|
||||
t.Fatalf("real-time room_exited must keep business presence")
|
||||
}
|
||||
if seat := findSeat(exitResp.GetRoom(), 1); seat == nil || seat.GetUserId() != 0 {
|
||||
t.Fatalf("real-time room_exited should release mic seat: seat=%+v", seat)
|
||||
}
|
||||
|
||||
// snapshotEveryN=100 保证 room_exited 后没有最新快照;node-b 必须通过 command log replay 恢复同一语义。
|
||||
directory.ForceExpire(roomRouteKeyForTest(roomID))
|
||||
serviceB := newRoomServiceWithClock("node-b", 100, directory, repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{}, usedClock, time.Minute)
|
||||
recoveredResp, err := serviceB.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 3)})
|
||||
if err != nil {
|
||||
t.Fatalf("JoinRoom on recovered service failed: %v", err)
|
||||
}
|
||||
if user := findRoomUser(recoveredResp.GetRoom(), 2); user == nil {
|
||||
t.Fatalf("replayed room_exited must keep business presence")
|
||||
}
|
||||
if seat := findSeat(recoveredResp.GetRoom(), 1); seat == nil || seat.GetUserId() != 0 || seat.GetMicSessionId() != "" {
|
||||
t.Fatalf("replayed room_exited should keep released mic seat: seat=%+v", seat)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMicPublishTimeoutMicDownsPendingSession(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repository := mysqltest.NewRepository(t)
|
||||
|
||||
@ -4,6 +4,7 @@ package mysql
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -178,6 +179,16 @@ func (r *Repository) Migrate(ctx context.Context) error {
|
||||
PRIMARY KEY (app_code, user_id, feed_type, room_id),
|
||||
KEY idx_room_user_feed_list (app_code, user_id, feed_type, updated_at_ms DESC, room_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
||||
`CREATE TABLE IF NOT EXISTS room_seat_configs (
|
||||
app_code VARCHAR(32) NOT NULL,
|
||||
allowed_seat_counts VARCHAR(128) NOT NULL,
|
||||
default_seat_count INT NOT NULL,
|
||||
created_at_ms BIGINT NOT NULL,
|
||||
updated_at_ms BIGINT NOT NULL,
|
||||
PRIMARY KEY (app_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
||||
`INSERT IGNORE INTO room_seat_configs (app_code, allowed_seat_counts, default_seat_count, created_at_ms, updated_at_ms)
|
||||
VALUES ('lalu', '[10,15,20,25,30]', 15, 0, 0)`,
|
||||
`ALTER TABLE rooms MODIFY COLUMN app_code VARCHAR(32) NOT NULL`,
|
||||
`ALTER TABLE room_list_entries MODIFY COLUMN app_code VARCHAR(32) NOT NULL`,
|
||||
`ALTER TABLE room_user_presence MODIFY COLUMN app_code VARCHAR(32) NOT NULL`,
|
||||
@ -185,6 +196,7 @@ func (r *Repository) Migrate(ctx context.Context) error {
|
||||
`ALTER TABLE room_command_log MODIFY COLUMN app_code VARCHAR(32) NOT NULL`,
|
||||
`ALTER TABLE room_outbox MODIFY COLUMN app_code VARCHAR(32) NOT NULL`,
|
||||
`ALTER TABLE room_user_feed_entries MODIFY COLUMN app_code VARCHAR(32) NOT NULL`,
|
||||
`ALTER TABLE room_seat_configs MODIFY COLUMN app_code VARCHAR(32) NOT NULL`,
|
||||
}
|
||||
|
||||
for _, statement := range statements {
|
||||
@ -401,10 +413,87 @@ func (r *Repository) SaveMutation(ctx context.Context, commit roomservice.Mutati
|
||||
return err
|
||||
}
|
||||
}
|
||||
if commit.RoomSeatCount != nil {
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
`UPDATE rooms
|
||||
SET seat_count = ?, updated_at_ms = ?
|
||||
WHERE app_code = ? AND room_id = ?`,
|
||||
*commit.RoomSeatCount,
|
||||
commit.Command.CreatedAtMS,
|
||||
appCode,
|
||||
commit.Command.RoomID,
|
||||
); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
`UPDATE room_list_entries
|
||||
SET seat_count = ?, updated_at_ms = ?
|
||||
WHERE app_code = ? AND room_id = ?`,
|
||||
*commit.RoomSeatCount,
|
||||
commit.Command.CreatedAtMS,
|
||||
appCode,
|
||||
commit.Command.RoomID,
|
||||
); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// GetRoomSeatConfig 读取当前 App 的房间座位数配置。
|
||||
func (r *Repository) GetRoomSeatConfig(ctx context.Context) (roomservice.RoomSeatConfig, bool, error) {
|
||||
row := r.db.QueryRowContext(ctx,
|
||||
`SELECT app_code, allowed_seat_counts, default_seat_count, updated_at_ms
|
||||
FROM room_seat_configs
|
||||
WHERE app_code = ?`,
|
||||
appcode.FromContext(ctx),
|
||||
)
|
||||
|
||||
var config roomservice.RoomSeatConfig
|
||||
var rawAllowed string
|
||||
if err := row.Scan(&config.AppCode, &rawAllowed, &config.DefaultSeatCount, &config.UpdatedAtMS); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return roomservice.RoomSeatConfig{}, false, nil
|
||||
}
|
||||
return roomservice.RoomSeatConfig{}, false, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(rawAllowed), &config.AllowedSeatCounts); err != nil {
|
||||
return roomservice.RoomSeatConfig{}, false, err
|
||||
}
|
||||
|
||||
return config, true, nil
|
||||
}
|
||||
|
||||
// UpsertRoomSeatConfig 写入当前 App 的房间座位数配置。
|
||||
func (r *Repository) UpsertRoomSeatConfig(ctx context.Context, config roomservice.RoomSeatConfig) error {
|
||||
appCode := normalizedRecordAppCode(ctx, config.AppCode)
|
||||
rawAllowed, err := json.Marshal(config.AllowedSeatCounts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nowMS := config.UpdatedAtMS
|
||||
if nowMS <= 0 {
|
||||
nowMS = time.Now().UTC().UnixMilli()
|
||||
}
|
||||
_, err = r.db.ExecContext(ctx,
|
||||
`INSERT INTO room_seat_configs (app_code, allowed_seat_counts, default_seat_count, created_at_ms, updated_at_ms)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
allowed_seat_counts = VALUES(allowed_seat_counts),
|
||||
default_seat_count = VALUES(default_seat_count),
|
||||
updated_at_ms = VALUES(updated_at_ms)`,
|
||||
appCode,
|
||||
string(rawAllowed),
|
||||
config.DefaultSeatCount,
|
||||
nowMS,
|
||||
nowMS,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// ListCommandsAfter 返回快照版本之后的可回放命令日志。
|
||||
func (r *Repository) ListCommandsAfter(ctx context.Context, roomID string, roomVersion int64) ([]roomservice.CommandRecord, error) {
|
||||
// 恢复必须按 room_version 再按自增 id 排序,确保同版本异常数据也有稳定顺序。
|
||||
|
||||
@ -47,6 +47,13 @@ func (s *Server) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest)
|
||||
return mapServiceResult(s.svc.CreateRoom(ctx, req))
|
||||
}
|
||||
|
||||
// UpdateRoomProfile 代理到领域服务。
|
||||
func (s *Server) UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomProfileRequest) (*roomv1.UpdateRoomProfileResponse, error) {
|
||||
// 房间资料和座位数变更必须经 Room Cell 串行提交,gRPC 层不直接修改快照。
|
||||
ctx = contextWithMetaApp(ctx, req.GetMeta())
|
||||
return mapServiceResult(s.svc.UpdateRoomProfile(ctx, req))
|
||||
}
|
||||
|
||||
// JoinRoom 代理到领域服务。
|
||||
func (s *Server) JoinRoom(ctx context.Context, req *roomv1.JoinRoomRequest) (*roomv1.JoinRoomResponse, error) {
|
||||
// JoinRoom 只维护 room-service presence,真实消息连接由腾讯云 IM SDK 处理。
|
||||
|
||||
@ -225,6 +225,19 @@ type FriendApplication struct {
|
||||
UpdatedAtMs int64
|
||||
}
|
||||
|
||||
// BusinessUserLookupItem 是带业务场景权限裁剪后的用户查询结果。
|
||||
// 它只包含选择目标用户所需字段,不能扩展手机号、登录身份或钱包资产。
|
||||
type BusinessUserLookupItem struct {
|
||||
UserID int64
|
||||
DisplayUserID string
|
||||
Username string
|
||||
Avatar string
|
||||
Status Status
|
||||
RegionID int64
|
||||
RegionCode string
|
||||
RegionName string
|
||||
}
|
||||
|
||||
// Identity 是 user_id 与当前 active display_user_id 的绑定投影。
|
||||
type Identity struct {
|
||||
// AppCode 是短号所属 App。
|
||||
|
||||
38
services/user-service/internal/service/host/business.go
Normal file
38
services/user-service/internal/service/host/business.go
Normal file
@ -0,0 +1,38 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"hyapp/pkg/xerr"
|
||||
)
|
||||
|
||||
const (
|
||||
CapabilityManagerCenter = "manager_center"
|
||||
CapabilityManagerResourceGrant = "manager_resource_grant"
|
||||
)
|
||||
|
||||
// CheckBusinessCapability 是 App/H5 业务入口的服务端身份边界。
|
||||
// 首版经理中心只认 active Agency owner,不把客户端自报角色或普通 Agency 成员当作授权依据。
|
||||
func (s *Service) CheckBusinessCapability(ctx context.Context, actorUserID int64, capability string) (bool, string, error) {
|
||||
if s.repository == nil {
|
||||
return false, "", xerr.New(xerr.Unavailable, "host repository is not configured")
|
||||
}
|
||||
if actorUserID <= 0 {
|
||||
return false, "", xerr.New(xerr.InvalidArgument, "actor_user_id is required")
|
||||
}
|
||||
capability = strings.ToLower(strings.TrimSpace(capability))
|
||||
switch capability {
|
||||
case CapabilityManagerCenter, CapabilityManagerResourceGrant:
|
||||
_, ok, err := s.repository.HasActiveAgencyOwner(ctx, actorUserID)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
if !ok {
|
||||
return false, "active_agency_owner_required", nil
|
||||
}
|
||||
return true, "", nil
|
||||
default:
|
||||
return false, "", xerr.New(xerr.InvalidArgument, "business capability is invalid")
|
||||
}
|
||||
}
|
||||
@ -33,6 +33,7 @@ type Repository interface {
|
||||
GetBDProfile(ctx context.Context, userID int64) (hostdomain.BDProfile, error)
|
||||
GetCoinSellerProfile(ctx context.Context, userID int64) (hostdomain.CoinSellerProfile, error)
|
||||
GetUserRoleSummary(ctx context.Context, userID int64) (hostdomain.UserRoleSummary, error)
|
||||
HasActiveAgencyOwner(ctx context.Context, userID int64) (int64, bool, error)
|
||||
ListAgencyMembers(ctx context.Context, agencyID int64, status string) ([]hostdomain.AgencyMembership, error)
|
||||
ListAgencyApplications(ctx context.Context, agencyID int64, status string) ([]hostdomain.AgencyApplication, error)
|
||||
}
|
||||
|
||||
53
services/user-service/internal/service/user/business.go
Normal file
53
services/user-service/internal/service/user/business.go
Normal file
@ -0,0 +1,53 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"hyapp/pkg/xerr"
|
||||
userdomain "hyapp/services/user-service/internal/domain/user"
|
||||
)
|
||||
|
||||
const (
|
||||
BusinessSceneManagerResourceGrant = "manager_resource_grant"
|
||||
|
||||
defaultBusinessLookupPageSize = 20
|
||||
maxBusinessLookupPageSize = 20
|
||||
)
|
||||
|
||||
// BusinessUserLookup 执行业务场景用户查询。调用方必须先完成该 scene 的能力校验;
|
||||
// service 层仍校验 scene 和 keyword,避免未来入口把它误用成全局用户搜索。
|
||||
func (s *Service) BusinessUserLookup(ctx context.Context, scene string, keyword string, pageSize int32) ([]userdomain.BusinessUserLookupItem, error) {
|
||||
scene = strings.ToLower(strings.TrimSpace(scene))
|
||||
if scene != BusinessSceneManagerResourceGrant {
|
||||
return nil, xerr.New(xerr.InvalidArgument, "business lookup scene is invalid")
|
||||
}
|
||||
if s.userRepository == nil {
|
||||
return nil, xerr.New(xerr.Unavailable, "user repository is not configured")
|
||||
}
|
||||
keyword = strings.TrimSpace(keyword)
|
||||
numericKeyword := isASCIIInteger(keyword)
|
||||
if keyword == "" || (!numericKeyword && utf8.RuneCountInString(keyword) < 2) {
|
||||
return nil, xerr.New(xerr.InvalidArgument, "keyword is invalid")
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = defaultBusinessLookupPageSize
|
||||
}
|
||||
if pageSize > maxBusinessLookupPageSize {
|
||||
pageSize = maxBusinessLookupPageSize
|
||||
}
|
||||
return s.userRepository.BusinessUserLookup(ctx, keyword, numericKeyword, pageSize)
|
||||
}
|
||||
|
||||
func isASCIIInteger(value string) bool {
|
||||
if value == "" {
|
||||
return false
|
||||
}
|
||||
for _, ch := range value {
|
||||
if ch < '0' || ch > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -36,6 +36,8 @@ type UserRepository interface {
|
||||
ListFriends(ctx context.Context, userID int64, page int32, pageSize int32, cursorUpdatedAtMS int64, cursorUserID int64) ([]userdomain.FriendRecord, int64, error)
|
||||
// ListFriendApplications 分页读取好友申请。
|
||||
ListFriendApplications(ctx context.Context, userID int64, direction string, page int32, pageSize int32) ([]userdomain.FriendApplication, int64, error)
|
||||
// BusinessUserLookup 按已授权业务场景查询目标用户,返回字段必须保持最小化。
|
||||
BusinessUserLookup(ctx context.Context, keyword string, numericKeyword bool, pageSize int32) ([]userdomain.BusinessUserLookupItem, error)
|
||||
// BatchGetUsers 批量查询用户主状态,缺失用户不应返回占位对象。
|
||||
BatchGetUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.User, error)
|
||||
// ListUserIDs 按稳定 user_id 游标读取低频后台任务目标用户。
|
||||
|
||||
@ -147,6 +147,16 @@ func (r *Repository) GetUserRoleSummary(ctx context.Context, userID int64) (host
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
// HasActiveAgencyOwner 精确判断用户是否为 active Agency owner。
|
||||
// 普通 Agency 成员会在 UserRoleSummary 中有 Agency 入口,但不能获得经理中心资源赠送权限。
|
||||
func (r *Repository) HasActiveAgencyOwner(ctx context.Context, userID int64) (int64, bool, error) {
|
||||
agency, ok, err := queryActiveAgencyByOwner(ctx, r.db, userID, false)
|
||||
if err != nil || !ok {
|
||||
return 0, ok, err
|
||||
}
|
||||
return agency.AgencyID, true, nil
|
||||
}
|
||||
|
||||
// ListAgencyMembers 读取 Agency 成员关系列表。
|
||||
func (r *Repository) ListAgencyMembers(ctx context.Context, agencyID int64, status string) ([]hostdomain.AgencyMembership, error) {
|
||||
query := fmt.Sprintf(`SELECT %s FROM agency_memberships WHERE app_code = ? AND agency_id = ?`, agencyMembershipColumns)
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"hyapp/pkg/appcode"
|
||||
"hyapp/pkg/xerr"
|
||||
userdomain "hyapp/services/user-service/internal/domain/user"
|
||||
)
|
||||
|
||||
// BusinessUserLookup 只返回同 app_code 的 active 用户,并按查询类型收敛匹配范围。
|
||||
// 数字 keyword 只做短号和 user_id 精确匹配;昵称搜索要求调用方已校验最小长度。
|
||||
func (r *Repository) BusinessUserLookup(ctx context.Context, keyword string, numericKeyword bool, pageSize int32) ([]userdomain.BusinessUserLookupItem, error) {
|
||||
if r == nil || r.db == nil {
|
||||
return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
||||
}
|
||||
app := appcode.FromContext(ctx)
|
||||
limit := int(pageSize)
|
||||
if limit <= 0 {
|
||||
limit = 20
|
||||
}
|
||||
|
||||
var args []any
|
||||
query := `SELECT ` + userSelectColumns + ` FROM users WHERE app_code = ? AND status = ?`
|
||||
args = append(args, app, string(userdomain.StatusActive))
|
||||
if numericKeyword {
|
||||
userID := int64(0)
|
||||
if parsed, err := strconv.ParseInt(keyword, 10, 64); err == nil {
|
||||
userID = parsed
|
||||
}
|
||||
query += ` AND (current_display_user_id = ? OR user_id = ?)
|
||||
ORDER BY CASE WHEN current_display_user_id = ? THEN 0 WHEN user_id = ? THEN 1 ELSE 2 END, updated_at_ms DESC, user_id DESC
|
||||
LIMIT ?`
|
||||
args = append(args, keyword, userID, keyword, userID, limit)
|
||||
} else {
|
||||
query += ` AND username LIKE ? ESCAPE '\\'
|
||||
ORDER BY updated_at_ms DESC, user_id DESC
|
||||
LIMIT ?`
|
||||
args = append(args, "%"+escapeLikeKeyword(keyword)+"%", limit)
|
||||
}
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := make([]userdomain.BusinessUserLookupItem, 0, limit)
|
||||
for rows.Next() {
|
||||
user, err := ScanUser(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, businessUserLookupItem(user))
|
||||
}
|
||||
return items, rows.Err()
|
||||
}
|
||||
|
||||
func businessUserLookupItem(user userdomain.User) userdomain.BusinessUserLookupItem {
|
||||
return userdomain.BusinessUserLookupItem{
|
||||
UserID: user.UserID,
|
||||
DisplayUserID: user.CurrentDisplayUserID,
|
||||
Username: user.Username,
|
||||
Avatar: user.Avatar,
|
||||
Status: user.Status,
|
||||
RegionID: user.RegionID,
|
||||
RegionCode: user.RegionCode,
|
||||
RegionName: user.RegionName,
|
||||
}
|
||||
}
|
||||
|
||||
func escapeLikeKeyword(value string) string {
|
||||
value = strings.ReplaceAll(value, `\`, `\\`)
|
||||
value = strings.ReplaceAll(value, `%`, `\%`)
|
||||
value = strings.ReplaceAll(value, `_`, `\_`)
|
||||
return value
|
||||
}
|
||||
@ -80,6 +80,19 @@ func toProtoUser(user userdomain.User) *userv1.User {
|
||||
}
|
||||
}
|
||||
|
||||
func toProtoBusinessUserLookupItem(item userdomain.BusinessUserLookupItem) *userv1.BusinessUserLookupItem {
|
||||
return &userv1.BusinessUserLookupItem{
|
||||
UserId: item.UserID,
|
||||
DisplayUserId: item.DisplayUserID,
|
||||
Username: item.Username,
|
||||
Avatar: item.Avatar,
|
||||
Status: toProtoStatus(item.Status),
|
||||
RegionId: item.RegionID,
|
||||
RegionCode: item.RegionCode,
|
||||
RegionName: item.RegionName,
|
||||
}
|
||||
}
|
||||
|
||||
func toProtoInviteOverview(overview invitedomain.Overview) *userv1.InviteOverview {
|
||||
if overview.MyInviteCode == "" && !overview.InviteEnabled && overview.InviteCount == 0 && overview.ValidInviteCount == 0 && overview.ValidInviteThresholdCoin == 0 {
|
||||
return nil
|
||||
|
||||
@ -220,6 +220,19 @@ func (s *Server) GetUserRoleSummary(ctx context.Context, req *userv1.GetUserRole
|
||||
return &userv1.GetUserRoleSummaryResponse{Summary: toProtoUserRoleSummary(summary)}, nil
|
||||
}
|
||||
|
||||
// CheckBusinessCapability 返回 H5/App 业务入口可用性,具体身份规则由 host service 统一判断。
|
||||
func (s *Server) CheckBusinessCapability(ctx context.Context, req *userv1.CheckBusinessCapabilityRequest) (*userv1.CheckBusinessCapabilityResponse, error) {
|
||||
if s.hostSvc == nil {
|
||||
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
|
||||
}
|
||||
ctx = contextWithApp(ctx, req.GetMeta())
|
||||
allowed, reason, err := s.hostSvc.CheckBusinessCapability(ctx, req.GetActorUserId(), req.GetCapability())
|
||||
if err != nil {
|
||||
return nil, xerr.ToGRPCError(err)
|
||||
}
|
||||
return &userv1.CheckBusinessCapabilityResponse{Allowed: allowed, Reason: reason}, nil
|
||||
}
|
||||
|
||||
// GetAgencyMembers 返回 Agency 成员关系事实列表。
|
||||
func (s *Server) GetAgencyMembers(ctx context.Context, req *userv1.GetAgencyMembersRequest) (*userv1.GetAgencyMembersResponse, error) {
|
||||
if s.hostSvc == nil {
|
||||
|
||||
@ -3,6 +3,7 @@ package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
userv1 "hyapp.local/api/proto/user/v1"
|
||||
"hyapp/pkg/appcode"
|
||||
@ -178,6 +179,46 @@ func (s *Server) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*user
|
||||
return &userv1.GetUserResponse{User: toProtoUser(user)}, nil
|
||||
}
|
||||
|
||||
// BusinessUserLookup 是带业务场景和能力校验的用户查询入口。
|
||||
func (s *Server) BusinessUserLookup(ctx context.Context, req *userv1.BusinessUserLookupRequest) (*userv1.BusinessUserLookupResponse, error) {
|
||||
if s.hostSvc == nil {
|
||||
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
|
||||
}
|
||||
ctx = contextWithApp(ctx, req.GetMeta())
|
||||
capability, ok := capabilityForBusinessLookupScene(req.GetScene())
|
||||
if !ok {
|
||||
return nil, xerr.ToGRPCError(xerr.New(xerr.InvalidArgument, "business lookup scene is invalid"))
|
||||
}
|
||||
allowed, reason, err := s.hostSvc.CheckBusinessCapability(ctx, req.GetActorUserId(), capability)
|
||||
if err != nil {
|
||||
return nil, xerr.ToGRPCError(err)
|
||||
}
|
||||
if !allowed {
|
||||
if reason == "" {
|
||||
reason = "permission denied"
|
||||
}
|
||||
return nil, xerr.ToGRPCError(xerr.New(xerr.PermissionDenied, reason))
|
||||
}
|
||||
users, err := s.userSvc.BusinessUserLookup(ctx, req.GetScene(), req.GetKeyword(), req.GetPageSize())
|
||||
if err != nil {
|
||||
return nil, xerr.ToGRPCError(err)
|
||||
}
|
||||
resp := &userv1.BusinessUserLookupResponse{Users: make([]*userv1.BusinessUserLookupItem, 0, len(users))}
|
||||
for _, user := range users {
|
||||
resp.Users = append(resp.Users, toProtoBusinessUserLookupItem(user))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func capabilityForBusinessLookupScene(scene string) (string, bool) {
|
||||
switch strings.ToLower(strings.TrimSpace(scene)) {
|
||||
case userservice.BusinessSceneManagerResourceGrant:
|
||||
return hostservice.CapabilityManagerResourceGrant, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// GetMyProfileStats 返回我的页顶部统计计数。
|
||||
func (s *Server) GetMyProfileStats(ctx context.Context, req *userv1.GetMyProfileStatsRequest) (*userv1.GetMyProfileStatsResponse, error) {
|
||||
ctx = contextWithApp(ctx, req.GetMeta())
|
||||
|
||||
@ -160,6 +160,7 @@ CREATE TABLE IF NOT EXISTS resources (
|
||||
name VARCHAR(128) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
grantable BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
manager_grant_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
grant_strategy VARCHAR(32) NOT NULL,
|
||||
wallet_asset_type VARCHAR(32) NOT NULL DEFAULT '',
|
||||
wallet_asset_amount BIGINT NOT NULL DEFAULT 0,
|
||||
@ -174,9 +175,28 @@ CREATE TABLE IF NOT EXISTS resources (
|
||||
created_at_ms BIGINT NOT NULL,
|
||||
updated_at_ms BIGINT NOT NULL,
|
||||
UNIQUE KEY uk_resources_code (app_code, resource_code),
|
||||
KEY idx_resources_type_status (app_code, resource_type, status, sort_order)
|
||||
KEY idx_resources_type_status (app_code, resource_type, status, sort_order),
|
||||
KEY idx_resources_manager_grant (app_code, manager_grant_enabled, status, grantable, sort_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
SET @ddl := IF(
|
||||
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'resources' AND COLUMN_NAME = 'manager_grant_enabled') = 0,
|
||||
'ALTER TABLE resources ADD COLUMN manager_grant_enabled BOOLEAN NOT NULL DEFAULT TRUE AFTER grantable',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @ddl := IF(
|
||||
(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'resources' AND INDEX_NAME = 'idx_resources_manager_grant') = 0,
|
||||
'ALTER TABLE resources ADD KEY idx_resources_manager_grant (app_code, manager_grant_enabled, status, grantable, sort_order)',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS resource_groups (
|
||||
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu',
|
||||
group_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
@ -27,10 +27,11 @@ const (
|
||||
GrantSubjectResource = "resource"
|
||||
GrantSubjectGroup = "resource_group"
|
||||
|
||||
GrantSourceAdmin = "admin"
|
||||
GrantStatusDone = "succeeded"
|
||||
ResultWalletCredit = "wallet_credit"
|
||||
ResultEntitlement = "entitlement"
|
||||
GrantSourceAdmin = "admin"
|
||||
GrantSourceManagerCenter = "manager_center"
|
||||
GrantStatusDone = "succeeded"
|
||||
ResultWalletCredit = "wallet_credit"
|
||||
ResultEntitlement = "entitlement"
|
||||
|
||||
GroupItemTypeResource = "resource"
|
||||
GroupItemTypeWalletAsset = "wallet_asset"
|
||||
@ -72,6 +73,7 @@ type Resource struct {
|
||||
UpdatedByUserID int64
|
||||
CreatedAtMS int64
|
||||
UpdatedAtMS int64
|
||||
ManagerGrantEnabled bool
|
||||
}
|
||||
|
||||
type ResourceGroupItem struct {
|
||||
@ -182,6 +184,7 @@ type ListResourcesQuery struct {
|
||||
Page int32
|
||||
PageSize int32
|
||||
ActiveOnly bool
|
||||
ManagerGrantOnly bool
|
||||
}
|
||||
|
||||
type ResourceCommand struct {
|
||||
@ -202,6 +205,7 @@ type ResourceCommand struct {
|
||||
MetadataJSON string
|
||||
SortOrder int32
|
||||
OperatorUserID int64
|
||||
ManagerGrantEnabled bool
|
||||
}
|
||||
|
||||
type StatusCommand struct {
|
||||
|
||||
@ -48,14 +48,7 @@ func (r *Repository) ListResources(ctx context.Context, query resourcedomain.Lis
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT app_code, resource_id, resource_code, resource_type, name, status, grantable,
|
||||
grant_strategy, wallet_asset_type, wallet_asset_amount,
|
||||
COALESCE(CAST(usage_scope_json AS CHAR), '[]'),
|
||||
asset_url, preview_url, animation_url,
|
||||
COALESCE(CAST(metadata_json AS CHAR), '{}'),
|
||||
sort_order, created_by_user_id, updated_by_user_id, created_at_ms, updated_at_ms
|
||||
FROM resources
|
||||
rows, err := r.db.QueryContext(ctx, resourceSelectSQL()+`
|
||||
`+where+`
|
||||
ORDER BY sort_order ASC, resource_id DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
@ -118,17 +111,18 @@ func (r *Repository) CreateResource(ctx context.Context, command resourcedomain.
|
||||
|
||||
result, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO resources (
|
||||
app_code, resource_code, resource_type, name, status, grantable, grant_strategy,
|
||||
app_code, resource_code, resource_type, name, status, grantable, manager_grant_enabled, grant_strategy,
|
||||
wallet_asset_type, wallet_asset_amount, usage_scope_json, asset_url, preview_url,
|
||||
animation_url, metadata_json, sort_order, created_by_user_id, updated_by_user_id,
|
||||
created_at_ms, updated_at_ms
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
command.AppCode,
|
||||
command.ResourceCode,
|
||||
command.ResourceType,
|
||||
command.Name,
|
||||
command.Status,
|
||||
command.Grantable,
|
||||
command.ManagerGrantEnabled,
|
||||
command.GrantStrategy,
|
||||
command.WalletAssetType,
|
||||
command.WalletAssetAmount,
|
||||
@ -193,7 +187,7 @@ func (r *Repository) UpdateResource(ctx context.Context, command resourcedomain.
|
||||
|
||||
result, err := tx.ExecContext(ctx, `
|
||||
UPDATE resources
|
||||
SET resource_code = ?, resource_type = ?, name = ?, status = ?, grantable = ?,
|
||||
SET resource_code = ?, resource_type = ?, name = ?, status = ?, grantable = ?, manager_grant_enabled = ?,
|
||||
grant_strategy = ?, wallet_asset_type = ?, wallet_asset_amount = ?,
|
||||
usage_scope_json = ?, asset_url = ?, preview_url = ?, animation_url = ?,
|
||||
metadata_json = ?, sort_order = ?, updated_by_user_id = ?, updated_at_ms = ?
|
||||
@ -203,6 +197,7 @@ func (r *Repository) UpdateResource(ctx context.Context, command resourcedomain.
|
||||
command.Name,
|
||||
command.Status,
|
||||
command.Grantable,
|
||||
command.ManagerGrantEnabled,
|
||||
command.GrantStrategy,
|
||||
command.WalletAssetType,
|
||||
command.WalletAssetAmount,
|
||||
@ -624,7 +619,7 @@ func (r *Repository) GrantResource(ctx context.Context, command resourcedomain.G
|
||||
if err != nil {
|
||||
return resourcedomain.ResourceGrant{}, err
|
||||
}
|
||||
if err := validateGrantableResource(resource); err != nil {
|
||||
if err := validateGrantableResourceForSource(resource, command.GrantSource); err != nil {
|
||||
return resourcedomain.ResourceGrant{}, err
|
||||
}
|
||||
grantID := resourceGrantID(command.AppCode, command.CommandID)
|
||||
@ -1638,6 +1633,7 @@ func scanResource(scanner scanTarget) (resourcedomain.Resource, error) {
|
||||
&resource.Name,
|
||||
&resource.Status,
|
||||
&resource.Grantable,
|
||||
&resource.ManagerGrantEnabled,
|
||||
&resource.GrantStrategy,
|
||||
&resource.WalletAssetType,
|
||||
&resource.WalletAssetAmount,
|
||||
@ -1694,6 +1690,7 @@ func scanResourceGroupItem(scanner scanTarget) (resourcedomain.ResourceGroupItem
|
||||
&resource.Name,
|
||||
&resource.Status,
|
||||
&resource.Grantable,
|
||||
&resource.ManagerGrantEnabled,
|
||||
&resource.GrantStrategy,
|
||||
&resource.WalletAssetType,
|
||||
&resource.WalletAssetAmount,
|
||||
@ -1748,6 +1745,7 @@ func scanGiftConfig(scanner scanTarget) (resourcedomain.GiftConfig, error) {
|
||||
&resource.Name,
|
||||
&resource.Status,
|
||||
&resource.Grantable,
|
||||
&resource.ManagerGrantEnabled,
|
||||
&resource.GrantStrategy,
|
||||
&resource.WalletAssetType,
|
||||
&resource.WalletAssetAmount,
|
||||
@ -1794,6 +1792,7 @@ func scanUserResourceEntitlement(scanner scanTarget) (resourcedomain.UserResourc
|
||||
&resource.Name,
|
||||
&resource.Status,
|
||||
&resource.Grantable,
|
||||
&resource.ManagerGrantEnabled,
|
||||
&resource.GrantStrategy,
|
||||
&resource.WalletAssetType,
|
||||
&resource.WalletAssetAmount,
|
||||
@ -1844,7 +1843,7 @@ func resourceColumnsWithAlias(alias string) string {
|
||||
prefix = alias + "."
|
||||
}
|
||||
return prefix + `app_code, ` + prefix + `resource_id, ` + prefix + `resource_code, ` + prefix + `resource_type, ` +
|
||||
prefix + `name, ` + prefix + `status, ` + prefix + `grantable, ` + prefix + `grant_strategy, ` +
|
||||
prefix + `name, ` + prefix + `status, ` + prefix + `grantable, ` + prefix + `manager_grant_enabled, ` + prefix + `grant_strategy, ` +
|
||||
prefix + `wallet_asset_type, ` + prefix + `wallet_asset_amount, COALESCE(CAST(` + prefix + `usage_scope_json AS CHAR), '[]'), ` +
|
||||
prefix + `asset_url, ` + prefix + `preview_url, ` + prefix + `animation_url, COALESCE(CAST(` + prefix + `metadata_json AS CHAR), '{}'), ` +
|
||||
prefix + `sort_order, ` + prefix + `created_by_user_id, ` + prefix + `updated_by_user_id, ` + prefix + `created_at_ms, ` + prefix + `updated_at_ms`
|
||||
@ -1857,7 +1856,7 @@ func nullableResourceColumnsWithAlias(alias string) string {
|
||||
}
|
||||
return `COALESCE(` + prefix + `app_code, ''), COALESCE(` + prefix + `resource_id, 0), COALESCE(` + prefix + `resource_code, ''), ` +
|
||||
`COALESCE(` + prefix + `resource_type, ''), COALESCE(` + prefix + `name, ''), COALESCE(` + prefix + `status, ''), ` +
|
||||
`COALESCE(` + prefix + `grantable, FALSE), COALESCE(` + prefix + `grant_strategy, ''), COALESCE(` + prefix + `wallet_asset_type, ''), ` +
|
||||
`COALESCE(` + prefix + `grantable, FALSE), COALESCE(` + prefix + `manager_grant_enabled, FALSE), COALESCE(` + prefix + `grant_strategy, ''), COALESCE(` + prefix + `wallet_asset_type, ''), ` +
|
||||
`COALESCE(` + prefix + `wallet_asset_amount, 0), COALESCE(CAST(` + prefix + `usage_scope_json AS CHAR), '[]'), ` +
|
||||
`COALESCE(` + prefix + `asset_url, ''), COALESCE(` + prefix + `preview_url, ''), COALESCE(` + prefix + `animation_url, ''), ` +
|
||||
`COALESCE(CAST(` + prefix + `metadata_json AS CHAR), '{}'), COALESCE(` + prefix + `sort_order, 0), ` +
|
||||
@ -1900,7 +1899,9 @@ func resourceGrantSelectSQL() string {
|
||||
func resourceWhereSQL(query resourcedomain.ListResourcesQuery) (string, []any) {
|
||||
where := `WHERE app_code = ?`
|
||||
args := []any{query.AppCode}
|
||||
if query.ActiveOnly {
|
||||
if query.ManagerGrantOnly {
|
||||
where += ` AND status = 'active' AND grantable = TRUE AND manager_grant_enabled = TRUE`
|
||||
} else if query.ActiveOnly {
|
||||
where += ` AND status = 'active'`
|
||||
} else if query.Status != "" {
|
||||
where += ` AND status = ?`
|
||||
@ -2218,6 +2219,19 @@ func validateGrantResourceGroupCommand(command resourcedomain.GrantResourceGroup
|
||||
if command.CommandID == "" || command.TargetUserID <= 0 || command.GroupID <= 0 || command.OperatorUserID <= 0 || command.Reason == "" {
|
||||
return xerr.New(xerr.InvalidArgument, "resource group grant command is incomplete")
|
||||
}
|
||||
if command.GrantSource == resourcedomain.GrantSourceManagerCenter {
|
||||
return xerr.New(xerr.InvalidArgument, "manager center cannot grant resource group")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateGrantableResourceForSource(resource resourcedomain.Resource, grantSource string) error {
|
||||
if err := validateGrantableResource(resource); err != nil {
|
||||
return err
|
||||
}
|
||||
if grantSource == resourcedomain.GrantSourceManagerCenter && !resource.ManagerGrantEnabled {
|
||||
return xerr.New(xerr.PermissionDenied, "resource is not enabled for manager center grant")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -11,13 +11,14 @@ import (
|
||||
|
||||
func (s *Server) ListResources(ctx context.Context, req *walletv1.ListResourcesRequest) (*walletv1.ListResourcesResponse, error) {
|
||||
items, total, err := s.svc.ListResources(ctx, resourcedomain.ListResourcesQuery{
|
||||
AppCode: req.GetAppCode(),
|
||||
ResourceType: req.GetResourceType(),
|
||||
Status: req.GetStatus(),
|
||||
Keyword: req.GetKeyword(),
|
||||
Page: req.GetPage(),
|
||||
PageSize: req.GetPageSize(),
|
||||
ActiveOnly: req.GetActiveOnly(),
|
||||
AppCode: req.GetAppCode(),
|
||||
ResourceType: req.GetResourceType(),
|
||||
Status: req.GetStatus(),
|
||||
Keyword: req.GetKeyword(),
|
||||
Page: req.GetPage(),
|
||||
PageSize: req.GetPageSize(),
|
||||
ActiveOnly: req.GetActiveOnly(),
|
||||
ManagerGrantOnly: req.GetManagerGrantOnly(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerr.ToGRPCError(err)
|
||||
@ -270,8 +271,9 @@ func resourceCommandFromCreate(req *walletv1.CreateResourceRequest) resourcedoma
|
||||
PreviewURL: req.GetPreviewUrl(),
|
||||
AnimationURL: req.GetAnimationUrl(),
|
||||
MetadataJSON: req.GetMetadataJson(),
|
||||
SortOrder: req.GetSortOrder(),
|
||||
OperatorUserID: req.GetOperatorUserId(),
|
||||
SortOrder: req.GetSortOrder(),
|
||||
OperatorUserID: req.GetOperatorUserId(),
|
||||
ManagerGrantEnabled: managerGrantEnabledOrDefault(req.ManagerGrantEnabled),
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,8 +293,9 @@ func resourceCommandFromUpdate(req *walletv1.UpdateResourceRequest) resourcedoma
|
||||
PreviewUrl: req.GetPreviewUrl(),
|
||||
AnimationUrl: req.GetAnimationUrl(),
|
||||
MetadataJson: req.GetMetadataJson(),
|
||||
SortOrder: req.GetSortOrder(),
|
||||
OperatorUserId: req.GetOperatorUserId(),
|
||||
SortOrder: req.GetSortOrder(),
|
||||
OperatorUserId: req.GetOperatorUserId(),
|
||||
ManagerGrantEnabled: managerGrantEnabledOrDefault(req.ManagerGrantEnabled),
|
||||
})
|
||||
command.ResourceID = req.GetResourceId()
|
||||
return command
|
||||
@ -412,11 +415,20 @@ func resourceToProto(resource resourcedomain.Resource) *walletv1.Resource {
|
||||
SortOrder: resource.SortOrder,
|
||||
CreatedByUserId: resource.CreatedByUserID,
|
||||
UpdatedByUserId: resource.UpdatedByUserID,
|
||||
CreatedAtMs: resource.CreatedAtMS,
|
||||
UpdatedAtMs: resource.UpdatedAtMS,
|
||||
CreatedAtMs: resource.CreatedAtMS,
|
||||
UpdatedAtMs: resource.UpdatedAtMS,
|
||||
ManagerGrantEnabled: resource.ManagerGrantEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
func managerGrantEnabledOrDefault(value *bool) bool {
|
||||
if value == nil {
|
||||
// 新建/编辑入口如果没显式携带开关,仍按产品规则默认允许经理中心赠送。
|
||||
return true
|
||||
}
|
||||
return *value
|
||||
}
|
||||
|
||||
func resourceGroupToProto(group resourcedomain.ResourceGroup) *walletv1.ResourceGroup {
|
||||
items := make([]*walletv1.ResourceGroupItem, 0, len(group.Items))
|
||||
for _, item := range group.Items {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user