mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-06-26 12:29:28 +08:00
用户设置
This commit is contained in:
parent
42064d31a7
commit
e9d75d8e78
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user