diff --git a/internal/push/fcm/push.go b/internal/push/fcm/push.go index cedccbebe..55f655bd4 100644 --- a/internal/push/fcm/push.go +++ b/internal/push/fcm/push.go @@ -5,7 +5,6 @@ import ( "Open_IM/pkg/common/config" "Open_IM/pkg/common/db" "Open_IM/pkg/common/log" - "Open_IM/pkg/tools/splitter" "context" "path/filepath" "strconv" @@ -47,53 +46,68 @@ func newFcmClient() *Fcm { return &Fcm{FcmMsgCli: fcmMsgClient} } -func (f *Fcm) Push(accounts []string, alert, detailContent, operationID string, opts push.PushOpts) (string, error) { +func (f *Fcm) Push(accounts []string, title, detailContent, operationID string, opts push.PushOpts) (string, error) { // accounts->registrationToken - Tokens := make([]string, 0) + allTokens := make(map[string][]string, 0) for _, account := range accounts { - IosfcmToken, IosErr := db.DB.GetFcmToken(account, 1) - AndroidfcmToken, AndroidErr := db.DB.GetFcmToken(account, 2) - - if IosErr == nil { - Tokens = append(Tokens, IosfcmToken) - } - if AndroidErr == nil { - Tokens = append(Tokens, AndroidfcmToken) + var personTokens []string + for _, v := range push.PushTerminal { + Token, err := db.DB.GetFcmToken(account, v) + if err == nil { + personTokens = append(personTokens, Token) + } } + allTokens[account] = personTokens } Success := 0 Fail := 0 - result := splitter.NewSplitter(SinglePushCountLimit, Tokens).GetSplitResult() - Msg := new(messaging.MulticastMessage) - Msg.Notification = &messaging.Notification{} - Msg.Notification.Body = detailContent - Msg.Notification.Title = alert - Msg.APNS = &messaging.APNSConfig{Payload: &messaging.APNSPayload{Aps: &messaging.Aps{}}} - if opts.IOSBadgeCount { - i := 1 - Msg.APNS.Payload.Aps.Badge = &i - } - if opts.IOSPushSound != "" { - Msg.APNS.Payload.Aps.Sound = opts.IOSPushSound - } + notification := &messaging.Notification{} + notification.Body = detailContent + notification.Title = title + var messages []*messaging.Message ctx := context.Background() - for _, v := range result { - Msg.Tokens = v.Item - //SendMulticast sends the given multicast message to all the FCM registration tokens specified. - //The tokens array in MulticastMessage may contain up to 500 tokens. - //SendMulticast uses the `SendAll()` function to send the given message to all the target recipients. - //The responses list obtained from the return value corresponds to the order of the input tokens. - //An error from SendMulticast indicates a total failure -- i.e. - //the message could not be sent to any of the recipients. - //Partial failures are indicated by a `BatchResponse` return value. - response, err := f.FcmMsgCli.SendMulticast(ctx, Msg) - if err != nil { - Fail = Fail + len(v.Item) - log.Info(operationID, "some token push err", err.Error(), len(v.Item)) - continue + apns := &messaging.APNSConfig{Payload: &messaging.APNSPayload{Aps: &messaging.Aps{Sound: opts.IOSPushSound}}} + for uid, personTokens := range allTokens { + messageCount := len(messages) + if messageCount >= SinglePushCountLimit { + response, err := f.FcmMsgCli.SendAll(ctx, messages) + if err != nil { + Fail = Fail + messageCount + log.Info(operationID, "some token push err", err.Error(), messageCount) + } else { + Success = Success + response.SuccessCount + Fail = Fail + response.FailureCount + } + messages = messages[0:0] + } + if opts.IOSBadgeCount { + unreadCountSum, err := db.DB.IncrUserBadgeUnreadCountSum(uid) + if err == nil { + apns.Payload.Aps.Badge = &unreadCountSum + } else { + continue + } + } + for _, token := range personTokens { + temp := &messaging.Message{ + Token: token, + Notification: notification, + APNS: apns, + } + messages = append(messages, temp) + } + + } + messageCount := len(messages) + if messageCount > 0 { + response, err := f.FcmMsgCli.SendAll(ctx, messages) + if err != nil { + Fail = Fail + messageCount + log.Info(operationID, "some token push err", err.Error(), messageCount) + } else { + Success = Success + response.SuccessCount + Fail = Fail + response.FailureCount } - Success = Success + response.SuccessCount - Fail = Fail + response.FailureCount } return strconv.Itoa(Success) + " Success," + strconv.Itoa(Fail) + " Fail", nil } diff --git a/internal/push/getui/push.go b/internal/push/getui/push.go index 359d847f0..eb3a72280 100644 --- a/internal/push/getui/push.go +++ b/internal/push/getui/push.go @@ -112,7 +112,7 @@ func newGetuiClient() *Getui { return &Getui{} } -func (g *Getui) Push(userIDList []string, alert, detailContent, operationID string, opts push.PushOpts) (resp string, err error) { +func (g *Getui) Push(userIDList []string, title, detailContent, operationID string, opts push.PushOpts) (resp string, err error) { token, err := db.DB.GetGetuiToken() log.NewDebug(operationID, utils.GetSelfFuncName(), "token:", token) if err != nil { @@ -132,18 +132,18 @@ func (g *Getui) Push(userIDList []string, alert, detailContent, operationID stri }{Alias: []string{userIDList[0]}}, } pushReq.PushMessage.Notification = Notification{ - Title: alert, + Title: title, Body: detailContent, ClickType: "startapp", } pushReq.PushChannel.Ios.Aps.Sound = "default" pushReq.PushChannel.Ios.Aps.Alert = Alert{ - Title: alert, - Body: alert, + Title: title, + Body: title, } pushReq.PushChannel.Android.Ups.Notification = Notification{ - Title: alert, - Body: alert, + Title: title, + Body: title, ClickType: "startapp", } pushReq.PushChannel.Android.Ups.Options = Options{ diff --git a/internal/push/jpush/push.go b/internal/push/jpush/push.go index 060b11d78..f53058b7f 100644 --- a/internal/push/jpush/push.go +++ b/internal/push/jpush/push.go @@ -33,7 +33,7 @@ func (j *JPush) SetAlias(cid, alias string) (resp string, err error) { return resp, nil } -func (j *JPush) Push(accounts []string, alert, detailContent, operationID string, opts push.PushOpts) (string, error) { +func (j *JPush) Push(accounts []string, title, detailContent, operationID string, opts push.PushOpts) (string, error) { var pf requestBody.Platform pf.SetAll() @@ -47,7 +47,7 @@ func (j *JPush) Push(accounts []string, alert, detailContent, operationID string } no.IOSEnableMutableContent() no.SetExtras(extras) - no.SetAlert(alert) + no.SetAlert(title) var me requestBody.Message me.SetMsgContent(detailContent) var o requestBody.Options diff --git a/internal/push/logic/callback.go b/internal/push/logic/callback.go index 1bb51fcc1..68860757a 100644 --- a/internal/push/logic/callback.go +++ b/internal/push/logic/callback.go @@ -12,7 +12,7 @@ import ( http2 "net/http" ) -func callbackOfflinePush(operationID string, userIDList []string, msg *commonPb.MsgData, offlinePushUserIDList *[]string, offlineInfo *commonPb.OfflinePushInfo) cbApi.CommonCallbackResp { +func callbackOfflinePush(operationID string, userIDList []string, msg *commonPb.MsgData, offlinePushUserIDList *[]string) cbApi.CommonCallbackResp { callbackResp := cbApi.CommonCallbackResp{OperationID: operationID} if !config.Config.Callback.CallbackOfflinePush.Enable { return callbackResp @@ -52,7 +52,7 @@ func callbackOfflinePush(operationID string, userIDList []string, msg *commonPb. *offlinePushUserIDList = resp.UserIDList } if resp.OfflinePushInfo != nil { - *offlineInfo = *resp.OfflinePushInfo + msg.OfflinePushInfo = resp.OfflinePushInfo } } log.NewDebug(operationID, utils.GetSelfFuncName(), offlinePushUserIDList, resp.UserIDList) diff --git a/internal/push/logic/init.go b/internal/push/logic/init.go index bfe5e1187..22d03603f 100644 --- a/internal/push/logic/init.go +++ b/internal/push/logic/init.go @@ -21,7 +21,6 @@ import ( var ( rpcServer RPCServer pushCh PushConsumerHandler - pushTerminal []int32 producer *kafka.Producer offlinePusher pusher.OfflinePusher successCount uint64 @@ -30,7 +29,6 @@ var ( func Init(rpcPort int) { rpcServer.Init(rpcPort) pushCh.Init() - pushTerminal = []int32{constant.IOSPlatformID, constant.AndroidPlatformID} } func init() { diff --git a/internal/push/logic/push_to_client.go b/internal/push/logic/push_to_client.go index 13ea1bb68..2b8bbcfd8 100644 --- a/internal/push/logic/push_to_client.go +++ b/internal/push/logic/push_to_client.go @@ -17,10 +17,8 @@ import ( pbPush "Open_IM/pkg/proto/push" pbRelay "Open_IM/pkg/proto/relay" pbRtc "Open_IM/pkg/proto/rtc" - commonPb "Open_IM/pkg/proto/sdk_ws" "Open_IM/pkg/utils" "context" - "encoding/json" "strings" "github.com/golang/protobuf/proto" @@ -85,48 +83,8 @@ func MsgToUser(pushMsg *pbPush.PushMsgReq) { return } } - customContent := OpenIMContent{ - SessionType: int(pushMsg.MsgData.SessionType), - From: pushMsg.MsgData.SendID, - To: pushMsg.MsgData.RecvID, - Seq: pushMsg.MsgData.Seq, - } - bCustomContent, _ := json.Marshal(customContent) - jsonCustomContent := string(bCustomContent) - var content string - if pushMsg.MsgData.OfflinePushInfo != nil { - content = pushMsg.MsgData.OfflinePushInfo.Title - jsonCustomContent = pushMsg.MsgData.OfflinePushInfo.Desc - } - if content == "" { - switch pushMsg.MsgData.ContentType { - case constant.Text: - content = constant.ContentType2PushContent[constant.Text] - case constant.Picture: - content = constant.ContentType2PushContent[constant.Picture] - case constant.Voice: - content = constant.ContentType2PushContent[constant.Voice] - case constant.Video: - content = constant.ContentType2PushContent[constant.Video] - case constant.File: - content = constant.ContentType2PushContent[constant.File] - case constant.AtText: - a := AtContent{} - _ = utils.JsonStringToStruct(string(pushMsg.MsgData.Content), &a) - if utils.IsContain(pushMsg.PushToUserID, a.AtUserList) { - content = constant.ContentType2PushContent[constant.AtText] + constant.ContentType2PushContent[constant.Common] - } else { - content = constant.ContentType2PushContent[constant.GroupMsg] - } - case constant.SignalingNotification: - content = constant.ContentType2PushContent[constant.SignalMsg] - default: - content = constant.ContentType2PushContent[constant.Common] - - } - } - var offlineInfo commonPb.OfflinePushInfo - callbackResp := callbackOfflinePush(pushMsg.OperationID, UIDList, pushMsg.MsgData, &[]string{}, &offlineInfo) + var title, detailContent string + callbackResp := callbackOfflinePush(pushMsg.OperationID, UIDList, pushMsg.MsgData, &[]string{}) log.NewDebug(pushMsg.OperationID, utils.GetSelfFuncName(), "offline callback Resp") if callbackResp.ErrCode != 0 { log.NewError(pushMsg.OperationID, utils.GetSelfFuncName(), "callbackOfflinePush result: ", callbackResp) @@ -135,12 +93,11 @@ func MsgToUser(pushMsg *pbPush.PushMsgReq) { log.NewDebug(pushMsg.OperationID, utils.GetSelfFuncName(), "offlinePush stop") return } - if offlineInfo.Title != "" { - content = offlineInfo.Title - } - if offlineInfo.Desc != "" { - jsonCustomContent = offlineInfo.Desc + if pushMsg.MsgData.OfflinePushInfo != nil { + title = pushMsg.MsgData.OfflinePushInfo.Title + detailContent = pushMsg.MsgData.OfflinePushInfo.Desc } + if offlinePusher == nil { return } @@ -148,8 +105,36 @@ func MsgToUser(pushMsg *pbPush.PushMsgReq) { if err != nil { log.NewError(pushMsg.OperationID, utils.GetSelfFuncName(), "GetOfflinePushOpts failed", pushMsg, err.Error()) } - log.NewInfo(pushMsg.OperationID, utils.GetSelfFuncName(), UIDList, content, jsonCustomContent, "opts:", opts) - pushResult, err := offlinePusher.Push(UIDList, content, jsonCustomContent, pushMsg.OperationID, opts) + log.NewInfo(pushMsg.OperationID, utils.GetSelfFuncName(), UIDList, title, detailContent, "opts:", opts) + if title == "" { + switch pushMsg.MsgData.ContentType { + case constant.Text: + fallthrough + case constant.Picture: + fallthrough + case constant.Voice: + fallthrough + case constant.Video: + fallthrough + case constant.File: + title = constant.ContentType2PushContent[int64(pushMsg.MsgData.ContentType)] + case constant.AtText: + a := AtContent{} + _ = utils.JsonStringToStruct(string(pushMsg.MsgData.Content), &a) + if utils.IsContain(pushMsg.PushToUserID, a.AtUserList) { + title = constant.ContentType2PushContent[constant.AtText] + constant.ContentType2PushContent[constant.Common] + } else { + title = constant.ContentType2PushContent[constant.GroupMsg] + } + case constant.SignalingNotification: + title = constant.ContentType2PushContent[constant.SignalMsg] + default: + title = constant.ContentType2PushContent[constant.Common] + + } + detailContent = title + } + pushResult, err := offlinePusher.Push(UIDList, title, detailContent, pushMsg.OperationID, opts) if err != nil { log.NewError(pushMsg.OperationID, "offline push error", pushMsg.String(), err.Error()) } else { @@ -229,51 +214,11 @@ func MsgToSuperGroupUser(pushMsg *pbPush.PushMsgReq) { } onlineFailedUserIDList := utils.DifferenceString(onlineSuccessUserIDList, pushToUserIDList) //Use offline push messaging - customContent := OpenIMContent{ - SessionType: int(pushMsg.MsgData.SessionType), - From: pushMsg.MsgData.SendID, - To: pushMsg.MsgData.RecvID, - Seq: pushMsg.MsgData.Seq, - } - bCustomContent, _ := json.Marshal(customContent) - jsonCustomContent := string(bCustomContent) - var content string - if pushMsg.MsgData.OfflinePushInfo != nil { - content = pushMsg.MsgData.OfflinePushInfo.Title - jsonCustomContent = pushMsg.MsgData.OfflinePushInfo.Desc - - } else { - switch pushMsg.MsgData.ContentType { - case constant.Text: - content = constant.ContentType2PushContent[constant.Text] - case constant.Picture: - content = constant.ContentType2PushContent[constant.Picture] - case constant.Voice: - content = constant.ContentType2PushContent[constant.Voice] - case constant.Video: - content = constant.ContentType2PushContent[constant.Video] - case constant.File: - content = constant.ContentType2PushContent[constant.File] - case constant.AtText: - a := AtContent{} - _ = utils.JsonStringToStruct(string(pushMsg.MsgData.Content), &a) - if utils.IsContain(pushMsg.PushToUserID, a.AtUserList) { - content = constant.ContentType2PushContent[constant.AtText] + constant.ContentType2PushContent[constant.Common] - } else { - content = constant.ContentType2PushContent[constant.GroupMsg] - } - case constant.SignalingNotification: - content = constant.ContentType2PushContent[constant.SignalMsg] - default: - content = constant.ContentType2PushContent[constant.Common] - - } - } + var title, detailContent string if len(onlineFailedUserIDList) > 0 { var offlinePushUserIDList []string var needOfflinePushUserIDList []string - var offlineInfo commonPb.OfflinePushInfo - callbackResp := callbackOfflinePush(pushMsg.OperationID, onlineFailedUserIDList, pushMsg.MsgData, &offlinePushUserIDList, &offlineInfo) + callbackResp := callbackOfflinePush(pushMsg.OperationID, onlineFailedUserIDList, pushMsg.MsgData, &offlinePushUserIDList) log.NewDebug(pushMsg.OperationID, utils.GetSelfFuncName(), "offline callback Resp") if callbackResp.ErrCode != 0 { log.NewError(pushMsg.OperationID, utils.GetSelfFuncName(), "callbackOfflinePush result: ", callbackResp) @@ -282,17 +227,16 @@ func MsgToSuperGroupUser(pushMsg *pbPush.PushMsgReq) { log.NewDebug(pushMsg.OperationID, utils.GetSelfFuncName(), "offlinePush stop") return } + if pushMsg.MsgData.OfflinePushInfo != nil { + title = pushMsg.MsgData.OfflinePushInfo.Title + detailContent = pushMsg.MsgData.OfflinePushInfo.Desc + } if len(offlinePushUserIDList) > 0 { needOfflinePushUserIDList = offlinePushUserIDList } else { needOfflinePushUserIDList = onlineFailedUserIDList } - if offlineInfo.Title != "" { - content = offlineInfo.Title - } - if offlineInfo.Desc != "" { - jsonCustomContent = offlineInfo.Desc - } + if offlinePusher == nil { return } @@ -300,8 +244,36 @@ func MsgToSuperGroupUser(pushMsg *pbPush.PushMsgReq) { if err != nil { log.NewError(pushMsg.OperationID, utils.GetSelfFuncName(), "GetOfflinePushOpts failed", pushMsg, err.Error()) } - log.NewInfo(pushMsg.OperationID, utils.GetSelfFuncName(), onlineFailedUserIDList, content, jsonCustomContent, "opts:", opts) - pushResult, err := offlinePusher.Push(needOfflinePushUserIDList, content, jsonCustomContent, pushMsg.OperationID, opts) + log.NewInfo(pushMsg.OperationID, utils.GetSelfFuncName(), onlineFailedUserIDList, title, detailContent, "opts:", opts) + if title == "" { + switch pushMsg.MsgData.ContentType { + case constant.Text: + fallthrough + case constant.Picture: + fallthrough + case constant.Voice: + fallthrough + case constant.Video: + fallthrough + case constant.File: + title = constant.ContentType2PushContent[int64(pushMsg.MsgData.ContentType)] + case constant.AtText: + a := AtContent{} + _ = utils.JsonStringToStruct(string(pushMsg.MsgData.Content), &a) + if utils.IsContain(pushMsg.PushToUserID, a.AtUserList) { + title = constant.ContentType2PushContent[constant.AtText] + constant.ContentType2PushContent[constant.Common] + } else { + title = constant.ContentType2PushContent[constant.GroupMsg] + } + case constant.SignalingNotification: + title = constant.ContentType2PushContent[constant.SignalMsg] + default: + title = constant.ContentType2PushContent[constant.Common] + + } + detailContent = title + } + pushResult, err := offlinePusher.Push(needOfflinePushUserIDList, title, detailContent, pushMsg.OperationID, opts) if err != nil { log.NewError(pushMsg.OperationID, "offline push error", pushMsg.String(), err.Error()) } else { diff --git a/internal/push/push_interface.go b/internal/push/push_interface.go index 1e177e74e..7b60b10e1 100644 --- a/internal/push/push_interface.go +++ b/internal/push/push_interface.go @@ -1,7 +1,11 @@ package push +import "Open_IM/pkg/common/constant" + +var PushTerminal = []int{constant.IOSPlatformID, constant.AndroidPlatformID} + type OfflinePusher interface { - Push(userIDList []string, alert, detailContent, operationID string, opts PushOpts) (resp string, err error) + Push(userIDList []string, title, detailContent, operationID string, opts PushOpts) (resp string, err error) } type PushOpts struct { diff --git a/internal/rpc/conversation/conversaion.go b/internal/rpc/conversation/conversaion.go index 87e201aa1..5d543ae25 100644 --- a/internal/rpc/conversation/conversaion.go +++ b/internal/rpc/conversation/conversaion.go @@ -74,7 +74,15 @@ func (rpc *rpcConversation) ModifyConversationField(c context.Context, req *pbCo err = imdb.UpdateColumnsConversations(haveUserID, req.Conversation.ConversationID, map[string]interface{}{"attached_info": conversation.AttachedInfo}) case constant.FieldUnread: isSyncConversation = false - err = imdb.UpdateColumnsConversations(haveUserID, req.Conversation.ConversationID, map[string]interface{}{"update_unread_count_time": utils.GetCurrentTimestampByMill()}) + err = imdb.UpdateColumnsConversations(haveUserID, req.Conversation.ConversationID, map[string]interface{}{"update_unread_count_time": conversation.UpdateUnreadCountTime}) + for _, v := range req.UserIDList { + if err = db.DB.SetUserBadgeUnreadCountSum(v, int(req.BadgeUnreadCountSum)); err != nil { + log.NewError(req.OperationID, utils.GetSelfFuncName(), "SetUserBadgeUnreadCountSum, rpc return", err.Error()) + resp.CommonResp = &pbConversation.CommonResp{ErrCode: constant.ErrDB.ErrCode, ErrMsg: err.Error()} + return resp, nil + } + } + } if err != nil { log.NewError(req.OperationID, utils.GetSelfFuncName(), "UpdateColumnsConversations error", err.Error()) @@ -83,7 +91,6 @@ func (rpc *rpcConversation) ModifyConversationField(c context.Context, req *pbCo } for _, v := range utils.DifferenceString(haveUserID, req.UserIDList) { conversation.OwnerUserID = v - conversation.UpdateUnreadCountTime = utils.GetCurrentTimestampByMill() err = rocksCache.DelUserConversationIDListFromCache(v) if err != nil { log.NewError(req.OperationID, utils.GetSelfFuncName(), v, req.Conversation.ConversationID, err.Error()) diff --git a/internal/utils/local_cache.go b/internal/utils/local_cache.go index 3be1226a1..5efec4fb3 100644 --- a/internal/utils/local_cache.go +++ b/internal/utils/local_cache.go @@ -18,7 +18,7 @@ type GroupMemberUserIDListHash struct { UserIDList []string } -var CacheGroupMemberUserIDList map[string]*GroupMemberUserIDListHash = make(map[string]*GroupMemberUserIDListHash, 0) +var CacheGroupMemberUserIDList = make(map[string]*GroupMemberUserIDListHash, 0) var CacheGroupMtx sync.RWMutex func GetGroupMemberUserIDList(groupID string, operationID string) ([]string, error) { diff --git a/pkg/base_info/conversation_api_struct.go b/pkg/base_info/conversation_api_struct.go index be913a3c0..ba369022d 100644 --- a/pkg/base_info/conversation_api_struct.go +++ b/pkg/base_info/conversation_api_struct.go @@ -61,9 +61,10 @@ type SetConversationResp struct { } type ModifyConversationFieldReq struct { Conversation - FieldType int32 `json:"fieldType" binding:"required"` - UserIDList []string `json:"userIDList" binding:"required"` - OperationID string `json:"operationID" binding:"required"` + FieldType int32 `json:"fieldType" binding:"required"` + UserIDList []string `json:"userIDList" binding:"required"` + BadgeUnreadCountSum int32 `json:"badgeUnreadCountSum"` + OperationID string `json:"operationID" binding:"required"` } type ModifyConversationFieldResp struct { CommResp diff --git a/pkg/common/db/RedisModel.go b/pkg/common/db/RedisModel.go index b211739e1..3240b8ce9 100644 --- a/pkg/common/db/RedisModel.go +++ b/pkg/common/db/RedisModel.go @@ -37,6 +37,7 @@ const ( groupMaxSeq = "GROUP_MAX_SEQ:" groupMinSeq = "GROUP_MIN_SEQ:" sendMsgFailedFlag = "SEND_MSG_FAILED_FLAG:" + userBadgeUnreadCountSum = "USER_BADGE_UNREAD_COUNT_SUM:" ) func (d *DataBases) JudgeAccountEXISTS(account string) (bool, error) { @@ -411,3 +412,12 @@ func (d *DataBases) GetFcmToken(account string, platformid int) (string, error) key := FcmToken + account + ":" + strconv.Itoa(platformid) return d.RDB.Get(context.Background(), key).Result() } +func (d *DataBases) IncrUserBadgeUnreadCountSum(uid string) (int, error) { + key := userBadgeUnreadCountSum + uid + seq, err := d.RDB.Incr(context.Background(), key).Result() + return int(seq), err +} +func (d *DataBases) SetUserBadgeUnreadCountSum(uid string, value int) error { + key := userBadgeUnreadCountSum + uid + return d.RDB.Set(context.Background(), key, value, 0).Err() +} diff --git a/pkg/proto/conversation/conversation.proto b/pkg/proto/conversation/conversation.proto index 48973fdc1..c07928e52 100644 --- a/pkg/proto/conversation/conversation.proto +++ b/pkg/proto/conversation/conversation.proto @@ -29,6 +29,7 @@ message ModifyConversationFieldReq{ int32 fieldType = 2; repeated string userIDList = 3; string operationID = 4; + int32 badgeUnreadCountSum = 5; } message ModifyConversationFieldResp{