From 37ef9a8a30ded7a7e635ce3a2261912b60a78080 Mon Sep 17 00:00:00 2001 From: liuxiaobo <1224730913@qq.com> Date: Fri, 20 Jun 2025 23:29:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86=E8=A1=A8=E6=93=8D=E4=BD=9C=E6=8C=AA?= =?UTF-8?q?=E5=88=B0=E5=A4=96=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/model | 2 +- common/modelOperator/tableOperator.go | 264 ++++++++++++++++++++++++++ server/db/operation/user.go | 8 +- server/db/operation/userGameLog.go | 10 +- server/db/operation/userResource.go | 10 +- 5 files changed, 279 insertions(+), 15 deletions(-) create mode 100644 common/modelOperator/tableOperator.go diff --git a/common/model b/common/model index cad5b2a..314f586 160000 --- a/common/model +++ b/common/model @@ -1 +1 @@ -Subproject commit cad5b2a6f2f5b6bb82d4bbe6cdda49b4c1b26e9e +Subproject commit 314f58680be0d9ed55f1b0fe52234f16da93c7cd diff --git a/common/modelOperator/tableOperator.go b/common/modelOperator/tableOperator.go new file mode 100644 index 0000000..9081117 --- /dev/null +++ b/common/modelOperator/tableOperator.go @@ -0,0 +1,264 @@ +package modelOperator + +import ( + "context" + "fmt" + "game/common/proto/pb" + "game/common/serialization" + "game/common/utils" + "github.com/fox/fox/log" + "github.com/go-redis/redis/v8" + "gorm.io/gorm" + "reflect" + "strconv" + "time" +) + +const ( + TableExpire = 7 * 24 * time.Hour // 七天后过期 +) + +type iTable interface { + GetId() int64 + TableName() string +} + +/* +T:Table,如果不想操作redis则将rds设置为nil +*/ +type TableOp[T iTable] struct { + db *gorm.DB + rds *redis.Client +} + +func NewTableOp[T iTable](db *gorm.DB, rds *redis.Client) *TableOp[T] { + return &TableOp[T]{db: db, rds: rds} +} + +func (s *TableOp[T]) tableName() string { + var result utils.TValue[T] + return result.V.TableName() +} + +func (s *TableOp[T]) redisKey(id int64) string { + return fmt.Sprintf("%s:%d", s.tableName(), id) +} + +// 查找并返回结构体 +func (s *TableOp[T]) findByRedis(id int64) *T { + maps := s.findByRedisMaps(id) + if len(maps) == 0 { + return nil + } + us, err := serialization.MapToStruct[T](maps) + if err != nil { + log.ErrorF("serialization map to struct err: %v", err) + return nil + } + //log.DebugF("findByRedis redis-key:%v result:%v", s.redisKey(id), us) + return us +} + +// 查找并返回map +func (s *TableOp[T]) findByRedisMaps(id int64) map[string]string { + if s.rds == nil { + return nil + } + maps, err := s.rds.HGetAll(context.Background(), s.redisKey(id)).Result() + if err != nil { + log.ErrorF("redis-key:%v HGetAll err: %v", s.redisKey(id), err) + return nil + } + if len(maps) == 0 { + return nil + } + return maps +} + +func (s *TableOp[T]) writeRedis(id int64, t *T) { + if s.rds == nil { + return + } + maps := serialization.StructToMap(t) + if len(maps) == 0 { + log.ErrorF("table struct is empty:%v", s.tableName()) + } + s.updateRedis(id, maps) +} + +// 查看在redis中是否存在 +func (s *TableOp[T]) existRedis(t *T) bool { + if s.rds == nil { + return false + } + // 查看redis中是否存在该键 + exist, _ := s.rds.Exists(context.Background(), s.redisKey((*t).GetId())).Result() + return exist == 1 +} + +func (s *TableOp[T]) updateRedis(id int64, maps map[string]any) { + if s.rds == nil { + return + } + 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) + } + _ = s.rds.Expire(context.Background(), s.redisKey(id), TableExpire).Err() +} + +func (s *TableOp[T]) deleteRedis(id int64) { + if s.rds == nil { + return + } + _ = s.rds.Del(context.Background(), s.redisKey(id)).Err() +} + +func (s *TableOp[T]) Create(t *T) (*T, pb.ErrCode) { + if err := s.db.Create(t).Error; err != nil { + log.ErrorF("create table:%v err:%v", s.tableName(), err) + return nil, pb.ErrCode_SystemErr + } + s.writeRedis((*t).GetId(), t) + return t, pb.ErrCode_OK +} + +func (s *TableOp[T]) Find(id int64) (*T, pb.ErrCode) { + // 先从redis中查询,redis中没有则从mysql中查询 + if table := s.findByRedis(id); table != nil { + return table, pb.ErrCode_OK + } + var result utils.TValue[T] + err := s.db.Where("id = ?", id).First(&result.V).Error + if err != nil { + log.DebugF("find table:%v id:%v err:%v", s.tableName(), id, err) + return nil, pb.ErrCode_SystemErr + } + return &result.V, pb.ErrCode_OK +} + +// 根据条件查询,只在mysql中查询,无法在redis中查询 +func (s *TableOp[T]) FindCondition(condition map[string]any) (*T, pb.ErrCode) { + us := new(T) + err := s.db.Where(condition).First(us).Error + if err != nil { + log.ErrorF("find table:%v condition:%v err:%v", s.tableName(), utils.JsonMarshal(condition), err) + return nil, pb.ErrCode_SystemErr + } + // 查看redis中是否存在该键,不存在则写入数据 + if !s.existRedis(us) { + s.writeRedis((*us).GetId(), us) + } + return us, pb.ErrCode_OK +} + +func (s *TableOp[T]) Update(id int64, updates map[string]any) (*T, pb.ErrCode) { + var result utils.TValue[T] + err := s.db.Model(&result.V).Where("id = ?", id).Updates(updates).Error + if err != nil { + log.ErrorF("update table:%v id:%v err:%v", s.tableName(), id, err) + return nil, pb.ErrCode_SystemErr + } + s.updateRedis(id, updates) + return &result.V, pb.ErrCode_OK +} + +// 辅助函数:将map的keys转为字符串slice +func keysToStringSlice(m map[string]int64) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} + +// 获取资源 +func (s *TableOp[T]) GetInt(id int64, resName []string) (map[string]int64, pb.ErrCode) { + mapFields := s.findByRedisMaps(id) + // 查询更新后的值到map + updatedValues := make(map[string]int64) + if len(mapFields) != 0 { + for _, name := range resName { + v, _ := strconv.ParseInt(mapFields[name], 10, 64) + updatedValues[name] = v + } + return updatedValues, pb.ErrCode_OK + } + + // redis中没有值,从表中加载并写入redis + mapAny := make(map[string]any) + err := s.db.Model(new(T)). + Where("id = ?", id). + Take(&mapAny). // 扫描到map + Error + + if err != nil { + log.ErrorF("query updated values table:%v id:%v err:%v", s.tableName(), id, err) + return nil, pb.ErrCode_SystemErr + } + s.updateRedis(id, mapAny) + + // 查询更新后的值到map + updatedValues = make(map[string]int64) + for _, name := range resName { + if val, ok := mapAny[name]; ok { + var v64 int64 + switch v := val.(type) { + case int64: + v64 = v + case int, int32, uint, uint32, uint64: + v64 = reflect.ValueOf(v).Int() + case float32, float64: + v64 = int64(reflect.ValueOf(v).Float()) + default: + // 处理无法转换的情况 + } + updatedValues[name] = v64 + } + } + return updatedValues, pb.ErrCode_OK +} + +// 增加或减少资源 +func (s *TableOp[T]) AddInt(id int64, res map[string]int64) (map[string]int64, pb.ErrCode) { + addRes := map[string]any{} + for k, v := range res { + addRes[k] = gorm.Expr(fmt.Sprintf("%v + ?", k), v) + } + var result utils.TValue[T] + err := s.db.Model(&result.V).Where("id = ?", id).Updates(addRes).Error + if err != nil { + log.ErrorF("add table:%v id:%v err:%v", s.tableName(), id, err) + return nil, pb.ErrCode_SystemErr + } + + // 查询更新后的值到map + updatedValues := make(map[string]int64) + err = s.db.Model(new(T)). + Select(keysToStringSlice(res)). // 只选择需要返回的字段 + Where("id = ?", id). + Take(&updatedValues). // 扫描到map + Error + + if err != nil { + log.ErrorF("query updated values table:%v id:%v err:%v", s.tableName(), id, err) + return nil, pb.ErrCode_SystemErr + } + + mapAny := make(map[string]any) + for k, v := range updatedValues { + mapAny[k] = v + } + s.updateRedis(id, mapAny) + return updatedValues, pb.ErrCode_OK +} + +func (s *TableOp[T]) Delete(id int64) (*T, pb.ErrCode) { + var result utils.TValue[T] + err := s.db.Delete(&result.V, id).Error + if err != nil { + log.ErrorF("delete table:%v err:%v", s.tableName(), err) + return nil, pb.ErrCode_SystemErr + } + s.deleteRedis(id) + return &result.V, pb.ErrCode_OK +} diff --git a/server/db/operation/user.go b/server/db/operation/user.go index 0d74459..9bbd1e1 100644 --- a/server/db/operation/user.go +++ b/server/db/operation/user.go @@ -4,8 +4,8 @@ import ( "context" "errors" "fmt" - "game/common/model" "game/common/model/user" + "game/common/modelOperator" "game/common/proto/pb" "game/common/utils" "github.com/fox/fox/log" @@ -19,7 +19,7 @@ import ( type UserOp struct { db *gorm.DB userRedis *redis.Client - *model.TableOp[user.User] + *modelOperator.TableOp[user.User] } // 玩家表 @@ -27,7 +27,7 @@ func NewUserOp() *UserOp { return &UserOp{ db: UserDB, userRedis: UserRedis, - TableOp: model.NewTableOp[user.User](UserDB, UserRedis), + TableOp: modelOperator.NewTableOp[user.User](UserDB, UserRedis), } } @@ -103,6 +103,6 @@ func (s *UserOp) createUserAccountId(accountId int64) (*user.User, pb.ErrCode) { return nil, code } // 建立索引 - _ = s.userRedis.Set(context.Background(), s.redisKey(us.AccountId), us.ID, model.TableExpire).Err() + _ = s.userRedis.Set(context.Background(), s.redisKey(us.AccountId), us.ID, modelOperator.TableExpire).Err() return us, pb.ErrCode_OK } diff --git a/server/db/operation/userGameLog.go b/server/db/operation/userGameLog.go index b635b92..dd59ae2 100644 --- a/server/db/operation/userGameLog.go +++ b/server/db/operation/userGameLog.go @@ -1,16 +1,16 @@ package operation import ( - "game/common/model" "game/common/model/user" + "game/common/modelOperator" ) // 游戏对局日志 -func NewGameRecordLogOp() *model.TableOp[user.GameRecordLog] { - return model.NewTableOp[user.GameRecordLog](LogDB, nil) +func NewGameRecordLogOp() *modelOperator.TableOp[user.GameRecordLog] { + return modelOperator.NewTableOp[user.GameRecordLog](LogDB, nil) } // 玩家游戏日志 -func NewUserRecordLogOp() *model.TableOp[user.UserRecordLog] { - return model.NewTableOp[user.UserRecordLog](LogDB, nil) +func NewUserRecordLogOp() *modelOperator.TableOp[user.UserRecordLog] { + return modelOperator.NewTableOp[user.UserRecordLog](LogDB, nil) } diff --git a/server/db/operation/userResource.go b/server/db/operation/userResource.go index b395541..b95db54 100644 --- a/server/db/operation/userResource.go +++ b/server/db/operation/userResource.go @@ -1,16 +1,16 @@ package operation import ( - "game/common/model" "game/common/model/user" + "game/common/modelOperator" ) // 玩家资源表 -func NewUserResourcesOp() *model.TableOp[user.UserResources] { - return model.NewTableOp[user.UserResources](UserDB, UserRedis) +func NewUserResourcesOp() *modelOperator.TableOp[user.UserResources] { + return modelOperator.NewTableOp[user.UserResources](UserDB, UserRedis) } // 玩家资源表 -func NewUserResourcesLogOp() *model.TableOp[user.UserResourcesLog] { - return model.NewTableOp[user.UserResourcesLog](LogDB, nil) +func NewUserResourcesLogOp() *modelOperator.TableOp[user.UserResourcesLog] { + return modelOperator.NewTableOp[user.UserResourcesLog](LogDB, nil) }