db服
This commit is contained in:
parent
5aa67f5153
commit
e0e44380b7
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"game/common/proto/pb"
|
"game/common/proto/pb"
|
||||||
"game/common/serialization"
|
"game/common/serialization"
|
||||||
|
"game/common/utils"
|
||||||
"github.com/fox/fox/log"
|
"github.com/fox/fox/log"
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -13,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tableExpire = 7 * 24 * time.Hour // 七天后过期
|
TableExpire = 7 * 24 * time.Hour // 七天后过期
|
||||||
)
|
)
|
||||||
|
|
||||||
type resultT[T any] struct {
|
type resultT[T any] struct {
|
||||||
@ -33,7 +34,7 @@ type TableOp[T iTable] struct {
|
|||||||
rds *redis.Client
|
rds *redis.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTableOp[T iTable](db *gorm.DB, rds *redis.Client) *TableOp[T] {
|
func NewTableOp[T iTable](db *gorm.DB, rds *redis.Client) *TableOp[T] {
|
||||||
return &TableOp[T]{db: db, rds: rds}
|
return &TableOp[T]{db: db, rds: rds}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ func (s *TableOp[T]) updateRedis(id uint, maps map[string]any) {
|
|||||||
if err := s.rds.HMSet(context.Background(), s.redisKey(id), maps).Err(); err != nil {
|
if err := s.rds.HMSet(context.Background(), s.redisKey(id), maps).Err(); err != nil {
|
||||||
log.ErrorF("redis-key:%v HMSet err: %v", s.redisKey(id), err)
|
log.ErrorF("redis-key:%v HMSet err: %v", s.redisKey(id), err)
|
||||||
}
|
}
|
||||||
_ = s.rds.Expire(context.Background(), s.redisKey(id), tableExpire).Err()
|
_ = s.rds.Expire(context.Background(), s.redisKey(id), TableExpire).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TableOp[T]) deleteRedis(id uint) {
|
func (s *TableOp[T]) deleteRedis(id uint) {
|
||||||
@ -97,6 +98,7 @@ func (s *TableOp[T]) Create(t *T) (*T, pb.ErrCode) {
|
|||||||
log.ErrorF("create table:%v err:%v", s.tableName(), err)
|
log.ErrorF("create table:%v err:%v", s.tableName(), err)
|
||||||
return nil, pb.ErrCode_SystemErr
|
return nil, pb.ErrCode_SystemErr
|
||||||
}
|
}
|
||||||
|
s.writeRedis((*t).GetId(), t)
|
||||||
return t, pb.ErrCode_OK
|
return t, pb.ErrCode_OK
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,16 +116,21 @@ func (s *TableOp[T]) Find(id uint) (*T, pb.ErrCode) {
|
|||||||
return &result.ret, pb.ErrCode_OK
|
return &result.ret, pb.ErrCode_OK
|
||||||
}
|
}
|
||||||
|
|
||||||
//// 根据条件查询,只在mysql中查询,无法在redis中查询
|
// 根据条件查询,只在mysql中查询,无法在redis中查询
|
||||||
//func (s *TableOp[T]) FindCondition(condition map[string]any) (*T, error) {
|
func (s *TableOp[T]) FindCondition(condition map[string]any) (*T, error) {
|
||||||
// var result resultT[T]
|
var result resultT[T]
|
||||||
// err := s.db.Where(condition).First(&result.ret).Error
|
err := s.db.Where(condition).First(&result.ret).Error
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// log.ErrorF("find table:%v condition:%v err:%v", s.tableName(), utils.JsonMarshal(condition), err)
|
log.ErrorF("find table:%v condition:%v err:%v", s.tableName(), utils.JsonMarshal(condition), err)
|
||||||
// return nil, err
|
return nil, err
|
||||||
// }
|
}
|
||||||
// return &result.ret, nil
|
// 查看redis中是否存在该键,不存在则写入数据
|
||||||
//}
|
exist, _ := s.rds.Exists(context.Background(), s.redisKey(result.ret.GetId())).Result()
|
||||||
|
if exist != 1 {
|
||||||
|
s.writeRedis(result.ret.GetId(), &result.ret)
|
||||||
|
}
|
||||||
|
return &result.ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TableOp[T]) Update(id uint, updates map[string]any) (*T, pb.ErrCode) {
|
func (s *TableOp[T]) Update(id uint, updates map[string]any) (*T, pb.ErrCode) {
|
||||||
var result resultT[T]
|
var result resultT[T]
|
19
common/model/user/user.go
Normal file
19
common/model/user/user.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 玩家账户表
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
accountId uint `gorm:"type:bigint;uniqueIndex;not null"` // 帐号id
|
||||||
|
Nickname string `gorm:"type:varchar(32);uniqueIndex;not null"` // 昵称
|
||||||
|
AvatarUrl string `gorm:"type:varchar(255)"` // 头像
|
||||||
|
AvatarFrame string `gorm:"type:varchar(255)"` // 头像框
|
||||||
|
VipExp int32 `gorm:"type:int"` // vip经验值
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u User) GetId() uint {
|
||||||
|
return u.ID
|
||||||
|
}
|
46
common/model/user/userAccount.go
Normal file
46
common/model/user/userAccount.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"` // 注册时间
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UserAccount) GetId() uint {
|
||||||
|
return u.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 玩家登录记录表
|
||||||
|
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)"` // 失败原因
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UserLoginLog) GetId() uint {
|
||||||
|
return u.ID
|
||||||
|
}
|
19
common/model/user/userResources.go
Normal file
19
common/model/user/userResources.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 玩家账户表
|
||||||
|
type UserResources struct {
|
||||||
|
gorm.Model
|
||||||
|
accountId uint `gorm:"type:bigint;uniqueIndex;not null"` // 帐号id
|
||||||
|
Nickname string `gorm:"type:varchar(32);uniqueIndex;not null"` // 昵称
|
||||||
|
AvatarUrl string `gorm:"type:varchar(255)"` // 头像
|
||||||
|
AvatarFrame string `gorm:"type:varchar(255)"` // 头像框
|
||||||
|
VipExp int32 `gorm:"type:int"` // vip经验值
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UserResources) GetId() uint {
|
||||||
|
return u.ID
|
||||||
|
}
|
@ -4,13 +4,14 @@ option go_package = "common/proto/pb";
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
命名规则:
|
命名规则:
|
||||||
1. 所有游戏id都在msgId.proto的MsgId中定义,前缀需有C2S,S2C,Ntf三种之一,后缀统一为Id
|
1. 所有消息id都在msgId.proto的MsgId中定义,前缀需有C2S,S2C,Ntf三种之一,后缀统一为Id
|
||||||
2. 所有错误码都在code.proto的ErrCode中定义
|
2. 所有错误码都在code.proto的ErrCode中定义
|
||||||
3. 所有消息名为对应消息id去掉后缀Id组成
|
3. 所有消息名为对应消息id去掉后缀Id组成
|
||||||
*/
|
*/
|
||||||
enum MsgId
|
enum MsgId
|
||||||
{
|
{
|
||||||
MI_Unknown = 0;
|
MI_Unknown = 0;
|
||||||
|
|
||||||
// 聊天服 2000-2100
|
// 聊天服 2000-2100
|
||||||
C2SChatId = 2000; // 玩家聊天消息
|
C2SChatId = 2000; // 玩家聊天消息
|
||||||
S2CChatId = 2001; // 复用C2SChatMsg
|
S2CChatId = 2001; // 复用C2SChatMsg
|
||||||
|
14
common/utils/password.go
Normal file
14
common/utils/password.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Password(password string) (string, error) {
|
||||||
|
// 密码加密
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(hashedPassword), nil
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 玩家账户表
|
|
||||||
type User struct {
|
|
||||||
gorm.Model
|
|
||||||
Nickname string `gorm:"type:varchar(32);uniqueIndex;not null"` // 用户名
|
|
||||||
AvatarUrl string `gorm:"type:varchar(255)"` // 头像
|
|
||||||
AvatarBorder string `gorm:"type:varchar(255)"` // 头像框
|
|
||||||
Gold int64 `gorm:"type:bigint;default:0"` // 金币
|
|
||||||
VipExp int32 `gorm:"type:int"` // vip经验值
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u User) GetId() uint {
|
|
||||||
return u.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserOp() *TableOp[User] {
|
|
||||||
return newTableOp[User](UserDB, UserRedis)
|
|
||||||
}
|
|
@ -1,152 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
package model
|
package operation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"game/common/model/user"
|
||||||
"game/server/db/config"
|
"game/server/db/config"
|
||||||
"github.com/fox/fox/db"
|
"github.com/fox/fox/db"
|
||||||
"github.com/fox/fox/log"
|
"github.com/fox/fox/log"
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
UserRedis *redis.Client
|
UserRedis *redis.Client
|
||||||
|
AccountRedis *redis.Client
|
||||||
UserDB *gorm.DB
|
UserDB *gorm.DB
|
||||||
LogDB *gorm.DB
|
LogDB *gorm.DB
|
||||||
)
|
)
|
||||||
@ -23,6 +25,11 @@ func InitRedis() {
|
|||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
AccountRedis, err = db.InitRedis(cfg.Password, cfg.Host, cfg.Port, 1)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitDb() {
|
func InitDb() {
|
||||||
@ -42,7 +49,7 @@ func InitDb() {
|
|||||||
}
|
}
|
||||||
// 自动迁移game库表结构
|
// 自动迁移game库表结构
|
||||||
err = UserDB.AutoMigrate(
|
err = UserDB.AutoMigrate(
|
||||||
&UserAccount{},
|
&user.UserAccount{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
@ -50,7 +57,7 @@ func InitDb() {
|
|||||||
}
|
}
|
||||||
// 自动迁移game_log库表结构
|
// 自动迁移game_log库表结构
|
||||||
err = LogDB.AutoMigrate(
|
err = LogDB.AutoMigrate(
|
||||||
&UserLoginLog{},
|
&user.UserLoginLog{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
16
server/db/operation/operation.go
Normal file
16
server/db/operation/operation.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package operation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"game/common/model"
|
||||||
|
"game/common/model/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 玩家表
|
||||||
|
func NewUserOp() *model.TableOp[user.User] {
|
||||||
|
return model.NewTableOp[user.User](UserDB, UserRedis)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 玩家资源表
|
||||||
|
func NewUserResourcesOp() *model.TableOp[user.UserResources] {
|
||||||
|
return model.NewTableOp[user.UserResources](UserDB, UserRedis)
|
||||||
|
}
|
114
server/db/operation/userAccount.go
Normal file
114
server/db/operation/userAccount.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package operation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"game/common/model"
|
||||||
|
"game/common/model/user"
|
||||||
|
"game/common/proto/pb"
|
||||||
|
"game/common/utils"
|
||||||
|
"github.com/fox/fox/log"
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserAccountOp struct {
|
||||||
|
db *gorm.DB
|
||||||
|
accountRedis *redis.Client
|
||||||
|
accountOp *model.TableOp[user.UserAccount]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserAccountOp() *UserAccountOp {
|
||||||
|
return &UserAccountOp{
|
||||||
|
db: UserDB,
|
||||||
|
accountRedis: AccountRedis,
|
||||||
|
accountOp: model.NewTableOp[user.UserAccount](UserDB, AccountRedis),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserAccountOp) redisKey(username string) string {
|
||||||
|
return fmt.Sprintf("username:%s", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserAccountOp) GetUserAccount(username string) (*user.UserAccount, pb.ErrCode) {
|
||||||
|
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
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorF("find user:%v err:%v", username, err)
|
||||||
|
return nil, pb.ErrCode_SystemErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.ErrorF("find user:%v err:%v", username, err)
|
||||||
|
return nil, pb.ErrCode_SystemErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uid, _ := strconv.ParseInt(sUid, 10, 64)
|
||||||
|
if uid < 0 {
|
||||||
|
log.ErrorF("get user account:%v failed, uid is: %d", username, uid)
|
||||||
|
return nil, pb.ErrCode_SystemErr
|
||||||
|
}
|
||||||
|
return s.accountOp.Find(uint(uid))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建用户
|
||||||
|
func (s *UserAccountOp) CreateUserAccount(us *user.UserAccount) (*user.UserAccount, pb.ErrCode) {
|
||||||
|
// 密码加密
|
||||||
|
hashedPassword, err := utils.Password(us.Password)
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorF("username :%v generate password err:%v", us.Username, err)
|
||||||
|
return nil, pb.ErrCode_SystemErr
|
||||||
|
}
|
||||||
|
us.Password = hashedPassword
|
||||||
|
var code pb.ErrCode
|
||||||
|
us, code = s.accountOp.Create(us)
|
||||||
|
if code != pb.ErrCode_OK {
|
||||||
|
return nil, code
|
||||||
|
}
|
||||||
|
s.accountRedis.Set(context.Background(), s.redisKey(us.Username), us.ID, model.TableExpire)
|
||||||
|
return us, pb.ErrCode_OK
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新密码
|
||||||
|
func (s *UserAccountOp) UpdateUserPassword(us *user.UserAccount) (*user.UserAccount, pb.ErrCode) {
|
||||||
|
// 密码加密
|
||||||
|
hashedPassword, err := utils.Password(us.Password)
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorF("username :%v generate password err:%v", us.Username, err)
|
||||||
|
return nil, pb.ErrCode_SystemErr
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// 生成JWT令牌(简化版)
|
||||||
|
//func generateToken(userID uint, username string) (string, error) {
|
||||||
|
// _ = userID
|
||||||
|
// _ = username
|
||||||
|
// // 这里应该使用JWT库生成实际令牌
|
||||||
|
// // 简化实现,实际项目中请使用安全的JWT实现
|
||||||
|
// return "generated-token-placeholder", nil
|
||||||
|
//}
|
@ -1,57 +1,14 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"game/common/rpcName"
|
||||||
"game/common/proto/pb"
|
|
||||||
"game/server/db/model"
|
|
||||||
"github.com/fox/fox/ipb"
|
|
||||||
"github.com/fox/fox/processor"
|
"github.com/fox/fox/processor"
|
||||||
"github.com/fox/fox/service"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *DbService) initProcessor() {
|
func (s *DbService) initRpcProcessor() {
|
||||||
s.processor.RegisterMessages(processor.RegisterMetas{
|
s.RpcProcessor.RegisterMessages(map[string]processor.RpcHandler{
|
||||||
pb.MsgId_C2SUserLoginId: {pb.C2SUserLogin{}, s.onLoginOrRegister},
|
rpcName.CreateUserAccount: s.onCreateUserAccount,
|
||||||
|
rpcName.GetUserAccount: s.onGetUserAccount,
|
||||||
|
rpcName.UpdateUserPassword: s.onUpdateUserAccount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DbService) checkLoginOrRegister(req *pb.C2SUserLogin) (user *model.UserAccount, code pb.ErrCode) {
|
|
||||||
op := model.NewUserLoginOp()
|
|
||||||
var err error
|
|
||||||
user, err = op.Login(req.Username, req.Password, req.Ip, req.DeviceId)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return user, code
|
|
||||||
}
|
|
||||||
|
|
||||||
// 登录或注册
|
|
||||||
func (s *DbService) 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)
|
|
||||||
}
|
|
||||||
|
@ -4,22 +4,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"game/common/proto/pb"
|
"game/common/proto/pb"
|
||||||
"game/common/serviceName"
|
"game/common/serviceName"
|
||||||
"game/common/userBindService"
|
|
||||||
"game/server/db/config"
|
"game/server/db/config"
|
||||||
"game/server/db/model"
|
|
||||||
"github.com/fox/fox/ipb"
|
|
||||||
"github.com/fox/fox/log"
|
"github.com/fox/fox/log"
|
||||||
"github.com/fox/fox/processor"
|
|
||||||
"github.com/fox/fox/service"
|
"github.com/fox/fox/service"
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var DbSrv []*DbService
|
var DbSrv []*DbService
|
||||||
|
|
||||||
type DbService struct {
|
type DbService struct {
|
||||||
*service.NatsService
|
*service.NatsService
|
||||||
processor *processor.Processor
|
|
||||||
bindService *userBindService.UserBindService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
@ -62,9 +55,7 @@ func newLoginService(serviceId int) *DbService {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.bindService = userBindService.NewUserBindService(model.UserRedis, s.ServiceEtcd())
|
s.initRpcProcessor()
|
||||||
s.processor = processor.NewProcessor()
|
|
||||||
s.initProcessor()
|
|
||||||
s.OnInit()
|
s.OnInit()
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@ -88,29 +79,19 @@ func (s *DbService) OnStop() {
|
|||||||
|
|
||||||
// 处理其它服发送过来的消息
|
// 处理其它服发送过来的消息
|
||||||
func (s *DbService) OnMessage(data []byte) error {
|
func (s *DbService) OnMessage(data []byte) error {
|
||||||
var iMsg = &ipb.InternalMsg{}
|
_ = data
|
||||||
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))
|
//log.Debug(s.Log("received message:%v", iMsg.MsgId))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向内部服务发送消息
|
//// 向内部服务发送消息
|
||||||
func (s *DbService) SendServiceData(topic string, connId uint32, userId int64, msgId int32, data []byte) {
|
//func (s *DbService) SendServiceData(topic string, connId uint32, userId int64, msgId int32, data []byte) {
|
||||||
iMsg := ipb.MakeMsg(s.Name(), connId, userId, msgId, data)
|
// iMsg := ipb.MakeMsg(s.Name(), connId, userId, msgId, data)
|
||||||
_ = s.Send(topic, iMsg)
|
// _ = s.Send(topic, iMsg)
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 向内部服务发送消息
|
//// 向内部服务发送消息
|
||||||
func (s *DbService) SendServiceMsg(topic string, connId uint32, userId int64, msgId int32, msg proto.Message) {
|
//func (s *DbService) SendServiceMsg(topic string, connId uint32, userId int64, msgId int32, msg proto.Message) {
|
||||||
data, _ := proto.Marshal(msg)
|
// data, _ := proto.Marshal(msg)
|
||||||
s.SendServiceData(topic, connId, userId, msgId, data)
|
// s.SendServiceData(topic, connId, userId, msgId, data)
|
||||||
}
|
//}
|
||||||
|
51
server/db/server/user.go
Normal file
51
server/db/server/user.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -172,6 +172,9 @@ func (s *GateService) WsOnMessage(conn ws.IConn, data []byte) {
|
|||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if msg.MsgId < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
var topic string
|
var topic string
|
||||||
if msg.ServiceName != "" {
|
if msg.ServiceName != "" {
|
||||||
topic = service.TopicEx(msg.ServiceName)
|
topic = service.TopicEx(msg.ServiceName)
|
||||||
@ -189,7 +192,7 @@ func (s *GateService) WsOnMessage(conn ws.IConn, data []byte) {
|
|||||||
} else {
|
} else {
|
||||||
log.Error(s.Log("topic:%v not exist.user:%v", topic, conn.UserId()))
|
log.Error(s.Log("topic:%v not exist.user:%v", topic, conn.UserId()))
|
||||||
}
|
}
|
||||||
log.Debug(s.Log("received client message:%v", msg.MsgId))
|
log.Debug(s.Log("received client:%d user:%v message:%v", conn.Id(), conn.UserId(), msg.MsgId))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向内部服务发送消息
|
// 向内部服务发送消息
|
||||||
|
20
工作.txt
Normal file
20
工作.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
1.测试gate
|
||||||
|
1.1 每10分钟新起1000个连接,发送登陆,然后关闭。检查是否有内存及协程泄漏。
|
||||||
|
1.2 启动1000个链接,每小时固定发送登陆消息,一天后查看连接是否还在。检查心跳机制。
|
||||||
|
|
||||||
|
2.编写db服
|
||||||
|
2.1 login服向db服请求数据及向log db服写入日志。测试rpc机制。
|
||||||
|
|
||||||
|
3.编写color game玩法
|
||||||
|
3.1 服务端玩法
|
||||||
|
3.2 客户端用控制台编写逻辑,不涉及ui
|
||||||
|
|
||||||
|
4. 编写管理后台
|
||||||
|
4.1 玩法配置
|
||||||
|
4.2 金流查询
|
||||||
|
4.3 牌局日志
|
||||||
|
|
||||||
|
5. 客户端编写 u3d
|
||||||
|
5.1 网络连接
|
||||||
|
5.2 ui,动画等
|
||||||
|
5.3 玩法逻辑
|
Loading…
x
Reference in New Issue
Block a user