| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- 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
- }
|