From e9d75d8e7864d40520d783e20f471be66e5db6f4 Mon Sep 17 00:00:00 2001 From: hawklin2017 <32898629+hawklin2017@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:13:04 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/rpc/msg/send.go | 41 +++++----- internal/rpc/msg/verify.go | 160 +++++++++++++++++++++++-------------- internal/rpc/user/user.go | 30 +++++++ 3 files changed, 149 insertions(+), 82 deletions(-) diff --git a/internal/rpc/msg/send.go b/internal/rpc/msg/send.go index a45edfe9e..98a9f7616 100644 --- a/internal/rpc/msg/send.go +++ b/internal/rpc/msg/send.go @@ -153,13 +153,12 @@ func (m *msgServer) sendMsgNotification(ctx context.Context, req *pbmsg.SendMsgR } func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, err error) { - if err := m.messageVerification(ctx, req); err != nil { - return nil, err - } - isSend := true isNotification := msgprocessor.IsNotificationByMsg(req.MsgData) + log.ZInfo(ctx, "sendMsgSingleChat", "isNotification", isNotification, "msgdata", req.MsgData) + + isSend := true if !isNotification { - log.ZInfo(ctx, "sendMsgSingleChat", "isNotification", isNotification, "msgdata", req.MsgData) + // 非通知类消息:执行发送权限校验 + 接收偏好校验(含 blacklist / MsgReceiveSetting / webhook / FriendVerify / globalOpt / convOpt) isSend, err = m.modifyMessageByUserMessageReceiveOpt( ctx, req.MsgData.RecvID, @@ -174,23 +173,21 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq if !isSend { prommetrics.SingleChatMsgProcessFailedCounter.Inc() return nil, nil - } else { - if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil { - return nil, err - } - - log.ZInfo(ctx, "sendMsgSingleChat", "isNotification", isNotification, "msgdata", req.MsgData) + } - if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil { - prommetrics.SingleChatMsgProcessFailedCounter.Inc() - return nil, err - } - m.webhookAfterSendSingleMsg(ctx, &m.config.WebhooksConfig.AfterSendSingleMsg, req) - prommetrics.SingleChatMsgProcessSuccessCounter.Inc() - return &pbmsg.SendMsgResp{ - ServerMsgID: req.MsgData.ServerMsgID, - ClientMsgID: req.MsgData.ClientMsgID, - SendTime: req.MsgData.SendTime, - }, nil + if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil { + return nil, err + } + log.ZInfo(ctx, "sendMsgSingleChat after modify", "isNotification", isNotification, "msgdata", req.MsgData) + if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil { + prommetrics.SingleChatMsgProcessFailedCounter.Inc() + return nil, err } + m.webhookAfterSendSingleMsg(ctx, &m.config.WebhooksConfig.AfterSendSingleMsg, req) + prommetrics.SingleChatMsgProcessSuccessCounter.Inc() + return &pbmsg.SendMsgResp{ + ServerMsgID: req.MsgData.ServerMsgID, + ClientMsgID: req.MsgData.ClientMsgID, + SendTime: req.MsgData.SendTime, + }, nil } diff --git a/internal/rpc/msg/verify.go b/internal/rpc/msg/verify.go index d6e1ea6a2..574cc79c7 100644 --- a/internal/rpc/msg/verify.go +++ b/internal/rpc/msg/verify.go @@ -16,18 +16,19 @@ package msg import ( "context" - "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" - "github.com/openimsdk/tools/utils/datautil" - "github.com/openimsdk/tools/utils/encrypt" - "github.com/openimsdk/tools/utils/timeutil" "math/rand" "strconv" "time" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/encrypt" + "github.com/openimsdk/tools/utils/timeutil" ) var ExcludeContentType = []int{constant.HasReadReceipt} @@ -52,61 +53,14 @@ type MessageRevoked struct { func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgReq) error { switch data.MsgData.SessionType { case constant.SingleChatType: - if datautil.Contain(data.MsgData.SendID, m.config.Share.IMAdminUserID...) { - return nil - } - if data.MsgData.ContentType <= constant.NotificationEnd && - data.MsgData.ContentType >= constant.NotificationBegin { - return nil - } - // 先做本地轻量级拦截(黑名单 + 消息接收权限),避免不必要的 webhook 触发 - black, err := m.FriendLocalCache.IsBlack(ctx, data.MsgData.SendID, data.MsgData.RecvID) - if err != nil { - return err - } - if black { - return servererrs.ErrBlockedByPeer.Wrap() - } - // 校验接收方消息接收权限(MsgReceiveSetting) - // 0=所有人可发送,1=仅好友可发送,2=所有人不可发送 - recvUserInfo, err := m.UserLocalCache.GetUserInfo(ctx, data.MsgData.RecvID) - if err != nil { - return err - } - switch recvUserInfo.MsgReceiveSetting { - case 2: // MsgReceiveSettingNobody - return servererrs.ErrMsgReceiveNotAllowed.Wrap() - case 1: // MsgReceiveSettingFriends - isFriend, err := m.FriendLocalCache.IsFriend(ctx, data.MsgData.RecvID, data.MsgData.SendID) - if err != nil { - return err - } - if !isFriend { - return servererrs.ErrMsgReceiveNotAllowed.Wrap() - } - // 已确认是好友,触发 webhook 后放行,不做 FriendVerify 冗余查询 - if err := m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, data); err != nil { - return err - } - return nil - } - // MsgReceiveSetting==0(所有人可发),触发 webhook,再按全局 FriendVerify 兜底 - if err := m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, data); err != nil { - return err - } - if m.config.RpcConfig.FriendVerify { - friend, err := m.FriendLocalCache.IsFriend(ctx, data.MsgData.SendID, data.MsgData.RecvID) - if err != nil { - return err - } - if !friend { - return servererrs.ErrNotPeersFriend.Wrap() - } - } + // 单聊发送权限校验已迁移至 modifyMessageByUserMessageReceiveOpt return nil case constant.ReadGroupChatType: groupInfo, err := m.GroupLocalCache.GetGroupInfo(ctx, data.MsgData.GroupID) if err != nil { + log.ZError(ctx, "messageVerification group: GetGroupInfo failed", err, + "groupID", data.MsgData.GroupID, "sendID", data.MsgData.SendID, + "contentType", data.MsgData.ContentType, "clientMsgID", data.MsgData.ClientMsgID) return err } if groupInfo.Status == constant.GroupStatusDismissed && @@ -126,6 +80,9 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe } memberIDs, err := m.GroupLocalCache.GetGroupMemberIDMap(ctx, data.MsgData.GroupID) if err != nil { + log.ZError(ctx, "messageVerification group: GetGroupMemberIDMap failed", err, + "groupID", data.MsgData.GroupID, "sendID", data.MsgData.SendID, + "contentType", data.MsgData.ContentType, "clientMsgID", data.MsgData.ClientMsgID) return err } if _, ok := memberIDs[data.MsgData.SendID]; !ok { @@ -137,6 +94,9 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe if errs.ErrRecordNotFound.Is(err) { return servererrs.ErrNotInGroupYet.WrapMsg(err.Error()) } + log.ZError(ctx, "messageVerification group: GetGroupMember failed", err, + "groupID", data.MsgData.GroupID, "sendID", data.MsgData.SendID, + "contentType", data.MsgData.ContentType, "clientMsgID", data.MsgData.ClientMsgID) return err } if groupMemberInfo.RoleLevel == constant.GroupOwner { @@ -211,21 +171,101 @@ func GetMsgID(sendID string) string { } func (m *msgServer) modifyMessageByUserMessageReceiveOpt(ctx context.Context, userID, conversationID string, sessionType int, pb *msg.SendMsgReq) (bool, error) { + // 第一优先级:接收方全局接收设置 + // NotReceiveMessage 直接丢弃,无需执行后续任何权限或偏好查询 opt, err := m.UserLocalCache.GetUserGlobalMsgRecvOpt(ctx, userID) if err != nil { return false, err } - switch opt { - case constant.ReceiveMessage: - case constant.NotReceiveMessage: + if opt == constant.NotReceiveMessage { return false, nil - case constant.ReceiveNotNotifyMessage: + } + if opt == constant.ReceiveNotNotifyMessage { if pb.MsgData.Options == nil { pb.MsgData.Options = make(map[string]bool, 10) } datautil.SetSwitchFromOptions(pb.MsgData.Options, constant.IsOfflinePush, false) - return true, nil + // 全局静音:仅关闭离线推送,仍需继续执行发送权限校验 + 会话级偏好校验 + } + + // 第二优先级:单聊发送权限校验(从 messageVerification 迁移) + // 仅对非通知类消息生效(调用方已通过 !isNotification 做过前置过滤) + if sessionType == constant.SingleChatType { + // 管理员跳过发送权限拦截,直接进入接收偏好校验 + if !datautil.Contain(pb.MsgData.SendID, m.config.Share.IMAdminUserID...) { + // 黑名单拦截 + black, err := m.FriendLocalCache.IsBlack(ctx, pb.MsgData.SendID, pb.MsgData.RecvID) + if err != nil { + log.ZError(ctx, "modifyMessageByUserMessageReceiveOpt: IsBlack failed", err, + "sendID", pb.MsgData.SendID, "recvID", pb.MsgData.RecvID, + "contentType", pb.MsgData.ContentType, "clientMsgID", pb.MsgData.ClientMsgID) + return false, err + } + if black { + return false, servererrs.ErrBlockedByPeer.Wrap() + } + + // 接收方消息接收权限(MsgReceiveSetting) + // 0=所有人可发送,1=仅好友可发送,2=所有人不可发送 + recvUserInfo, err := m.UserLocalCache.GetUserInfo(ctx, pb.MsgData.RecvID) + if err != nil { + log.ZError(ctx, "modifyMessageByUserMessageReceiveOpt: GetUserInfo(recv) failed", err, + "sendID", pb.MsgData.SendID, "recvID", pb.MsgData.RecvID, + "contentType", pb.MsgData.ContentType, "clientMsgID", pb.MsgData.ClientMsgID) + return false, err + } + + // skipFriendVerify: MsgReceiveSetting=1 已确认好友关系,无需再做 FriendVerify 重复查询 + skipFriendVerify := false + switch recvUserInfo.MsgReceiveSetting { + case 2: // MsgReceiveSettingNobody + return false, servererrs.ErrMsgReceiveNotAllowed.Wrap() + case 1: // MsgReceiveSettingFriends + isFriend, err := m.FriendLocalCache.IsFriend(ctx, pb.MsgData.RecvID, pb.MsgData.SendID) + if err != nil { + log.ZError(ctx, "modifyMessageByUserMessageReceiveOpt: IsFriend failed (MsgReceiveSetting)", err, + "sendID", pb.MsgData.SendID, "recvID", pb.MsgData.RecvID, + "contentType", pb.MsgData.ContentType, "clientMsgID", pb.MsgData.ClientMsgID) + return false, err + } + if !isFriend { + return false, servererrs.ErrMsgReceiveNotAllowed.Wrap() + } + // 已确认好友关系,触发 webhook 后跳过 FriendVerify,直接进入接收偏好校验 + if err := m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, pb); err != nil { + log.ZError(ctx, "modifyMessageByUserMessageReceiveOpt: webhookBeforeSendSingleMsg failed (friends-only)", err, + "sendID", pb.MsgData.SendID, "recvID", pb.MsgData.RecvID, + "contentType", pb.MsgData.ContentType, "clientMsgID", pb.MsgData.ClientMsgID) + return false, err + } + skipFriendVerify = true + } + + if !skipFriendVerify { + // MsgReceiveSetting==0(所有人可发),触发 webhook,再按全局 FriendVerify 兜底 + if err := m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, pb); err != nil { + log.ZError(ctx, "modifyMessageByUserMessageReceiveOpt: webhookBeforeSendSingleMsg failed", err, + "sendID", pb.MsgData.SendID, "recvID", pb.MsgData.RecvID, + "contentType", pb.MsgData.ContentType, "clientMsgID", pb.MsgData.ClientMsgID) + return false, err + } + if m.config.RpcConfig.FriendVerify { + friend, err := m.FriendLocalCache.IsFriend(ctx, pb.MsgData.SendID, pb.MsgData.RecvID) + if err != nil { + log.ZError(ctx, "modifyMessageByUserMessageReceiveOpt: IsFriend failed (FriendVerify)", err, + "sendID", pb.MsgData.SendID, "recvID", pb.MsgData.RecvID, + "contentType", pb.MsgData.ContentType, "clientMsgID", pb.MsgData.ClientMsgID) + return false, err + } + if !friend { + return false, servererrs.ErrNotPeersFriend.Wrap() + } + } + } + } } + + // 第三优先级:会话级接收偏好 singleOpt, err := m.ConversationLocalCache.GetSingleConversationRecvMsgOpt(ctx, userID, conversationID) if errs.ErrRecordNotFound.Is(err) { return true, nil diff --git a/internal/rpc/user/user.go b/internal/rpc/user/user.go index d84b27d3a..d45d4d995 100644 --- a/internal/rpc/user/user.go +++ b/internal/rpc/user/user.go @@ -48,6 +48,7 @@ import ( "github.com/openimsdk/tools/db/pagination" registry "github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" "github.com/openimsdk/tools/utils/datautil" "google.golang.org/grpc" ) @@ -137,10 +138,14 @@ func (s *userServer) GetDesignateUsers(ctx context.Context, req *pbuser.GetDesig resp = &pbuser.GetDesignateUsersResp{} users, err := s.db.Find(ctx, req.UserIDs) if err != nil { + log.ZError(ctx, "GetDesignateUsers: db.Find failed", err, + "opUserID", mcontext.GetOpUserID(ctx), "reqUserCount", len(req.UserIDs)) return nil, err } if blocked, err := s.globalBlackDB.FindBlocked(ctx, req.UserIDs); err != nil { + log.ZError(ctx, "GetDesignateUsers: globalBlackDB.FindBlocked failed", err, + "opUserID", mcontext.GetOpUserID(ctx), "reqUserCount", len(req.UserIDs)) return nil, err } else if len(blocked) > 0 { bannedIDs := make([]string, 0, len(blocked)) @@ -153,6 +158,8 @@ func (s *userServer) GetDesignateUsers(ctx context.Context, req *pbuser.GetDesig pbUsers := convert.UsersDB2Pb(users) viewerID := mcontext.GetOpUserID(ctx) if err := s.applyPhoneVisibility(ctx, viewerID, pbUsers, users); err != nil { + log.ZError(ctx, "GetDesignateUsers: applyPhoneVisibility failed", err, + "opUserID", viewerID, "userCount", len(users)) return nil, err } resp.UsersInfo = pbUsers @@ -182,6 +189,8 @@ func (s *userServer) applyPhoneVisibility(ctx context.Context, viewerID string, } isFriend, err := s.relationClient.IsFriend(ctx, viewerID, db.UserID) if err != nil { + log.ZError(ctx, "applyPhoneVisibility: IsFriend failed", err, + "viewerID", viewerID, "targetUserID", db.UserID) return err } if !isFriend { @@ -288,9 +297,13 @@ func (s *userServer) SetPhoneVisibility(ctx context.Context, req *pbuser.SetPhon return nil, errs.ErrArgs.WrapMsg("phoneVisibility must be 0, 1 or 2") } if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil { + log.ZWarn(ctx, "SetPhoneVisibility: access denied", err, + "opUserID", mcontext.GetOpUserID(ctx), "targetUserID", req.UserID) return nil, err } if _, err := s.db.FindWithError(ctx, []string{req.UserID}); err != nil { + log.ZError(ctx, "SetPhoneVisibility: user not found or db error", err, + "opUserID", mcontext.GetOpUserID(ctx), "targetUserID", req.UserID) return nil, err } m := map[string]any{ @@ -300,6 +313,9 @@ func (s *userServer) SetPhoneVisibility(ctx context.Context, req *pbuser.SetPhon m["phone"] = req.Phone } if err := s.db.UpdateByMap(ctx, req.UserID, m); err != nil { + log.ZError(ctx, "SetPhoneVisibility: UpdateByMap failed", err, + "opUserID", mcontext.GetOpUserID(ctx), "targetUserID", req.UserID, + "phoneVisibility", req.PhoneVisibility, "hasPhoneUpdate", req.Phone != "") return nil, err } s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserID) @@ -316,14 +332,21 @@ func (s *userServer) SetCallAcceptSetting(ctx context.Context, req *pbuser.SetCa return nil, errs.ErrArgs.WrapMsg("callAcceptSetting must be 0, 1 or 2") } if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil { + log.ZWarn(ctx, "SetCallAcceptSetting: access denied", err, + "opUserID", mcontext.GetOpUserID(ctx), "targetUserID", req.UserID) return nil, err } if _, err := s.db.FindWithError(ctx, []string{req.UserID}); err != nil { + log.ZError(ctx, "SetCallAcceptSetting: user not found or db error", err, + "opUserID", mcontext.GetOpUserID(ctx), "targetUserID", req.UserID) return nil, err } if err := s.db.UpdateByMap(ctx, req.UserID, map[string]any{ "call_accept_setting": req.CallAcceptSetting, }); err != nil { + log.ZError(ctx, "SetCallAcceptSetting: UpdateByMap failed", err, + "opUserID", mcontext.GetOpUserID(ctx), "targetUserID", req.UserID, + "callAcceptSetting", req.CallAcceptSetting) return nil, err } s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserID) @@ -340,14 +363,21 @@ func (s *userServer) SetMsgReceiveSetting(ctx context.Context, req *pbuser.SetMs return nil, errs.ErrArgs.WrapMsg("msgReceiveSetting must be 0, 1 or 2") } if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil { + log.ZWarn(ctx, "SetMsgReceiveSetting: access denied", err, + "opUserID", mcontext.GetOpUserID(ctx), "targetUserID", req.UserID) return nil, err } if _, err := s.db.FindWithError(ctx, []string{req.UserID}); err != nil { + log.ZError(ctx, "SetMsgReceiveSetting: user not found or db error", err, + "opUserID", mcontext.GetOpUserID(ctx), "targetUserID", req.UserID) return nil, err } if err := s.db.UpdateByMap(ctx, req.UserID, map[string]any{ "msg_receive_setting": req.MsgReceiveSetting, }); err != nil { + log.ZError(ctx, "SetMsgReceiveSetting: UpdateByMap failed", err, + "opUserID", mcontext.GetOpUserID(ctx), "targetUserID", req.UserID, + "msgReceiveSetting", req.MsgReceiveSetting) return nil, err } s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserID)