diff --git a/common/model/user/userAccount.go b/common/model/user/userAccount.go index 942e26a..d52aabb 100644 --- a/common/model/user/userAccount.go +++ b/common/model/user/userAccount.go @@ -6,7 +6,7 @@ import ( ) const ( - AccountNormal = 1 // 正常 + //AccountNormal = 1 // 正常 AccountFrozen = 2 // 冻结 AccountBanned = 3 // 封禁 ) @@ -33,7 +33,7 @@ func (u UserAccount) GetId() uint { // 玩家登录记录表 type UserLoginLog struct { gorm.Model - PlayerID uint `gorm:"index"` // 关联玩家ID + UID uint `gorm:"index"` // 关联玩家ID LoginIP string `gorm:"type:varchar(45);not null"` // 登录IP LoginTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 登录时间 DeviceInfo string `gorm:"type:varchar(255)"` // 设备信息(JSON格式) diff --git a/common/pb/code.proto b/common/pb/code.proto index 47603a5..e9c3670 100644 --- a/common/pb/code.proto +++ b/common/pb/code.proto @@ -10,7 +10,8 @@ enum ErrCode LoginUserOrPwdErr = 102; // 帐号或密码错误 AccountFrozen = 103; // 帐号已冻结 AccountBanned = 104; // 帐号已封禁 - RegisterUserExist = 120; // 已有该帐号,无法注册 + RegisterUserExist = 110; // 已有该帐号,无法注册 + VersionTooLow = 115; // 版本太低,无法登陆 } diff --git a/common/pb/service.proto b/common/pb/service.proto index 62b8934..c2a523f 100644 --- a/common/pb/service.proto +++ b/common/pb/service.proto @@ -8,6 +8,7 @@ enum ServiceTypeId STI_Gate = 100; // 网关id STI_Login = 101; // 登陆服 STI_Chat = 102; // 聊天服 + STI_DB = 103; // db服 } diff --git a/common/proto/pb/code.pb.go b/common/proto/pb/code.pb.go index 5c49227..fa2f4db 100644 --- a/common/proto/pb/code.pb.go +++ b/common/proto/pb/code.pb.go @@ -30,7 +30,8 @@ const ( ErrCode_LoginUserOrPwdErr ErrCode = 102 // 帐号或密码错误 ErrCode_AccountFrozen ErrCode = 103 // 帐号已冻结 ErrCode_AccountBanned ErrCode = 104 // 帐号已封禁 - ErrCode_RegisterUserExist ErrCode = 120 // 已有该帐号,无法注册 + ErrCode_RegisterUserExist ErrCode = 110 // 已有该帐号,无法注册 + ErrCode_VersionTooLow ErrCode = 115 // 版本太低,无法登陆 ) // Enum value maps for ErrCode. @@ -42,7 +43,8 @@ var ( 102: "LoginUserOrPwdErr", 103: "AccountFrozen", 104: "AccountBanned", - 120: "RegisterUserExist", + 110: "RegisterUserExist", + 115: "VersionTooLow", } ErrCode_value = map[string]int32{ "OK": 0, @@ -51,7 +53,8 @@ var ( "LoginUserOrPwdErr": 102, "AccountFrozen": 103, "AccountBanned": 104, - "RegisterUserExist": 120, + "RegisterUserExist": 110, + "VersionTooLow": 115, } ) @@ -87,7 +90,7 @@ var File_code_proto protoreflect.FileDescriptor const file_code_proto_rawDesc = "" + "\n" + "\n" + - "code.proto\x12\x02pb*\x86\x01\n" + + "code.proto\x12\x02pb*\x99\x01\n" + "\aErrCode\x12\x06\n" + "\x02OK\x10\x00\x12\r\n" + "\tSystemErr\x10\x01\x12\x10\n" + @@ -95,7 +98,8 @@ const file_code_proto_rawDesc = "" + "\x11LoginUserOrPwdErr\x10f\x12\x11\n" + "\rAccountFrozen\x10g\x12\x11\n" + "\rAccountBanned\x10h\x12\x15\n" + - "\x11RegisterUserExist\x10xB\x11Z\x0fcommon/proto/pbb\x06proto3" + "\x11RegisterUserExist\x10n\x12\x11\n" + + "\rVersionTooLow\x10sB\x11Z\x0fcommon/proto/pbb\x06proto3" var ( file_code_proto_rawDescOnce sync.Once diff --git a/common/proto/pb/msgId.pb.go b/common/proto/pb/msgId.pb.go index 83c1501..64ce516 100644 --- a/common/proto/pb/msgId.pb.go +++ b/common/proto/pb/msgId.pb.go @@ -22,7 +22,7 @@ const ( ) // 命名规则: -// 1. 所有游戏id都在msgId.proto的MsgId中定义,前缀需有C2S,S2C,Ntf三种之一,后缀统一为Id +// 1. 所有消息id都在msgId.proto的MsgId中定义,前缀需有C2S,S2C,Ntf三种之一,后缀统一为Id // 2. 所有错误码都在code.proto的ErrCode中定义 // 3. 所有消息名为对应消息id去掉后缀Id组成 type MsgId int32 diff --git a/common/proto/pb/service.pb.go b/common/proto/pb/service.pb.go index 76b28a9..20ec4a1 100644 --- a/common/proto/pb/service.pb.go +++ b/common/proto/pb/service.pb.go @@ -28,6 +28,7 @@ const ( ServiceTypeId_STI_Gate ServiceTypeId = 100 // 网关id ServiceTypeId_STI_Login ServiceTypeId = 101 // 登陆服 ServiceTypeId_STI_Chat ServiceTypeId = 102 // 聊天服 + ServiceTypeId_STI_DB ServiceTypeId = 103 // db服 ) // Enum value maps for ServiceTypeId. @@ -37,12 +38,14 @@ var ( 100: "STI_Gate", 101: "STI_Login", 102: "STI_Chat", + 103: "STI_DB", } ServiceTypeId_value = map[string]int32{ "STI_Unknown": 0, "STI_Gate": 100, "STI_Login": 101, "STI_Chat": 102, + "STI_DB": 103, } ) @@ -77,12 +80,14 @@ var File_service_proto protoreflect.FileDescriptor const file_service_proto_rawDesc = "" + "\n" + - "\rservice.proto\x12\x02pb*K\n" + + "\rservice.proto\x12\x02pb*W\n" + "\rServiceTypeId\x12\x0f\n" + "\vSTI_Unknown\x10\x00\x12\f\n" + "\bSTI_Gate\x10d\x12\r\n" + "\tSTI_Login\x10e\x12\f\n" + - "\bSTI_Chat\x10fB\x11Z\x0fcommon/proto/pbb\x06proto3" + "\bSTI_Chat\x10f\x12\n" + + "\n" + + "\x06STI_DB\x10gB\x11Z\x0fcommon/proto/pbb\x06proto3" var ( file_service_proto_rawDescOnce sync.Once diff --git a/common/rpcName/rpcName.go b/common/rpcName/rpcName.go new file mode 100644 index 0000000..7d2029a --- /dev/null +++ b/common/rpcName/rpcName.go @@ -0,0 +1,8 @@ +package rpcName + +const ( + GetUserAccount = "get.user.account.rpc" + CreateUserAccount = "create.user.account.rpc" + UpdateUserPassword = "update.user.password.rpc" + LogUserAccountLogin = "user.login.rpc" +) diff --git a/common/rpcName/topicName.go b/common/rpcName/topicName.go deleted file mode 100644 index 12187e3..0000000 --- a/common/rpcName/topicName.go +++ /dev/null @@ -1,7 +0,0 @@ -package rpcName - -const ( - GetUserAccount = "get.user.account.rpc" - CreateUserAccount = "create.user.account.rpc" - UpdateUserPassword = "update.user.password.rpc" -) diff --git a/common/serviceName/serviceName.go b/common/serviceName/serviceName.go index a4502d0..dbd2394 100644 --- a/common/serviceName/serviceName.go +++ b/common/serviceName/serviceName.go @@ -4,4 +4,5 @@ const ( Gate = "gate" Chat = "chat" Login = "login" + Db = "db" ) diff --git a/common/utils/redis.go b/common/utils/redis.go new file mode 100644 index 0000000..ec65465 --- /dev/null +++ b/common/utils/redis.go @@ -0,0 +1,23 @@ +package utils + +import ( + "github.com/fox/fox/ksync" + "github.com/go-redis/redis/v8" + "time" +) + +// 动态调整redis池 +func AutoSetRedisPool(rdb *redis.Client) { + // 连接池自动扩展 + ksync.GoSafe(func() { + for { + stats := rdb.PoolStats() + usage := float64(stats.TotalConns-stats.IdleConns) / float64(stats.TotalConns) + if usage > 0.8 { // 使用率超过80% + // 动态调整PoolSize + rdb.Options().PoolSize = int(float64(rdb.Options().PoolSize) * 1.2) + } + time.Sleep(10 * time.Second) + } + }, nil) +} diff --git a/common/utils/util.go b/common/utils/util.go new file mode 100644 index 0000000..9ff663a --- /dev/null +++ b/common/utils/util.go @@ -0,0 +1,8 @@ +package utils + +func Tie[T any](ret bool, v1, v2 T) T { + if ret { + return v1 + } + return v2 +} diff --git a/server/db/operation/db.go b/server/db/operation/db.go index 68410b7..404324e 100644 --- a/server/db/operation/db.go +++ b/server/db/operation/db.go @@ -2,6 +2,7 @@ package operation import ( "game/common/model/user" + "game/common/utils" "game/server/db/config" "github.com/fox/fox/db" "github.com/fox/fox/log" @@ -25,11 +26,13 @@ func InitRedis() { log.Fatal(err.Error()) return } + utils.AutoSetRedisPool(UserRedis) AccountRedis, err = db.InitRedis(cfg.Password, cfg.Host, cfg.Port, 1) if err != nil { log.Fatal(err.Error()) return } + utils.AutoSetRedisPool(AccountRedis) } func InitDb() { diff --git a/server/db/operation/userAccount.go b/server/db/operation/userAccount.go index 26a894f..0a4f579 100644 --- a/server/db/operation/userAccount.go +++ b/server/db/operation/userAccount.go @@ -7,6 +7,7 @@ import ( "game/common/model" "game/common/model/user" "game/common/proto/pb" + "game/common/serialization" "game/common/utils" "github.com/fox/fox/log" "github.com/go-redis/redis/v8" @@ -15,6 +16,7 @@ import ( ) type UserAccountOp struct { + logDb *gorm.DB db *gorm.DB accountRedis *redis.Client accountOp *model.TableOp[user.UserAccount] @@ -22,6 +24,7 @@ type UserAccountOp struct { func NewUserAccountOp() *UserAccountOp { return &UserAccountOp{ + logDb: LogDB, db: UserDB, accountRedis: AccountRedis, accountOp: model.NewTableOp[user.UserAccount](UserDB, AccountRedis), @@ -36,12 +39,18 @@ func (s *UserAccountOp) GetUserAccount(username string) (*user.UserAccount, pb.E sUid, err := s.accountRedis.Get(context.Background(), s.redisKey(username)).Result() if err != nil { if errors.Is(err, redis.Nil) { - var us user.UserAccount - err = s.db.Where("username = ?", username).First(&us).Error + us := &user.UserAccount{} + err = s.db.Where("username = ?", username).First(us).Error if err != nil { log.ErrorF("find user:%v err:%v", username, err) return nil, pb.ErrCode_SystemErr } + // 从db中查到后写入redis,并建立索引 + if us.Username != "" && us.ID > 0 { + _, _ = s.accountOp.Update(us.ID, serialization.StructToMap(us)) + _ = s.accountRedis.Set(context.Background(), s.redisKey(username), us.ID, model.TableExpire).Err() + } + return us, pb.ErrCode_OK } else { log.ErrorF("find user:%v err:%v", username, err) return nil, pb.ErrCode_SystemErr @@ -69,7 +78,8 @@ func (s *UserAccountOp) CreateUserAccount(us *user.UserAccount) (*user.UserAccou if code != pb.ErrCode_OK { return nil, code } - s.accountRedis.Set(context.Background(), s.redisKey(us.Username), us.ID, model.TableExpire) + // 建立索引 + _ = s.accountRedis.Set(context.Background(), s.redisKey(us.Username), us.ID, model.TableExpire).Err() return us, pb.ErrCode_OK } @@ -84,25 +94,18 @@ func (s *UserAccountOp) UpdateUserPassword(us *user.UserAccount) (*user.UserAcco var code pb.ErrCode us, code = s.accountOp.Update(us.ID, map[string]any{"password": hashedPassword}) if code != pb.ErrCode_OK { - s.accountRedis.Expire(context.Background(), s.redisKey(us.Username), model.TableExpire) + _ = s.accountRedis.Expire(context.Background(), s.redisKey(us.Username), model.TableExpire).Err() } return us, code } -//// 记录登录日志 -//func (s *UserAccountOp) recordLoginLog(userID uint, ip, deviceID string, success bool, failReason string) { -// logEntry := user.UserLoginLog{ -// PlayerID: userID, -// LoginIP: ip, -// DeviceInfo: deviceID, -// LoginResult: success, -// FailReason: failReason, -// } -// -// if err := s.logDb.Create(&logEntry).Error; err != nil { -// log.ErrorF("记录登录日志失败: %v", err) -// } -//} +// 记录登录日志 +func (s *UserAccountOp) RecordLoginLog(logEntry *user.UserLoginLog) { + if err := s.logDb.Create(&logEntry).Error; err != nil { + log.ErrorF("记录登录日志失败: %v", err) + } +} + // //// 生成JWT令牌(简化版) //func generateToken(userID uint, username string) (string, error) { diff --git a/server/db/server/handlerUser.go b/server/db/server/handlerUser.go new file mode 100644 index 0000000..69fe8de --- /dev/null +++ b/server/db/server/handlerUser.go @@ -0,0 +1,83 @@ +package server + +import ( + "encoding/json" + "game/common/model/user" + "game/common/proto/pb" + "game/server/db/operation" + "github.com/fox/fox/ipb" + "github.com/fox/fox/log" +) + +// 获取帐号 +func (s *DbService) onGetUserAccount(iMsg *ipb.InternalMsg) *ipb.InternalMsg { + operationDb[user.UserAccount](iMsg, func(us *user.UserAccount) (*user.UserAccount, pb.ErrCode) { + return operation.NewUserAccountOp().GetUserAccount(us.Username) + }) + return iMsg +} + +// 创建帐号 +func (s *DbService) onCreateUserAccount(iMsg *ipb.InternalMsg) *ipb.InternalMsg { + operationDb[user.UserAccount](iMsg, operation.NewUserAccountOp().CreateUserAccount) + return iMsg +} + +type result[T any] struct { + ret T +} + +// 装饰器 +func operationDb[T any](iMsg *ipb.InternalMsg, operation func(table *T) (*T, pb.ErrCode)) { + t := result[T]{} + err := json.Unmarshal(iMsg.Msg, &t.ret) + if err != nil { + log.ErrorF("error unmarshalling user account %v", err) + return + } + table, code := operation(&t.ret) + if code != pb.ErrCode_OK { + return + } + iMsg.Msg, err = json.Marshal(table) + if err != nil { + log.ErrorF("error marshalling user account %v", err) + return + } + return +} + +// 修改密码 +func (s *DbService) onUpdateUserAccount(iMsg *ipb.InternalMsg) *ipb.InternalMsg { + operationDb[user.UserAccount](iMsg, operation.NewUserAccountOp().UpdateUserPassword) + return iMsg +} + +// 日志操作无需返回值 +func (s *DbService) onLogUserAccountLogin(iMsg *ipb.InternalMsg) *ipb.InternalMsg { + operationDb[user.UserLoginLog](iMsg, func(us *user.UserLoginLog) (*user.UserLoginLog, pb.ErrCode) { + operation.NewUserAccountOp().RecordLoginLog(us) + return nil, pb.ErrCode_OK + }) + return iMsg +} + +//func (s *DbService) operation(iMsg *ipb.InternalMsg, operation func(us *user.UserAccount) (*user.UserAccount, pb.ErrCode)) { +// us := &user.UserAccount{} +// err := json.Unmarshal(iMsg.Msg, us) +// if err != nil { +// log.ErrorF("error unmarshalling user account %v", err) +// return +// } +// var code pb.ErrCode +// us, code = operation(us) +// if code != pb.ErrCode_OK { +// return +// } +// iMsg.Msg, err = json.Marshal(us) +// if err != nil { +// log.ErrorF("error marshalling user account %v", err) +// return +// } +// return +//} diff --git a/server/db/server/processor.go b/server/db/server/processor.go index d8c9ad0..4952786 100644 --- a/server/db/server/processor.go +++ b/server/db/server/processor.go @@ -7,8 +7,9 @@ import ( func (s *DbService) initRpcProcessor() { s.RpcProcessor.RegisterMessages(map[string]processor.RpcHandler{ - rpcName.CreateUserAccount: s.onCreateUserAccount, - rpcName.GetUserAccount: s.onGetUserAccount, - rpcName.UpdateUserPassword: s.onUpdateUserAccount, + rpcName.CreateUserAccount: s.onCreateUserAccount, + rpcName.GetUserAccount: s.onGetUserAccount, + rpcName.UpdateUserPassword: s.onUpdateUserAccount, + rpcName.LogUserAccountLogin: s.onLogUserAccountLogin, }) } diff --git a/server/db/server/service.go b/server/db/server/service.go index 1a4be5e..2364614 100644 --- a/server/db/server/service.go +++ b/server/db/server/service.go @@ -19,7 +19,7 @@ func Init() { log.DebugF("init service begin id:%v, num:%v", config.Command.ServiceId, config.Command.ServiceNum) for i := 0; i < config.Command.ServiceNum; i++ { sid := config.Command.ServiceId + i - if srv := newLoginService(sid); srv != nil { + if srv := newDbService(sid); srv != nil { DbSrv = append(DbSrv, srv) } } @@ -35,20 +35,20 @@ func Stop() { } } -func newLoginService(serviceId int) *DbService { +func newDbService(serviceId int) *DbService { var err error s := new(DbService) - sName := fmt.Sprintf("%v-%d", serviceName.Login, serviceId) + sName := fmt.Sprintf("%v-%d", serviceName.Db, serviceId) if s.NatsService, err = service.NewNatsService(&service.InitNatsServiceParams{ EtcdAddress: config.Cfg.Etcd.Address, EtcdUsername: "", EtcdPassword: "", NatsAddress: config.Cfg.Nats.Address, - ServiceType: serviceName.Login, + ServiceType: serviceName.Db, ServiceName: sName, OnFunc: s, - TypeId: int(pb.ServiceTypeId_STI_Login), + TypeId: int(pb.ServiceTypeId_STI_DB), Version: config.Cfg.BuildDate, }); err != nil { log.Fatal(err.Error()) @@ -80,18 +80,5 @@ func (s *DbService) OnStop() { // 处理其它服发送过来的消息 func (s *DbService) OnMessage(data []byte) error { _ = data - //log.Debug(s.Log("received message:%v", iMsg.MsgId)) return nil } - -//// 向内部服务发送消息 -//func (s *DbService) SendServiceData(topic string, connId uint32, userId int64, msgId int32, data []byte) { -// iMsg := ipb.MakeMsg(s.Name(), connId, userId, msgId, data) -// _ = s.Send(topic, iMsg) -//} -// -//// 向内部服务发送消息 -//func (s *DbService) SendServiceMsg(topic string, connId uint32, userId int64, msgId int32, msg proto.Message) { -// data, _ := proto.Marshal(msg) -// s.SendServiceData(topic, connId, userId, msgId, data) -//} diff --git a/server/db/server/user.go b/server/db/server/user.go deleted file mode 100644 index 7931b44..0000000 --- a/server/db/server/user.go +++ /dev/null @@ -1,51 +0,0 @@ -package server - -import ( - "encoding/json" - "game/common/model/user" - "game/common/proto/pb" - "game/server/db/operation" - "github.com/fox/fox/ipb" - "github.com/fox/fox/log" -) - -// 获取帐号 -func (s *DbService) onGetUserAccount(iMsg *ipb.InternalMsg) *ipb.InternalMsg { - s.operation(iMsg, func(us *user.UserAccount) (*user.UserAccount, pb.ErrCode) { - return operation.NewUserAccountOp().GetUserAccount(us.Username) - }) - return iMsg -} - -// 创建帐号 -func (s *DbService) onCreateUserAccount(iMsg *ipb.InternalMsg) *ipb.InternalMsg { - s.operation(iMsg, operation.NewUserAccountOp().CreateUserAccount) - return iMsg -} - -// 修改密码 -func (s *DbService) operation(iMsg *ipb.InternalMsg, operation func(us *user.UserAccount) (*user.UserAccount, pb.ErrCode)) { - us := &user.UserAccount{} - err := json.Unmarshal(iMsg.Msg, us) - if err != nil { - log.ErrorF("error unmarshalling user account %v", err) - return - } - var code pb.ErrCode - us, code = operation(us) - if code != pb.ErrCode_OK { - return - } - iMsg.Msg, err = json.Marshal(us) - if err != nil { - log.ErrorF("error marshalling user account %v", err) - return - } - return -} - -// 修改密码 -func (s *DbService) onUpdateUserAccount(iMsg *ipb.InternalMsg) *ipb.InternalMsg { - s.operation(iMsg, operation.NewUserAccountOp().UpdateUserPassword) - return iMsg -} diff --git a/server/gate/model/db.go b/server/gate/model/db.go index 478e9b3..8ba7029 100644 --- a/server/gate/model/db.go +++ b/server/gate/model/db.go @@ -1,6 +1,7 @@ package model import ( + "game/common/utils" "game/server/gate/config" "github.com/fox/fox/db" "github.com/fox/fox/log" @@ -16,4 +17,5 @@ func InitRedis() { log.Fatal(err.Error()) return } + utils.AutoSetRedisPool(UserRedis) } diff --git a/server/gate/server/service.go b/server/gate/server/service.go index 2e11e94..01a08a9 100644 --- a/server/gate/server/service.go +++ b/server/gate/server/service.go @@ -192,7 +192,7 @@ func (s *GateService) WsOnMessage(conn ws.IConn, data []byte) { } else { log.Error(s.Log("topic:%v not exist.user:%v", topic, conn.UserId())) } - log.Debug(s.Log("received client:%d user:%v message:%v", conn.Id(), conn.UserId(), msg.MsgId)) + log.Debug(s.Log("received conn:%d user:%v message:%v", conn.Id(), conn.UserId(), msg.MsgId)) } // 向内部服务发送消息 diff --git a/server/login/cmd/cmd.go b/server/login/cmd/cmd.go index c709758..c2f4c57 100644 --- a/server/login/cmd/cmd.go +++ b/server/login/cmd/cmd.go @@ -3,7 +3,6 @@ package cmd import ( "fmt" "game/server/login/config" - "game/server/login/model" "game/server/login/server" "github.com/fox/fox/log" "os" @@ -12,8 +11,8 @@ import ( ) func initRepo() { - model.InitRedis() - model.InitDb() + //model.InitRedis() + //model.InitDb() } func Run(GitCommit, GitBranch, BuildDate string) { diff --git a/server/login/model/db.go b/server/login/model/db.go index 9cd4cb2..5ef10e9 100644 --- a/server/login/model/db.go +++ b/server/login/model/db.go @@ -1,59 +1,59 @@ package model -import ( - "game/server/login/config" - "github.com/fox/fox/db" - "github.com/fox/fox/log" - "github.com/go-redis/redis/v8" - "gorm.io/gorm" -) - -var ( - UserRedis *redis.Client - UserDB *gorm.DB - LogDB *gorm.DB -) - -func InitRedis() { - log.Debug("init redis") - var err error - cfg := &config.Cfg.Redis - UserRedis, err = db.InitRedis(cfg.Password, cfg.Host, cfg.Port, 0) - if err != nil { - log.Fatal(err.Error()) - return - } -} - -func InitDb() { - log.Debug("init db") - var err error - cfg := &config.Cfg.Mysql - UserDB, err = db.InitMysql(cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.DbName) - if err != nil { - log.Fatal(err.Error()) - return - } - cfg = &config.Cfg.MysqlLog - LogDB, err = db.InitMysql(cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.DbName) - if err != nil { - log.Fatal(err.Error()) - return - } - // 自动迁移game库表结构 - err = UserDB.AutoMigrate( - &UserAccount{}, - ) - if err != nil { - log.Fatal(err.Error()) - return - } - // 自动迁移game_log库表结构 - err = LogDB.AutoMigrate( - &UserLoginLog{}, - ) - if err != nil { - log.Fatal(err.Error()) - return - } -} +//import ( +// "game/server/login/config" +// "github.com/fox/fox/db" +// "github.com/fox/fox/log" +// "github.com/go-redis/redis/v8" +// "gorm.io/gorm" +//) +// +//var ( +// UserRedis *redis.Client +// UserDB *gorm.DB +// LogDB *gorm.DB +//) +// +//func InitRedis() { +// log.Debug("init redis") +// var err error +// cfg := &config.Cfg.Redis +// UserRedis, err = db.InitRedis(cfg.Password, cfg.Host, cfg.Port, 0) +// if err != nil { +// log.Fatal(err.Error()) +// return +// } +//} +// +//func InitDb() { +// log.Debug("init db") +// var err error +// cfg := &config.Cfg.Mysql +// UserDB, err = db.InitMysql(cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.DbName) +// if err != nil { +// log.Fatal(err.Error()) +// return +// } +// cfg = &config.Cfg.MysqlLog +// LogDB, err = db.InitMysql(cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.DbName) +// if err != nil { +// log.Fatal(err.Error()) +// return +// } +// // 自动迁移game库表结构 +// err = UserDB.AutoMigrate( +// &UserAccount{}, +// ) +// if err != nil { +// log.Fatal(err.Error()) +// return +// } +// // 自动迁移game_log库表结构 +// err = LogDB.AutoMigrate( +// &UserLoginLog{}, +// ) +// if err != nil { +// log.Fatal(err.Error()) +// return +// } +//} diff --git a/server/login/model/userAccount.go b/server/login/model/userAccount.go index cc25888..0030e2a 100644 --- a/server/login/model/userAccount.go +++ b/server/login/model/userAccount.go @@ -1,152 +1,152 @@ package model -import ( - "errors" - "github.com/fox/fox/log" - "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" - "time" -) - -const ( - AccountNormal = 1 // 正常 - AccountFrozen = 2 // 冻结 - AccountBanned = 3 // 封禁 -) - -// 玩家账户表 -type UserAccount struct { - gorm.Model - Username string `gorm:"type:varchar(32);uniqueIndex;not null"` // 用户名 - Password string `gorm:"type:varchar(255);not null"` // 密码哈希 - Email string `gorm:"type:varchar(100)"` // 邮箱(可选) - Phone string `gorm:"type:varchar(20)"` // 手机号(可选) - DeviceID string `gorm:"type:varchar(64);index"` // 设备ID - LastLoginIP string `gorm:"type:varchar(45)"` // 最后登录IP(支持IPv6) - LastLoginTime time.Time // 最后登录时间 - Status int `gorm:"type:tinyint;default:1"` // 账号状态 1-正常 2-冻结 3-封禁 - RegisterIP string `gorm:"type:varchar(45)"` // 注册IP - RegisterTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 注册时间 -} - -// 玩家登录记录表 -type UserLoginLog struct { - gorm.Model - PlayerID uint `gorm:"index"` // 关联玩家ID - LoginIP string `gorm:"type:varchar(45);not null"` // 登录IP - LoginTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 登录时间 - DeviceInfo string `gorm:"type:varchar(255)"` // 设备信息(JSON格式) - LoginResult bool // 登录结果 true-成功 false-失败 - FailReason string `gorm:"type:varchar(100)"` // 失败原因 -} - -type UserLoginOp struct { - db *gorm.DB - logDb *gorm.DB -} - -func NewUserLoginOp() *UserLoginOp { - return &UserLoginOp{db: UserDB, logDb: LogDB} -} - -var ( - ErrUserOrPassword = errors.New("user or password was error") - ErrAccountFrozen = errors.New("account frozen") - ErrAccountBanned = errors.New("account banned") -) - -func (s *UserLoginOp) Login(username, password, ip, deviceID string) (*UserAccount, error) { - var user UserAccount - err := s.db.Where("username = ?", username).First(&user).Error - if err != nil { - return nil, err - } - // 验证密码 - if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { - s.recordLoginLog(user.ID, ip, deviceID, false, ErrUserOrPassword.Error()) - return nil, ErrUserOrPassword - } - // 检查账号状态 - switch user.Status { - case AccountNormal: - - case AccountFrozen: - s.recordLoginLog(user.ID, ip, deviceID, false, ErrAccountFrozen.Error()) - return nil, ErrAccountFrozen - case AccountBanned: - s.recordLoginLog(user.ID, ip, deviceID, false, ErrAccountBanned.Error()) - return nil, ErrAccountBanned - } - // 更新最后登录信息 - user.LastLoginIP = ip - user.LastLoginTime = time.Now() - _ = s.db.Save(&user).Error - - // 记录成功登录日志 - s.recordLoginLog(user.ID, ip, deviceID, true, "") - - // 6. 生成访问令牌 - token, err := generateToken(user.ID, user.Username) - if err != nil { - return nil, err - } - user.Password = token - return &user, err -} - -// 注册新用户 -func (s *UserLoginOp) RegisterNewUser(username, password, ip, deviceID string) (*UserAccount, error) { - // 密码加密 - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - return nil, err - } - user := UserAccount{ - Username: username, - Password: string(hashedPassword), - DeviceID: deviceID, - RegisterIP: ip, - Status: 1, - LastLoginIP: ip, - LastLoginTime: time.Now(), - } - - if err := s.db.Create(&user).Error; err != nil { - return nil, err - } - - s.recordLoginLog(user.ID, ip, deviceID, true, "") - - // 生成访问令牌 - token, err := generateToken(user.ID, user.Username) - if err != nil { - return nil, err - } - user.Password = token - - return &user, nil -} - -// 记录登录日志 -func (s *UserLoginOp) recordLoginLog(userID uint, ip, deviceID string, success bool, failReason string) { - logEntry := UserLoginLog{ - PlayerID: userID, - LoginIP: ip, - DeviceInfo: deviceID, - LoginResult: success, - FailReason: failReason, - } - - if err := s.logDb.Create(&logEntry).Error; err != nil { - log.ErrorF("记录登录日志失败: %v", err) - } -} - -// 生成JWT令牌(简化版) -func generateToken(userID uint, username string) (string, error) { - _ = userID - _ = username - // 这里应该使用JWT库生成实际令牌 - // 简化实现,实际项目中请使用安全的JWT实现 - return "generated-token-placeholder", nil -} +//import ( +// "errors" +// "github.com/fox/fox/log" +// "golang.org/x/crypto/bcrypt" +// "gorm.io/gorm" +// "time" +//) +// +//const ( +// AccountNormal = 1 // 正常 +// AccountFrozen = 2 // 冻结 +// AccountBanned = 3 // 封禁 +//) +// +//// 玩家账户表 +//type UserAccount struct { +// gorm.Model +// Username string `gorm:"type:varchar(32);uniqueIndex;not null"` // 用户名 +// Password string `gorm:"type:varchar(255);not null"` // 密码哈希 +// Email string `gorm:"type:varchar(100)"` // 邮箱(可选) +// Phone string `gorm:"type:varchar(20)"` // 手机号(可选) +// DeviceID string `gorm:"type:varchar(64);index"` // 设备ID +// LastLoginIP string `gorm:"type:varchar(45)"` // 最后登录IP(支持IPv6) +// LastLoginTime time.Time // 最后登录时间 +// Status int `gorm:"type:tinyint;default:1"` // 账号状态 1-正常 2-冻结 3-封禁 +// RegisterIP string `gorm:"type:varchar(45)"` // 注册IP +// RegisterTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 注册时间 +//} +// +//// 玩家登录记录表 +//type UserLoginLog struct { +// gorm.Model +// PlayerID uint `gorm:"index"` // 关联玩家ID +// LoginIP string `gorm:"type:varchar(45);not null"` // 登录IP +// LoginTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 登录时间 +// DeviceInfo string `gorm:"type:varchar(255)"` // 设备信息(JSON格式) +// LoginResult bool // 登录结果 true-成功 false-失败 +// FailReason string `gorm:"type:varchar(100)"` // 失败原因 +//} +// +//type UserLoginOp struct { +// db *gorm.DB +// logDb *gorm.DB +//} +// +//func NewUserLoginOp() *UserLoginOp { +// return &UserLoginOp{db: UserDB, logDb: LogDB} +//} +// +//var ( +// ErrUserOrPassword = errors.New("user or password was error") +// ErrAccountFrozen = errors.New("account frozen") +// ErrAccountBanned = errors.New("account banned") +//) +// +//func (s *UserLoginOp) Login(username, password, ip, deviceID string) (*UserAccount, error) { +// var user UserAccount +// err := s.db.Where("username = ?", username).First(&user).Error +// if err != nil { +// return nil, err +// } +// // 验证密码 +// if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { +// s.recordLoginLog(user.ID, ip, deviceID, false, ErrUserOrPassword.Error()) +// return nil, ErrUserOrPassword +// } +// // 检查账号状态 +// switch user.Status { +// case AccountNormal: +// +// case AccountFrozen: +// s.recordLoginLog(user.ID, ip, deviceID, false, ErrAccountFrozen.Error()) +// return nil, ErrAccountFrozen +// case AccountBanned: +// s.recordLoginLog(user.ID, ip, deviceID, false, ErrAccountBanned.Error()) +// return nil, ErrAccountBanned +// } +// // 更新最后登录信息 +// user.LastLoginIP = ip +// user.LastLoginTime = time.Now() +// _ = s.db.Save(&user).Error +// +// // 记录成功登录日志 +// s.recordLoginLog(user.ID, ip, deviceID, true, "") +// +// // 6. 生成访问令牌 +// token, err := generateToken(user.ID, user.Username) +// if err != nil { +// return nil, err +// } +// user.Password = token +// return &user, err +//} +// +//// 注册新用户 +//func (s *UserLoginOp) RegisterNewUser(username, password, ip, deviceID string) (*UserAccount, error) { +// // 密码加密 +// hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) +// if err != nil { +// return nil, err +// } +// user := UserAccount{ +// Username: username, +// Password: string(hashedPassword), +// DeviceID: deviceID, +// RegisterIP: ip, +// Status: 1, +// LastLoginIP: ip, +// LastLoginTime: time.Now(), +// } +// +// if err := s.db.Create(&user).Error; err != nil { +// return nil, err +// } +// +// s.recordLoginLog(user.ID, ip, deviceID, true, "") +// +// // 生成访问令牌 +// token, err := generateToken(user.ID, user.Username) +// if err != nil { +// return nil, err +// } +// user.Password = token +// +// return &user, nil +//} +// +//// 记录登录日志 +//func (s *UserLoginOp) recordLoginLog(userID uint, ip, deviceID string, success bool, failReason string) { +// logEntry := UserLoginLog{ +// PlayerID: userID, +// LoginIP: ip, +// DeviceInfo: deviceID, +// LoginResult: success, +// FailReason: failReason, +// } +// +// if err := s.logDb.Create(&logEntry).Error; err != nil { +// log.ErrorF("记录登录日志失败: %v", err) +// } +//} +// +//// 生成JWT令牌(简化版) +//func generateToken(userID uint, username string) (string, error) { +// _ = userID +// _ = username +// // 这里应该使用JWT库生成实际令牌 +// // 简化实现,实际项目中请使用安全的JWT实现 +// return "generated-token-placeholder", nil +//} diff --git a/server/login/server/processor.go b/server/login/server/processor.go index 904bb1b..3db29c7 100644 --- a/server/login/server/processor.go +++ b/server/login/server/processor.go @@ -1,13 +1,22 @@ package server import ( - "errors" + "encoding/json" + "game/common/model/user" "game/common/proto/pb" - "game/server/login/model" + "game/common/rpcName" + "game/common/utils" + "github.com/fox/fox/etcd" "github.com/fox/fox/ipb" + "github.com/fox/fox/ksync" + "github.com/fox/fox/log" "github.com/fox/fox/processor" "github.com/fox/fox/service" - "gorm.io/gorm" + "time" +) + +const ( + timeout = time.Second * 30 ) func (s *LoginService) initProcessor() { @@ -16,42 +25,92 @@ func (s *LoginService) initProcessor() { }) } -func (s *LoginService) checkLoginOrRegister(req *pb.C2SUserLogin) (user *model.UserAccount, code pb.ErrCode) { - op := model.NewUserLoginOp() +func (s *LoginService) checkLoginOrRegister(req *pb.C2SUserLogin) (us *user.UserAccount, code pb.ErrCode, node *etcd.ServiceNode) { var err error - user, err = op.Login(req.Username, req.Password, req.Ip, req.DeviceId) + node, err = s.bindService.RandServiceNode(pb.ServiceTypeId_STI_DB) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - user, err = op.RegisterNewUser(req.Username, req.Password, req.Ip, req.DeviceId) - if err != nil { - code = pb.ErrCode_RegisterUserExist - return - } - } else if errors.Is(err, model.ErrUserOrPassword) { - code = pb.ErrCode_LoginUserOrPwdErr - return - } else if errors.Is(err, model.ErrAccountFrozen) { - code = pb.ErrCode_AccountFrozen - return - } else if errors.Is(err, model.ErrAccountBanned) { - code = pb.ErrCode_AccountBanned - return - } else { - code = pb.ErrCode_SystemErr + log.ErrorF(s.Log("not find db service.err:%s ", err.Error())) + return nil, pb.ErrCode_SystemErr, node + } + if req.Version < "20250601123030" { + return nil, pb.ErrCode_VersionTooLow, node + } + us = &user.UserAccount{ + Username: req.Username, + Password: req.Password, + DeviceID: req.DeviceId, + LastLoginIP: req.Ip, + } + rpcMsg := ipb.MakeRpcMsg[user.UserAccount](rpcName.GetUserAccount, 0, us) + rspMsg, err := s.Call(service.RpcTopicEx(node.Name), timeout, rpcMsg) + if err != nil { + log.ErrorF(s.Log("call rpc:%v err:%s ", rpcMsg.RpcMsgId, err.Error())) + return nil, pb.ErrCode_SystemErr, node + } + _ = json.Unmarshal(rspMsg.Msg, us) + if us.ID == 0 { + // 没有帐号,创建帐号 + rpcMsg.RpcMsgId = rpcName.CreateUserAccount + rspMsg, err = s.Call(service.RpcTopicEx(node.Name), timeout, rpcMsg) + if err != nil { + log.ErrorF(s.Log("call rpc:%v err:%s ", rpcMsg.RpcMsgId, err.Error())) + return nil, pb.ErrCode_SystemErr, node + } + _ = json.Unmarshal(rspMsg.Msg, us) + if us.ID == 0 { + log.ErrorF(s.Log("call rpc:%v err", rpcMsg.RpcMsgId)) + return nil, pb.ErrCode_SystemErr, node } } - return user, code + if pwd, _ := utils.Password(req.Password); pwd != us.Password { + return nil, pb.ErrCode_LoginUserOrPwdErr, node + } + switch us.Status { + case user.AccountFrozen: + return nil, pb.ErrCode_AccountFrozen, node + case user.AccountBanned: + return nil, pb.ErrCode_AccountBanned, node + default: + return us, pb.ErrCode_OK, node + } +} + +// 生成JWT令牌(简化版) +func generateToken(userID uint, username string) (string, error) { + _ = userID + _ = username + // 这里应该使用JWT库生成实际令牌 + // 简化实现,实际项目中请使用安全的JWT实现 + return "generated-token-placeholder", nil } // 登录或注册 func (s *LoginService) onLoginOrRegister(iMsg *ipb.InternalMsg, req *pb.C2SUserLogin) { - user, code := s.checkLoginOrRegister(req) - userId := int64(0) - rsp := &pb.S2CUserLogin{Code: code} - if user != nil && code == pb.ErrCode_OK { - rsp.UserId = int64(user.ID) - rsp.Token = user.Password - userId = rsp.UserId - } - s.SendServiceMsg(service.TopicEx(iMsg.ServiceName), iMsg.ConnId, userId, int32(pb.MsgId_S2CUserLoginId), rsp) + ksync.GoSafe(func() { + us, code, node := s.checkLoginOrRegister(req) + userId := int64(0) + rsp := &pb.S2CUserLogin{Code: code} + if us != nil && code == pb.ErrCode_OK { + rsp.UserId = int64(us.ID) + rsp.Token, _ = generateToken(us.ID, us.Username) + userId = rsp.UserId + } + s.SendServiceMsg(service.TopicEx(iMsg.ServiceName), iMsg.ConnId, userId, int32(pb.MsgId_S2CUserLoginId), rsp) + + if us != nil && us.ID > 0 { + switch code { + case pb.ErrCode_LoginUserOrPwdErr: + rpcMsg := ipb.MakeRpcMsg[user.UserLoginLog](rpcName.GetUserAccount, 0, &user.UserLoginLog{ + UID: us.ID, + LoginIP: us.LastLoginIP, + LoginTime: time.Now(), + DeviceInfo: us.DeviceID, + LoginResult: code == pb.ErrCode_OK, + FailReason: code.String(), + }) + _, _ = s.Call(service.RpcTopicEx(node.Name), timeout, rpcMsg) + } + } + + }, nil) } diff --git a/工作.txt b/工作.txt index 797c63e..fb64012 100644 --- a/工作.txt +++ b/工作.txt @@ -1,6 +1,8 @@ 1.测试gate - 1.1 每10分钟新起1000个连接,发送登陆,然后关闭。检查是否有内存及协程泄漏。(有泄漏) + 1.1 每10分钟新起1000个连接,发送登陆,然后关闭。检查是否有内存及协程泄漏。(已修复) 1.2 启动1000个链接,每小时固定发送登陆消息,一天后查看连接是否还在。检查心跳机制。(已修复) + 1.3 修改完后清除调试日志。(已完成) + 1.4 客户端stop关闭连接,触发服务端连接崩溃。(已修复) 2.编写db服 2.1 login服向db服请求数据及向log db服写入日志。测试rpc机制。