// Copyright © 2023 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package msg import ( "context" "encoding/json" "time" "git.imall.cloud/openim/open-im-server-deploy/pkg/apistruct" "git.imall.cloud/openim/open-im-server-deploy/pkg/authverify" "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model" "git.imall.cloud/openim/open-im-server-deploy/pkg/msgprocessor" "git.imall.cloud/openim/open-im-server-deploy/pkg/util/conversationutil" "git.imall.cloud/openim/protocol/constant" "git.imall.cloud/openim/protocol/msg" "git.imall.cloud/openim/protocol/sdkws" "github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/log" "github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/jsonutil" "github.com/openimsdk/tools/utils/timeutil" ) func (m *msgServer) PullMessageBySeqs(ctx context.Context, req *sdkws.PullMessageBySeqsReq) (*sdkws.PullMessageBySeqsResp, error) { if err := authverify.CheckAccess(ctx, req.UserID); err != nil { return nil, err } // 设置请求超时,防止大量数据拉取导致pod异常 // 对于大量大群用户,需要更长的超时时间(60秒) queryTimeout := 60 * time.Second queryCtx, cancel := context.WithTimeout(ctx, queryTimeout) defer cancel() // 参数验证:限制 SeqRanges 数量和每个范围的大小,防止内存溢出 const maxSeqRanges = 100 const maxSeqRangeSize = 10000 // 限制响应总大小,防止pod内存溢出(估算:每条消息平均1KB,最多50MB) const maxTotalMessages = 50000 if len(req.SeqRanges) > maxSeqRanges { log.ZWarn(ctx, "SeqRanges count exceeds limit", nil, "count", len(req.SeqRanges), "limit", maxSeqRanges) return nil, errs.ErrArgs.WrapMsg("too many seq ranges", "count", len(req.SeqRanges), "limit", maxSeqRanges) } for _, seq := range req.SeqRanges { // 验证每个 seq range 的合理性 if seq.Begin < 0 || seq.End < 0 { log.ZWarn(ctx, "invalid seq range: negative values", nil, "begin", seq.Begin, "end", seq.End) continue } if seq.End < seq.Begin { log.ZWarn(ctx, "invalid seq range: end < begin", nil, "begin", seq.Begin, "end", seq.End) continue } seqRangeSize := seq.End - seq.Begin + 1 if seqRangeSize > maxSeqRangeSize { log.ZWarn(ctx, "seq range size exceeds limit, will be limited", nil, "conversationID", seq.ConversationID, "begin", seq.Begin, "end", seq.End, "size", seqRangeSize, "limit", maxSeqRangeSize) } } resp := &sdkws.PullMessageBySeqsResp{} resp.Msgs = make(map[string]*sdkws.PullMsgs) resp.NotificationMsgs = make(map[string]*sdkws.PullMsgs) var totalMessages int for _, seq := range req.SeqRanges { // 检查总消息数,防止内存溢出 if totalMessages >= maxTotalMessages { log.ZWarn(ctx, "total messages count exceeds limit, stopping", nil, "totalMessages", totalMessages, "limit", maxTotalMessages) break } if !msgprocessor.IsNotification(seq.ConversationID) { conversation, err := m.ConversationLocalCache.GetConversation(queryCtx, req.UserID, seq.ConversationID) if err != nil { log.ZError(ctx, "GetConversation error", err, "conversationID", seq.ConversationID) continue } minSeq, maxSeq, msgs, err := m.MsgDatabase.GetMsgBySeqsRange(queryCtx, req.UserID, seq.ConversationID, seq.Begin, seq.End, seq.Num, conversation.MaxSeq) if err != nil { // 如果是超时错误,记录更详细的日志 if queryCtx.Err() == context.DeadlineExceeded { log.ZWarn(ctx, "GetMsgBySeqsRange timeout", err, "conversationID", seq.ConversationID, "seq", seq, "timeout", queryTimeout) return nil, errs.ErrInternalServer.WrapMsg("message pull timeout, data too large or query too slow") } log.ZWarn(ctx, "GetMsgBySeqsRange error", err, "conversationID", seq.ConversationID, "seq", seq) continue } totalMessages += len(msgs) var isEnd bool switch req.Order { case sdkws.PullOrder_PullOrderAsc: isEnd = maxSeq <= seq.End case sdkws.PullOrder_PullOrderDesc: isEnd = seq.Begin <= minSeq } if len(msgs) == 0 { log.ZWarn(ctx, "not have msgs", nil, "conversationID", seq.ConversationID, "seq", seq) continue } // 过滤禁言通知消息(只保留群主、管理员和被禁言成员本人可以看到的) msgs = m.filterMuteNotificationMsgs(ctx, req.UserID, seq.ConversationID, msgs) // 填充红包消息的领取信息 msgs = m.enrichRedPacketMessages(ctx, req.UserID, msgs) resp.Msgs[seq.ConversationID] = &sdkws.PullMsgs{Msgs: msgs, IsEnd: isEnd} } else { // 限制通知消息的查询范围,防止内存溢出 const maxNotificationSeqRange = 5000 var seqs []int64 seqRange := seq.End - seq.Begin + 1 if seqRange > maxNotificationSeqRange { log.ZWarn(ctx, "notification seq range too large, limiting", nil, "conversationID", seq.ConversationID, "begin", seq.Begin, "end", seq.End, "range", seqRange, "limit", maxNotificationSeqRange) // 只取最后 maxNotificationSeqRange 条 for i := seq.End - maxNotificationSeqRange + 1; i <= seq.End; i++ { seqs = append(seqs, i) } } else { for i := seq.Begin; i <= seq.End; i++ { seqs = append(seqs, i) } } minSeq, maxSeq, notificationMsgs, err := m.MsgDatabase.GetMsgBySeqs(queryCtx, req.UserID, seq.ConversationID, seqs) if err != nil { // 如果是超时错误,记录更详细的日志 if queryCtx.Err() == context.DeadlineExceeded { log.ZWarn(ctx, "GetMsgBySeqs timeout", err, "conversationID", seq.ConversationID, "seq", seq, "timeout", queryTimeout) return nil, errs.ErrInternalServer.WrapMsg("notification message pull timeout, data too large or query too slow") } log.ZWarn(ctx, "GetMsgBySeqs error", err, "conversationID", seq.ConversationID, "seq", seq) continue } totalMessages += len(notificationMsgs) var isEnd bool switch req.Order { case sdkws.PullOrder_PullOrderAsc: isEnd = maxSeq <= seq.End case sdkws.PullOrder_PullOrderDesc: isEnd = seq.Begin <= minSeq } if len(notificationMsgs) == 0 { log.ZWarn(ctx, "not have notificationMsgs", nil, "conversationID", seq.ConversationID, "seq", seq) continue } // 过滤禁言通知消息(只保留群主、管理员和被禁言成员本人可以看到的) notificationMsgs = m.filterMuteNotificationMsgs(ctx, req.UserID, seq.ConversationID, notificationMsgs) // 填充红包消息的领取信息 notificationMsgs = m.enrichRedPacketMessages(ctx, req.UserID, notificationMsgs) resp.NotificationMsgs[seq.ConversationID] = &sdkws.PullMsgs{Msgs: notificationMsgs, IsEnd: isEnd} } } return resp, nil } func (m *msgServer) GetSeqMessage(ctx context.Context, req *msg.GetSeqMessageReq) (*msg.GetSeqMessageResp, error) { resp := &msg.GetSeqMessageResp{ Msgs: make(map[string]*sdkws.PullMsgs), NotificationMsgs: make(map[string]*sdkws.PullMsgs), } for _, conv := range req.Conversations { isEnd, endSeq, msgs, err := m.MsgDatabase.GetMessagesBySeqWithBounds(ctx, req.UserID, conv.ConversationID, conv.Seqs, req.GetOrder()) if err != nil { return nil, err } var pullMsgs *sdkws.PullMsgs if ok := false; conversationutil.IsNotificationConversationID(conv.ConversationID) { pullMsgs, ok = resp.NotificationMsgs[conv.ConversationID] if !ok { pullMsgs = &sdkws.PullMsgs{} resp.NotificationMsgs[conv.ConversationID] = pullMsgs } } else { pullMsgs, ok = resp.Msgs[conv.ConversationID] if !ok { pullMsgs = &sdkws.PullMsgs{} resp.Msgs[conv.ConversationID] = pullMsgs } } // 过滤禁言通知消息(只保留群主、管理员和被禁言成员本人可以看到的) filteredMsgs := m.filterMuteNotificationMsgs(ctx, req.UserID, conv.ConversationID, msgs) // 填充红包消息的领取信息 filteredMsgs = m.enrichRedPacketMessages(ctx, req.UserID, filteredMsgs) pullMsgs.Msgs = append(pullMsgs.Msgs, filteredMsgs...) pullMsgs.IsEnd = isEnd pullMsgs.EndSeq = endSeq } return resp, nil } // filterMuteNotificationMsgs 过滤禁言、取消禁言、踢出群聊、退出群聊、进入群聊、群成员信息设置和角色变更通知消息,只保留群主、管理员和相关成员本人可以看到的消息 func (m *msgServer) filterMuteNotificationMsgs(ctx context.Context, userID, conversationID string, msgs []*sdkws.MsgData) []*sdkws.MsgData { // 如果不是群聊会话,直接返回 if !conversationutil.IsGroupConversationID(conversationID) { return msgs } // 提取群ID groupID := conversationutil.GetGroupIDFromConversationID(conversationID) if groupID == "" { log.ZWarn(ctx, "filterMuteNotificationMsgs: invalid group conversationID", nil, "conversationID", conversationID) return msgs } var filteredMsgs []*sdkws.MsgData var needCheckPermission bool // 先检查是否有需要过滤的消息 for _, msg := range msgs { if msg.ContentType == constant.GroupMemberMutedNotification || msg.ContentType == constant.GroupMemberCancelMutedNotification || msg.ContentType == constant.MemberKickedNotification || msg.ContentType == constant.MemberQuitNotification || msg.ContentType == constant.MemberInvitedNotification || msg.ContentType == constant.MemberEnterNotification || msg.ContentType == constant.GroupMemberInfoSetNotification || msg.ContentType == constant.GroupMemberSetToAdminNotification || msg.ContentType == constant.GroupMemberSetToOrdinaryUserNotification { needCheckPermission = true break } } // 如果没有需要过滤的消息,直接返回 if !needCheckPermission { return msgs } // 对于被踢出的用户,可能无法获取成员信息,需要特殊处理 // 先收集所有被踢出的用户ID,以便后续判断 var allKickedUserIDs []string if needCheckPermission { for _, msg := range msgs { if msg.ContentType == constant.MemberKickedNotification { var tips sdkws.MemberKickedTips if err := unmarshalNotificationContent(string(msg.Content), &tips); err == nil { kickedUserIDs := datautil.Slice(tips.KickedUserList, func(e *sdkws.GroupMemberFullInfo) string { return e.UserID }) allKickedUserIDs = append(allKickedUserIDs, kickedUserIDs...) } } } allKickedUserIDs = datautil.Distinct(allKickedUserIDs) } // 检查用户是否是被踢出的用户(即使已经被踢出,也应该能看到自己被踢出的通知) isKickedUserInMsgs := datautil.Contain(userID, allKickedUserIDs...) // 获取当前用户在群中的角色(如果用户已经被踢出,这里会返回错误) // 添加超时保护,防止大群查询阻塞(3秒超时) memberCtx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() member, err := m.GroupLocalCache.GetGroupMember(memberCtx, groupID, userID) isOwnerOrAdmin := false if err != nil { if memberCtx.Err() == context.DeadlineExceeded { log.ZWarn(ctx, "filterMuteNotificationMsgs: GetGroupMember timeout", err, "groupID", groupID, "userID", userID) } else { log.ZDebug(ctx, "filterMuteNotificationMsgs: GetGroupMember failed (user may be kicked)", err, "groupID", groupID, "userID", userID, "isKickedUserInMsgs", isKickedUserInMsgs) } // 如果获取失败(可能已经被踢出或超时),仍然需要检查是否是相关成员本人 // 继续处理,但 isOwnerOrAdmin 保持为 false // 如果是被踢出的用户,仍然可以查看自己被踢出的通知 } else { isOwnerOrAdmin = member.RoleLevel == constant.GroupOwner || member.RoleLevel == constant.GroupAdmin } // 过滤消息 for _, msg := range msgs { if msg.ContentType == constant.GroupMemberMutedNotification { var tips sdkws.GroupMemberMutedTips if err := unmarshalNotificationContent(string(msg.Content), &tips); err != nil { log.ZWarn(ctx, "filterMuteNotificationMsgs: unmarshal GroupMemberMutedTips failed", err) filteredMsgs = append(filteredMsgs, msg) continue } mutedUserID := tips.MutedUser.UserID if isOwnerOrAdmin || userID == mutedUserID { filteredMsgs = append(filteredMsgs, msg) log.ZDebug(ctx, "filterMuteNotificationMsgs: GroupMemberMutedNotification allowed", "userID", userID, "mutedUserID", mutedUserID, "isOwnerOrAdmin", isOwnerOrAdmin) } else { log.ZDebug(ctx, "filterMuteNotificationMsgs: GroupMemberMutedNotification filtered", "userID", userID, "mutedUserID", mutedUserID, "isOwnerOrAdmin", isOwnerOrAdmin) } } else if msg.ContentType == constant.GroupMemberCancelMutedNotification { var tips sdkws.GroupMemberCancelMutedTips if err := unmarshalNotificationContent(string(msg.Content), &tips); err != nil { log.ZWarn(ctx, "filterMuteNotificationMsgs: unmarshal GroupMemberCancelMutedTips failed", err) filteredMsgs = append(filteredMsgs, msg) continue } cancelMutedUserID := tips.MutedUser.UserID if isOwnerOrAdmin || userID == cancelMutedUserID { filteredMsgs = append(filteredMsgs, msg) log.ZDebug(ctx, "filterMuteNotificationMsgs: GroupMemberCancelMutedNotification allowed", "userID", userID, "cancelMutedUserID", cancelMutedUserID, "isOwnerOrAdmin", isOwnerOrAdmin) } else { log.ZDebug(ctx, "filterMuteNotificationMsgs: GroupMemberCancelMutedNotification filtered", "userID", userID, "cancelMutedUserID", cancelMutedUserID, "isOwnerOrAdmin", isOwnerOrAdmin) } } else if msg.ContentType == constant.MemberQuitNotification { var tips sdkws.MemberQuitTips if err := unmarshalNotificationContent(string(msg.Content), &tips); err != nil { log.ZWarn(ctx, "filterMuteNotificationMsgs: unmarshal MemberQuitTips failed", err) filteredMsgs = append(filteredMsgs, msg) continue } quitUserID := tips.QuitUser.UserID // 退出群聊通知只允许群主和管理员看到,退出者本人不通知 if isOwnerOrAdmin { filteredMsgs = append(filteredMsgs, msg) log.ZDebug(ctx, "filterMuteNotificationMsgs: MemberQuitNotification allowed", "userID", userID, "quitUserID", quitUserID, "isOwnerOrAdmin", isOwnerOrAdmin) } else { log.ZDebug(ctx, "filterMuteNotificationMsgs: MemberQuitNotification filtered", "userID", userID, "quitUserID", quitUserID, "isOwnerOrAdmin", isOwnerOrAdmin) } } else if msg.ContentType == constant.MemberInvitedNotification { var tips sdkws.MemberInvitedTips if err := unmarshalNotificationContent(string(msg.Content), &tips); err != nil { log.ZWarn(ctx, "filterMuteNotificationMsgs: unmarshal MemberInvitedTips failed", err) filteredMsgs = append(filteredMsgs, msg) continue } // 获取被邀请的用户ID列表 invitedUserIDs := datautil.Slice(tips.InvitedUserList, func(e *sdkws.GroupMemberFullInfo) string { return e.UserID }) isInvitedUser := datautil.Contain(userID, invitedUserIDs...) // 邀请入群通知:允许群主、管理员和被邀请的用户本人看到 if isOwnerOrAdmin || isInvitedUser { filteredMsgs = append(filteredMsgs, msg) log.ZDebug(ctx, "filterMuteNotificationMsgs: MemberInvitedNotification allowed", "userID", userID, "invitedUserIDs", invitedUserIDs, "isOwnerOrAdmin", isOwnerOrAdmin, "isInvitedUser", isInvitedUser) } else { log.ZDebug(ctx, "filterMuteNotificationMsgs: MemberInvitedNotification filtered", "userID", userID, "invitedUserIDs", invitedUserIDs, "isOwnerOrAdmin", isOwnerOrAdmin, "isInvitedUser", isInvitedUser) } } else if msg.ContentType == constant.MemberEnterNotification { var tips sdkws.MemberEnterTips if err := unmarshalNotificationContent(string(msg.Content), &tips); err != nil { log.ZWarn(ctx, "filterMuteNotificationMsgs: unmarshal MemberEnterTips failed", err) filteredMsgs = append(filteredMsgs, msg) continue } entrantUserID := tips.EntrantUser.UserID // 进入群聊通知只允许群主和管理员看到 if isOwnerOrAdmin { filteredMsgs = append(filteredMsgs, msg) log.ZDebug(ctx, "filterMuteNotificationMsgs: MemberEnterNotification allowed", "userID", userID, "entrantUserID", entrantUserID, "isOwnerOrAdmin", isOwnerOrAdmin) } else { log.ZDebug(ctx, "filterMuteNotificationMsgs: MemberEnterNotification filtered", "userID", userID, "entrantUserID", entrantUserID, "isOwnerOrAdmin", isOwnerOrAdmin) } } else if msg.ContentType == constant.MemberKickedNotification { var tips sdkws.MemberKickedTips if err := unmarshalNotificationContent(string(msg.Content), &tips); err != nil { log.ZWarn(ctx, "filterMuteNotificationMsgs: unmarshal MemberKickedTips failed", err) filteredMsgs = append(filteredMsgs, msg) continue } // 获取被踢出的用户ID列表 kickedUserIDs := datautil.Slice(tips.KickedUserList, func(e *sdkws.GroupMemberFullInfo) string { return e.UserID }) isKickedUser := datautil.Contain(userID, kickedUserIDs...) // 被踢出群聊通知:允许群主、管理员和被踢出的用户本人看到 if isOwnerOrAdmin || isKickedUser { filteredMsgs = append(filteredMsgs, msg) log.ZDebug(ctx, "filterMuteNotificationMsgs: MemberKickedNotification allowed", "userID", userID, "kickedUserIDs", kickedUserIDs, "isOwnerOrAdmin", isOwnerOrAdmin, "isKickedUser", isKickedUser) } else { log.ZDebug(ctx, "filterMuteNotificationMsgs: MemberKickedNotification filtered", "userID", userID, "kickedUserIDs", kickedUserIDs, "isOwnerOrAdmin", isOwnerOrAdmin, "isKickedUser", isKickedUser) } } else if msg.ContentType == constant.GroupMemberInfoSetNotification { var tips sdkws.GroupMemberInfoSetTips if err := unmarshalNotificationContent(string(msg.Content), &tips); err != nil { log.ZWarn(ctx, "filterMuteNotificationMsgs: unmarshal GroupMemberInfoSetTips failed", err) filteredMsgs = append(filteredMsgs, msg) continue } changedUserID := tips.ChangedUser.UserID // 群成员信息设置通知(如背景音)只允许群主和管理员看到 if isOwnerOrAdmin { filteredMsgs = append(filteredMsgs, msg) log.ZDebug(ctx, "filterMuteNotificationMsgs: GroupMemberInfoSetNotification allowed", "userID", userID, "changedUserID", changedUserID, "isOwnerOrAdmin", isOwnerOrAdmin) } else { log.ZDebug(ctx, "filterMuteNotificationMsgs: GroupMemberInfoSetNotification filtered", "userID", userID, "changedUserID", changedUserID, "isOwnerOrAdmin", isOwnerOrAdmin) } } else if msg.ContentType == constant.GroupMemberSetToAdminNotification || msg.ContentType == constant.GroupMemberSetToOrdinaryUserNotification { var tips sdkws.GroupMemberInfoSetTips if err := unmarshalNotificationContent(string(msg.Content), &tips); err != nil { log.ZWarn(ctx, "filterMuteNotificationMsgs: unmarshal GroupMemberInfoSetTips failed", err) filteredMsgs = append(filteredMsgs, msg) continue } changedUserID := tips.ChangedUser.UserID // 设置为管理员/普通用户通知:允许群主、管理员和本人看到 if isOwnerOrAdmin || userID == changedUserID { filteredMsgs = append(filteredMsgs, msg) log.ZDebug(ctx, "filterMuteNotificationMsgs: GroupMemberSetToAdmin/OrdinaryUserNotification allowed", "userID", userID, "changedUserID", changedUserID, "isOwnerOrAdmin", isOwnerOrAdmin, "contentType", msg.ContentType) } else { log.ZDebug(ctx, "filterMuteNotificationMsgs: GroupMemberSetToAdmin/OrdinaryUserNotification filtered", "userID", userID, "changedUserID", changedUserID, "isOwnerOrAdmin", isOwnerOrAdmin, "contentType", msg.ContentType) } } else { // 其他消息直接通过 filteredMsgs = append(filteredMsgs, msg) } } return filteredMsgs } // unmarshalNotificationContent 解析通知消息内容 func unmarshalNotificationContent(content string, v interface{}) error { var notificationElem sdkws.NotificationElem if err := json.Unmarshal([]byte(content), ¬ificationElem); err != nil { return err } return jsonutil.JsonUnmarshal([]byte(notificationElem.Detail), v) } // enrichRedPacketMessages 填充红包消息的领取信息和状态 func (m *msgServer) enrichRedPacketMessages(ctx context.Context, userID string, msgs []*sdkws.MsgData) []*sdkws.MsgData { if m.redPacketReceiveDB == nil || m.redPacketDB == nil { return msgs } for _, msg := range msgs { // 只处理自定义消息类型 if msg.ContentType != constant.Custom { continue } // 解析自定义消息内容 var customElem apistruct.CustomElem if err := json.Unmarshal(msg.Content, &customElem); err != nil { continue } // 检查是否为红包消息(通过description字段判断二级类型) if customElem.Description != "redpacket" { continue } // 解析红包消息内容(从data字段中解析) var redPacketElem apistruct.RedPacketElem if err := json.Unmarshal([]byte(customElem.Data), &redPacketElem); err != nil { log.ZWarn(ctx, "enrichRedPacketMessages: failed to unmarshal red packet data", err, "redPacketID", redPacketElem.RedPacketID) continue } // 查询红包记录 redPacket, err := m.redPacketDB.Take(ctx, redPacketElem.RedPacketID) if err != nil { log.ZWarn(ctx, "enrichRedPacketMessages: failed to get red packet", err, "redPacketID", redPacketElem.RedPacketID) // 如果查询失败,保持原有状态,不填充信息 continue } // 填充红包状态信息 redPacketElem.Status = redPacket.Status // 判断是否已过期(检查过期时间和状态) now := time.Now() isExpired := redPacket.Status == model.RedPacketStatusExpired || (redPacket.ExpireTime.Before(now) && redPacket.Status == model.RedPacketStatusActive) redPacketElem.IsExpired = isExpired // 判断是否已领完 isFinished := redPacket.Status == model.RedPacketStatusFinished || redPacket.RemainCount <= 0 redPacketElem.IsFinished = isFinished // 如果已过期,更新状态 if isExpired && redPacket.Status == model.RedPacketStatusActive { redPacket.Status = model.RedPacketStatusExpired redPacketElem.Status = model.RedPacketStatusExpired } // 查询用户是否已领取 receive, err := m.redPacketReceiveDB.FindByUserAndRedPacketID(ctx, userID, redPacketElem.RedPacketID) if err != nil { // 如果查询失败或未找到记录,说明未领取 redPacketElem.IsReceived = false redPacketElem.ReceiveInfo = nil } else { // 已领取,填充领取信息(包括金额) redPacketElem.IsReceived = true redPacketElem.ReceiveInfo = &apistruct.RedPacketReceiveInfo{ Amount: receive.Amount, ReceiveTime: receive.ReceiveTime.UnixMilli(), IsLucky: false, // 已去掉手气最佳功能,始终返回 false } } // 更新自定义消息的data字段(包含领取信息和状态) redPacketData := jsonutil.StructToJsonString(redPacketElem) customElem.Data = redPacketData // 重新序列化并更新消息内容 newContent, err := json.Marshal(customElem) if err != nil { log.ZWarn(ctx, "enrichRedPacketMessages: failed to marshal custom elem", err, "redPacketID", redPacketElem.RedPacketID) continue } msg.Content = newContent } return msgs } func (m *msgServer) GetMaxSeq(ctx context.Context, req *sdkws.GetMaxSeqReq) (*sdkws.GetMaxSeqResp, error) { if err := authverify.CheckAccess(ctx, req.UserID); err != nil { return nil, err } conversationIDs, err := m.ConversationLocalCache.GetConversationIDs(ctx, req.UserID) if err != nil { return nil, err } for _, conversationID := range conversationIDs { conversationIDs = append(conversationIDs, conversationutil.GetNotificationConversationIDByConversationID(conversationID)) } conversationIDs = append(conversationIDs, conversationutil.GetSelfNotificationConversationID(req.UserID)) log.ZDebug(ctx, "GetMaxSeq", "conversationIDs", conversationIDs) maxSeqs, err := m.MsgDatabase.GetMaxSeqs(ctx, conversationIDs) if err != nil { log.ZWarn(ctx, "GetMaxSeqs error", err, "conversationIDs", conversationIDs, "maxSeqs", maxSeqs) return nil, err } // avoid pulling messages from sessions with a large number of max seq values of 0 for conversationID, seq := range maxSeqs { if seq == 0 { delete(maxSeqs, conversationID) } } resp := new(sdkws.GetMaxSeqResp) resp.MaxSeqs = maxSeqs return resp, nil } func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq) (resp *msg.SearchMessageResp, err error) { // var chatLogs []*sdkws.MsgData var chatLogs []*msg.SearchedMsgData var total int64 resp = &msg.SearchMessageResp{} if total, chatLogs, err = m.MsgDatabase.SearchMessage(ctx, req); err != nil { return nil, err } var ( sendIDs []string recvIDs []string groupIDs []string sendNameMap = make(map[string]string) sendFaceMap = make(map[string]string) recvMap = make(map[string]string) groupMap = make(map[string]*sdkws.GroupInfo) seenSendIDs = make(map[string]struct{}) seenRecvIDs = make(map[string]struct{}) seenGroupIDs = make(map[string]struct{}) ) for _, chatLog := range chatLogs { if chatLog.MsgData.SenderNickname == "" || chatLog.MsgData.SenderFaceURL == "" { if _, ok := seenSendIDs[chatLog.MsgData.SendID]; !ok { seenSendIDs[chatLog.MsgData.SendID] = struct{}{} sendIDs = append(sendIDs, chatLog.MsgData.SendID) } } switch chatLog.MsgData.SessionType { case constant.SingleChatType, constant.NotificationChatType: if _, ok := seenRecvIDs[chatLog.MsgData.RecvID]; !ok { seenRecvIDs[chatLog.MsgData.RecvID] = struct{}{} recvIDs = append(recvIDs, chatLog.MsgData.RecvID) } case constant.WriteGroupChatType, constant.ReadGroupChatType: if _, ok := seenGroupIDs[chatLog.MsgData.GroupID]; !ok { seenGroupIDs[chatLog.MsgData.GroupID] = struct{}{} groupIDs = append(groupIDs, chatLog.MsgData.GroupID) } } } // Retrieve sender and receiver information if len(sendIDs) != 0 { sendInfos, err := m.UserLocalCache.GetUsersInfo(ctx, sendIDs) if err != nil { return nil, err } for _, sendInfo := range sendInfos { sendNameMap[sendInfo.UserID] = sendInfo.Nickname sendFaceMap[sendInfo.UserID] = sendInfo.FaceURL } } if len(recvIDs) != 0 { recvInfos, err := m.UserLocalCache.GetUsersInfo(ctx, recvIDs) if err != nil { return nil, err } for _, recvInfo := range recvInfos { recvMap[recvInfo.UserID] = recvInfo.Nickname } } // Retrieve group information including member counts if len(groupIDs) != 0 { groupInfos, err := m.GroupLocalCache.GetGroupInfos(ctx, groupIDs) if err != nil { return nil, err } for _, groupInfo := range groupInfos { groupMap[groupInfo.GroupID] = groupInfo // Get actual member count memberIDs, err := m.GroupLocalCache.GetGroupMemberIDs(ctx, groupInfo.GroupID) if err == nil { groupInfo.MemberCount = uint32(len(memberIDs)) // Update the member count with actual number } } } // Construct response with updated information for _, chatLog := range chatLogs { pbchatLog := &msg.ChatLog{} datautil.CopyStructFields(pbchatLog, chatLog.MsgData) pbchatLog.SendTime = chatLog.MsgData.SendTime pbchatLog.CreateTime = chatLog.MsgData.CreateTime if chatLog.MsgData.SenderNickname == "" { pbchatLog.SenderNickname = sendNameMap[chatLog.MsgData.SendID] } if chatLog.MsgData.SenderFaceURL == "" { pbchatLog.SenderFaceURL = sendFaceMap[chatLog.MsgData.SendID] } switch chatLog.MsgData.SessionType { case constant.SingleChatType, constant.NotificationChatType: pbchatLog.RecvNickname = recvMap[chatLog.MsgData.RecvID] case constant.ReadGroupChatType: groupInfo := groupMap[chatLog.MsgData.GroupID] pbchatLog.GroupMemberCount = groupInfo.MemberCount // Reflects actual member count pbchatLog.RecvID = groupInfo.GroupID pbchatLog.GroupName = groupInfo.GroupName pbchatLog.GroupOwner = groupInfo.OwnerUserID pbchatLog.GroupType = groupInfo.GroupType } searchChatLog := &msg.SearchChatLog{ChatLog: pbchatLog, IsRevoked: chatLog.IsRevoked} resp.ChatLogs = append(resp.ChatLogs, searchChatLog) } resp.ChatLogsNum = int32(total) return resp, nil } func (m *msgServer) GetServerTime(ctx context.Context, _ *msg.GetServerTimeReq) (*msg.GetServerTimeResp, error) { return &msg.GetServerTimeResp{ServerTime: timeutil.GetCurrentTimestampByMill()}, nil } func (m *msgServer) GetLastMessage(ctx context.Context, req *msg.GetLastMessageReq) (*msg.GetLastMessageResp, error) { msgs, err := m.MsgDatabase.GetLastMessage(ctx, req.ConversationIDs, req.UserID) if err != nil { return nil, err } return &msg.GetLastMessageResp{Msgs: msgs}, nil }