复制项目
This commit is contained in:
658
internal/rpc/msg/sync_msg.go
Normal file
658
internal/rpc/msg/sync_msg.go
Normal file
@@ -0,0 +1,658 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user