Files
open-im-server/internal/push/push_handler.go
T

424 lines
16 KiB
Go
Raw Normal View History

2023-06-30 09:45:02 +08:00
package push
import (
"context"
2024-04-19 22:23:08 +08:00
"encoding/json"
2024-11-18 16:25:46 +08:00
"math/rand"
"strconv"
"time"
"github.com/IBM/sarama"
2024-04-19 22:23:08 +08:00
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
2024-04-19 22:23:08 +08:00
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
"github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil"
"github.com/openimsdk/protocol/constant"
2024-07-16 10:46:21 +08:00
"github.com/openimsdk/protocol/msggateway"
2024-04-19 22:23:08 +08:00
pbpush "github.com/openimsdk/protocol/push"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/discovery"
2024-04-19 22:23:08 +08:00
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext"
2024-04-19 22:23:08 +08:00
"github.com/openimsdk/tools/mq/kafka"
"github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/jsonutil"
2024-04-19 22:23:08 +08:00
"github.com/openimsdk/tools/utils/timeutil"
"github.com/redis/go-redis/v9"
2024-03-05 10:51:55 +08:00
"google.golang.org/protobuf/proto"
2023-06-30 09:45:02 +08:00
)
type ConsumerHandler struct {
2024-04-19 22:23:08 +08:00
pushConsumerGroup *kafka.MConsumerGroup
offlinePusher offlinepush.OfflinePusher
onlinePusher OnlinePusher
pushDatabase controller.PushDatabase
2024-07-16 10:46:21 +08:00
onlineCache *rpccache.OnlineCache
2024-04-19 22:23:08 +08:00
groupLocalCache *rpccache.GroupLocalCache
conversationLocalCache *rpccache.ConversationLocalCache
msgRpcClient rpcclient.MessageRpcClient
conversationRpcClient rpcclient.ConversationRpcClient
groupRpcClient rpcclient.GroupRpcClient
webhookClient *webhook.Client
config *Config
2023-06-30 09:45:02 +08:00
}
func NewConsumerHandler(config *Config, database controller.PushDatabase, offlinePusher offlinepush.OfflinePusher, rdb redis.UniversalClient,
2024-04-19 22:23:08 +08:00
client discovery.SvcDiscoveryRegistry) (*ConsumerHandler, error) {
2023-06-30 09:45:02 +08:00
var consumerHandler ConsumerHandler
2024-02-02 10:11:13 +08:00
var err error
2024-04-19 22:23:08 +08:00
consumerHandler.pushConsumerGroup, err = kafka.NewMConsumerGroup(config.KafkaConfig.Build(), config.KafkaConfig.ToPushGroupID,
2024-05-14 18:21:36 +08:00
[]string{config.KafkaConfig.ToPushTopic}, true)
2024-02-02 10:11:13 +08:00
if err != nil {
return nil, err
}
2024-07-16 10:46:21 +08:00
userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID)
2024-09-12 10:38:17 +08:00
2024-04-19 22:23:08 +08:00
consumerHandler.offlinePusher = offlinePusher
consumerHandler.onlinePusher = NewOnlinePusher(client, config)
consumerHandler.groupRpcClient = rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group)
consumerHandler.groupLocalCache = rpccache.NewGroupLocalCache(consumerHandler.groupRpcClient, &config.LocalCacheConfig, rdb)
consumerHandler.msgRpcClient = rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg)
consumerHandler.conversationRpcClient = rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation)
2024-07-16 10:46:21 +08:00
consumerHandler.conversationLocalCache = rpccache.NewConversationLocalCache(consumerHandler.conversationRpcClient, &config.LocalCacheConfig, rdb)
2024-04-19 22:23:08 +08:00
consumerHandler.webhookClient = webhook.NewWebhookClient(config.WebhooksConfig.URL)
consumerHandler.config = config
consumerHandler.pushDatabase = database
2024-09-12 10:38:17 +08:00
consumerHandler.onlineCache, err = rpccache.NewOnlineCache(userRpcClient, consumerHandler.groupLocalCache, rdb, config.RpcConfig.FullUserCache, nil)
if err != nil {
return nil, err
}
2024-02-02 10:11:13 +08:00
return &consumerHandler, nil
2023-06-30 09:45:02 +08:00
}
func (c *ConsumerHandler) handleMs2PsChat(ctx context.Context, msg []byte) {
msgFromMQ := pbpush.PushMsgReq{}
2023-06-30 09:45:02 +08:00
if err := proto.Unmarshal(msg, &msgFromMQ); err != nil {
log.ZError(ctx, "push Unmarshal msg err", err, "msg", string(msg))
return
}
2023-06-30 09:45:02 +08:00
sec := msgFromMQ.MsgData.SendTime / 1000
2024-04-19 22:23:08 +08:00
nowSec := timeutil.GetCurrentTimestampBySecond()
2024-08-26 18:15:05 +08:00
2023-06-30 09:45:02 +08:00
if nowSec-sec > 10 {
prommetrics.MsgLoneTimePushCounter.Inc()
log.ZWarn(ctx, "its been a while since the message was sent", nil, "msg", msgFromMQ.String(), "sec", sec, "nowSec", nowSec, "nowSec-sec", nowSec-sec)
2023-06-30 09:45:02 +08:00
}
var err error
2023-06-30 09:45:02 +08:00
switch msgFromMQ.MsgData.SessionType {
2024-04-19 22:23:08 +08:00
case constant.ReadGroupChatType:
err = c.Push2Group(ctx, msgFromMQ.MsgData.GroupID, msgFromMQ.MsgData)
2023-06-30 09:45:02 +08:00
default:
var pushUserIDList []string
isSenderSync := datautil.GetSwitchFromOptions(msgFromMQ.MsgData.Options, constant.IsSenderSync)
if !isSenderSync || msgFromMQ.MsgData.SendID == msgFromMQ.MsgData.RecvID {
pushUserIDList = append(pushUserIDList, msgFromMQ.MsgData.RecvID)
} else {
pushUserIDList = append(pushUserIDList, msgFromMQ.MsgData.RecvID, msgFromMQ.MsgData.SendID)
}
err = c.Push2User(ctx, pushUserIDList, msgFromMQ.MsgData)
2023-06-30 09:45:02 +08:00
}
if err != nil {
log.ZWarn(ctx, "push failed", err, "msg", msgFromMQ.String())
2023-06-30 09:45:02 +08:00
}
}
2024-04-19 22:23:08 +08:00
func (*ConsumerHandler) Setup(sarama.ConsumerGroupSession) error { return nil }
func (*ConsumerHandler) Cleanup(sarama.ConsumerGroupSession) error { return nil }
func (c *ConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
2024-09-12 10:38:17 +08:00
c.onlineCache.Lock.Lock()
for c.onlineCache.CurrentPhase.Load() < rpccache.DoSubscribeOver {
c.onlineCache.Cond.Wait()
}
c.onlineCache.Lock.Unlock()
ctx := mcontext.SetOperationID(context.TODO(), strconv.FormatInt(time.Now().UnixNano()+int64(rand.Uint32()), 10))
log.ZInfo(ctx, "begin consume messages")
2023-06-30 09:45:02 +08:00
for msg := range claim.Messages() {
ctx := c.pushConsumerGroup.GetContextFromMsg(msg)
c.handleMs2PsChat(ctx, msg.Value)
sess.MarkMessage(msg, "")
}
return nil
}
2024-04-19 22:23:08 +08:00
// Push2User Suitable for two types of conversations, one is SingleChatType and the other is NotificationChatType.
2024-07-16 10:46:21 +08:00
func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *sdkws.MsgData) (err error) {
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "Get msg from msg_transfer And push msg", "userIDs", userIDs, "msg", msg.String())
defer func(duration time.Time) {
t := time.Since(duration)
log.ZInfo(ctx, "Get msg from msg_transfer And push msg", "msg", msg.String(), "time cost", t)
}(time.Now())
2024-04-19 22:23:08 +08:00
if err := c.webhookBeforeOnlinePush(ctx, &c.config.WebhooksConfig.BeforeOnlinePush, userIDs, msg); err != nil {
return err
}
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "webhookBeforeOnlinePush end")
2024-07-16 10:46:21 +08:00
wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, userIDs)
2024-04-19 22:23:08 +08:00
if err != nil {
return err
}
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "single and notification push result", "result", wsResults, "msg", msg, "push_to_userID", userIDs)
2024-04-19 22:23:08 +08:00
if !c.shouldPushOffline(ctx, msg) {
return nil
}
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "shouldPushOffline end")
2024-04-19 22:23:08 +08:00
for _, v := range wsResults {
//message sender do not need offline push
if msg.SendID == v.UserID {
continue
}
//receiver online push success
if v.OnlinePush {
return nil
}
}
2024-08-21 11:44:00 +08:00
offlinePushUserID := []string{msg.RecvID}
2024-04-19 22:23:08 +08:00
//receiver offline push
if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush,
2024-08-21 11:44:00 +08:00
offlinePushUserID, msg, nil); err != nil {
2024-04-19 22:23:08 +08:00
return err
}
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "webhookBeforeOfflinePush end")
2024-08-21 11:44:00 +08:00
err = c.offlinePushMsg(ctx, msg, offlinePushUserID)
2024-04-19 22:23:08 +08:00
if err != nil {
2024-08-21 11:44:00 +08:00
log.ZWarn(ctx, "offlinePushMsg failed", err, "offlinePushUserID", offlinePushUserID, "msg", msg)
return nil
2024-04-19 22:23:08 +08:00
}
return nil
}
func (c *ConsumerHandler) shouldPushOffline(_ context.Context, msg *sdkws.MsgData) bool {
isOfflinePush := datautil.GetSwitchFromOptions(msg.Options, constant.IsOfflinePush)
if !isOfflinePush {
return false
}
if msg.ContentType == constant.SignalingNotification {
return false
}
return true
}
2024-07-16 10:46:21 +08:00
func (c *ConsumerHandler) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) ([]*msggateway.SingleMsgToUserResults, error) {
if msg != nil && msg.Status == constant.MsgStatusSending {
msg.Status = constant.MsgStatusSendSuccess
}
2024-09-12 10:38:17 +08:00
onlineUserIDs, offlineUserIDs, err := c.onlineCache.GetUsersOnline(ctx, pushToUserIDs)
if err != nil {
return nil, err
2024-07-16 10:46:21 +08:00
}
2024-09-12 10:38:17 +08:00
2024-07-31 10:50:55 +08:00
log.ZDebug(ctx, "GetConnsAndOnlinePush online cache", "sendID", msg.SendID, "recvID", msg.RecvID, "groupID", msg.GroupID, "sessionType", msg.SessionType, "clientMsgID", msg.ClientMsgID, "serverMsgID", msg.ServerMsgID, "offlineUserIDs", offlineUserIDs, "onlineUserIDs", onlineUserIDs)
2024-07-16 10:46:21 +08:00
var result []*msggateway.SingleMsgToUserResults
if len(onlineUserIDs) > 0 {
var err error
2024-08-01 15:11:17 +08:00
result, err = c.onlinePusher.GetConnsAndOnlinePush(ctx, msg, onlineUserIDs)
2024-07-16 10:46:21 +08:00
if err != nil {
return nil, err
}
}
for _, userID := range offlineUserIDs {
result = append(result, &msggateway.SingleMsgToUserResults{
UserID: userID,
})
}
return result, nil
}
2024-04-24 12:11:24 +08:00
func (c *ConsumerHandler) Push2Group(ctx context.Context, groupID string, msg *sdkws.MsgData) (err error) {
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "Get group msg from msg_transfer and push msg", "msg", msg.String(), "groupID", groupID)
defer func(duration time.Time) {
t := time.Since(duration)
log.ZInfo(ctx, "Get group msg from msg_transfer and push msg end", "msg", msg.String(), "groupID", groupID, "time cost", t)
}(time.Now())
2024-04-19 22:23:08 +08:00
var pushToUserIDs []string
if err = c.webhookBeforeGroupOnlinePush(ctx, &c.config.WebhooksConfig.BeforeGroupOnlinePush, groupID, msg,
&pushToUserIDs); err != nil {
return err
}
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "webhookBeforeGroupOnlinePush end")
2024-04-19 22:23:08 +08:00
err = c.groupMessagesHandler(ctx, groupID, &pushToUserIDs, msg)
if err != nil {
return err
}
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "groupMessagesHandler end")
2024-04-19 22:23:08 +08:00
2024-07-16 10:46:21 +08:00
wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs)
2024-04-19 22:23:08 +08:00
if err != nil {
return err
}
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "group push result", "result", wsResults, "msg", msg)
2024-04-19 22:23:08 +08:00
if !c.shouldPushOffline(ctx, msg) {
return nil
}
needOfflinePushUserIDs := c.onlinePusher.GetOnlinePushFailedUserIDs(ctx, msg, wsResults, &pushToUserIDs)
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "GetOnlinePushFailedUserIDs end")
2024-04-19 22:23:08 +08:00
//filter some user, like don not disturb or don't need offline push etc.
needOfflinePushUserIDs, err = c.filterGroupMessageOfflinePush(ctx, groupID, msg, needOfflinePushUserIDs)
if err != nil {
return err
}
2024-09-12 10:38:17 +08:00
log.ZInfo(ctx, "filterGroupMessageOfflinePush end")
2024-04-19 22:23:08 +08:00
// Use offline push messaging
if len(needOfflinePushUserIDs) > 0 {
c.asyncOfflinePush(ctx, needOfflinePushUserIDs, msg)
}
2024-04-19 22:23:08 +08:00
return nil
}
2024-04-19 22:23:08 +08:00
func (c *ConsumerHandler) asyncOfflinePush(ctx context.Context, needOfflinePushUserIDs []string, msg *sdkws.MsgData) {
var offlinePushUserIDs []string
err := c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, needOfflinePushUserIDs, msg, &offlinePushUserIDs)
if err != nil {
log.ZWarn(ctx, "webhookBeforeOfflinePush failed", err, "msg", msg)
return
2024-04-19 22:23:08 +08:00
}
if len(offlinePushUserIDs) > 0 {
needOfflinePushUserIDs = offlinePushUserIDs
}
if err := c.pushDatabase.MsgToOfflinePushMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(msg.SendID, msg.RecvID), needOfflinePushUserIDs, msg); err != nil {
log.ZError(ctx, "Msg To OfflinePush MQ error", err, "needOfflinePushUserIDs",
needOfflinePushUserIDs, "msg", msg)
prommetrics.SingleChatMsgProcessFailedCounter.Inc()
return
}
2024-04-19 22:23:08 +08:00
}
2024-04-19 22:23:08 +08:00
func (c *ConsumerHandler) groupMessagesHandler(ctx context.Context, groupID string, pushToUserIDs *[]string, msg *sdkws.MsgData) (err error) {
if len(*pushToUserIDs) == 0 {
*pushToUserIDs, err = c.groupLocalCache.GetGroupMemberIDs(ctx, groupID)
if err != nil {
return err
}
switch msg.ContentType {
case constant.MemberQuitNotification:
var tips sdkws.MemberQuitTips
if unmarshalNotificationElem(msg.Content, &tips) != nil {
return err
}
if err = c.DeleteMemberAndSetConversationSeq(ctx, groupID, []string{tips.QuitUser.UserID}); err != nil {
log.ZError(ctx, "MemberQuitNotification DeleteMemberAndSetConversationSeq", err, "groupID", groupID, "userID", tips.QuitUser.UserID)
}
*pushToUserIDs = append(*pushToUserIDs, tips.QuitUser.UserID)
case constant.MemberKickedNotification:
var tips sdkws.MemberKickedTips
if unmarshalNotificationElem(msg.Content, &tips) != nil {
return err
}
kickedUsers := datautil.Slice(tips.KickedUserList, func(e *sdkws.GroupMemberFullInfo) string { return e.UserID })
if err = c.DeleteMemberAndSetConversationSeq(ctx, groupID, kickedUsers); err != nil {
log.ZError(ctx, "MemberKickedNotification DeleteMemberAndSetConversationSeq", err, "groupID", groupID, "userIDs", kickedUsers)
}
*pushToUserIDs = append(*pushToUserIDs, kickedUsers...)
case constant.GroupDismissedNotification:
2024-04-24 15:42:43 +08:00
if msgprocessor.IsNotification(msgprocessor.GetConversationIDByMsg(msg)) {
2024-04-19 22:23:08 +08:00
var tips sdkws.GroupDismissedTips
if unmarshalNotificationElem(msg.Content, &tips) != nil {
return err
}
2024-09-12 10:38:17 +08:00
log.ZDebug(ctx, "GroupDismissedNotificationInfo****", "groupID", groupID, "num", len(*pushToUserIDs), "list", pushToUserIDs)
2024-04-19 22:23:08 +08:00
if len(c.config.Share.IMAdminUserID) > 0 {
ctx = mcontext.WithOpUserIDContext(ctx, c.config.Share.IMAdminUserID[0])
}
defer func(groupID string) {
if err = c.groupRpcClient.DismissGroup(ctx, groupID); err != nil {
log.ZError(ctx, "DismissGroup Notification clear members", err, "groupID", groupID)
}
}(groupID)
}
}
}
return err
}
func (c *ConsumerHandler) offlinePushMsg(ctx context.Context, msg *sdkws.MsgData, offlinePushUserIDs []string) error {
title, content, opts, err := c.getOfflinePushInfos(msg)
if err != nil {
2024-11-18 16:25:46 +08:00
log.ZError(ctx, "getOfflinePushInfos failed", err, "msg", msg)
2024-04-19 22:23:08 +08:00
return err
}
err = c.offlinePusher.Push(ctx, offlinePushUserIDs, title, content, opts)
if err != nil {
prommetrics.MsgOfflinePushFailedCounter.Inc()
return err
}
return nil
}
func (c *ConsumerHandler) filterGroupMessageOfflinePush(ctx context.Context, groupID string, msg *sdkws.MsgData,
offlinePushUserIDs []string) (userIDs []string, err error) {
//todo local cache Obtain the difference set through local comparison.
needOfflinePushUserIDs, err := c.conversationRpcClient.GetConversationOfflinePushUserIDs(
ctx, conversationutil.GenGroupConversationID(groupID), offlinePushUserIDs)
if err != nil {
return nil, err
}
return needOfflinePushUserIDs, nil
}
func (c *ConsumerHandler) getOfflinePushInfos(msg *sdkws.MsgData) (title, content string, opts *options.Opts, err error) {
type AtTextElem struct {
Text string `json:"text,omitempty"`
AtUserList []string `json:"atUserList,omitempty"`
IsAtSelf bool `json:"isAtSelf"`
}
2024-11-18 16:25:46 +08:00
opts = &options.Opts{Signal: &options.Signal{ClientMsgID: msg.ClientMsgID}}
2024-04-19 22:23:08 +08:00
if msg.OfflinePushInfo != nil {
opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount
opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound
opts.Ex = msg.OfflinePushInfo.Ex
}
if msg.OfflinePushInfo != nil {
title = msg.OfflinePushInfo.Title
content = msg.OfflinePushInfo.Desc
}
if title == "" {
switch msg.ContentType {
case constant.Text:
fallthrough
case constant.Picture:
fallthrough
case constant.Voice:
fallthrough
case constant.Video:
fallthrough
case constant.File:
title = constant.ContentType2PushContent[int64(msg.ContentType)]
case constant.AtText:
ac := AtTextElem{}
_ = jsonutil.JsonStringToStruct(string(msg.Content), &ac)
case constant.SignalingNotification:
title = constant.ContentType2PushContent[constant.SignalMsg]
default:
title = constant.ContentType2PushContent[constant.Common]
}
}
if content == "" {
content = title
}
return
}
2024-09-12 10:38:17 +08:00
2024-04-19 22:23:08 +08:00
func (c *ConsumerHandler) DeleteMemberAndSetConversationSeq(ctx context.Context, groupID string, userIDs []string) error {
conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, groupID)
maxSeq, err := c.msgRpcClient.GetConversationMaxSeq(ctx, conversationID)
if err != nil {
return err
}
return c.conversationRpcClient.SetConversationMaxSeq(ctx, userIDs, conversationID, maxSeq)
}
2024-09-12 10:38:17 +08:00
2024-04-19 22:23:08 +08:00
func unmarshalNotificationElem(bytes []byte, t any) error {
var notification sdkws.NotificationElem
if err := json.Unmarshal(bytes, &notification); err != nil {
return err
}
return json.Unmarshal([]byte(notification.Detail), t)
}