package redis import ( "errors" "f1-game/internal/enum" nameRedis "f1-game/internal/name/redis" "f1-game/internal/pb" "fmt" "slices" cmapStructure "github.com/cherry-game/cherry/extend/mapstructure" cstring "github.com/cherry-game/cherry/extend/string" clog "github.com/cherry-game/cherry/logger" cprofile "github.com/cherry-game/cherry/profile" "github.com/go-redis/redis/v8" jsoniter "github.com/json-iterator/go" ) type ( game struct { *client playerDataKey string // 玩家数据key playerTeamKey string // 玩家部队key playerHeroKey string // 玩家英雄key battleReportGroupKey string // 战报组key battleReportKey string // 战报key settingKey string // 玩家设置 mapExchangeLogKey string // 地图交易所日志key } ) var notNilFunc = func(v any) bool { return v != nil } func newGame() *game { return &game{} } func (p *game) setKey() { prefix := fmt.Sprintf("%s-game:", cprofile.Env()) p.playerDataKey = prefix + "player_data:%d" // type = hash, redisKey = player_data: value = playerData p.playerTeamKey = prefix + "player_team:%d" // type = hash, redisKey = player_team: value = pb.BattleTeam p.playerHeroKey = prefix + "player_hero:%d" // type = hash, redisKey = player_hero: value = pb.PlayerHero p.battleReportGroupKey = prefix + "battle_report_group:%s" // type = list, redisKey = battle_report_group: value = reportID 列表 p.battleReportKey = prefix + "battle_report:%s" // Key = battle_report: value = pb.Report p.settingKey = prefix + "setting:%d" // type = hash, redisKey = setting: value = setting p.mapExchangeLogKey = prefix + "map_exchange_log:%s:%d:%d" // type = list, redisKey = map_exchange_log::: value = pb.MapExchangeLog } func (p *game) ConfigID() string { return "game_redis_id" } func (p *game) load(client *client) { p.client = client p.setKey() } func (p *game) GetPlayerDataHash(playerID int64, fields ...string) map[string]any { if len(fields) == 0 { return nil } key := p.buildPlayerDataKey(playerID) result, err := p.HMGet(p.Context(), key, fields...).Result() if err != nil { clog.Warnf("[GetPlayerData] error: %v", err) return nil } maps := make(map[string]any, len(fields)) for i := range len(fields) { maps[fields[i]] = result[i] } return maps } // GetPlayerData 获取玩家数据 func (p *game) GetPlayerData(playerID int64, fields ...string) *pb.PlayerData { dataMaps := p.GetPlayerDataHash(playerID, fields...) if dataMaps == nil { return nil } var playerData pb.PlayerData if err := cmapStructure.WeakDecode(dataMaps, &playerData); err != nil { clog.Errorf("[GetPlayerData] decode error: %v", err) return nil } return &playerData } // GetChatPlayer 获取玩家聊天数据信息 func (p *game) GetChatPlayer(playerID int64) *pb.ChatPlayer { dataMaps := p.GetPlayerDataHash(playerID, nameRedis.PlayerChatFields...) var chatPlayer pb.ChatPlayer if err := cmapStructure.WeakDecode(dataMaps, &chatPlayer); err != nil { clog.Errorf("[GetChatPlayer] decode error: %v", err) return nil } return &chatPlayer } // GetPlayerDataList 批量获取玩家数据列表 func (p *game) GetPlayerDataList(playerIDS []int64, fields ...string) []*pb.PlayerData { if len(fields) == 0 { return nil } pipeline := p.Pipeline() defer pipeline.Close() cmds := make([]*redis.SliceCmd, 0, len(playerIDS)) for _, playerID := range playerIDS { key := p.buildPlayerDataKey(playerID) cmds = append(cmds, pipeline.HMGet(p.Context(), key, fields...)) } if _, err := pipeline.Exec(p.Context()); err != nil { clog.Errorf("[GetPlayerDataMap] error: %v", err) return nil } players := make([]*pb.PlayerData, 0, len(playerIDS)) for i, playerID := range playerIDS { result, err := cmds[i].Result() if err != nil { continue } maps := make(map[string]any, len(fields)) for i := range len(fields) { maps[fields[i]] = result[i] } // 添加玩家 ID 字段 maps[nameRedis.Player_PlayerID] = playerID var playerData pb.PlayerData if err := cmapStructure.WeakDecode(maps, &playerData); err != nil { continue } players = append(players, &playerData) } return players } // 获取玩家数据(聊天玩家基础信息) func (p *game) GetChatPlayerMap(playerIDs ...int64) map[int64]*pb.ChatPlayer { if len(playerIDs) == 0 { return nil } fields := nameRedis.PlayerChatFields pipeline := p.Pipeline() defer pipeline.Close() cmds := make([]*redis.SliceCmd, 0, len(playerIDs)) for _, playerID := range playerIDs { key := p.buildPlayerDataKey(playerID) cmds = append(cmds, pipeline.HMGet(p.Context(), key, fields...)) } if _, err := pipeline.Exec(p.Context()); err != nil { clog.Errorf("[GetPlayerDataMap] error: %v", err) return nil } players := make(map[int64]*pb.ChatPlayer, len(playerIDs)) for i, playerID := range playerIDs { result, err := cmds[i].Result() if err != nil { continue } // hmget 的机制:当 key 不存在时,返回一个全为 nil 的 slice if !slices.ContainsFunc(result, notNilFunc) { continue } maps := make(map[string]any, len(fields)) for i := range len(fields) { maps[fields[i]] = result[i] } // 添加玩家 ID 字段 maps[nameRedis.Player_PlayerID] = playerID var chatPlayer pb.ChatPlayer if err := cmapStructure.WeakDecode(maps, &chatPlayer); err != nil { continue } players[playerID] = &chatPlayer } return players } func (p *game) getPlayerDataMap(pipeline redis.Pipeliner, playerIDs []int64, fields ...string) map[int64]*pb.PlayerData { if len(fields) == 0 { return nil } cmds := make([]*redis.SliceCmd, 0, len(playerIDs)) for _, playerID := range playerIDs { key := p.buildPlayerDataKey(playerID) cmds = append(cmds, pipeline.HMGet(p.Context(), key, fields...)) } if _, err := pipeline.Exec(p.Context()); err != nil { clog.Errorf("[GetPlayerDataMap] error: %v", err) return nil } players := make(map[int64]*pb.PlayerData, len(playerIDs)) for i, playerID := range playerIDs { result, err := cmds[i].Result() if err != nil { continue } // hmget 的机制:当 key 不存在时,返回一个全为 nil 的 slice if !slices.ContainsFunc(result, notNilFunc) { continue } maps := make(map[string]any, len(fields)) for i := range len(fields) { maps[fields[i]] = result[i] } // 添加玩家 ID 字段 maps[nameRedis.Player_PlayerID] = playerID var playerData pb.PlayerData if err := cmapStructure.WeakDecode(maps, &playerData); err != nil { continue } players[playerID] = &playerData } return players } func (p *game) GetPlayerDataMap(playerIDs []int64, fields ...string) map[int64]*pb.PlayerData { pipeline := p.Pipeline() defer pipeline.Close() return p.getPlayerDataMap(pipeline, playerIDs, fields...) } // SavePlayerDataFields 保存玩家数据 func (p *game) SavePlayerDataFields(playerID int64, fields map[string]any) bool { if len(fields) == 0 { return false } key := p.buildPlayerDataKey(playerID) _, err := p.HSet(p.Context(), key, fields).Result() if err != nil { clog.Errorf("[SavePlayerDataFields] error: %v", err) return false } return true } // 更新玩家的联盟信息,支持多个 func (p *game) UpdatePlayerDataLeagueInfo(playerLeagueInfoList ...*pb.PlayerLeagueInfo) { if len(playerLeagueInfoList) == 0 { return } // 批量修改玩家联盟ID和名称字段 pipeline := p.Pipeline() defer pipeline.Close() cmds := make([]*redis.BoolCmd, 0, len(playerLeagueInfoList)) for _, playerLeagueInfo := range playerLeagueInfoList { key := p.buildPlayerDataKey(playerLeagueInfo.PlayerID) cmds = append(cmds, pipeline.HMSet(p.Context(), key, map[string]any{ nameRedis.Player_LeagueID: playerLeagueInfo.LeagueID, nameRedis.Player_LeagueName: playerLeagueInfo.LeagueName, nameRedis.Player_LeagueAbbName: playerLeagueInfo.LeagueAbbName, nameRedis.Player_LeagueJob: playerLeagueInfo.LeagueJob, })) } if _, err := pipeline.Exec(p.Context()); err != nil { clog.Errorf("[UpdatePlayerDataLeagueInfo] error: %v", err) return } } // UpdatePlayerDataField 更新玩家数据 func (p *game) UpdatePlayerDataField(playerID int64, field string, value any) bool { key := p.buildPlayerDataKey(playerID) _, err := p.HSet(p.Context(), key, field, value).Result() if err != nil { clog.Errorf("[UpdatePlayerDataField] error: %v", err) return false } return true } // DeletePlayerDataFields 删除玩家数据字段 func (p *game) DeletePlayerDataFields(playerID int64, fields ...string) bool { if len(fields) == 0 { return false } key := p.buildPlayerDataKey(playerID) _, err := p.HDel(p.Context(), key, fields...).Result() if err != nil { clog.Errorf("[DeletePlayerDataFields] error: %v", err) return false } return true } // 获取部队信息 func (p *game) GetPlayerTeamsByID(playerID int64, teamIDList ...int32) map[int32]*pb.BattleTeam { if len(teamIDList) == 0 { return nil } key := p.buildPlayerTeamKey(playerID) fields := make([]string, 0, len(teamIDList)) for _, teamID := range teamIDList { fields = append(fields, cstring.ToString(teamID)) } result, err := p.HMGet(p.Context(), key, fields...).Result() if err != nil { if !errors.Is(err, redis.Nil) { clog.Errorf("[GetPlayerTeamsByID] playerID = %d, error: %v", playerID, err) } return nil } ret := map[int32]*pb.BattleTeam{} for _, teamStr := range result { if teamStr == nil { continue } team := &pb.BattleTeam{} if err := jsoniter.UnmarshalFromString(teamStr.(string), team); err != nil { clog.Errorf("[GetPlayerTeamsByID] playerID = %d, unmarshal error: %v", playerID, err) return nil } ret[team.TeamID] = team } return ret } // 获取部队信息 func (p *game) GetPlayerTeamByID(playerID int64, teamID int32) (*pb.BattleTeam, bool) { m := p.GetPlayerTeamsByID(playerID, teamID) if m == nil { return nil, false } team, ok := m[teamID] return team, ok } // 更新玩家队伍 func (p *game) UpdatePlayerTeams(playerID int64, battleTeams ...*pb.BattleTeam) { if len(battleTeams) == 0 { return } key := p.buildPlayerTeamKey(playerID) values := make([]any, 0, len(battleTeams)*2) for _, team := range battleTeams { values = append(values, cstring.ToString(team.TeamID)) jsonStr, err := jsoniter.MarshalToString(team) if err != nil { clog.Errorf("[UpdatePlayerTeams] player = %d, marshal error: %v", playerID, err) return } values = append(values, jsonStr) } err := p.HSet(p.Context(), key, values).Err() if err != nil { clog.Errorf("[UpdatePlayerTeams] player = %d, error: %v", playerID, err) } } // 获取玩家英雄 func (p *game) GetPlayerHeros(playerID int64, heroIDList ...int64) map[int64]*pb.PlayerHero { if len(heroIDList) == 0 { return nil } key := p.buildPlayerHeroKey(playerID) fields := make([]string, 0, len(heroIDList)) for _, heroID := range heroIDList { fields = append(fields, cstring.ToString(heroID)) } result, err := p.HMGet(p.Context(), key, fields...).Result() if err != nil { if !errors.Is(err, redis.Nil) { clog.Errorf("[GetPlayerHeros] playerID = %d, error: %v", playerID, err) } return nil } ret := map[int64]*pb.PlayerHero{} for _, playerHeroStr := range result { if playerHeroStr == nil { continue } playerHero := &pb.PlayerHero{} if err := jsoniter.UnmarshalFromString(playerHeroStr.(string), playerHero); err != nil { clog.Errorf("[GetPlayerHeros] playerID = %d, unmarshal error: %v", playerID, err) return nil } ret[playerHero.Id] = playerHero } return ret } // 更新玩家英雄信息 func (p *game) UpdatePlayerHeros(playerID int64, heroList ...*pb.PlayerHero) { if len(heroList) == 0 { return } key := p.buildPlayerHeroKey(playerID) values := make([]any, 0, len(heroList)*2) for _, hero := range heroList { values = append(values, cstring.ToString(hero.Id)) jsonStr, err := jsoniter.MarshalToString(hero) if err != nil { clog.Errorf("[UpdatePlayerHeros] player = %d, marshal error: %v", playerID, err) return } values = append(values, jsonStr) } err := p.HSet(p.Context(), key, values).Err() if err != nil { clog.Errorf("[UpdatePlayerHeros] player = %d, error: %v", playerID, err) } } // 删除玩家英雄 func (p *game) DeletePlayerHeros(playerID int64, heroIDList ...int64) { if len(heroIDList) == 0 { return } key := p.buildPlayerHeroKey(playerID) fields := make([]string, 0, len(heroIDList)) for _, heroID := range heroIDList { fields = append(fields, cstring.ToString(heroID)) } err := p.HDel(p.Context(), key, fields...).Err() if err != nil { clog.Errorf("[DeletePlayerHeros] player = %d, error: %v", playerID, err) } } // 根据玩家战斗部队数据获取部队和英雄数据 func (p *game) GetPlayerTeamHeroData(playerID int64, teamID int32, heroGUID int64) (*pb.PlayerData, *pb.BattleTeam, *pb.PlayerHero) { pipeline := p.Pipeline() defer pipeline.Close() // 获取玩家数据 playerKey := p.buildPlayerDataKey(playerID) playerCmd := pipeline.HMGet(p.Context(), playerKey, nameRedis.PlayerBaseFields...) // 获取队伍数据 teamKey := p.buildPlayerTeamKey(playerID) teamCmd := pipeline.HGet(p.Context(), teamKey, cstring.ToString(teamID)) // 获取英雄数据 heroKey := p.buildPlayerHeroKey(playerID) heroCmd := pipeline.HGet(p.Context(), heroKey, cstring.ToString(heroGUID)) if _, err := pipeline.Exec(p.Context()); err != nil && !errors.Is(err, redis.Nil) { clog.Warnf("[GetPlayerTeamHeroData] err = %v", err) return nil, nil, nil } playerData, _ := p.decodePlayerData(playerID, nameRedis.PlayerBaseFields, playerCmd) teamData, _ := p.decodeBattleTeam(teamID, teamCmd) heroData, _ := p.decodePlayerHero(heroGUID, heroCmd) return playerData, teamData, heroData } // 根据玩家战斗部队数据获取部队和英雄数据 func (p *game) GetPlayerBattleData(battleTeamDataList ...*pb.BattleTeamData) (map[int64]*pb.BattleTeam, map[int64]*pb.PlayerHero) { if len(battleTeamDataList) == 0 { return nil, nil } // 收集每个玩家对应的 teamID(每个玩家只有一个部队)和 heroID 列表 playerTeamIDs := map[int64]int32{} playerHeroIDs := map[int64][]int64{} for _, teamData := range battleTeamDataList { if teamData == nil { continue } playerTeamIDs[teamData.PlayerID] = teamData.TeamID playerHeroIDs[teamData.PlayerID] = teamData.TeamHeros } // 去重玩家列表并保持顺序(用于 pipeline 对应) playerIDs := make([]int64, 0, len(playerTeamIDs)) for pid := range playerTeamIDs { playerIDs = append(playerIDs, pid) } pipeline := p.Pipeline() defer pipeline.Close() // 为每个玩家创建 HGET 请求(因为每个玩家只有一个部队字段) teamCmds := make([]*redis.StringCmd, 0, len(playerIDs)) heroCmds := make([]*redis.SliceCmd, 0, len(playerIDs)) for _, pid := range playerIDs { teamKey := p.buildPlayerTeamKey(pid) field := cstring.ToString(playerTeamIDs[pid]) teamCmds = append(teamCmds, pipeline.HGet(p.Context(), teamKey, field)) // 为每个玩家创建 HMGET 请求(因为每个玩家有多个英雄字段) heroKey := p.buildPlayerHeroKey(pid) var fields []string for _, hid := range playerHeroIDs[pid] { fields = append(fields, cstring.ToString(hid)) } heroCmds = append(heroCmds, pipeline.HMGet(p.Context(), heroKey, fields...)) } if _, err := pipeline.Exec(p.Context()); err != nil { clog.Errorf("[GetPlayerBattleData] pipeline exec error: %v", err) return nil, nil } teamsMap := map[int64]*pb.BattleTeam{} heroesMap := map[int64]*pb.PlayerHero{} // 解析部队结果 for i, cmd := range teamCmds { playerID := playerIDs[i] str, err := cmd.Result() if err != nil { if !errors.Is(err, redis.Nil) { clog.Errorf("[GetPlayerBattleData] get team error player = %d, err = %v", playerID, err) } continue } if str == "" { continue } var team pb.BattleTeam if err := jsoniter.UnmarshalFromString(str, &team); err != nil { clog.Errorf("[GetPlayerBattleData] unmarshal team error player = %d, err = %v", playerID, err) continue } teamsMap[playerID] = &team } // 解析英雄结果 for _, cmd := range heroCmds { result, err := cmd.Result() if err != nil { if !errors.Is(err, redis.Nil) { clog.Errorf("[GetPlayerBattleData] get heroes error: %v", err) } } else { for _, item := range result { if item == nil { continue } str, ok := item.(string) if !ok || str == "" { continue } var hero pb.PlayerHero if err := jsoniter.UnmarshalFromString(str, &hero); err != nil { clog.Errorf("[GetPlayerBattleData] unmarshal hero error, err = %v", err) continue } heroesMap[hero.Id] = &hero } } } return teamsMap, heroesMap } // 保存战报数据,并追加到战报组数据中 func (p *game) AddReports(groupID string, reports ...*pb.Report) bool { if len(reports) == 0 { return false } groupKey := p.buildBattleReportGroupKey(groupID) pipeline := p.Pipeline() defer pipeline.Close() var idValues []any for _, report := range reports { if report == nil { continue } jsonStr, err := jsoniter.MarshalToString(report) if err != nil { clog.Errorf("[AddReports] marshal error: %v", err) continue } // 先写入战报 key reportKey := p.buildBattleReportKey(report.ReportID) pipeline.Set(p.Context(), reportKey, jsonStr, NoExpire) // 只有序列化成功的战报ID才追加到组列表 idValues = append(idValues, report.ReportID) } // 如果没有任何要写入的 id,则仍可能成功(但无实际写入),根据需要可视为失败,这里视为失败 if len(idValues) == 0 { clog.Warnf("[AddReports] no valid reports to save for group = %s", groupID) return false } // 将所有战报ID 追加到组列表 pipeline.LPush(p.Context(), groupKey, idValues...) if _, err := pipeline.Exec(p.Context()); err != nil { clog.Errorf("[AddReports] pipeline error: %v", err) return false } return true } // 获取战报组的所有战报ID集合 func (p *game) GetReportIDListOfGroup(groupIDList ...string) map[string][]string { resultMap := map[string][]string{} if len(groupIDList) == 0 { return resultMap } pipeline := p.Pipeline() defer pipeline.Close() cmds := make([]*redis.StringSliceCmd, 0, len(groupIDList)) for _, groupID := range groupIDList { key := p.buildBattleReportGroupKey(groupID) cmds = append(cmds, pipeline.LRange(p.Context(), key, 0, -1)) } if _, err := pipeline.Exec(p.Context()); err != nil { clog.Errorf("[GetReportIDListOfGroup] pipeline error: %v", err) return resultMap } for i, groupID := range groupIDList { result, err := cmds[i].Result() if err != nil { if !errors.Is(err, redis.Nil) { clog.Errorf("[GetReportIDListOfGroup] get error: %v", err) } continue } var idList []string for _, id := range result { if id != "" { idList = append(idList, id) } } resultMap[groupID] = idList } return resultMap } // 获取战报 func (p *game) GetBattleReports(reportIDList ...string) map[string]*pb.Report { reportMap := map[string]*pb.Report{} if len(reportIDList) == 0 { return reportMap } pipeline := p.Pipeline() defer pipeline.Close() cmds := make([]*redis.StringCmd, 0, len(reportIDList)) for _, reportID := range reportIDList { key := p.buildBattleReportKey(reportID) cmds = append(cmds, pipeline.Get(p.Context(), key)) } if _, err := pipeline.Exec(p.Context()); err != nil { clog.Errorf("[GetBattleReports] pipeline error: %v", err) return reportMap } for i, reportID := range reportIDList { result, err := cmds[i].Result() if err != nil { if !errors.Is(err, redis.Nil) { clog.Errorf("[GetBattleReports] get error: %v", err) } continue } var report pb.Report if err := jsoniter.UnmarshalFromString(result, &report); err != nil { clog.Errorf("[GetBattleReports] unmarshal error: %v", err) continue } reportMap[reportID] = &report } return reportMap } // 更新玩家设置信息 func (p *game) UpdatePlayerSetting(playerID int64, setting *pb.LordShowDataList) { key := p.buildPlayerSettingKey(playerID) value, err := jsoniter.MarshalToString(setting) if err != nil { clog.Errorf("[UpdatePlayerSetting] player = %d, marshal error: %v", playerID, err) return } err = p.Set(p.Context(), key, value, 0).Err() if err != nil { clog.Errorf("[UpdatePlayerSetting] player = %d, error: %v", playerID, err) } } // 获取玩家设置信息 func (p *game) GetPlayerSetting(playerID int64) *pb.LordShowDataList { key := p.buildPlayerSettingKey(playerID) value, err := p.Get(p.Context(), key).Result() if err != nil { if !errors.Is(err, redis.Nil) { clog.Errorf("[GetPlayerSetting] player = %d, error: %v", playerID, err) } return nil } list := &pb.LordShowDataList{} if err := jsoniter.UnmarshalFromString(value, list); err != nil { clog.Errorf("[GetBattleReports] unmarshal error: %v", err) return nil } return list } func (p *game) buildPlayerDataKey(playerID int64) string { return fmt.Sprintf(p.playerDataKey, playerID) } func (p *game) buildPlayerTeamKey(playerID int64) string { return fmt.Sprintf(p.playerTeamKey, playerID) } func (p *game) buildPlayerHeroKey(playerID int64) string { return fmt.Sprintf(p.playerHeroKey, playerID) } func (p *game) buildBattleReportGroupKey(groupID string) string { return fmt.Sprintf(p.battleReportGroupKey, groupID) } func (p *game) buildBattleReportKey(reportID string) string { return fmt.Sprintf(p.battleReportKey, reportID) } func (p *game) buildPlayerSettingKey(playerID int64) string { return fmt.Sprintf(p.settingKey, playerID) } func (p *game) decodePlayerData(playerID int64, fields []string, value *redis.SliceCmd) (*pb.PlayerData, bool) { result, err := value.Result() if err != nil { if !errors.Is(err, redis.Nil) { clog.Errorf("[GetPlayerData] player = %d, error: %v", playerID, err) } return nil, false } maps := make(map[string]any, len(fields)) for i := range len(fields) { maps[fields[i]] = result[i] } // 添加玩家 ID 字段 maps[nameRedis.Player_PlayerID] = playerID var playerData pb.PlayerData if err := cmapStructure.WeakDecode(maps, &playerData); err != nil { clog.Errorf("[GetPlayerData] player = %d, error: %v", playerID, err) return nil, false } return &playerData, true } func (p *game) decodeBattleTeam(teamID int32, value *redis.StringCmd) (*pb.BattleTeam, bool) { result, err := value.Result() if err != nil { if !errors.Is(err, redis.Nil) { clog.Errorf("[decodeBattleTeam] team = %d, error: %v", teamID, err) } return nil, false } var teamData pb.BattleTeam if err := jsoniter.UnmarshalFromString(result, &teamData); err != nil { clog.Errorf("[decodeBattleTeam] team = %d, error: %v", teamID, err) return nil, false } return &teamData, true } func (p *game) decodePlayerHero(playerID int64, value *redis.StringCmd) (*pb.PlayerHero, bool) { result, err := value.Result() if err != nil { if !errors.Is(err, redis.Nil) { clog.Errorf("[decodePlayerHero] player = %d, error: %v", playerID, err) } return nil, false } var heroData pb.PlayerHero if err := jsoniter.UnmarshalFromString(result, &heroData); err != nil { clog.Errorf("[decodePlayerHero] player = %d, error: %v", playerID, err) return nil, false } return &heroData, true } func (p *game) buildMapExchangeLogKey(mapNodeID string, playerID int64, logType enum.ExchangeLogType) string { return fmt.Sprintf(p.mapExchangeLogKey, mapNodeID, playerID, logType) } // 获取地图交易所日志列表 // lastLogID > 0 获取最近pageSize条消息,不包括lastLogID // lastLogID = 0 获取最新的pageSize条消息 func (p *game) GetMapExchangeLogList(mapNodeID string, playerID int64, logType enum.ExchangeLogType, lastLogID int64) *pb.ExchangeLogList { key := p.buildMapExchangeLogKey(mapNodeID, playerID, logType) return p.getMapExchangeLogList(key, lastLogID) } // 获取地图交易所日志列表 // lastLogID > 0 获取最近pageSize条消息,不包括lastLogID // lastLogID = 0 获取最新的pageSize条消息 func (p *game) getMapExchangeLogList(key string, lastLogID int64) *pb.ExchangeLogList { zSliceToChatMsgs := func(zs []redis.Z, isReverse bool) *pb.ExchangeLogList { logList := &pb.ExchangeLogList{} for _, v := range zs { if v.Member == nil { continue } var log pb.ExchangeLog if err := jsoniter.UnmarshalFromString(v.Member.(string), &log); err == nil { logList.List = append(logList.List, &log) } } // 反转 if isReverse { for i, j := 0, len(logList.List)-1; i < j; i, j = i+1, j-1 { logList.List[i], logList.List[j] = logList.List[j], logList.List[i] } } return logList } if lastLogID > 0 { // 获取 lastLogID 之前的 20 条 values, err := p.ZRevRangeByScoreWithScores(p.Context(), key, &redis.ZRangeBy{ Max: fmt.Sprintf("(%d", lastLogID), // 开区间,排除自身 Min: "-inf", Offset: 0, Count: pageSize, }).Result() if err != nil && err != redis.Nil { clog.Warnf("[getMapExchangeLogList] ZRevRangeByScoreWithScores error. [key = %s, lastLogID = %d, error: %v]", key, lastLogID, err) return nil } return zSliceToChatMsgs(values, false) } else { // 最新 20 条,降序 values, err := p.ZRevRangeWithScores(p.Context(), key, 0, pageSize-1).Result() if err != nil && err != redis.Nil { clog.Warnf("[getMapExchangeLogList] ZRevRangeWithScores error. [key = %s, lastLogID = %d, error: %v]", key, lastLogID, err) return nil } // 反转成升序 return zSliceToChatMsgs(values, false) } } // 添加一条地图交易所日志 func (p *game) AddMapExchangeLog(mapNodeID string, playerID int64, logType enum.ExchangeLogType, maxLimit int64, log *pb.ExchangeLog) { key := p.buildMapExchangeLogKey(mapNodeID, playerID, logType) p.addMapExchangeLog(key, maxLimit, log) } // 保存一条日志消息 func (p *game) addMapExchangeLog(key string, maxLimit int64, log *pb.ExchangeLog) { data, err := jsoniter.MarshalToString(log) if err != nil { clog.Warnf("[addMapExchangeLog] marshal error. [key = %s, maxLimit = %d, error: %v]", key, maxLimit, err) return } // 使用 LogID 作为 score,保证顺序 err = p.ZAdd(p.Context(), key, &redis.Z{ Score: float64(log.LogID), Member: data, }).Err() if err != nil { clog.Warnf("[addMapExchangeLog] zadd error. [key = %s, maxLimit = %d, error: %v]", key, maxLimit, err) return } // 裁剪到最多 maxLimit 条 err = p.ZRemRangeByRank(p.Context(), key, 0, -maxLimit-1).Err() if err != nil { clog.Warnf("[addMapExchangeLog] zremrangebyrank error. [key = %s, maxLimit = %d, error: %v]", key, maxLimit, err) } }