chat.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. package redis
  2. import (
  3. "f1-game/internal/enum"
  4. "f1-game/internal/pb"
  5. "fmt"
  6. cstring "github.com/cherry-game/cherry/extend/string"
  7. clog "github.com/cherry-game/cherry/logger"
  8. cprofile "github.com/cherry-game/cherry/profile"
  9. "github.com/go-redis/redis/v8"
  10. jsoniter "github.com/json-iterator/go"
  11. )
  12. const (
  13. pageSize = int64(20) // 默认分页大小
  14. )
  15. type (
  16. chat struct {
  17. *client
  18. chatGroupMemberKey string // 群组聊天室成员 key
  19. chatLeagueMemberKey string // 联盟聊天室成员 key
  20. chatChatMsgKey string // 聊天历史记录key
  21. }
  22. )
  23. func newChat() *chat {
  24. return &chat{}
  25. }
  26. func (p *chat) setKey() {
  27. prefix := fmt.Sprintf("%s-chat:", cprofile.Env())
  28. p.chatGroupMemberKey = prefix + "group:%d:member" //key = group:{chatID}:member
  29. p.chatLeagueMemberKey = prefix + "league:%d:member" //key = league:{chatID}:member
  30. p.chatChatMsgKey = prefix + "msg:%d:%v" //key = msg:{chatType}:{chatid}
  31. }
  32. func (p *chat) ConfigID() string {
  33. return "chat_redis_id"
  34. }
  35. func (p *chat) load(client *client) {
  36. p.client = client
  37. p.setKey()
  38. }
  39. // 获取聊天消息列表对象
  40. // lastMsgID > 0 获取最近pageSize条消息,不包括lastMsgID
  41. // lastMsgID = 0 获取最新的pageSize条消息
  42. func (p *chat) GetChatMsgList(chatType enum.ChatType, chatID int64, lastMsgID int64) *pb.ChatMsgList {
  43. msgs := p.GetChatMsgs(chatType, chatID, lastMsgID)
  44. rsp := &pb.ChatMsgList{
  45. ChatType: int32(chatType),
  46. ChatID: chatID,
  47. List: msgs.List,
  48. }
  49. return rsp
  50. }
  51. // 获取聊天消息列表
  52. // lastMsgID > 0 获取最近pageSize条消息,不包括lastMsgID
  53. // lastMsgID = 0 获取最新的pageSize条消息
  54. func (p *chat) GetChatMsgs(chatType enum.ChatType, chatID int64, lastMsgID int64) *pb.ChatMsgs {
  55. key := p.buildMsgKey(chatType, chatID)
  56. return p.getChatMsgs(key, lastMsgID)
  57. }
  58. // 获取指定ID的聊天消息
  59. func (p *chat) GetChatMsg(chatType enum.ChatType, chatID int64, msgID int64) *pb.ChatMsg {
  60. key := p.buildMsgKey(chatType, chatID)
  61. score := cstring.ToString(msgID)
  62. // 只查分数等于msgID的消息
  63. values, err := p.ZRangeByScoreWithScores(p.Context(), key, &redis.ZRangeBy{
  64. Min: score,
  65. Max: score,
  66. // Count: 1, // 可加可不加,分数唯一时只会有一条
  67. }).Result()
  68. if err != nil || len(values) == 0 {
  69. clog.Warnf("[GetChatMsg] zrangebyscore error. [key = %s, msgID = %d, error: %v]", key, msgID, err)
  70. return nil
  71. }
  72. var msg pb.ChatMsg
  73. if err := jsoniter.UnmarshalFromString(values[0].Member.(string), &msg); err != nil {
  74. return nil
  75. }
  76. return &msg
  77. }
  78. // 获取聊天室消息
  79. // lastMsgID > 0 获取最近pageSize条消息,不包括lastMsgID
  80. // lastMsgID = 0 获取最新的pageSize条消息
  81. func (p *chat) getChatMsgs(key string, lastMsgID int64) *pb.ChatMsgs {
  82. zSliceToChatMsgs := func(zs []redis.Z, isReverse bool) *pb.ChatMsgs {
  83. msgs := &pb.ChatMsgs{}
  84. for _, v := range zs {
  85. if v.Member == nil {
  86. continue
  87. }
  88. var msg pb.ChatMsg
  89. if err := jsoniter.UnmarshalFromString(v.Member.(string), &msg); err == nil {
  90. msgs.List = append(msgs.List, &msg)
  91. }
  92. }
  93. // 反转
  94. if isReverse {
  95. for i, j := 0, len(msgs.List)-1; i < j; i, j = i+1, j-1 {
  96. msgs.List[i], msgs.List[j] = msgs.List[j], msgs.List[i]
  97. }
  98. }
  99. return msgs
  100. }
  101. if lastMsgID > 0 {
  102. // 获取 lastMsgID 之前的 20 条
  103. values, err := p.ZRevRangeByScoreWithScores(p.Context(), key, &redis.ZRangeBy{
  104. Max: fmt.Sprintf("(%d", lastMsgID), // 开区间,排除自身
  105. Min: "-inf",
  106. Offset: 0,
  107. Count: pageSize,
  108. }).Result()
  109. if err != nil && err != redis.Nil {
  110. clog.Warnf("[getChatMsgs] ZRevRangeByScoreWithScores error. [key = %s, lastMsgID = %d, error: %v]", key, lastMsgID, err)
  111. return nil
  112. }
  113. return zSliceToChatMsgs(values, true)
  114. } else {
  115. // 最新 20 条,降序
  116. values, err := p.ZRevRangeWithScores(p.Context(), key, 0, pageSize-1).Result()
  117. if err != nil && err != redis.Nil {
  118. clog.Warnf("[getChatMsgs] ZRevRangeWithScores error. [key = %s, lastMsgID = %d, error: %v]", key, lastMsgID, err)
  119. return nil
  120. }
  121. // 反转成升序
  122. return zSliceToChatMsgs(values, true)
  123. }
  124. }
  125. func (p *chat) buildMsgKey(chatType enum.ChatType, chatID any) string {
  126. return fmt.Sprintf(p.chatChatMsgKey, chatType, chatID)
  127. }
  128. func (p *chat) AddMsg(chatType enum.ChatType, chatID int64, maxLimit int64, msg *pb.ChatMsg) {
  129. key := p.buildMsgKey(chatType, chatID)
  130. p.addMsg(key, maxLimit, msg)
  131. }
  132. // 保存一条聊天消息
  133. func (p *chat) addMsg(key string, maxLimit int64, msg *pb.ChatMsg) {
  134. data, err := jsoniter.MarshalToString(msg)
  135. if err != nil {
  136. clog.Warnf("[addMsg] marshal error. [key = %s, maxLimit = %d, error: %v]", key, maxLimit, err)
  137. return
  138. }
  139. // 使用 MsgID 作为 score,保证顺序
  140. err = p.ZAdd(p.Context(), key, &redis.Z{
  141. Score: float64(msg.MsgID),
  142. Member: data,
  143. }).Err()
  144. if err != nil {
  145. clog.Warnf("[addMsg] zadd error. [key = %s, maxLimit = %d, error: %v]", key, maxLimit, err)
  146. return
  147. }
  148. // 裁剪到最多 maxLimit 条
  149. err = p.ZRemRangeByRank(p.Context(), key, 0, -maxLimit-1).Err()
  150. if err != nil {
  151. clog.Warnf("[addMsg] zremrangebyrank error. [key = %s, maxLimit = %d, error: %v]", key, maxLimit, err)
  152. }
  153. }
  154. // 删除指定聊天室的所有聊天记录
  155. func (p *chat) DelAllMsg(chatType enum.ChatType, chatID int64) bool {
  156. key := p.buildMsgKey(chatType, chatID)
  157. return p.Del(key)
  158. }
  159. func (p *chat) DelMsgByMsgID(chatType enum.ChatType, chatID int64, msgID int64) bool {
  160. key := p.buildMsgKey(chatType, chatID)
  161. return p.delMsgByMsgID(key, msgID)
  162. }
  163. // 根据消息ID删除指定聊天室中的单条消息
  164. func (p *chat) delMsgByMsgID(key string, msgID int64) bool {
  165. var (
  166. score = cstring.ToString(msgID)
  167. )
  168. // 删除 score = messageID 的消息
  169. err := p.ZRemRangeByScore(p.Context(), key, score, score).Err()
  170. if err != nil {
  171. clog.Warnf("[delMsgByMsgID] zremrangebyscore error. [key = %s, msgID = %d, error: %v]", key, score, err)
  172. return false
  173. }
  174. return true
  175. }
  176. // 构建私聊ID 单独记录
  177. func (p *chat) buildPrivateChatID(playerID int64, targetID int64) string {
  178. return fmt.Sprintf("%d-%d", playerID, targetID)
  179. }
  180. func (p *chat) GetPrivateMsgs(playerID, targetID int64, lastMsgID int64) *pb.ChatMsgs {
  181. chatID := p.buildPrivateChatID(playerID, targetID)
  182. key := p.buildMsgKey(enum.ChatType_Private, chatID)
  183. return p.getChatMsgs(key, lastMsgID)
  184. }
  185. func (p *chat) AddPrivateMsg(playerID, targetID int64, maxLimit int64, msg *pb.ChatMsg) {
  186. chatID := p.buildPrivateChatID(playerID, targetID)
  187. key := p.buildMsgKey(enum.ChatType_Private, chatID)
  188. p.addMsg(key, maxLimit, msg)
  189. }
  190. func (p *chat) DelPrivateMsg(playerID, targetID int64) bool {
  191. chatID := p.buildPrivateChatID(playerID, targetID)
  192. key := p.buildMsgKey(enum.ChatType_Private, chatID)
  193. ok := p.Del(key)
  194. if !ok {
  195. clog.Warnf("[DelPrivateMsg] del error. [key = %s]", key)
  196. }
  197. return ok
  198. }
  199. // 构建群组成员key
  200. func (p *chat) buildGroupMemberKey(chatID int64) string {
  201. return fmt.Sprintf(p.chatGroupMemberKey, chatID)
  202. }
  203. // 添加群组成员
  204. // 使用Redis的Set结构确保成员ID不重复
  205. func (p *chat) AddGroupMember(chatID int64, memberID int64) bool {
  206. key := p.buildGroupMemberKey(chatID)
  207. // 使用SAdd添加成员,如果成员已存在则不会重复添加
  208. err := p.SAdd(p.Context(), key, memberID).Err()
  209. if err != nil {
  210. clog.Warnf("[AddGroupMember] sadd error. [key = %s, memberID = %d, err = %v]", key, memberID, err)
  211. return false
  212. }
  213. return true
  214. }
  215. // 移除群组成员
  216. func (p *chat) DelGroupMember(chatID int64, memberIDList ...int64) bool {
  217. key := p.buildGroupMemberKey(chatID)
  218. if len(memberIDList) == 0 {
  219. return true
  220. }
  221. // 将 []int64 转为 []interface{} 并展开,避免把整个切片当成单个成员传入
  222. args := make([]any, 0, len(memberIDList))
  223. for _, id := range memberIDList {
  224. args = append(args, id)
  225. }
  226. // 使用 SRem 移除成员
  227. err := p.SRem(p.Context(), key, args...).Err()
  228. if err != nil {
  229. clog.Warnf("[DelGroupMember] srem error. [key = %s, memberIDList = %v, err = %v]", key, memberIDList, err)
  230. return false
  231. }
  232. return true
  233. }
  234. // 获取群组成员总数
  235. func (p *chat) GetGroupMemberCount(chatID int64) int64 {
  236. key := p.buildGroupMemberKey(chatID)
  237. count, _ := p.SCard(p.Context(), key).Result()
  238. return count
  239. }
  240. // 获取群组所有成员
  241. func (p *chat) GetGroupMembers(chatID int64) ([]int64, bool) {
  242. key := p.buildGroupMemberKey(chatID)
  243. // 获取所有成员
  244. members, err := p.SMembers(p.Context(), key).Result()
  245. if err != nil {
  246. clog.Warnf("[GetGroupMembers] smembers error. [key = %s, err = %v]", key, err)
  247. return nil, false
  248. }
  249. // 将字符串转换为int64
  250. memberIDList := make([]int64, 0, len(members))
  251. for _, m := range members {
  252. if id, ok := cstring.ToInt64(m); ok {
  253. memberIDList = append(memberIDList, id)
  254. }
  255. }
  256. return memberIDList, true
  257. }
  258. // 删除群组所有成员
  259. func (p *chat) DelAllGroupMembers(chatID int64) bool {
  260. key := p.buildGroupMemberKey(chatID)
  261. // 删除整个集合
  262. ok := p.Del(key)
  263. if !ok {
  264. clog.Warnf("[DelAllGroupMembers] del error. [key = %s]", key)
  265. }
  266. return ok
  267. }
  268. func (p *chat) buildLeagueMemberKey(chatID int64) string {
  269. return fmt.Sprintf(p.chatLeagueMemberKey, chatID)
  270. }
  271. // 获取联盟成员列表
  272. func (p *chat) GetLeagueMembers(chatID int64) []int64 {
  273. key := p.buildLeagueMemberKey(chatID)
  274. members, err := p.SMembers(p.Context(), key).Result()
  275. if err != nil {
  276. clog.Warnf("[GetLeagueMembers] smembers error. [key = %s, err = %v]", key, err)
  277. return nil
  278. }
  279. var memberIDList []int64
  280. for _, m := range members {
  281. if id, ok := cstring.ToInt64(m); ok {
  282. memberIDList = append(memberIDList, id)
  283. }
  284. }
  285. return memberIDList
  286. }
  287. // 添加联盟成员
  288. func (p *chat) AddLeagueMember(chatID int64, memberID int64) bool {
  289. key := p.buildLeagueMemberKey(chatID)
  290. err := p.SAdd(p.Context(), key, memberID).Err()
  291. if err != nil {
  292. clog.Warnf("[AddLeagueMember] sadd error. [key = %s, memberID = %d, err = %v]", key, memberID, err)
  293. return false
  294. }
  295. return true
  296. }
  297. // 删除联盟成员
  298. func (p *chat) DelLeagueMember(chatID int64, memberID int64) bool {
  299. key := p.buildLeagueMemberKey(chatID)
  300. err := p.SRem(p.Context(), key, memberID).Err()
  301. if err != nil {
  302. clog.Warnf("[DelLeagueMember] srem error. [key = %s, memberID = %d, err = %v]", key, memberID, err)
  303. return false
  304. }
  305. return true
  306. }