复制项目

This commit is contained in:
kim.dev.6789
2026-01-14 22:16:44 +08:00
parent e2577b8cee
commit e50142a3b9
691 changed files with 97009 additions and 1 deletions

View 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), &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
}