Files
open-im-server-deploy/internal/rpc/msg/sync_msg.go
kim.dev.6789 e50142a3b9 复制项目
2026-01-14 22:16:44 +08:00

659 lines
28 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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), &notificationElem); 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
}