From 15a4b8256a469e96ad714dc3d9a66b1193003622 Mon Sep 17 00:00:00 2001 From: liuxiaobo <1224730913@qq.com> Date: Sat, 14 Jun 2025 12:31:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0lobby=E6=9C=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/baseroom/baseRoom.go | 14 +-- common/baseroom/hundredRoom.go | 12 +-- common/baseroom/interface.go | 2 +- common/pb/chat.proto | 6 +- common/pb/common.proto | 4 +- common/pb/login.proto | 8 +- common/pb/match.proto | 8 +- common/pb/msgId.proto | 1 + common/pb/service.proto | 1 + common/pb/user.proto | 14 +-- common/proto/pb/chat.pb.go | 28 +++--- common/proto/pb/common.pb.go | 16 ++-- common/proto/pb/login.pb.go | 90 +++++++++++++----- common/proto/pb/match.pb.go | 30 +++--- common/proto/pb/msgId.pb.go | 20 ++-- common/proto/pb/service.pb.go | 8 +- common/proto/pb/user.pb.go | 50 +++++----- common/serviceName/serviceName.go | 1 + common/userBindService/userService.go | 130 ++++++++++++++++---------- server/colorgame/room/c2s.go | 5 + server/colorgame/room/colorRoom.go | 6 +- server/hilo/cmd/cmd.go | 47 ---------- server/lobby/cmd/cmd.go | 31 ++++++ server/lobby/config/config.go | 38 ++++++++ server/{hilo => lobby}/main.go | 8 +- server/lobby/model/db.go | 20 ++++ server/lobby/server/processor.go | 25 +++++ server/lobby/server/service.go | 116 +++++++++++++++++++++++ server/login/server/processor.go | 3 +- server/match/server/match.go | 10 +- 30 files changed, 516 insertions(+), 236 deletions(-) delete mode 100644 server/hilo/cmd/cmd.go create mode 100644 server/lobby/cmd/cmd.go create mode 100644 server/lobby/config/config.go rename server/{hilo => lobby}/main.go (65%) create mode 100644 server/lobby/model/db.go create mode 100644 server/lobby/server/processor.go create mode 100644 server/lobby/server/service.go diff --git a/common/baseroom/baseRoom.go b/common/baseroom/baseRoom.go index 6792aa1..d896fe8 100644 --- a/common/baseroom/baseRoom.go +++ b/common/baseroom/baseRoom.go @@ -25,7 +25,7 @@ const ( type BaseRoom[Seat ISeat] struct { id int roomType int // 房间配置id 初级,中级,高级 - playType int // 玩法配置id color玩法id + gameId int // 玩法配置id color玩法id gameNo string seats []Seat processor *processor.Processor @@ -35,11 +35,11 @@ type BaseRoom[Seat ISeat] struct { srv service.IService } -func NewBaseRoom[Seat ISeat](id, roomType, playType, seatNum int, srv service.IService) (*BaseRoom[Seat], pb.ErrCode) { +func NewBaseRoom[Seat ISeat](id, roomType, gameId, seatNum int, srv service.IService) (*BaseRoom[Seat], pb.ErrCode) { room := &BaseRoom[Seat]{ id: id, roomType: roomType, - playType: playType, + gameId: gameId, gameNo: "", seats: make([]Seat, seatNum), timeTypes: make(map[timer.ITimeType]uint32), @@ -59,8 +59,8 @@ func (r *BaseRoom[Seat]) RoomType() int { return r.roomType } -func (r *BaseRoom[Seat]) PlayType() int { - return r.playType +func (r *BaseRoom[Seat]) GameId() int { + return r.gameId } // 入座玩家数量 @@ -257,10 +257,6 @@ func (r *BaseRoom[Seat]) RegisterMessages(metas processor.RegisterMetas) { r.processor.RegisterMessages(metas) } -func (r *BaseRoom[Seat]) TimerDispatch(cmd timer.ITimeType, params ...any) error { - return r.timeProcessor.Dispatch(cmd, params...) -} - // 注册时间事件及处理 func (r *BaseRoom[Seat]) RegisterTimerMessages(metas processor.RegisterTimerMetas) { r.timeProcessor.RegisterMessages(metas) diff --git a/common/baseroom/hundredRoom.go b/common/baseroom/hundredRoom.go index c7faae3..1bd8efe 100644 --- a/common/baseroom/hundredRoom.go +++ b/common/baseroom/hundredRoom.go @@ -18,8 +18,8 @@ type HundredRoom struct { users map[int64]IPlayer // 所有玩家 } -func NewHundredRoom(id, roomType, playType int, srv service.IService) (*HundredRoom, pb.ErrCode) { - baseRoom, code := NewBaseRoom[*BaseSeat](id, roomType, playType, 0, srv) +func NewHundredRoom(id, roomType, gameId int, srv service.IService) (*HundredRoom, pb.ErrCode) { + baseRoom, code := NewBaseRoom[*BaseSeat](id, roomType, gameId, 0, srv) if code != pb.ErrCode_OK { return nil, code } @@ -37,8 +37,8 @@ func (r *HundredRoom) RoomType() int { return r.room.RoomType() } -func (r *HundredRoom) PlayType() int { - return r.room.PlayType() +func (r *HundredRoom) GameId() int { + return r.room.GameId() } // 房间玩家数量 @@ -169,10 +169,6 @@ func (r *HundredRoom) RegisterMessages(metas processor.RegisterMetas) { r.room.RegisterMessages(metas) } -func (r *HundredRoom) TimerDispatch(cmd timer.ITimeType, params ...any) error { - return r.room.TimerDispatch(cmd, params...) -} - // 注册时间事件及处理 func (r *HundredRoom) RegisterTimerMessages(metas processor.RegisterTimerMetas) { r.room.RegisterTimerMessages(metas) diff --git a/common/baseroom/interface.go b/common/baseroom/interface.go index a9a2139..fbca3d0 100644 --- a/common/baseroom/interface.go +++ b/common/baseroom/interface.go @@ -8,7 +8,7 @@ import ( type IRoom interface { Id() int RoomType() int // 房间配置id - PlayType() int + GameId() int OnInit() // SeatPlayerNum() int // FindEmptySeat() bool diff --git a/common/pb/chat.proto b/common/pb/chat.proto index ccff083..f1682cf 100644 --- a/common/pb/chat.proto +++ b/common/pb/chat.proto @@ -18,10 +18,10 @@ enum ChatType // 聊天消息 message ReqChat { - ChatUser src_user = 1; // 说话的人 - ChatUser dst_user = 2; // 接收者 + ChatUser srcUser = 1; // 说话的人 + ChatUser dstUser = 2; // 接收者 ChatType type = 3; // 聊天类型 - ServiceTypeId game_id = 4; // 游戏id,只在本玩法中显示的聊天信息 + ServiceTypeId gameId = 4; // 游戏id,只在本玩法中显示的聊天信息 string content = 5; // 内容 } diff --git a/common/pb/common.proto b/common/pb/common.proto index b70904b..56ef1cf 100644 --- a/common/pb/common.proto +++ b/common/pb/common.proto @@ -22,8 +22,8 @@ message ReqEnterRoom message RspEnterRoom { ErrCode code = 1; - int32 PlayType = 2; // 玩法id - int32 RoomType = 3; // 房间类型,低级,中级,高级等 + int32 gameId = 2; // 玩法id + int32 roomType = 3; // 房间类型,低级,中级,高级等 } // 玩家离开房间 diff --git a/common/pb/login.proto b/common/pb/login.proto index 6c931de..3606e18 100644 --- a/common/pb/login.proto +++ b/common/pb/login.proto @@ -24,7 +24,13 @@ message RspUserLogin // 上线通知 message NtfUserOnline { - int64 user_id = 2; // 玩家id + int64 user_id = 2; // 玩家id +} + +// 通知玩家在哪些服务里,方便玩家重进这些服务,重连使用 +message NtfUserInService +{ + repeated string serviceNames = 3; // 玩家所在玩法的服务名 } // 玩家登陆 diff --git a/common/pb/match.proto b/common/pb/match.proto index c25a2d3..b5253bf 100644 --- a/common/pb/match.proto +++ b/common/pb/match.proto @@ -8,8 +8,8 @@ import "user.proto"; // 房间匹配 message ReqMatchRoom { - int32 PlayType = 1; // 玩法id - int32 RoomType = 2; // 房间类型,低级,中级,高级等 + int32 gameId = 1; // 玩法id + int32 roomType = 2; // 房间类型,低级,中级,高级等 } // 玩家进房间返回 匹配服返回该消息,假房间。匹配成功后发给对应玩法服,玩法服发NotifyUserEnterRoom才是真房间 @@ -22,8 +22,8 @@ message RspMatchRoom } ErrCode code = 1; - int32 PlayType = 2; // 玩法id - int32 RoomType = 3; // 房间类型,低级,中级,高级等 + int32 gameId = 2; // 玩法id + int32 roomType = 3; // 房间类型,低级,中级,高级等 GameUser User = 4; // 玩家数据 ColorInfo colorInfo = 20; // color玩法配置信息 diff --git a/common/pb/msgId.proto b/common/pb/msgId.proto index 46efcae..da12c88 100644 --- a/common/pb/msgId.proto +++ b/common/pb/msgId.proto @@ -27,6 +27,7 @@ enum MsgId ReqUserLoginId = 2100; // 玩家登陆 RspUserLoginId = 2101; NtfUserOnlineId = 2102; + NtfUserInServiceId = 2103; // 将玩家之前呆的服务同步给玩家,方便玩家重连进来 ReqUserLogoutId = 2104; RspUserLogoutId = 2105; NtfUserOfflineId = 2106; diff --git a/common/pb/service.proto b/common/pb/service.proto index 03d082f..ed14155 100644 --- a/common/pb/service.proto +++ b/common/pb/service.proto @@ -10,6 +10,7 @@ enum ServiceTypeId STI_Chat = 102; // 聊天服 STI_DB = 103; // db服 STI_Match = 104; // 匹配服 + STI_Lobby = 105; // 大厅服 STI_ColorGame = 120; // color game } diff --git a/common/pb/user.proto b/common/pb/user.proto index ae7d8ce..b706d59 100644 --- a/common/pb/user.proto +++ b/common/pb/user.proto @@ -6,22 +6,22 @@ option go_package = "common/proto/pb"; // 聊天中显示的玩家基础信息 message ChatUser { - int64 user_id = 1; + int64 userId = 1; string nickname = 2; // 昵称 string avatar = 3; // 头像 - string avatar_frame = 4; // 头像框 - string vip_level = 5; // vip等级 + string avatarFrame = 4; // 头像框 + string vipLevel = 5; // vip等级 } // 房间内的玩家数据 message GameUser { - int64 user_id = 1; + int64 userId = 1; string nickname = 2; // 昵称 string avatar = 3; // 头像 - string avatar_frame = 4; // 头像框 - int32 vip_level = 5; // vip等级 - int32 vip_exp = 6; // vip经验 + string avatarFrame = 4; // 头像框 + int32 vipLevel = 5; // vip等级 + int32 vipExp = 6; // vip经验 int64 seat = 20; // 座位 int64 gold = 25; // 金币 } diff --git a/common/proto/pb/chat.pb.go b/common/proto/pb/chat.pb.go index 925f2bd..b19b823 100644 --- a/common/proto/pb/chat.pb.go +++ b/common/proto/pb/chat.pb.go @@ -77,11 +77,11 @@ func (ChatType) EnumDescriptor() ([]byte, []int) { // 聊天消息 type ReqChat struct { state protoimpl.MessageState `protogen:"open.v1"` - SrcUser *ChatUser `protobuf:"bytes,1,opt,name=src_user,json=srcUser,proto3" json:"src_user,omitempty"` // 说话的人 - DstUser *ChatUser `protobuf:"bytes,2,opt,name=dst_user,json=dstUser,proto3" json:"dst_user,omitempty"` // 接收者 - Type ChatType `protobuf:"varint,3,opt,name=type,proto3,enum=pb.ChatType" json:"type,omitempty"` // 聊天类型 - GameId ServiceTypeId `protobuf:"varint,4,opt,name=game_id,json=gameId,proto3,enum=pb.ServiceTypeId" json:"game_id,omitempty"` // 游戏id,只在本玩法中显示的聊天信息 - Content string `protobuf:"bytes,5,opt,name=content,proto3" json:"content,omitempty"` // 内容 + SrcUser *ChatUser `protobuf:"bytes,1,opt,name=srcUser,proto3" json:"srcUser,omitempty"` // 说话的人 + DstUser *ChatUser `protobuf:"bytes,2,opt,name=dstUser,proto3" json:"dstUser,omitempty"` // 接收者 + Type ChatType `protobuf:"varint,3,opt,name=type,proto3,enum=pb.ChatType" json:"type,omitempty"` // 聊天类型 + GameId ServiceTypeId `protobuf:"varint,4,opt,name=gameId,proto3,enum=pb.ServiceTypeId" json:"gameId,omitempty"` // 游戏id,只在本玩法中显示的聊天信息 + Content string `protobuf:"bytes,5,opt,name=content,proto3" json:"content,omitempty"` // 内容 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -157,12 +157,12 @@ const file_chat_proto_rawDesc = "" + "\n" + "\n" + "chat.proto\x12\x02pb\x1a\n" + - "user.proto\x1a\rservice.proto\"\xc3\x01\n" + - "\aReqChat\x12'\n" + - "\bsrc_user\x18\x01 \x01(\v2\f.pb.ChatUserR\asrcUser\x12'\n" + - "\bdst_user\x18\x02 \x01(\v2\f.pb.ChatUserR\adstUser\x12 \n" + - "\x04type\x18\x03 \x01(\x0e2\f.pb.ChatTypeR\x04type\x12*\n" + - "\agame_id\x18\x04 \x01(\x0e2\x11.pb.ServiceTypeIdR\x06gameId\x12\x18\n" + + "user.proto\x1a\rservice.proto\"\xc0\x01\n" + + "\aReqChat\x12&\n" + + "\asrcUser\x18\x01 \x01(\v2\f.pb.ChatUserR\asrcUser\x12&\n" + + "\adstUser\x18\x02 \x01(\v2\f.pb.ChatUserR\adstUser\x12 \n" + + "\x04type\x18\x03 \x01(\x0e2\f.pb.ChatTypeR\x04type\x12)\n" + + "\x06gameId\x18\x04 \x01(\x0e2\x11.pb.ServiceTypeIdR\x06gameId\x12\x18\n" + "\acontent\x18\x05 \x01(\tR\acontent*H\n" + "\bChatType\x12\x0e\n" + "\n" + @@ -194,10 +194,10 @@ var file_chat_proto_goTypes = []any{ (ServiceTypeId)(0), // 3: pb.ServiceTypeId } var file_chat_proto_depIdxs = []int32{ - 2, // 0: pb.ReqChat.src_user:type_name -> pb.ChatUser - 2, // 1: pb.ReqChat.dst_user:type_name -> pb.ChatUser + 2, // 0: pb.ReqChat.srcUser:type_name -> pb.ChatUser + 2, // 1: pb.ReqChat.dstUser:type_name -> pb.ChatUser 0, // 2: pb.ReqChat.type:type_name -> pb.ChatType - 3, // 3: pb.ReqChat.game_id:type_name -> pb.ServiceTypeId + 3, // 3: pb.ReqChat.gameId:type_name -> pb.ServiceTypeId 4, // [4:4] is the sub-list for method output_type 4, // [4:4] is the sub-list for method input_type 4, // [4:4] is the sub-list for extension type_name diff --git a/common/proto/pb/common.pb.go b/common/proto/pb/common.pb.go index 26457f6..92a2ec1 100644 --- a/common/proto/pb/common.pb.go +++ b/common/proto/pb/common.pb.go @@ -109,8 +109,8 @@ func (*ReqEnterRoom) Descriptor() ([]byte, []int) { type RspEnterRoom struct { state protoimpl.MessageState `protogen:"open.v1"` Code ErrCode `protobuf:"varint,1,opt,name=code,proto3,enum=pb.ErrCode" json:"code,omitempty"` - PlayType int32 `protobuf:"varint,2,opt,name=PlayType,proto3" json:"PlayType,omitempty"` // 玩法id - RoomType int32 `protobuf:"varint,3,opt,name=RoomType,proto3" json:"RoomType,omitempty"` // 房间类型,低级,中级,高级等 + GameId int32 `protobuf:"varint,2,opt,name=gameId,proto3" json:"gameId,omitempty"` // 玩法id + RoomType int32 `protobuf:"varint,3,opt,name=roomType,proto3" json:"roomType,omitempty"` // 房间类型,低级,中级,高级等 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -152,9 +152,9 @@ func (x *RspEnterRoom) GetCode() ErrCode { return ErrCode_OK } -func (x *RspEnterRoom) GetPlayType() int32 { +func (x *RspEnterRoom) GetGameId() int32 { if x != nil { - return x.PlayType + return x.GameId } return 0 } @@ -255,11 +255,11 @@ const file_common_proto_rawDesc = "" + "code.proto\"1\n" + "\x0eNtfKickOutUser\x12\x1f\n" + "\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04code\"\x0e\n" + - "\fReqEnterRoom\"g\n" + + "\fReqEnterRoom\"c\n" + "\fRspEnterRoom\x12\x1f\n" + - "\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04code\x12\x1a\n" + - "\bPlayType\x18\x02 \x01(\x05R\bPlayType\x12\x1a\n" + - "\bRoomType\x18\x03 \x01(\x05R\bRoomType\"\x0e\n" + + "\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04code\x12\x16\n" + + "\x06gameId\x18\x02 \x01(\x05R\x06gameId\x12\x1a\n" + + "\broomType\x18\x03 \x01(\x05R\broomType\"\x0e\n" + "\fReqLeaveRoom\"/\n" + "\fRspLeaveRoom\x12\x1f\n" + "\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04codeB\x11Z\x0fcommon/proto/pbb\x06proto3" diff --git a/common/proto/pb/login.pb.go b/common/proto/pb/login.pb.go index 92bea2f..db0f253 100644 --- a/common/proto/pb/login.pb.go +++ b/common/proto/pb/login.pb.go @@ -203,6 +203,51 @@ func (x *NtfUserOnline) GetUserId() int64 { return 0 } +// 通知玩家在哪些服务里,方便玩家重进这些服务,重连使用 +type NtfUserInService struct { + state protoimpl.MessageState `protogen:"open.v1"` + ServiceNames []string `protobuf:"bytes,3,rep,name=serviceNames,proto3" json:"serviceNames,omitempty"` // 玩家所在玩法的服务名 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NtfUserInService) Reset() { + *x = NtfUserInService{} + mi := &file_login_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NtfUserInService) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NtfUserInService) ProtoMessage() {} + +func (x *NtfUserInService) ProtoReflect() protoreflect.Message { + mi := &file_login_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NtfUserInService.ProtoReflect.Descriptor instead. +func (*NtfUserInService) Descriptor() ([]byte, []int) { + return file_login_proto_rawDescGZIP(), []int{3} +} + +func (x *NtfUserInService) GetServiceNames() []string { + if x != nil { + return x.ServiceNames + } + return nil +} + // 玩家登陆 type ReqUserLogout struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -212,7 +257,7 @@ type ReqUserLogout struct { func (x *ReqUserLogout) Reset() { *x = ReqUserLogout{} - mi := &file_login_proto_msgTypes[3] + mi := &file_login_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -224,7 +269,7 @@ func (x *ReqUserLogout) String() string { func (*ReqUserLogout) ProtoMessage() {} func (x *ReqUserLogout) ProtoReflect() protoreflect.Message { - mi := &file_login_proto_msgTypes[3] + mi := &file_login_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -237,7 +282,7 @@ func (x *ReqUserLogout) ProtoReflect() protoreflect.Message { // Deprecated: Use ReqUserLogout.ProtoReflect.Descriptor instead. func (*ReqUserLogout) Descriptor() ([]byte, []int) { - return file_login_proto_rawDescGZIP(), []int{3} + return file_login_proto_rawDescGZIP(), []int{4} } type RspUserLogout struct { @@ -249,7 +294,7 @@ type RspUserLogout struct { func (x *RspUserLogout) Reset() { *x = RspUserLogout{} - mi := &file_login_proto_msgTypes[4] + mi := &file_login_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -261,7 +306,7 @@ func (x *RspUserLogout) String() string { func (*RspUserLogout) ProtoMessage() {} func (x *RspUserLogout) ProtoReflect() protoreflect.Message { - mi := &file_login_proto_msgTypes[4] + mi := &file_login_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -274,7 +319,7 @@ func (x *RspUserLogout) ProtoReflect() protoreflect.Message { // Deprecated: Use RspUserLogout.ProtoReflect.Descriptor instead. func (*RspUserLogout) Descriptor() ([]byte, []int) { - return file_login_proto_rawDescGZIP(), []int{4} + return file_login_proto_rawDescGZIP(), []int{5} } func (x *RspUserLogout) GetCode() ErrCode { @@ -294,7 +339,7 @@ type NtfUserOffline struct { func (x *NtfUserOffline) Reset() { *x = NtfUserOffline{} - mi := &file_login_proto_msgTypes[5] + mi := &file_login_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -306,7 +351,7 @@ func (x *NtfUserOffline) String() string { func (*NtfUserOffline) ProtoMessage() {} func (x *NtfUserOffline) ProtoReflect() protoreflect.Message { - mi := &file_login_proto_msgTypes[5] + mi := &file_login_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -319,7 +364,7 @@ func (x *NtfUserOffline) ProtoReflect() protoreflect.Message { // Deprecated: Use NtfUserOffline.ProtoReflect.Descriptor instead. func (*NtfUserOffline) Descriptor() ([]byte, []int) { - return file_login_proto_rawDescGZIP(), []int{5} + return file_login_proto_rawDescGZIP(), []int{6} } func (x *NtfUserOffline) GetUserId() int64 { @@ -347,7 +392,9 @@ const file_login_proto_rawDesc = "" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x14\n" + "\x05token\x18\x03 \x01(\tR\x05token\"(\n" + "\rNtfUserOnline\x12\x17\n" + - "\auser_id\x18\x02 \x01(\x03R\x06userId\"\x0f\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\"6\n" + + "\x10NtfUserInService\x12\"\n" + + "\fserviceNames\x18\x03 \x03(\tR\fserviceNames\"\x0f\n" + "\rReqUserLogout\"0\n" + "\rRspUserLogout\x12\x1f\n" + "\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04code\")\n" + @@ -366,19 +413,20 @@ func file_login_proto_rawDescGZIP() []byte { return file_login_proto_rawDescData } -var file_login_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_login_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_login_proto_goTypes = []any{ - (*ReqUserLogin)(nil), // 0: pb.ReqUserLogin - (*RspUserLogin)(nil), // 1: pb.RspUserLogin - (*NtfUserOnline)(nil), // 2: pb.NtfUserOnline - (*ReqUserLogout)(nil), // 3: pb.ReqUserLogout - (*RspUserLogout)(nil), // 4: pb.RspUserLogout - (*NtfUserOffline)(nil), // 5: pb.NtfUserOffline - (ErrCode)(0), // 6: pb.ErrCode + (*ReqUserLogin)(nil), // 0: pb.ReqUserLogin + (*RspUserLogin)(nil), // 1: pb.RspUserLogin + (*NtfUserOnline)(nil), // 2: pb.NtfUserOnline + (*NtfUserInService)(nil), // 3: pb.NtfUserInService + (*ReqUserLogout)(nil), // 4: pb.ReqUserLogout + (*RspUserLogout)(nil), // 5: pb.RspUserLogout + (*NtfUserOffline)(nil), // 6: pb.NtfUserOffline + (ErrCode)(0), // 7: pb.ErrCode } var file_login_proto_depIdxs = []int32{ - 6, // 0: pb.RspUserLogin.code:type_name -> pb.ErrCode - 6, // 1: pb.RspUserLogout.code:type_name -> pb.ErrCode + 7, // 0: pb.RspUserLogin.code:type_name -> pb.ErrCode + 7, // 1: pb.RspUserLogout.code:type_name -> pb.ErrCode 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name @@ -398,7 +446,7 @@ func file_login_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_login_proto_rawDesc), len(file_login_proto_rawDesc)), NumEnums: 0, - NumMessages: 6, + NumMessages: 7, NumExtensions: 0, NumServices: 0, }, diff --git a/common/proto/pb/match.pb.go b/common/proto/pb/match.pb.go index 2c4e487..8647b3f 100644 --- a/common/proto/pb/match.pb.go +++ b/common/proto/pb/match.pb.go @@ -24,8 +24,8 @@ const ( // 房间匹配 type ReqMatchRoom struct { state protoimpl.MessageState `protogen:"open.v1"` - PlayType int32 `protobuf:"varint,1,opt,name=PlayType,proto3" json:"PlayType,omitempty"` // 玩法id - RoomType int32 `protobuf:"varint,2,opt,name=RoomType,proto3" json:"RoomType,omitempty"` // 房间类型,低级,中级,高级等 + GameId int32 `protobuf:"varint,1,opt,name=gameId,proto3" json:"gameId,omitempty"` // 玩法id + RoomType int32 `protobuf:"varint,2,opt,name=roomType,proto3" json:"roomType,omitempty"` // 房间类型,低级,中级,高级等 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -60,9 +60,9 @@ func (*ReqMatchRoom) Descriptor() ([]byte, []int) { return file_match_proto_rawDescGZIP(), []int{0} } -func (x *ReqMatchRoom) GetPlayType() int32 { +func (x *ReqMatchRoom) GetGameId() int32 { if x != nil { - return x.PlayType + return x.GameId } return 0 } @@ -78,8 +78,8 @@ func (x *ReqMatchRoom) GetRoomType() int32 { type RspMatchRoom struct { state protoimpl.MessageState `protogen:"open.v1"` Code ErrCode `protobuf:"varint,1,opt,name=code,proto3,enum=pb.ErrCode" json:"code,omitempty"` - PlayType int32 `protobuf:"varint,2,opt,name=PlayType,proto3" json:"PlayType,omitempty"` // 玩法id - RoomType int32 `protobuf:"varint,3,opt,name=RoomType,proto3" json:"RoomType,omitempty"` // 房间类型,低级,中级,高级等 + GameId int32 `protobuf:"varint,2,opt,name=gameId,proto3" json:"gameId,omitempty"` // 玩法id + RoomType int32 `protobuf:"varint,3,opt,name=roomType,proto3" json:"roomType,omitempty"` // 房间类型,低级,中级,高级等 User *GameUser `protobuf:"bytes,4,opt,name=User,proto3" json:"User,omitempty"` // 玩家数据 ColorInfo *RspMatchRoom_ColorInfo `protobuf:"bytes,20,opt,name=colorInfo,proto3" json:"colorInfo,omitempty"` // color玩法配置信息 unknownFields protoimpl.UnknownFields @@ -123,9 +123,9 @@ func (x *RspMatchRoom) GetCode() ErrCode { return ErrCode_OK } -func (x *RspMatchRoom) GetPlayType() int32 { +func (x *RspMatchRoom) GetGameId() int32 { if x != nil { - return x.PlayType + return x.GameId } return 0 } @@ -238,14 +238,14 @@ const file_match_proto_rawDesc = "" + "\n" + "\vmatch.proto\x12\x02pb\x1a\n" + "code.proto\x1a\n" + - "user.proto\"F\n" + - "\fReqMatchRoom\x12\x1a\n" + - "\bPlayType\x18\x01 \x01(\x05R\bPlayType\x12\x1a\n" + - "\bRoomType\x18\x02 \x01(\x05R\bRoomType\"\xd0\x01\n" + + "user.proto\"B\n" + + "\fReqMatchRoom\x12\x16\n" + + "\x06gameId\x18\x01 \x01(\x05R\x06gameId\x12\x1a\n" + + "\broomType\x18\x02 \x01(\x05R\broomType\"\xcc\x01\n" + "\fRspMatchRoom\x12\x1f\n" + - "\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04code\x12\x1a\n" + - "\bPlayType\x18\x02 \x01(\x05R\bPlayType\x12\x1a\n" + - "\bRoomType\x18\x03 \x01(\x05R\bRoomType\x12 \n" + + "\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04code\x12\x16\n" + + "\x06gameId\x18\x02 \x01(\x05R\x06gameId\x12\x1a\n" + + "\broomType\x18\x03 \x01(\x05R\broomType\x12 \n" + "\x04User\x18\x04 \x01(\v2\f.pb.GameUserR\x04User\x128\n" + "\tcolorInfo\x18\x14 \x01(\v2\x1a.pb.RspMatchRoom.ColorInfoR\tcolorInfo\x1a\v\n" + "\tColorInfo\"7\n" + diff --git a/common/proto/pb/msgId.pb.go b/common/proto/pb/msgId.pb.go index 0d868b0..2232b81 100644 --- a/common/proto/pb/msgId.pb.go +++ b/common/proto/pb/msgId.pb.go @@ -39,12 +39,13 @@ const ( MsgId_ReqChatId MsgId = 2000 // 玩家聊天消息 MsgId_RspChatId MsgId = 2001 // 复用C2SChatMsg // 登陆服 2100-2199 - MsgId_ReqUserLoginId MsgId = 2100 // 玩家登陆 - MsgId_RspUserLoginId MsgId = 2101 - MsgId_NtfUserOnlineId MsgId = 2102 - MsgId_ReqUserLogoutId MsgId = 2104 - MsgId_RspUserLogoutId MsgId = 2105 - MsgId_NtfUserOfflineId MsgId = 2106 + MsgId_ReqUserLoginId MsgId = 2100 // 玩家登陆 + MsgId_RspUserLoginId MsgId = 2101 + MsgId_NtfUserOnlineId MsgId = 2102 + MsgId_NtfUserInServiceId MsgId = 2103 // 将玩家之前呆的服务同步给玩家,方便玩家重连进来 + MsgId_ReqUserLogoutId MsgId = 2104 + MsgId_RspUserLogoutId MsgId = 2105 + MsgId_NtfUserOfflineId MsgId = 2106 // 匹配服 2200-2299 MsgId_ReqMatchRoomId MsgId = 2200 // 匹配服 MsgId_RspMatchRoomId MsgId = 2201 @@ -78,6 +79,7 @@ var ( 2100: "ReqUserLoginId", 2101: "RspUserLoginId", 2102: "NtfUserOnlineId", + 2103: "NtfUserInServiceId", 2104: "ReqUserLogoutId", 2105: "RspUserLogoutId", 2106: "NtfUserOfflineId", @@ -109,6 +111,7 @@ var ( "ReqUserLoginId": 2100, "RspUserLoginId": 2101, "NtfUserOnlineId": 2102, + "NtfUserInServiceId": 2103, "ReqUserLogoutId": 2104, "RspUserLogoutId": 2105, "NtfUserOfflineId": 2106, @@ -160,7 +163,7 @@ var File_msgId_proto protoreflect.FileDescriptor const file_msgId_proto_rawDesc = "" + "\n" + - "\vmsgId.proto\x12\x02pb*\x91\x05\n" + + "\vmsgId.proto\x12\x02pb*\xaa\x05\n" + "\x05MsgId\x12\x0e\n" + "\n" + "MI_Unknown\x10\x00\x12\x12\n" + @@ -174,7 +177,8 @@ const file_msgId_proto_rawDesc = "" + "\tRspChatId\x10\xd1\x0f\x12\x13\n" + "\x0eReqUserLoginId\x10\xb4\x10\x12\x13\n" + "\x0eRspUserLoginId\x10\xb5\x10\x12\x14\n" + - "\x0fNtfUserOnlineId\x10\xb6\x10\x12\x14\n" + + "\x0fNtfUserOnlineId\x10\xb6\x10\x12\x17\n" + + "\x12NtfUserInServiceId\x10\xb7\x10\x12\x14\n" + "\x0fReqUserLogoutId\x10\xb8\x10\x12\x14\n" + "\x0fRspUserLogoutId\x10\xb9\x10\x12\x15\n" + "\x10NtfUserOfflineId\x10\xba\x10\x12\x13\n" + diff --git a/common/proto/pb/service.pb.go b/common/proto/pb/service.pb.go index 3ddf1e6..5194bc6 100644 --- a/common/proto/pb/service.pb.go +++ b/common/proto/pb/service.pb.go @@ -30,6 +30,7 @@ const ( ServiceTypeId_STI_Chat ServiceTypeId = 102 // 聊天服 ServiceTypeId_STI_DB ServiceTypeId = 103 // db服 ServiceTypeId_STI_Match ServiceTypeId = 104 // 匹配服 + ServiceTypeId_STI_Lobby ServiceTypeId = 105 // 大厅服 ServiceTypeId_STI_ColorGame ServiceTypeId = 120 // color game ) @@ -42,6 +43,7 @@ var ( 102: "STI_Chat", 103: "STI_DB", 104: "STI_Match", + 105: "STI_Lobby", 120: "STI_ColorGame", } ServiceTypeId_value = map[string]int32{ @@ -51,6 +53,7 @@ var ( "STI_Chat": 102, "STI_DB": 103, "STI_Match": 104, + "STI_Lobby": 105, "STI_ColorGame": 120, } ) @@ -86,7 +89,7 @@ var File_service_proto protoreflect.FileDescriptor const file_service_proto_rawDesc = "" + "\n" + - "\rservice.proto\x12\x02pb*y\n" + + "\rservice.proto\x12\x02pb*\x88\x01\n" + "\rServiceTypeId\x12\x0f\n" + "\vSTI_Unknown\x10\x00\x12\f\n" + "\bSTI_Gate\x10d\x12\r\n" + @@ -94,7 +97,8 @@ const file_service_proto_rawDesc = "" + "\bSTI_Chat\x10f\x12\n" + "\n" + "\x06STI_DB\x10g\x12\r\n" + - "\tSTI_Match\x10h\x12\x11\n" + + "\tSTI_Match\x10h\x12\r\n" + + "\tSTI_Lobby\x10i\x12\x11\n" + "\rSTI_ColorGame\x10xB\x11Z\x0fcommon/proto/pbb\x06proto3" var ( diff --git a/common/proto/pb/user.pb.go b/common/proto/pb/user.pb.go index d7322cd..4942064 100644 --- a/common/proto/pb/user.pb.go +++ b/common/proto/pb/user.pb.go @@ -24,11 +24,11 @@ const ( // 聊天中显示的玩家基础信息 type ChatUser struct { state protoimpl.MessageState `protogen:"open.v1"` - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Nickname string `protobuf:"bytes,2,opt,name=nickname,proto3" json:"nickname,omitempty"` // 昵称 - Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` // 头像 - AvatarFrame string `protobuf:"bytes,4,opt,name=avatar_frame,json=avatarFrame,proto3" json:"avatar_frame,omitempty"` // 头像框 - VipLevel string `protobuf:"bytes,5,opt,name=vip_level,json=vipLevel,proto3" json:"vip_level,omitempty"` // vip等级 + UserId int64 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` + Nickname string `protobuf:"bytes,2,opt,name=nickname,proto3" json:"nickname,omitempty"` // 昵称 + Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` // 头像 + AvatarFrame string `protobuf:"bytes,4,opt,name=avatarFrame,proto3" json:"avatarFrame,omitempty"` // 头像框 + VipLevel string `protobuf:"bytes,5,opt,name=vipLevel,proto3" json:"vipLevel,omitempty"` // vip等级 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -101,14 +101,14 @@ func (x *ChatUser) GetVipLevel() string { // 房间内的玩家数据 type GameUser struct { state protoimpl.MessageState `protogen:"open.v1"` - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Nickname string `protobuf:"bytes,2,opt,name=nickname,proto3" json:"nickname,omitempty"` // 昵称 - Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` // 头像 - AvatarFrame string `protobuf:"bytes,4,opt,name=avatar_frame,json=avatarFrame,proto3" json:"avatar_frame,omitempty"` // 头像框 - VipLevel int32 `protobuf:"varint,5,opt,name=vip_level,json=vipLevel,proto3" json:"vip_level,omitempty"` // vip等级 - VipExp int32 `protobuf:"varint,6,opt,name=vip_exp,json=vipExp,proto3" json:"vip_exp,omitempty"` // vip经验 - Seat int64 `protobuf:"varint,20,opt,name=seat,proto3" json:"seat,omitempty"` // 座位 - Gold int64 `protobuf:"varint,25,opt,name=gold,proto3" json:"gold,omitempty"` // 金币 + UserId int64 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` + Nickname string `protobuf:"bytes,2,opt,name=nickname,proto3" json:"nickname,omitempty"` // 昵称 + Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` // 头像 + AvatarFrame string `protobuf:"bytes,4,opt,name=avatarFrame,proto3" json:"avatarFrame,omitempty"` // 头像框 + VipLevel int32 `protobuf:"varint,5,opt,name=vipLevel,proto3" json:"vipLevel,omitempty"` // vip等级 + VipExp int32 `protobuf:"varint,6,opt,name=vipExp,proto3" json:"vipExp,omitempty"` // vip经验 + Seat int64 `protobuf:"varint,20,opt,name=seat,proto3" json:"seat,omitempty"` // 座位 + Gold int64 `protobuf:"varint,25,opt,name=gold,proto3" json:"gold,omitempty"` // 金币 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -204,20 +204,20 @@ var File_user_proto protoreflect.FileDescriptor const file_user_proto_rawDesc = "" + "\n" + "\n" + - "user.proto\x12\x02pb\"\x97\x01\n" + - "\bChatUser\x12\x17\n" + - "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" + + "user.proto\x12\x02pb\"\x94\x01\n" + + "\bChatUser\x12\x16\n" + + "\x06userId\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" + "\bnickname\x18\x02 \x01(\tR\bnickname\x12\x16\n" + - "\x06avatar\x18\x03 \x01(\tR\x06avatar\x12!\n" + - "\favatar_frame\x18\x04 \x01(\tR\vavatarFrame\x12\x1b\n" + - "\tvip_level\x18\x05 \x01(\tR\bvipLevel\"\xd8\x01\n" + - "\bGameUser\x12\x17\n" + - "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" + + "\x06avatar\x18\x03 \x01(\tR\x06avatar\x12 \n" + + "\vavatarFrame\x18\x04 \x01(\tR\vavatarFrame\x12\x1a\n" + + "\bvipLevel\x18\x05 \x01(\tR\bvipLevel\"\xd4\x01\n" + + "\bGameUser\x12\x16\n" + + "\x06userId\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" + "\bnickname\x18\x02 \x01(\tR\bnickname\x12\x16\n" + - "\x06avatar\x18\x03 \x01(\tR\x06avatar\x12!\n" + - "\favatar_frame\x18\x04 \x01(\tR\vavatarFrame\x12\x1b\n" + - "\tvip_level\x18\x05 \x01(\x05R\bvipLevel\x12\x17\n" + - "\avip_exp\x18\x06 \x01(\x05R\x06vipExp\x12\x12\n" + + "\x06avatar\x18\x03 \x01(\tR\x06avatar\x12 \n" + + "\vavatarFrame\x18\x04 \x01(\tR\vavatarFrame\x12\x1a\n" + + "\bvipLevel\x18\x05 \x01(\x05R\bvipLevel\x12\x16\n" + + "\x06vipExp\x18\x06 \x01(\x05R\x06vipExp\x12\x12\n" + "\x04seat\x18\x14 \x01(\x03R\x04seat\x12\x12\n" + "\x04gold\x18\x19 \x01(\x03R\x04goldB\x11Z\x0fcommon/proto/pbb\x06proto3" diff --git a/common/serviceName/serviceName.go b/common/serviceName/serviceName.go index 6c52499..f0563c0 100644 --- a/common/serviceName/serviceName.go +++ b/common/serviceName/serviceName.go @@ -6,6 +6,7 @@ const ( Login = "login" Db = "db" Match = "match" + Lobby = "lobby" // 下面是具体玩法服 ColorGame = "color_game" diff --git a/common/userBindService/userService.go b/common/userBindService/userService.go index 9aec8dd..31f16aa 100644 --- a/common/userBindService/userService.go +++ b/common/userBindService/userService.go @@ -8,7 +8,6 @@ import ( "game/common/utils" "github.com/fox/fox/etcd" "github.com/fox/fox/log" - "github.com/fox/fox/service" "github.com/fox/fox/xrand" "github.com/go-redis/redis/v8" "time" @@ -24,6 +23,14 @@ const ( 客户端没有指定路由节点,则服务器从redis查找曾经的绑定节点,并验证有效然后转发到对应的节点。 如果redis信息已经失效(服务有更新),则从etcd中获取该玩法下所有最新版本的节点(game1,game2),然后随机发送到其中一个节点,并在redis中保存绑定关系。 如果客户端所有消息都不指定具体的节点名,则每次都需要从redis拉取绑定关系,会影响路由速度。 + +服务分为3种类型: +1.有状态服,如各玩法服,需要保存玩家在哪个状态服里。 +--1.1 这一类服务需要使用UserBindService.HashServiceNode来获取节点 +2.无状态服,如大厅服,登陆服,聊天服,这些服务不持有任何状态,包括玩家数据。无需保存玩家在哪个服务。直接通过group.topic随机路由即可。 +--2.1 这一类服务无需走UserBindService,直接通过group.topic路由即可。 +3.有序hash服,如db服,需要根据玩家id hash到固定的db服以保证操作的一致性。 +--3.1 这一类服务需要使用UserBindService.HashServiceNode来获取节点 */ type UserBindService struct { rdb *redis.Client @@ -37,14 +44,18 @@ func NewUserBindService(rdb *redis.Client, etcdRegistry *etcd.Registry[etcd.Serv } } -func (m *UserBindService) makeRedisKey(userId int64, typeId pb.ServiceTypeId) string { - return fmt.Sprintf("%s_%d:%d", prefix, userId, int(typeId)) +func (m *UserBindService) makeRedisKey(userId int64) string { + return fmt.Sprintf("%s:%s", prefix, userId) +} +func (m *UserBindService) makeRedisSubKey(typeId pb.ServiceTypeId) string { + return typeId.String() } // 从redis中加载玩家曾经访问过的服务节点名 func (m *UserBindService) LoadFromRedis(userId int64, typeId pb.ServiceTypeId) string { - k := m.makeRedisKey(userId, typeId) - if sName, err := m.rdb.Get(context.Background(), k).Result(); err != nil { + key := m.makeRedisKey(userId) + subKey := m.makeRedisSubKey(typeId) + if sName, err := m.rdb.HGet(context.Background(), key, subKey).Result(); err != nil { if !errors.Is(err, redis.Nil) { log.Error(err.Error()) } @@ -56,24 +67,34 @@ func (m *UserBindService) LoadFromRedis(userId int64, typeId pb.ServiceTypeId) s // 从redis中解除玩家与节点的绑定关系 func (m *UserBindService) DelUserService(userId int64, typeId pb.ServiceTypeId) { - k := m.makeRedisKey(userId, typeId) - _, _ = m.rdb.Del(context.Background(), k).Result() + key := m.makeRedisKey(userId) + subKey := m.makeRedisSubKey(typeId) + _, _ = m.rdb.HDel(context.Background(), key, subKey).Result() +} + +// 保存玩家与节点的绑定关系至从redis +func (m *UserBindService) SaveUserService(userId int64, typeId pb.ServiceTypeId, serviceName string) { + key := m.makeRedisKey(userId) + subKey := m.makeRedisSubKey(typeId) + m.rdb.HSet(context.Background(), key, subKey, serviceName, 2*24*time.Hour) } // 从etcd中检查节点是否有效,如果有game1(旧服),game2(新服),都算有效,但是旧服会拒绝新玩家进入, // 此时旧服不止要拒绝新玩家,还要删除redis中的绑定关系。方便客户端重新发消息时路由到新的服务。 -func (m *UserBindService) serviceIsValid(serviceName string) bool { +func (m *UserBindService) findServiceNodeByServiceName(serviceName string) (*etcd.ServiceNode, bool) { + var sNode *etcd.ServiceNode valid := false m.etcdRegistry.GetNodes().Range(func(k, v interface{}) bool { if node, ok := v.(etcd.ServiceNode); ok { if node.Name == serviceName { valid = true + sNode = &node return false } } return true }) - return valid + return sNode, valid } func (m *UserBindService) stringAllServiceNode() string { @@ -86,7 +107,7 @@ func (m *UserBindService) stringAllServiceNode() string { } // 从etcd中找可用服务节点随机选择一个 -func (m *UserBindService) RandServiceNode(typeId pb.ServiceTypeId) (*etcd.ServiceNode, error) { +func (m *UserBindService) randServiceNode(typeId pb.ServiceTypeId) (*etcd.ServiceNode, error) { var nodes []etcd.ServiceNode var version string m.etcdRegistry.GetNodes().Range(func(_, value any) bool { @@ -109,47 +130,12 @@ func (m *UserBindService) RandServiceNode(typeId pb.ServiceTypeId) (*etcd.Servic if len(nodes) == 0 { return nil, fmt.Errorf("not found service node.type id:%v. all node:%v", typeId, m.stringAllServiceNode()) } - n := xrand.IntN(len(nodes)) + n := xrand.RandN(len(nodes)) return &nodes[n], nil } -// 根据服务类型,路由到对应的服务节点 -func (m *UserBindService) findServiceName(userId int64, typeId pb.ServiceTypeId) (string, error) { - if userId > 0 { - // 向redis中查询。redis中保留的服务节点不一定是可用的,还需要向etcd中验证 - sName := m.LoadFromRedis(userId, typeId) - // log.DebugF("user:%v查找到服务节点:%s", userId, sName) - if sName != "" && m.serviceIsValid(sName) { - return sName, nil - } - } - - // log.DebugF("user:%v查找到服务节点:%s为无效节点", userId, sName) - // redis也没有玩家的服务节点信息,从etcd中找可用服务节点随机选择一个 - node, err := m.RandServiceNode(typeId) - if err != nil { - log.ErrorF("etcd中随机一个服务节点时,错误:%v", err) - return "", err - } - // log.DebugF("etcd中随机一个服务节点:%s", node.Name) - m.rdb.Set(context.Background(), m.makeRedisKey(userId, typeId), node.Name, 2*24*time.Hour) - return node.Name, nil -} - -/* -查找topic,根据serviceTypeId以及玩家id查找玩家过往访问该服务的节点,优先使用原节点 -*/ -func (m *UserBindService) FindTopic(userId int64, serviceTypeId pb.ServiceTypeId) (topic, sName string) { - var err error - if sName, err = m.findServiceName(userId, serviceTypeId); err == nil { - return service.TopicEx(sName), sName - } else { - log.Error(err.Error()) - } - return "", sName -} - -// 从etcd中hash一个服务节点 +// 从etcd中hash一个服务节点。不需要保存玩家在哪个服务。 +// 该服务是类似于db服这种有序操作的hash服 func (m *UserBindService) HashServiceNode(typeId pb.ServiceTypeId, uid int64) (*etcd.ServiceNode, error) { var nodes []etcd.ServiceNode var version string @@ -176,3 +162,51 @@ func (m *UserBindService) HashServiceNode(typeId pb.ServiceTypeId, uid int64) (* n := uid % int64(len(nodes)) return &nodes[n], nil } + +// 要查找的服务必须是状态服,无状态服不需要查找指定服务。 +// 如果玩家是首次使用该服务,则随机一个服务并保存玩家该服务节点。下次查找时返回该节点。 +func (m *UserBindService) FindServiceNode(userId int64, typeId pb.ServiceTypeId) (*etcd.ServiceNode, error) { + if userId > 0 { + // 向redis中查询。redis中保留的服务节点不一定是可用的,还需要向etcd中验证 + sName := m.LoadFromRedis(userId, typeId) + // log.DebugF("user:%v查找到服务节点:%s", userId, sName) + if node, ok := m.findServiceNodeByServiceName(sName); ok { + return node, nil + } + } + + // log.DebugF("user:%v查找到服务节点:%s为无效节点", userId, sName) + // redis也没有玩家的服务节点信息,从etcd中找可用服务节点随机选择一个 + node, err := m.randServiceNode(typeId) + if err != nil { + log.ErrorF("etcd中随机一个服务节点时,错误:%v", err) + return nil, err + } + // log.DebugF("etcd中随机一个服务节点:%s", node.Name) + m.rdb.HSet(context.Background(), m.makeRedisKey(userId), node.Name, 2*24*time.Hour) + return node, nil +} + +// 查找玩家所有的玩法节点 +func (m *UserBindService) GetAllUserInGameServiceNode(userId int64) ([]*etcd.ServiceNode, error) { + if userId > 0 { + maps, err := m.rdb.HGetAll(context.Background(), m.makeRedisKey(userId)).Result() + if err != nil && err != redis.Nil { + log.ErrorF("user:%v get all service error:%v", err.Error()) + return nil, err + } + if len(maps) > 0 { + inGames := make([]*etcd.ServiceNode, 0) + m.etcdRegistry.GetNodes().Range(func(k, v interface{}) bool { + if node, ok := v.(etcd.ServiceNode); ok { + if _, ok = maps[node.Name]; ok && node.TypeId != int(pb.ServiceTypeId_STI_Gate) { + inGames = append(inGames, &node) + } + } + return true + }) + return inGames, nil + } + } + return nil, nil +} diff --git a/server/colorgame/room/c2s.go b/server/colorgame/room/c2s.go index c8fb28b..561c58a 100644 --- a/server/colorgame/room/c2s.go +++ b/server/colorgame/room/c2s.go @@ -2,6 +2,8 @@ package room import ( "game/common/proto/pb" + "game/common/userBindService" + "game/server/colorgame/model" "github.com/fox/fox/ipb" ) @@ -53,6 +55,9 @@ func (rm *ColorRoom) checkLeaveRoom(user *ColorPlayer, iMsg *ipb.InternalMsg, re func (rm *ColorRoom) leaveRoom(user *ColorPlayer) { rm.DelPlayer(user.Id()) rm.userMgr.Del(user.ID) + // 移除redis中玩家与本服务绑定关系 + userService := userBindService.NewUserBindService(model.UserBindServiceRedis, nil) + userService.DelUserService(user.ID, pb.ServiceTypeId_STI_ColorGame) } func (rm *ColorRoom) onLeaveRoom(user *ColorPlayer, iMsg *ipb.InternalMsg, req *pb.ReqLeaveRoom) { diff --git a/server/colorgame/room/colorRoom.go b/server/colorgame/room/colorRoom.go index 5548413..12dfc6c 100644 --- a/server/colorgame/room/colorRoom.go +++ b/server/colorgame/room/colorRoom.go @@ -33,7 +33,7 @@ type ColorRoom struct { } func newColorRoom(id, roomType int, srv service.IService, userMgr *baseroom.PlayerMgr) (baseroom.IRoom, pb.ErrCode) { - playType := int(pb.ServiceTypeId_STI_ColorGame) + gameId := int(pb.ServiceTypeId_STI_ColorGame) rm := &ColorRoom{ HundredRoom: nil, userMgr: userMgr, @@ -44,13 +44,13 @@ func newColorRoom(id, roomType int, srv service.IService, userMgr *baseroom.Play jackpotValue: 0, endBetAreaMul: make([]*pb.ColorBetAreaMul, 0, len(pb.ColorBetArea_name)), winBetAreaMul: nil, - jackpotMgr: jackpot.NewJackpotMgr(playType, model.UserRedis), + jackpotMgr: jackpot.NewJackpotMgr(gameId, model.UserRedis), } rm.trend = rm.initGameTrend() rm.resetGameData() code := pb.ErrCode_OK - rm.HundredRoom, code = baseroom.NewHundredRoom(id, roomType, playType, srv) + rm.HundredRoom, code = baseroom.NewHundredRoom(id, roomType, gameId, srv) if code != pb.ErrCode_OK { log.ErrorF("new color room err code:%v", code) return nil, code diff --git a/server/hilo/cmd/cmd.go b/server/hilo/cmd/cmd.go deleted file mode 100644 index 98ec9d9..0000000 --- a/server/hilo/cmd/cmd.go +++ /dev/null @@ -1,47 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/fox/fox/log" - "os" - "os/signal" - "syscall" -) - -// func initLog(command *config.Command) { -// log.Open(fmt.Sprintf("%v/%v/%v.log", command.LogPath, servername.Money, servername.Money), command.LogLevel) -// } - -func initLog() { - log.Open("hilo.log", log.DebugL) -} - -func initRepo() { - // model.InitUserDB(&config.Config.DBConfig) - // model.InitConfigDB(&config.Config.DBConfig) - // model.InitRedis(&config.Config.Redis) - // model.InitStub() - // RobotMgr.Load() - // ClubRobotMgr.Load() - // //log.Debug(fmt.Sprintf("%+v", stub.GGlobal)) -} - -func Run(version string) { - // command := config.ParseCommand() - // initLog(command) - initLog() - log.Info(fmt.Sprintf("版本信息.%v", version)) - // err := config.Load(command.ConfigPath) - // if err != nil { - // log.Error(fmt.Sprintf("load config err: %v", err)) - // } - // initRepo() - - // handler.InitService() - // 截获 SIGINT 和 SIGTERM 信号 - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) - sig := <-c - // handler.StopService() - log.Info(fmt.Sprintf("received %s, initiating shutdown...", sig)) -} diff --git a/server/lobby/cmd/cmd.go b/server/lobby/cmd/cmd.go new file mode 100644 index 0000000..c885ac6 --- /dev/null +++ b/server/lobby/cmd/cmd.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "fmt" + "game/server/lobby/config" + "game/server/lobby/model" + "game/server/lobby/server" + "github.com/fox/fox/log" + "os" + "os/signal" + "syscall" +) + +func initRepo() { + model.InitRedis() +} + +func Run(GitCommit, GitBranch, BuildDate string) { + config.InitLog() + config.LoadConfig(GitCommit, GitBranch, BuildDate) + log.Info(fmt.Sprintf("版本分支:%v,hash值:%v,编译时间:%v", GitBranch, GitCommit, BuildDate)) + initRepo() + + server.Init() + // 截获 SIGINT 和 SIGTERM 信号 + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + sig := <-c + server.Stop() + log.Info(fmt.Sprintf("received %s, initiating shutdown...", sig)) +} diff --git a/server/lobby/config/config.go b/server/lobby/config/config.go new file mode 100644 index 0000000..6eedcc5 --- /dev/null +++ b/server/lobby/config/config.go @@ -0,0 +1,38 @@ +package config + +import ( + "game/common/config" + "game/common/constant" + "github.com/fox/fox/db" + "github.com/fox/fox/log" +) + +var Command *config.Command +var Cfg *config.Common + +type LobbyConfig struct { +} + +func InitLog() { + log.Open("./log/chat.log", log.DebugL) + log.Info("") + log.Info("") + log.Info("") + log.Info("-----init log success-----") +} + +func LoadConfig(GitCommit, GitBranch, BuildDate string) { + Command = config.ParseCommand() + rdb, err := db.InitRedis(Command.RedisPassword, Command.RedisHost, Command.RedisPort, constant.Redis0Config) + if err != nil { + log.Error(err.Error()) + return + } + defer func() { _ = rdb.Close() }() + Cfg, err = config.LoadCommonConfig[LobbyConfig](rdb, GitCommit, GitBranch, BuildDate) + if err != nil { + log.Error(err.Error()) + return + } + log.DebugF("load common config success") +} diff --git a/server/hilo/main.go b/server/lobby/main.go similarity index 65% rename from server/hilo/main.go rename to server/lobby/main.go index 7f0c4b8..64f9c4d 100644 --- a/server/hilo/main.go +++ b/server/lobby/main.go @@ -1,8 +1,7 @@ package main import ( - "fmt" - "game/server/hilo/cmd" + "game/server/lobby/cmd" "github.com/fox/fox/ksync" "github.com/fox/fox/log" "time" @@ -19,7 +18,8 @@ func main() { if err == nil { BuildDate = tm.Format("2006-01-02 15:04:05") } + //BuildDate = "2025-05-31 09:56:06" ksync.RunSafe(func() { - cmd.Run(fmt.Sprintf("版本分支:%v,hash值:%v,编译时间:%v", GitBranch, GitCommit, BuildDate)) - }, func() { log.Debug("reset run") }) + cmd.Run(GitBranch, GitCommit, BuildDate) + }, func() { log.ErrorF("reset run") }) } diff --git a/server/lobby/model/db.go b/server/lobby/model/db.go new file mode 100644 index 0000000..d0decdd --- /dev/null +++ b/server/lobby/model/db.go @@ -0,0 +1,20 @@ +package model + +import ( + "game/common/constant" + "game/server/lobby/config" + "github.com/fox/fox/db" + "github.com/fox/fox/log" + "github.com/go-redis/redis/v8" +) + +var UserRedis *redis.Client +var err error + +func InitRedis() { + UserRedis, err = db.InitRedis(config.Cfg.Redis.Password, config.Cfg.Redis.Host, config.Cfg.Redis.Port, constant.Redis1User) + if err != nil { + log.Fatal(err.Error()) + return + } +} diff --git a/server/lobby/server/processor.go b/server/lobby/server/processor.go new file mode 100644 index 0000000..8987cb6 --- /dev/null +++ b/server/lobby/server/processor.go @@ -0,0 +1,25 @@ +package server + +import ( + "game/common/proto/pb" + "github.com/fox/fox/ipb" + "github.com/fox/fox/processor" + "github.com/fox/fox/service" +) + +func (s *LobbyService) initProcessor() { + s.processor.RegisterMessages(processor.RegisterMetas{ + pb.MsgId_NtfUserOnlineId: {pb.NtfUserOnline{}, s.onUserOnline}, + }) +} + +// 玩家上线,告诉玩家在哪个玩法中 +func (s *LobbyService) onUserOnline(iMsg *ipb.InternalMsg, msg *pb.NtfUserOnline) { + nodes, _ := s.bindService.GetAllUserInGameServiceNode(msg.UserId) + ntf := &pb.NtfUserInService{} + for _, node := range nodes { + ntf.ServiceNames = append(ntf.ServiceNames, node.Name) + } + s.SendServiceMsg(service.TopicEx(iMsg.ServiceName), iMsg.UserId, int32(pb.MsgId_NtfUserInServiceId), ntf) + +} diff --git a/server/lobby/server/service.go b/server/lobby/server/service.go new file mode 100644 index 0000000..728e63e --- /dev/null +++ b/server/lobby/server/service.go @@ -0,0 +1,116 @@ +package server + +import ( + "fmt" + "game/common/proto/pb" + "game/common/serviceName" + "game/common/userBindService" + "game/server/lobby/config" + "game/server/lobby/model" + "github.com/fox/fox/ipb" + "github.com/fox/fox/log" + "github.com/fox/fox/processor" + "github.com/fox/fox/service" + "github.com/golang/protobuf/proto" +) + +// 大厅服-无状态服,自身不持有任何状态包括玩家数据 +var lobby []*LobbyService + +type LobbyService struct { + *service.NatsService + processor *processor.Processor + bindService *userBindService.UserBindService +} + +func Init() { + for i := 0; i < config.Command.ServiceNum; i++ { + sid := config.Command.ServiceId + i + if srv := newLobbyService(sid); srv != nil { + lobby = append(lobby, srv) + } + } +} + +func Stop() { + for _, srv := range lobby { + srv.NotifyStop() + } + for _, srv := range lobby { + srv.WaitStop() + } +} + +func newLobbyService(serviceId int) *LobbyService { + var err error + s := new(LobbyService) + + sName := fmt.Sprintf("%v-%d", serviceName.Lobby, serviceId) + if s.NatsService, err = service.NewNatsService(&service.InitNatsServiceParams{ + EtcdAddress: config.Cfg.Etcd.Address, + EtcdUsername: "", + EtcdPassword: "", + NatsAddress: config.Cfg.Nats.Address, + ServiceType: serviceName.Lobby, + ServiceName: sName, + OnFunc: s, + TypeId: int(pb.ServiceTypeId_STI_Lobby), + Version: config.Cfg.BuildDate, + }); err != nil { + log.Fatal(err.Error()) + return nil + } + + s.bindService = userBindService.NewUserBindService(model.UserRedis, s.ServiceEtcd()) + s.processor = processor.NewProcessor() + s.initProcessor() + s.OnInit() + return s +} + +func (s *LobbyService) OnInit() { + if err := s.NatsService.QueueSubscribe(service.GroupTopic(s), service.GroupQueue(s)); err != nil { + log.Error(err.Error()) + } + s.NatsService.Run() + log.Debug("onInit") +} + +func (s *LobbyService) CanStop() bool { + //log.Debug("chatService.CanStop") + return true +} + +func (s *LobbyService) OnStop() { + s.NatsService.OnStop() + log.Debug("OnStop") +} + +// 处理其它服发送过来的消息 +func (s *LobbyService) OnMessage(data []byte) error { + var iMsg = &ipb.InternalMsg{} + var err error + if err = proto.Unmarshal(data, iMsg); err != nil { + log.Error(err.Error()) + return err + } + if req, err := s.processor.Unmarshal(iMsg.MsgId, iMsg.Msg); err == nil { + err = s.processor.Dispatch(iMsg.MsgId, iMsg, req) + } else { + log.Error(err.Error()) + } + //log.Debug(s.Log("received message:%v", iMsg.MsgId)) + return nil +} + +// 向内部服务发送消息 +func (s *LobbyService) SendServiceData(topic string, userId int64, msgId int32, data []byte) { + iMsg := ipb.MakeMsg(s.Name(), 0, userId, msgId, data) + _ = s.Send(topic, iMsg) +} + +// 向内部服务发送消息 +func (s *LobbyService) SendServiceMsg(topic string, userId int64, msgId int32, msg proto.Message) { + data, _ := proto.Marshal(msg) + s.SendServiceData(topic, userId, msgId, data) +} diff --git a/server/login/server/processor.go b/server/login/server/processor.go index a3a25e1..ba38e18 100644 --- a/server/login/server/processor.go +++ b/server/login/server/processor.go @@ -12,6 +12,7 @@ import ( "github.com/fox/fox/log" "github.com/fox/fox/processor" "github.com/fox/fox/service" + "github.com/fox/fox/xrand" "time" ) @@ -27,7 +28,7 @@ func (s *LoginService) initProcessor() { func (s *LoginService) checkLoginOrRegister(req *pb.ReqUserLogin) (us *user.UserAccount, code pb.ErrCode, node *etcd.ServiceNode) { var err error - node, err = s.bindService.RandServiceNode(pb.ServiceTypeId_STI_DB) + node, err = s.bindService.HashServiceNode(pb.ServiceTypeId_STI_DB, xrand.RandN[int64](100)) if err != nil { log.ErrorF(s.Log("not find db service.err:%s ", err.Error())) return nil, pb.ErrCode_SystemErr, node diff --git a/server/match/server/match.go b/server/match/server/match.go index 941d040..210b1fd 100644 --- a/server/match/server/match.go +++ b/server/match/server/match.go @@ -15,10 +15,10 @@ import ( func (s *MatchService) onMatchRoom(iMsg *ipb.InternalMsg, req *pb.ReqMatchRoom) { ksync.GoSafe(func() { // color game无需进入匹配队列 - playType := pb.ServiceTypeId(req.PlayType) - switch playType { + gameId := pb.ServiceTypeId(req.GameId) + switch gameId { case pb.ServiceTypeId_STI_ColorGame: - node, err := s.bindService.RandServiceNode(playType) + node, err := s.bindService.FindServiceNode(iMsg.UserId, gameId) if err != nil { log.ErrorF("db service node error:%v", err) s.SendServiceMsg(service.TopicEx(iMsg.ServiceName), iMsg.ConnId, iMsg.UserId, @@ -47,9 +47,9 @@ func (s *MatchService) onMatchRoom(iMsg *ipb.InternalMsg, req *pb.ReqMatchRoom) Seat: 0, Gold: us.Gold, } - rsp.PlayType = req.PlayType + rsp.GameId = req.GameId rsp.RoomType = req.RoomType - switch pb.ServiceTypeId(rsp.PlayType) { + switch pb.ServiceTypeId(rsp.GameId) { case pb.ServiceTypeId_STI_ColorGame: rsp.ColorInfo = &pb.RspMatchRoom_ColorInfo{} }