// 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" "git.imall.cloud/openim/open-im-server-deploy/pkg/authverify" "git.imall.cloud/openim/protocol/constant" "git.imall.cloud/openim/protocol/conversation" "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/timeutil" ) func (m *msgServer) getMinSeqs(maxSeqs map[string]int64) map[string]int64 { minSeqs := make(map[string]int64) for k, v := range maxSeqs { minSeqs[k] = v + 1 } return minSeqs } func (m *msgServer) validateDeleteSyncOpt(opt *msg.DeleteSyncOpt) (isSyncSelf, isSyncOther bool) { if opt == nil { return } return opt.IsSyncSelf, opt.IsSyncOther } func (m *msgServer) ClearConversationsMsg(ctx context.Context, req *msg.ClearConversationsMsgReq) (*msg.ClearConversationsMsgResp, error) { if err := authverify.CheckAccess(ctx, req.UserID); err != nil { return nil, err } if err := m.clearConversation(ctx, req.ConversationIDs, req.UserID, req.DeleteSyncOpt); err != nil { return nil, err } return &msg.ClearConversationsMsgResp{}, nil } func (m *msgServer) UserClearAllMsg(ctx context.Context, req *msg.UserClearAllMsgReq) (*msg.UserClearAllMsgResp, 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 } if err := m.clearConversation(ctx, conversationIDs, req.UserID, req.DeleteSyncOpt); err != nil { return nil, err } return &msg.UserClearAllMsgResp{}, nil } func (m *msgServer) DeleteMsgs(ctx context.Context, req *msg.DeleteMsgsReq) (*msg.DeleteMsgsResp, error) { if err := authverify.CheckAccess(ctx, req.UserID); err != nil { return nil, err } // 获取要删除的消息信息,用于权限检查 _, _, msgs, err := m.MsgDatabase.GetMsgBySeqs(ctx, req.UserID, req.ConversationID, req.Seqs) if err != nil { return nil, err } if len(msgs) == 0 { return nil, errs.ErrRecordNotFound.WrapMsg("messages not found") } // 权限检查:如果不是管理员,需要检查删除权限 if !authverify.IsAdmin(ctx) { // 收集所有消息的发送者ID sendIDs := make([]string, 0, len(msgs)) for _, msg := range msgs { if msg != nil && msg.SendID != "" { sendIDs = append(sendIDs, msg.SendID) } } sendIDs = datautil.Distinct(sendIDs) // 检查第一条消息的会话类型(假设所有消息来自同一会话) sessionType := msgs[0].SessionType switch sessionType { case constant.SingleChatType: // 单聊:只能删除自己发送的消息 for _, msg := range msgs { if msg != nil && msg.SendID != req.UserID { return nil, errs.ErrNoPermission.WrapMsg("can only delete own messages in single chat") } } case constant.ReadGroupChatType: // 群聊:检查权限 groupID := msgs[0].GroupID if groupID == "" { return nil, errs.ErrArgs.WrapMsg("groupID is empty") } // 获取操作者和所有消息发送者的群成员信息 allUserIDs := append([]string{req.UserID}, sendIDs...) members, err := m.GroupLocalCache.GetGroupMemberInfoMap(ctx, groupID, datautil.Distinct(allUserIDs)) if err != nil { return nil, err } // 检查操作者的角色 opMember, ok := members[req.UserID] if !ok { return nil, errs.ErrNoPermission.WrapMsg("user not in group") } // 检查每条消息的删除权限 for _, msg := range msgs { if msg == nil || msg.SendID == "" { continue } // 如果是自己发送的消息,可以删除 if msg.SendID == req.UserID { continue } // 如果不是自己发送的消息,需要检查权限 switch opMember.RoleLevel { case constant.GroupOwner: // 群主可以删除任何人的消息 case constant.GroupAdmin: // 管理员只能删除普通成员的消息 sendMember, ok := members[msg.SendID] if !ok { return nil, errs.ErrNoPermission.WrapMsg("message sender not in group") } if sendMember.RoleLevel != constant.GroupOrdinaryUsers { return nil, errs.ErrNoPermission.WrapMsg("group admin can only delete messages from ordinary members") } default: // 普通成员只能删除自己的消息 return nil, errs.ErrNoPermission.WrapMsg("can only delete own messages") } } default: return nil, errs.ErrInternalServer.WrapMsg("sessionType not supported", "sessionType", sessionType) } } isSyncSelf, isSyncOther := m.validateDeleteSyncOpt(req.DeleteSyncOpt) if isSyncOther { if err := m.MsgDatabase.DeleteMsgsPhysicalBySeqs(ctx, req.ConversationID, req.Seqs); err != nil { return nil, err } conv, err := m.conversationClient.GetConversationsByConversationID(ctx, req.ConversationID) if err != nil { return nil, err } tips := &sdkws.DeleteMsgsTips{UserID: req.UserID, ConversationID: req.ConversationID, Seqs: req.Seqs} m.notificationSender.NotificationWithSessionType(ctx, req.UserID, m.conversationAndGetRecvID(conv, req.UserID), constant.DeleteMsgsNotification, conv.ConversationType, tips) } else { if err := m.MsgDatabase.DeleteUserMsgsBySeqs(ctx, req.UserID, req.ConversationID, req.Seqs); err != nil { return nil, err } if isSyncSelf { tips := &sdkws.DeleteMsgsTips{UserID: req.UserID, ConversationID: req.ConversationID, Seqs: req.Seqs} m.notificationSender.NotificationWithSessionType(ctx, req.UserID, req.UserID, constant.DeleteMsgsNotification, constant.SingleChatType, tips) } } return &msg.DeleteMsgsResp{}, nil } func (m *msgServer) DeleteMsgPhysicalBySeq(ctx context.Context, req *msg.DeleteMsgPhysicalBySeqReq) (*msg.DeleteMsgPhysicalBySeqResp, error) { if err := authverify.CheckAdmin(ctx); err != nil { return nil, err } err := m.MsgDatabase.DeleteMsgsPhysicalBySeqs(ctx, req.ConversationID, req.Seqs) if err != nil { return nil, err } return &msg.DeleteMsgPhysicalBySeqResp{}, nil } func (m *msgServer) DeleteMsgPhysical(ctx context.Context, req *msg.DeleteMsgPhysicalReq) (*msg.DeleteMsgPhysicalResp, error) { if err := authverify.CheckAdmin(ctx); err != nil { return nil, err } remainTime := timeutil.GetCurrentTimestampBySecond() - req.Timestamp if _, err := m.DestructMsgs(ctx, &msg.DestructMsgsReq{Timestamp: remainTime, Limit: 9999}); err != nil { return nil, err } return &msg.DeleteMsgPhysicalResp{}, nil } func (m *msgServer) clearConversation(ctx context.Context, conversationIDs []string, userID string, deleteSyncOpt *msg.DeleteSyncOpt) error { conversations, err := m.conversationClient.GetConversationsByConversationIDs(ctx, conversationIDs) if err != nil { return err } var existConversations []*conversation.Conversation var existConversationIDs []string for _, conversation := range conversations { existConversations = append(existConversations, conversation) existConversationIDs = append(existConversationIDs, conversation.ConversationID) } log.ZDebug(ctx, "ClearConversationsMsg", "existConversationIDs", existConversationIDs) maxSeqs, err := m.MsgDatabase.GetMaxSeqs(ctx, existConversationIDs) if err != nil { return err } isSyncSelf, isSyncOther := m.validateDeleteSyncOpt(deleteSyncOpt) if !isSyncOther { setSeqs := m.getMinSeqs(maxSeqs) if err := m.MsgDatabase.SetUserConversationsMinSeqs(ctx, userID, setSeqs); err != nil { return err } ownerUserIDs := []string{userID} for conversationID, seq := range setSeqs { if err := m.conversationClient.SetConversationMinSeq(ctx, conversationID, ownerUserIDs, seq); err != nil { return err } } // notification 2 self if isSyncSelf { tips := &sdkws.ClearConversationTips{UserID: userID, ConversationIDs: existConversationIDs} m.notificationSender.NotificationWithSessionType(ctx, userID, userID, constant.ClearConversationNotification, constant.SingleChatType, tips) } } else { if err := m.MsgDatabase.SetMinSeqs(ctx, m.getMinSeqs(maxSeqs)); err != nil { return err } for _, conversation := range existConversations { tips := &sdkws.ClearConversationTips{UserID: userID, ConversationIDs: []string{conversation.ConversationID}} m.notificationSender.NotificationWithSessionType(ctx, userID, m.conversationAndGetRecvID(conversation, userID), constant.ClearConversationNotification, conversation.ConversationType, tips) } } if err := m.MsgDatabase.UserSetHasReadSeqs(ctx, userID, maxSeqs); err != nil { return err } return nil }