package redis import ( "f1-game/internal/enum" "f1-game/internal/pb" "fmt" 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" ) const ( pageSize = int64(20) // 默认分页大小 ) type ( chat struct { *client chatGroupMemberKey string // 群组聊天室成员 key chatLeagueMemberKey string // 联盟聊天室成员 key chatChatMsgKey string // 聊天历史记录key } ) func newChat() *chat { return &chat{} } func (p *chat) setKey() { prefix := fmt.Sprintf("%s-chat:", cprofile.Env()) p.chatGroupMemberKey = prefix + "group:%d:member" //key = group:{chatID}:member p.chatLeagueMemberKey = prefix + "league:%d:member" //key = league:{chatID}:member p.chatChatMsgKey = prefix + "msg:%d:%v" //key = msg:{chatType}:{chatid} } func (p *chat) ConfigID() string { return "chat_redis_id" } func (p *chat) load(client *client) { p.client = client p.setKey() } // 获取聊天消息列表对象 // lastMsgID > 0 获取最近pageSize条消息,不包括lastMsgID // lastMsgID = 0 获取最新的pageSize条消息 func (p *chat) GetChatMsgList(chatType enum.ChatType, chatID int64, lastMsgID int64) *pb.ChatMsgList { msgs := p.GetChatMsgs(chatType, chatID, lastMsgID) rsp := &pb.ChatMsgList{ ChatType: int32(chatType), ChatID: chatID, List: msgs.List, } return rsp } // 获取聊天消息列表 // lastMsgID > 0 获取最近pageSize条消息,不包括lastMsgID // lastMsgID = 0 获取最新的pageSize条消息 func (p *chat) GetChatMsgs(chatType enum.ChatType, chatID int64, lastMsgID int64) *pb.ChatMsgs { key := p.buildMsgKey(chatType, chatID) return p.getChatMsgs(key, lastMsgID) } // 获取指定ID的聊天消息 func (p *chat) GetChatMsg(chatType enum.ChatType, chatID int64, msgID int64) *pb.ChatMsg { key := p.buildMsgKey(chatType, chatID) score := cstring.ToString(msgID) // 只查分数等于msgID的消息 values, err := p.ZRangeByScoreWithScores(p.Context(), key, &redis.ZRangeBy{ Min: score, Max: score, // Count: 1, // 可加可不加,分数唯一时只会有一条 }).Result() if err != nil || len(values) == 0 { clog.Warnf("[GetChatMsg] zrangebyscore error. [key = %s, msgID = %d, error: %v]", key, msgID, err) return nil } var msg pb.ChatMsg if err := jsoniter.UnmarshalFromString(values[0].Member.(string), &msg); err != nil { return nil } return &msg } // 获取聊天室消息 // lastMsgID > 0 获取最近pageSize条消息,不包括lastMsgID // lastMsgID = 0 获取最新的pageSize条消息 func (p *chat) getChatMsgs(key string, lastMsgID int64) *pb.ChatMsgs { zSliceToChatMsgs := func(zs []redis.Z, isReverse bool) *pb.ChatMsgs { msgs := &pb.ChatMsgs{} for _, v := range zs { if v.Member == nil { continue } var msg pb.ChatMsg if err := jsoniter.UnmarshalFromString(v.Member.(string), &msg); err == nil { msgs.List = append(msgs.List, &msg) } } // 反转 if isReverse { for i, j := 0, len(msgs.List)-1; i < j; i, j = i+1, j-1 { msgs.List[i], msgs.List[j] = msgs.List[j], msgs.List[i] } } return msgs } if lastMsgID > 0 { // 获取 lastMsgID 之前的 20 条 values, err := p.ZRevRangeByScoreWithScores(p.Context(), key, &redis.ZRangeBy{ Max: fmt.Sprintf("(%d", lastMsgID), // 开区间,排除自身 Min: "-inf", Offset: 0, Count: pageSize, }).Result() if err != nil && err != redis.Nil { clog.Warnf("[getChatMsgs] ZRevRangeByScoreWithScores error. [key = %s, lastMsgID = %d, error: %v]", key, lastMsgID, err) return nil } return zSliceToChatMsgs(values, true) } else { // 最新 20 条,降序 values, err := p.ZRevRangeWithScores(p.Context(), key, 0, pageSize-1).Result() if err != nil && err != redis.Nil { clog.Warnf("[getChatMsgs] ZRevRangeWithScores error. [key = %s, lastMsgID = %d, error: %v]", key, lastMsgID, err) return nil } // 反转成升序 return zSliceToChatMsgs(values, true) } } func (p *chat) buildMsgKey(chatType enum.ChatType, chatID any) string { return fmt.Sprintf(p.chatChatMsgKey, chatType, chatID) } func (p *chat) AddMsg(chatType enum.ChatType, chatID int64, maxLimit int64, msg *pb.ChatMsg) { key := p.buildMsgKey(chatType, chatID) p.addMsg(key, maxLimit, msg) } // 保存一条聊天消息 func (p *chat) addMsg(key string, maxLimit int64, msg *pb.ChatMsg) { data, err := jsoniter.MarshalToString(msg) if err != nil { clog.Warnf("[addMsg] marshal error. [key = %s, maxLimit = %d, error: %v]", key, maxLimit, err) return } // 使用 MsgID 作为 score,保证顺序 err = p.ZAdd(p.Context(), key, &redis.Z{ Score: float64(msg.MsgID), Member: data, }).Err() if err != nil { clog.Warnf("[addMsg] 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("[addMsg] zremrangebyrank error. [key = %s, maxLimit = %d, error: %v]", key, maxLimit, err) } } // 删除指定聊天室的所有聊天记录 func (p *chat) DelAllMsg(chatType enum.ChatType, chatID int64) bool { key := p.buildMsgKey(chatType, chatID) return p.Del(key) } func (p *chat) DelMsgByMsgID(chatType enum.ChatType, chatID int64, msgID int64) bool { key := p.buildMsgKey(chatType, chatID) return p.delMsgByMsgID(key, msgID) } // 根据消息ID删除指定聊天室中的单条消息 func (p *chat) delMsgByMsgID(key string, msgID int64) bool { var ( score = cstring.ToString(msgID) ) // 删除 score = messageID 的消息 err := p.ZRemRangeByScore(p.Context(), key, score, score).Err() if err != nil { clog.Warnf("[delMsgByMsgID] zremrangebyscore error. [key = %s, msgID = %d, error: %v]", key, score, err) return false } return true } // 构建私聊ID 单独记录 func (p *chat) buildPrivateChatID(playerID int64, targetID int64) string { return fmt.Sprintf("%d-%d", playerID, targetID) } func (p *chat) GetPrivateMsgs(playerID, targetID int64, lastMsgID int64) *pb.ChatMsgs { chatID := p.buildPrivateChatID(playerID, targetID) key := p.buildMsgKey(enum.ChatType_Private, chatID) return p.getChatMsgs(key, lastMsgID) } func (p *chat) AddPrivateMsg(playerID, targetID int64, maxLimit int64, msg *pb.ChatMsg) { chatID := p.buildPrivateChatID(playerID, targetID) key := p.buildMsgKey(enum.ChatType_Private, chatID) p.addMsg(key, maxLimit, msg) } func (p *chat) DelPrivateMsg(playerID, targetID int64) bool { chatID := p.buildPrivateChatID(playerID, targetID) key := p.buildMsgKey(enum.ChatType_Private, chatID) ok := p.Del(key) if !ok { clog.Warnf("[DelPrivateMsg] del error. [key = %s]", key) } return ok } // 构建群组成员key func (p *chat) buildGroupMemberKey(chatID int64) string { return fmt.Sprintf(p.chatGroupMemberKey, chatID) } // 添加群组成员 // 使用Redis的Set结构确保成员ID不重复 func (p *chat) AddGroupMember(chatID int64, memberID int64) bool { key := p.buildGroupMemberKey(chatID) // 使用SAdd添加成员,如果成员已存在则不会重复添加 err := p.SAdd(p.Context(), key, memberID).Err() if err != nil { clog.Warnf("[AddGroupMember] sadd error. [key = %s, memberID = %d, err = %v]", key, memberID, err) return false } return true } // 移除群组成员 func (p *chat) DelGroupMember(chatID int64, memberIDList ...int64) bool { key := p.buildGroupMemberKey(chatID) if len(memberIDList) == 0 { return true } // 将 []int64 转为 []interface{} 并展开,避免把整个切片当成单个成员传入 args := make([]any, 0, len(memberIDList)) for _, id := range memberIDList { args = append(args, id) } // 使用 SRem 移除成员 err := p.SRem(p.Context(), key, args...).Err() if err != nil { clog.Warnf("[DelGroupMember] srem error. [key = %s, memberIDList = %v, err = %v]", key, memberIDList, err) return false } return true } // 获取群组成员总数 func (p *chat) GetGroupMemberCount(chatID int64) int64 { key := p.buildGroupMemberKey(chatID) count, _ := p.SCard(p.Context(), key).Result() return count } // 获取群组所有成员 func (p *chat) GetGroupMembers(chatID int64) ([]int64, bool) { key := p.buildGroupMemberKey(chatID) // 获取所有成员 members, err := p.SMembers(p.Context(), key).Result() if err != nil { clog.Warnf("[GetGroupMembers] smembers error. [key = %s, err = %v]", key, err) return nil, false } // 将字符串转换为int64 memberIDList := make([]int64, 0, len(members)) for _, m := range members { if id, ok := cstring.ToInt64(m); ok { memberIDList = append(memberIDList, id) } } return memberIDList, true } // 删除群组所有成员 func (p *chat) DelAllGroupMembers(chatID int64) bool { key := p.buildGroupMemberKey(chatID) // 删除整个集合 ok := p.Del(key) if !ok { clog.Warnf("[DelAllGroupMembers] del error. [key = %s]", key) } return ok } func (p *chat) buildLeagueMemberKey(chatID int64) string { return fmt.Sprintf(p.chatLeagueMemberKey, chatID) } // 获取联盟成员列表 func (p *chat) GetLeagueMembers(chatID int64) []int64 { key := p.buildLeagueMemberKey(chatID) members, err := p.SMembers(p.Context(), key).Result() if err != nil { clog.Warnf("[GetLeagueMembers] smembers error. [key = %s, err = %v]", key, err) return nil } var memberIDList []int64 for _, m := range members { if id, ok := cstring.ToInt64(m); ok { memberIDList = append(memberIDList, id) } } return memberIDList } // 添加联盟成员 func (p *chat) AddLeagueMember(chatID int64, memberID int64) bool { key := p.buildLeagueMemberKey(chatID) err := p.SAdd(p.Context(), key, memberID).Err() if err != nil { clog.Warnf("[AddLeagueMember] sadd error. [key = %s, memberID = %d, err = %v]", key, memberID, err) return false } return true } // 删除联盟成员 func (p *chat) DelLeagueMember(chatID int64, memberID int64) bool { key := p.buildLeagueMemberKey(chatID) err := p.SRem(p.Context(), key, memberID).Err() if err != nil { clog.Warnf("[DelLeagueMember] srem error. [key = %s, memberID = %d, err = %v]", key, memberID, err) return false } return true }