package model 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 }