feat: incr sync version.

This commit is contained in:
Gordon
2024-06-24 17:48:33 +08:00
parent e8ccae6349
commit 88b8043224
308 changed files with 55952 additions and 59 deletions

View File

@@ -0,0 +1,71 @@
// Copyright © 2023 OpenIM SDK. 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 friend
import (
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
"github.com/openimsdk/protocol/sdkws"
)
func ServerFriendRequestToLocalFriendRequest(info *sdkws.FriendRequest) *model_struct.LocalFriendRequest {
return &model_struct.LocalFriendRequest{
FromUserID: info.FromUserID,
FromNickname: info.FromNickname,
FromFaceURL: info.FromFaceURL,
//FromGender: info.FromGender,
ToUserID: info.ToUserID,
ToNickname: info.ToNickname,
ToFaceURL: info.ToFaceURL,
//ToGender: info.ToGender,
HandleResult: info.HandleResult,
ReqMsg: info.ReqMsg,
CreateTime: info.CreateTime,
HandlerUserID: info.HandlerUserID,
HandleMsg: info.HandleMsg,
HandleTime: info.HandleTime,
Ex: info.Ex,
//AttachedInfo: info.AttachedInfo,
}
}
func ServerFriendToLocalFriend(info *sdkws.FriendInfo) *model_struct.LocalFriend {
return &model_struct.LocalFriend{
OwnerUserID: info.OwnerUserID,
FriendUserID: info.FriendUser.UserID,
Remark: info.Remark,
CreateTime: info.CreateTime,
AddSource: info.AddSource,
OperatorUserID: info.OperatorUserID,
Nickname: info.FriendUser.Nickname,
FaceURL: info.FriendUser.FaceURL,
Ex: info.Ex,
//AttachedInfo: info.FriendUser.AttachedInfo,
IsPinned: info.IsPinned,
}
}
func ServerBlackToLocalBlack(info *sdkws.BlackInfo) *model_struct.LocalBlack {
return &model_struct.LocalBlack{
OwnerUserID: info.OwnerUserID,
BlockUserID: info.BlackUserInfo.UserID,
CreateTime: info.CreateTime,
AddSource: info.AddSource,
OperatorUserID: info.OperatorUserID,
Nickname: info.BlackUserInfo.Nickname,
FaceURL: info.BlackUserInfo.FaceURL,
Ex: info.Ex,
//AttachedInfo: info.FriendUser.AttachedInfo,
}
}

View File

@@ -0,0 +1,199 @@
// Copyright 2021 OpenIM Corporation
//
// 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 friend
import (
"context"
"github.com/openimsdk/openim-sdk-core/v3/internal/user"
"github.com/openimsdk/openim-sdk-core/v3/open_im_sdk_callback"
"github.com/openimsdk/openim-sdk-core/v3/pkg/common"
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface"
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
"github.com/openimsdk/openim-sdk-core/v3/pkg/page"
"github.com/openimsdk/openim-sdk-core/v3/pkg/syncer"
friend "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/log"
)
func NewFriend(loginUserID string, db db_interface.DataBase, user *user.User, conversationCh chan common.Cmd2Value) *Friend {
f := &Friend{loginUserID: loginUserID, db: db, user: user, conversationCh: conversationCh}
f.initSyncer()
return f
}
type Friend struct {
friendListener open_im_sdk_callback.OnFriendshipListenerSdk
loginUserID string
db db_interface.DataBase
user *user.User
friendSyncer *syncer.Syncer[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string]
blockSyncer *syncer.Syncer[*model_struct.LocalBlack, syncer.NoResp, [2]string]
requestRecvSyncer *syncer.Syncer[*model_struct.LocalFriendRequest, syncer.NoResp, [2]string]
requestSendSyncer *syncer.Syncer[*model_struct.LocalFriendRequest, syncer.NoResp, [2]string]
conversationCh chan common.Cmd2Value
listenerForService open_im_sdk_callback.OnListenerForService
}
func (f *Friend) initSyncer() {
f.friendSyncer = syncer.New2[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](
syncer.WithInsert[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, value *model_struct.LocalFriend) error {
return f.db.InsertFriend(ctx, value)
}),
syncer.WithDelete[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, value *model_struct.LocalFriend) error {
return f.db.DeleteFriendDB(ctx, value.FriendUserID)
}),
syncer.WithUpdate[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, server, local *model_struct.LocalFriend) error {
return f.db.UpdateFriend(ctx, server)
}),
syncer.WithUUID[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(value *model_struct.LocalFriend) [2]string {
return [...]string{value.OwnerUserID, value.FriendUserID}
}),
syncer.WithNotice[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, state int, server, local *model_struct.LocalFriend) error {
switch state {
case syncer.Insert:
f.friendListener.OnFriendAdded(*server)
case syncer.Delete:
log.ZDebug(ctx, "syncer OnFriendDeleted", "local", local)
f.friendListener.OnFriendDeleted(*local)
case syncer.Update:
f.friendListener.OnFriendInfoChanged(*server)
if local.Nickname != server.Nickname || local.FaceURL != server.FaceURL || local.Remark != server.Remark {
if server.Remark != "" {
server.Nickname = server.Remark
}
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{
Action: constant.UpdateConFaceUrlAndNickName,
Args: common.SourceIDAndSessionType{
SourceID: server.FriendUserID,
SessionType: constant.SingleChatType,
FaceURL: server.FaceURL,
Nickname: server.Nickname,
},
}, f.conversationCh)
_ = common.TriggerCmdUpdateMessage(ctx, common.UpdateMessageNode{
Action: constant.UpdateMsgFaceUrlAndNickName,
Args: common.UpdateMessageInfo{
SessionType: constant.SingleChatType,
UserID: server.FriendUserID,
FaceURL: server.FaceURL,
Nickname: server.Nickname,
},
}, f.conversationCh)
}
}
return nil
}),
syncer.WithBatchInsert[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, values []*model_struct.LocalFriend) error {
log.ZDebug(ctx, "BatchInsertFriend", "length", len(values))
return f.db.BatchInsertFriend(ctx, values)
}),
syncer.WithDeleteAll[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, _ string) error {
return f.db.DeleteAllFriend(ctx)
}),
syncer.WithBatchPageReq[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(entityID string) page.PageReq {
return &friend.GetPaginationFriendsReq{UserID: entityID,
Pagination: &sdkws.RequestPagination{ShowNumber: 100}}
}),
syncer.WithBatchPageRespConvertFunc[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(resp *friend.GetPaginationFriendsResp) []*model_struct.LocalFriend {
return datautil.Batch(ServerFriendToLocalFriend, resp.FriendsInfo)
}),
syncer.WithReqApiRouter[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](constant.GetFriendListRouter),
)
f.blockSyncer = syncer.New[*model_struct.LocalBlack, syncer.NoResp, [2]string](func(ctx context.Context, value *model_struct.LocalBlack) error {
return f.db.InsertBlack(ctx, value)
}, func(ctx context.Context, value *model_struct.LocalBlack) error {
return f.db.DeleteBlack(ctx, value.BlockUserID)
}, func(ctx context.Context, server *model_struct.LocalBlack, local *model_struct.LocalBlack) error {
return f.db.UpdateBlack(ctx, server)
}, func(value *model_struct.LocalBlack) [2]string {
return [...]string{value.OwnerUserID, value.BlockUserID}
}, nil, func(ctx context.Context, state int, server, local *model_struct.LocalBlack) error {
switch state {
case syncer.Insert:
f.friendListener.OnBlackAdded(*server)
case syncer.Delete:
f.friendListener.OnBlackDeleted(*local)
}
return nil
})
f.requestRecvSyncer = syncer.New[*model_struct.LocalFriendRequest, syncer.NoResp, [2]string](func(ctx context.Context, value *model_struct.LocalFriendRequest) error {
return f.db.InsertFriendRequest(ctx, value)
}, func(ctx context.Context, value *model_struct.LocalFriendRequest) error {
return f.db.DeleteFriendRequestBothUserID(ctx, value.FromUserID, value.ToUserID)
}, func(ctx context.Context, server *model_struct.LocalFriendRequest, local *model_struct.LocalFriendRequest) error {
return f.db.UpdateFriendRequest(ctx, server)
}, func(value *model_struct.LocalFriendRequest) [2]string {
return [...]string{value.FromUserID, value.ToUserID}
}, nil, func(ctx context.Context, state int, server, local *model_struct.LocalFriendRequest) error {
switch state {
case syncer.Insert:
f.friendListener.OnFriendApplicationAdded(*server)
case syncer.Delete:
f.friendListener.OnFriendApplicationDeleted(*local)
case syncer.Update:
switch server.HandleResult {
case constant.FriendResponseAgree:
f.friendListener.OnFriendApplicationAccepted(*server)
case constant.FriendResponseRefuse:
f.friendListener.OnFriendApplicationRejected(*server)
case constant.FriendResponseDefault:
f.friendListener.OnFriendApplicationAdded(*server)
}
}
return nil
})
f.requestSendSyncer = syncer.New[*model_struct.LocalFriendRequest, syncer.NoResp, [2]string](func(ctx context.Context, value *model_struct.LocalFriendRequest) error {
return f.db.InsertFriendRequest(ctx, value)
}, func(ctx context.Context, value *model_struct.LocalFriendRequest) error {
return f.db.DeleteFriendRequestBothUserID(ctx, value.FromUserID, value.ToUserID)
}, func(ctx context.Context, server *model_struct.LocalFriendRequest, local *model_struct.LocalFriendRequest) error {
return f.db.UpdateFriendRequest(ctx, server)
}, func(value *model_struct.LocalFriendRequest) [2]string {
return [...]string{value.FromUserID, value.ToUserID}
}, nil, func(ctx context.Context, state int, server, local *model_struct.LocalFriendRequest) error {
switch state {
case syncer.Insert:
f.friendListener.OnFriendApplicationAdded(*server)
case syncer.Delete:
f.friendListener.OnFriendApplicationDeleted(*local)
case syncer.Update:
switch server.HandleResult {
case constant.FriendResponseAgree:
f.friendListener.OnFriendApplicationAccepted(*server)
case constant.FriendResponseRefuse:
f.friendListener.OnFriendApplicationRejected(*server)
}
}
return nil
})
}
func (f *Friend) Db() db_interface.DataBase {
return f.db
}
func (f *Friend) SetListener(listener func() open_im_sdk_callback.OnFriendshipListener) {
f.friendListener = open_im_sdk_callback.NewOnFriendshipListenerSdk(listener)
}
func (f *Friend) SetListenerForService(listener open_im_sdk_callback.OnListenerForService) {
f.listenerForService = listener
}

View File

@@ -0,0 +1,33 @@
package friend
import (
"crypto/md5"
"encoding/binary"
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
"github.com/openimsdk/protocol/constant"
"github.com/openimsdk/tools/utils/datautil"
"strconv"
"strings"
)
func (f *Friend) CalculateHash(friends []*model_struct.LocalFriend) uint64 {
datautil.SortAny(friends, func(a, b *model_struct.LocalFriend) bool {
return a.CreateTime > b.CreateTime
})
if len(friends) > constant.MaxSyncPullNumber {
friends = friends[:constant.MaxSyncPullNumber]
}
hashStr := strings.Join(datautil.Slice(friends, func(f *model_struct.LocalFriend) string {
return strings.Join([]string{
f.FriendUserID,
f.Remark,
strconv.FormatInt(f.CreateTime, 10),
strconv.Itoa(int(f.AddSource)),
f.OperatorUserID,
f.Ex,
strconv.FormatBool(f.IsPinned),
}, ",")
}), ";")
sum := md5.Sum([]byte(hashStr))
return binary.BigEndian.Uint64(sum[:])
}

View File

@@ -0,0 +1,136 @@
// Copyright © 2023 OpenIM SDK. 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 friend
import (
"context"
"fmt"
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/log"
)
func (f *Friend) DoNotification(ctx context.Context, msg *sdkws.MsgData) {
go func() {
if err := f.doNotification(ctx, msg); err != nil {
log.ZError(ctx, "doNotification error", err, "msg", msg)
}
}()
}
func (f *Friend) doNotification(ctx context.Context, msg *sdkws.MsgData) error {
switch msg.ContentType {
case constant.FriendApplicationNotification:
tips := sdkws.FriendApplicationTips{}
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
return err
}
return f.SyncBothFriendRequest(ctx,
tips.FromToUserID.FromUserID, tips.FromToUserID.ToUserID)
case constant.FriendApplicationApprovedNotification:
var tips sdkws.FriendApplicationApprovedTips
err := utils.UnmarshalNotificationElem(msg.Content, &tips)
if err != nil {
return err
}
if tips.FromToUserID.FromUserID == f.loginUserID {
err = f.SyncFriends(ctx, []string{tips.FromToUserID.ToUserID})
} else if tips.FromToUserID.ToUserID == f.loginUserID {
err = f.SyncFriends(ctx, []string{tips.FromToUserID.FromUserID})
}
if err != nil {
return err
}
return f.SyncBothFriendRequest(ctx, tips.FromToUserID.FromUserID, tips.FromToUserID.ToUserID)
case constant.FriendApplicationRejectedNotification:
var tips sdkws.FriendApplicationRejectedTips
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
return err
}
return f.SyncBothFriendRequest(ctx, tips.FromToUserID.FromUserID, tips.FromToUserID.ToUserID)
case constant.FriendAddedNotification:
var tips sdkws.FriendAddedTips
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
return err
}
if tips.Friend != nil && tips.Friend.FriendUser != nil {
if tips.Friend.FriendUser.UserID == f.loginUserID {
return f.SyncFriends(ctx, []string{tips.Friend.OwnerUserID})
} else if tips.Friend.OwnerUserID == f.loginUserID {
return f.SyncFriends(ctx, []string{tips.Friend.FriendUser.UserID})
}
}
case constant.FriendDeletedNotification:
var tips sdkws.FriendDeletedTips
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
return err
}
if tips.FromToUserID != nil {
if tips.FromToUserID.FromUserID == f.loginUserID {
return f.deleteFriend(ctx, tips.FromToUserID.ToUserID)
}
}
case constant.FriendRemarkSetNotification:
var tips sdkws.FriendInfoChangedTips
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
return err
}
if tips.FromToUserID != nil {
if tips.FromToUserID.FromUserID == f.loginUserID {
return f.SyncFriends(ctx, []string{tips.FromToUserID.ToUserID})
}
}
case constant.FriendInfoUpdatedNotification:
var tips sdkws.UserInfoUpdatedTips
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
return err
}
if tips.UserID != f.loginUserID {
return f.SyncFriends(ctx, []string{tips.UserID})
}
case constant.BlackAddedNotification:
var tips sdkws.BlackAddedTips
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
return err
}
if tips.FromToUserID.FromUserID == f.loginUserID {
return f.SyncAllBlackList(ctx)
}
case constant.BlackDeletedNotification:
var tips sdkws.BlackDeletedTips
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
return err
}
if tips.FromToUserID.FromUserID == f.loginUserID {
return f.SyncAllBlackList(ctx)
}
case constant.FriendsInfoUpdateNotification:
var tips sdkws.FriendsInfoUpdateTips
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
return err
}
if tips.FromToUserID.ToUserID == f.loginUserID {
return f.SyncFriends(ctx, tips.FriendIDs)
}
default:
return fmt.Errorf("type failed %d", msg.ContentType)
}
return nil
}

View File

@@ -0,0 +1,344 @@
// Copyright 2021 OpenIM Corporation
//
// 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 friend
import (
"context"
friend "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/protocol/wrapperspb"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/openim-sdk-core/v3/internal/util"
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
"github.com/openimsdk/openim-sdk-core/v3/pkg/datafetcher"
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
sdk "github.com/openimsdk/openim-sdk-core/v3/pkg/sdk_params_callback"
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs"
"github.com/openimsdk/openim-sdk-core/v3/pkg/server_api_params"
"github.com/openimsdk/tools/log"
)
func (f *Friend) GetSpecifiedFriendsInfo(ctx context.Context, friendUserIDList []string) ([]*server_api_params.FullUserInfo, error) {
datafetcher := datafetcher.NewDataFetcher(
f.db,
f.friendListTableName(),
f.loginUserID,
func(localFriend *model_struct.LocalFriend) string {
return localFriend.FriendUserID
},
func(ctx context.Context, values []*model_struct.LocalFriend) error {
return f.db.BatchInsertFriend(ctx, values)
},
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalFriend, error) {
return f.db.GetFriendInfoList(ctx, userIDs)
},
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalFriend, error) {
serverFriend, err := f.GetDesignatedFriends(ctx, userIDs)
if err != nil {
return nil, err
}
return datautil.Batch(ServerFriendToLocalFriend, serverFriend), nil
},
)
localFriendList, err := datafetcher.FetchMissingAndFillLocal(ctx, friendUserIDList)
if err != nil {
return nil, err
}
log.ZDebug(ctx, "GetDesignatedFriendsInfo", "localFriendList", localFriendList)
blackList, err := f.db.GetBlackInfoList(ctx, friendUserIDList)
if err != nil {
return nil, err
}
log.ZDebug(ctx, "GetDesignatedFriendsInfo", "blackList", blackList)
m := make(map[string]*model_struct.LocalBlack)
for i, black := range blackList {
m[black.BlockUserID] = blackList[i]
}
res := make([]*server_api_params.FullUserInfo, 0, len(localFriendList))
for _, localFriend := range localFriendList {
res = append(res, &server_api_params.FullUserInfo{
PublicInfo: nil,
FriendInfo: localFriend,
BlackInfo: m[localFriend.FriendUserID],
})
}
return res, nil
}
func (f *Friend) AddFriend(ctx context.Context, userIDReqMsg *friend.ApplyToAddFriendReq) error {
if userIDReqMsg.FromUserID == "" {
userIDReqMsg.FromUserID = f.loginUserID
}
if err := util.ApiPost(ctx, constant.AddFriendRouter, userIDReqMsg, nil); err != nil {
return err
}
return f.SyncAllFriendApplication(ctx)
}
func (f *Friend) GetFriendApplicationListAsRecipient(ctx context.Context) ([]*model_struct.LocalFriendRequest, error) {
return f.db.GetRecvFriendApplication(ctx)
}
func (f *Friend) GetFriendApplicationListAsApplicant(ctx context.Context) ([]*model_struct.LocalFriendRequest, error) {
return f.db.GetSendFriendApplication(ctx)
}
func (f *Friend) AcceptFriendApplication(ctx context.Context, userIDHandleMsg *sdk.ProcessFriendApplicationParams) error {
return f.RespondFriendApply(ctx, &friend.RespondFriendApplyReq{FromUserID: userIDHandleMsg.ToUserID, ToUserID: f.loginUserID, HandleResult: constant.FriendResponseAgree, HandleMsg: userIDHandleMsg.HandleMsg})
}
func (f *Friend) RefuseFriendApplication(ctx context.Context, userIDHandleMsg *sdk.ProcessFriendApplicationParams) error {
return f.RespondFriendApply(ctx, &friend.RespondFriendApplyReq{FromUserID: userIDHandleMsg.ToUserID, ToUserID: f.loginUserID, HandleResult: constant.FriendResponseRefuse, HandleMsg: userIDHandleMsg.HandleMsg})
}
func (f *Friend) RespondFriendApply(ctx context.Context, req *friend.RespondFriendApplyReq) error {
if req.ToUserID == "" {
req.ToUserID = f.loginUserID
}
if err := util.ApiPost(ctx, constant.AddFriendResponse, req, nil); err != nil {
return err
}
if req.HandleResult == constant.FriendResponseAgree {
_ = f.SyncFriends(ctx, []string{req.FromUserID})
}
_ = f.SyncAllFriendApplication(ctx)
return nil
// return f.SyncFriendApplication(ctx)
}
func (f *Friend) CheckFriend(ctx context.Context, friendUserIDList []string) ([]*server_api_params.UserIDResult, error) {
friendList, err := f.db.GetFriendInfoList(ctx, friendUserIDList)
if err != nil {
return nil, err
}
blackList, err := f.db.GetBlackInfoList(ctx, friendUserIDList)
if err != nil {
return nil, err
}
res := make([]*server_api_params.UserIDResult, 0, len(friendUserIDList))
for _, v := range friendUserIDList {
var r server_api_params.UserIDResult
isBlack := false
isFriend := false
for _, b := range blackList {
if v == b.BlockUserID {
isBlack = true
break
}
}
for _, f := range friendList {
if v == f.FriendUserID {
isFriend = true
break
}
}
r.UserID = v
if isFriend && !isBlack {
r.Result = 1
} else {
r.Result = 0
}
res = append(res, &r)
}
return res, nil
}
func (f *Friend) DeleteFriend(ctx context.Context, friendUserID string) error {
if err := util.ApiPost(ctx, constant.DeleteFriendRouter, &friend.DeleteFriendReq{OwnerUserID: f.loginUserID, FriendUserID: friendUserID}, nil); err != nil {
return err
}
return f.deleteFriend(ctx, friendUserID)
}
func (f *Friend) GetFriendList(ctx context.Context) ([]*server_api_params.FullUserInfo, error) {
localFriendList, err := f.db.GetAllFriendList(ctx)
if err != nil {
return nil, err
}
localBlackList, err := f.db.GetBlackListDB(ctx)
if err != nil {
return nil, err
}
m := make(map[string]*model_struct.LocalBlack)
for i, black := range localBlackList {
m[black.BlockUserID] = localBlackList[i]
}
res := make([]*server_api_params.FullUserInfo, 0, len(localFriendList))
for _, localFriend := range localFriendList {
res = append(res, &server_api_params.FullUserInfo{
PublicInfo: nil,
FriendInfo: localFriend,
BlackInfo: m[localFriend.FriendUserID],
})
}
return res, nil
}
func (f *Friend) GetFriendListPage(ctx context.Context, offset, count int32) ([]*server_api_params.FullUserInfo, error) {
dataFetcher := datafetcher.NewDataFetcher(
f.db,
f.friendListTableName(),
f.loginUserID,
func(localFriend *model_struct.LocalFriend) string {
return localFriend.FriendUserID
},
func(ctx context.Context, values []*model_struct.LocalFriend) error {
return f.db.BatchInsertFriend(ctx, values)
},
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalFriend, error) {
return f.db.GetFriendInfoList(ctx, userIDs)
},
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalFriend, error) {
serverFriend, err := f.GetDesignatedFriends(ctx, userIDs)
if err != nil {
return nil, err
}
return datautil.Batch(ServerFriendToLocalFriend, serverFriend), nil
},
)
localFriendList, err := dataFetcher.FetchWithPagination(ctx, int(offset), int(count))
if err != nil {
return nil, err
}
// don't need extra handle. only full pull.
localBlackList, err := f.db.GetBlackListDB(ctx)
if err != nil {
return nil, err
}
m := make(map[string]*model_struct.LocalBlack)
for i, black := range localBlackList {
m[black.BlockUserID] = localBlackList[i]
}
res := make([]*server_api_params.FullUserInfo, 0, len(localFriendList))
for _, localFriend := range localFriendList {
res = append(res, &server_api_params.FullUserInfo{
PublicInfo: nil,
FriendInfo: localFriend,
BlackInfo: m[localFriend.FriendUserID],
})
}
return res, nil
}
func (f *Friend) SearchFriends(ctx context.Context, param *sdk.SearchFriendsParam) ([]*sdk.SearchFriendItem, error) {
if len(param.KeywordList) == 0 || (!param.IsSearchNickname && !param.IsSearchUserID && !param.IsSearchRemark) {
return nil, sdkerrs.ErrArgs.WrapMsg("keyword is null or search field all false")
}
localFriendList, err := f.db.SearchFriendList(ctx, param.KeywordList[0], param.IsSearchUserID, param.IsSearchNickname, param.IsSearchRemark)
if err != nil {
return nil, err
}
localBlackList, err := f.db.GetBlackListDB(ctx)
if err != nil {
return nil, err
}
m := make(map[string]struct{})
for _, black := range localBlackList {
m[black.BlockUserID] = struct{}{}
}
res := make([]*sdk.SearchFriendItem, 0, len(localFriendList))
for i, localFriend := range localFriendList {
var relationship int
if _, ok := m[localFriend.FriendUserID]; ok {
relationship = constant.BlackRelationship
} else {
relationship = constant.FriendRelationship
}
res = append(res, &sdk.SearchFriendItem{
LocalFriend: *localFriendList[i],
Relationship: relationship,
})
}
return res, nil
}
func (f *Friend) SetFriendRemark(ctx context.Context, userIDRemark *sdk.SetFriendRemarkParams) error {
if err := util.ApiPost(ctx, constant.SetFriendRemark, &friend.SetFriendRemarkReq{OwnerUserID: f.loginUserID, FriendUserID: userIDRemark.ToUserID, Remark: userIDRemark.Remark}, nil); err != nil {
return err
}
return f.SyncFriends(ctx, []string{userIDRemark.ToUserID})
}
func (f *Friend) PinFriends(ctx context.Context, friends *sdk.SetFriendPinParams) error {
if err := util.ApiPost(ctx, constant.UpdateFriends, &friend.UpdateFriendsReq{OwnerUserID: f.loginUserID, FriendUserIDs: friends.ToUserIDs, IsPinned: friends.IsPinned}, nil); err != nil {
return err
}
return f.SyncFriends(ctx, friends.ToUserIDs)
}
func (f *Friend) AddBlack(ctx context.Context, blackUserID string, ex string) error {
if err := util.ApiPost(ctx, constant.AddBlackRouter, &friend.AddBlackReq{OwnerUserID: f.loginUserID, BlackUserID: blackUserID, Ex: ex}, nil); err != nil {
return err
}
return f.SyncAllBlackList(ctx)
}
func (f *Friend) RemoveBlack(ctx context.Context, blackUserID string) error {
if err := util.ApiPost(ctx, constant.RemoveBlackRouter, &friend.RemoveBlackReq{OwnerUserID: f.loginUserID, BlackUserID: blackUserID}, nil); err != nil {
return err
}
return f.SyncAllBlackList(ctx)
}
func (f *Friend) GetBlackList(ctx context.Context) ([]*model_struct.LocalBlack, error) {
return f.db.GetBlackListDB(ctx)
}
func (f *Friend) SetFriendsEx(ctx context.Context, friendIDs []string, ex string) error {
if err := util.ApiPost(ctx, constant.UpdateFriends, &friend.UpdateFriendsReq{OwnerUserID: f.loginUserID, FriendUserIDs: friendIDs, Ex: &wrapperspb.StringValue{
Value: ex,
}}, nil); err != nil {
return err
}
// Check if the specified ID is a friend
friendResults, err := f.CheckFriend(ctx, friendIDs)
if err != nil {
return errs.WrapMsg(err, "Error checking friend status")
}
// Determine if friendID is indeed a friend
// Iterate over each friendID
for _, friendID := range friendIDs {
isFriend := false
// Check if this friendID is in the friendResults
for _, result := range friendResults {
if result.UserID == friendID && result.Result == 1 { // Assuming result 1 means they are friends
isFriend = true
break
}
}
// If this friendID is not a friend, return an error
if !isFriend {
return errs.ErrRecordNotFound.WrapMsg("Not friend")
}
}
// If the code reaches here, all friendIDs are confirmed as friends
// Update friend information if they are friends
updateErr := f.db.UpdateColumnsFriend(ctx, friendIDs, map[string]interface{}{"Ex": ex})
if updateErr != nil {
return errs.WrapMsg(updateErr, "Error updating friend information")
}
return nil
}

View File

@@ -0,0 +1,180 @@
// Copyright © 2023 OpenIM SDK. 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 friend
import (
"context"
"fmt"
"github.com/openimsdk/tools/utils/datautil"
"time"
"github.com/openimsdk/openim-sdk-core/v3/internal/util"
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
friend "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/log"
)
func (f *Friend) SyncBothFriendRequest(ctx context.Context, fromUserID, toUserID string) error {
var resp friend.GetDesignatedFriendsApplyResp
if err := util.ApiPost(ctx, constant.GetDesignatedFriendsApplyRouter, &friend.GetDesignatedFriendsApplyReq{FromUserID: fromUserID, ToUserID: toUserID}, &resp); err != nil {
return nil
}
localData, err := f.db.GetBothFriendReq(ctx, fromUserID, toUserID)
if err != nil {
return err
}
if toUserID == f.loginUserID {
return f.requestRecvSyncer.Sync(ctx, datautil.Batch(ServerFriendRequestToLocalFriendRequest, resp.FriendRequests), localData, nil)
} else if fromUserID == f.loginUserID {
return f.requestSendSyncer.Sync(ctx, datautil.Batch(ServerFriendRequestToLocalFriendRequest, resp.FriendRequests), localData, nil)
}
return nil
}
// send
func (f *Friend) SyncAllSelfFriendApplication(ctx context.Context) error {
req := &friend.GetPaginationFriendsApplyFromReq{UserID: f.loginUserID, Pagination: &sdkws.RequestPagination{}}
fn := func(resp *friend.GetPaginationFriendsApplyFromResp) []*sdkws.FriendRequest {
return resp.FriendRequests
}
requests, err := util.GetPageAll(ctx, constant.GetSelfFriendApplicationListRouter, req, fn)
if err != nil {
return err
}
localData, err := f.db.GetSendFriendApplication(ctx)
if err != nil {
return err
}
return f.requestSendSyncer.Sync(ctx, datautil.Batch(ServerFriendRequestToLocalFriendRequest, requests), localData, nil)
}
// recv
func (f *Friend) SyncAllFriendApplication(ctx context.Context) error {
req := &friend.GetPaginationFriendsApplyToReq{UserID: f.loginUserID, Pagination: &sdkws.RequestPagination{}}
fn := func(resp *friend.GetPaginationFriendsApplyToResp) []*sdkws.FriendRequest { return resp.FriendRequests }
requests, err := util.GetPageAll(ctx, constant.GetFriendApplicationListRouter, req, fn)
if err != nil {
return err
}
localData, err := f.db.GetRecvFriendApplication(ctx)
if err != nil {
return err
}
return f.requestRecvSyncer.Sync(ctx, datautil.Batch(ServerFriendRequestToLocalFriendRequest, requests), localData, nil)
}
func (f *Friend) SyncAllFriendList(ctx context.Context) error {
t := time.Now()
defer func(start time.Time) {
elapsed := time.Since(start).Milliseconds()
log.ZDebug(ctx, "SyncAllFriendList fn call end", "cost time", fmt.Sprintf("%d ms", elapsed))
}(t)
return f.IncrSyncFriends(ctx)
//req := &friend.GetPaginationFriendsReq{UserID: f.loginUserID, Pagination: &sdkws.RequestPagination{}}
//fn := func(resp *friend.GetPaginationFriendsResp) []*sdkws.FriendInfo { return resp.FriendsInfo }
//friends, err := util.GetPageAll(ctx, constant.GetFriendListRouter, req, fn)
//if err != nil {
// return err
//}
//localData, err := f.db.GetAllFriendList(ctx)
//if err != nil {
// return err
//}
//log.ZDebug(ctx, "sync friend", "data from server", friends, "data from local", localData)
//return f.friendSyncer.Sync(ctx, util.Batch(ServerFriendToLocalFriend, friends), localData, nil)
}
func (f *Friend) deleteFriend(ctx context.Context, friendUserID string) error {
return f.IncrSyncFriends(ctx)
//friends, err := f.db.GetFriendInfoList(ctx, []string{friendUserID})
//if err != nil {
// return err
//}
//if len(friends) == 0 {
// return sdkerrs.ErrUserIDNotFound.WrapMsg("friendUserID not found")
//}
//if err := f.db.DeleteFriendDB(ctx, friendUserID); err != nil {
// return err
//}
//f.friendListener.OnFriendDeleted(*friends[0])
//return nil
}
func (f *Friend) SyncFriends(ctx context.Context, friendIDs []string) error {
return f.IncrSyncFriends(ctx)
//var resp friend.GetDesignatedFriendsResp
//if err := util.ApiPost(ctx, constant.GetDesignatedFriendsRouter, &friend.GetDesignatedFriendsReq{OwnerUserID: f.loginUserID, FriendUserIDs: friendIDs}, &resp); err != nil {
// return err
//}
//localData, err := f.db.GetFriendInfoList(ctx, friendIDs)
//if err != nil {
// return err
//}
//log.ZDebug(ctx, "sync friend", "data from server", resp.FriendsInfo, "data from local", localData)
//return f.friendSyncer.Sync(ctx, util.Batch(ServerFriendToLocalFriend, resp.FriendsInfo), localData, nil)
}
//func (f *Friend) SyncFriendPart(ctx context.Context) error {
// hashResp, err := util.CallApi[friend.GetFriendHashResp](ctx, constant.GetFriendHash, &friend.GetFriendHashReq{UserID: f.loginUserID})
// if err != nil {
// return err
// }
// friends, err := f.db.GetAllFriendList(ctx)
// if err != nil {
// return err
// }
// hashCode := f.CalculateHash(friends)
// log.ZDebug(ctx, "SyncFriendPart", "serverHash", hashResp.Hash, "serverTotal", hashResp.Total, "localHash", hashCode, "localTotal", len(friends))
// if hashCode == hashResp.Hash {
// return nil
// }
// req := &friend.GetPaginationFriendsReq{
// UserID: f.loginUserID,
// Pagination: &sdkws.RequestPagination{PageNumber: pconstant.FirstPageNumber, ShowNumber: pconstant.MaxSyncPullNumber},
// }
// resp, err := util.CallApi[friend.GetPaginationFriendsResp](ctx, constant.GetFriendListRouter, req)
// if err != nil {
// return err
// }
// serverFriends := util.Batch(ServerFriendToLocalFriend, resp.FriendsInfo)
// return f.friendSyncer.Sync(ctx, serverFriends, friends, nil)
//}
func (f *Friend) SyncAllBlackList(ctx context.Context) error {
req := &friend.GetPaginationBlacksReq{UserID: f.loginUserID, Pagination: &sdkws.RequestPagination{}}
fn := func(resp *friend.GetPaginationBlacksResp) []*sdkws.BlackInfo { return resp.Blacks }
serverData, err := util.GetPageAll(ctx, constant.GetBlackListRouter, req, fn)
if err != nil {
return err
}
log.ZDebug(ctx, "black from server", "data", serverData)
localData, err := f.db.GetBlackListDB(ctx)
if err != nil {
return err
}
log.ZDebug(ctx, "black from local", "data", localData)
return f.blockSyncer.Sync(ctx, datautil.Batch(ServerBlackToLocalBlack, serverData), localData, nil)
}
func (f *Friend) GetDesignatedFriends(ctx context.Context, friendIDs []string) ([]*sdkws.FriendInfo, error) {
resp := &friend.GetDesignatedFriendsResp{}
if err := util.ApiPost(ctx, constant.GetDesignatedFriendsRouter, &friend.GetDesignatedFriendsReq{OwnerUserID: f.loginUserID, FriendUserIDs: friendIDs}, &resp); err != nil {
return nil, err
}
return resp.FriendsInfo, nil
}

View File

@@ -0,0 +1,72 @@
package friend
import (
"context"
"github.com/openimsdk/openim-sdk-core/v3/internal/incrversion"
"github.com/openimsdk/openim-sdk-core/v3/internal/util"
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
friend "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/tools/utils/datautil"
)
const (
LocalFriendSyncMaxNum = 1000
)
func (f *Friend) IncrSyncFriends(ctx context.Context) error {
friendSyncer := incrversion.VersionSynchronizer[*model_struct.LocalFriend, *friend.GetIncrementalFriendsResp]{
Ctx: ctx,
DB: f.db,
TableName: f.friendListTableName(),
EntityID: f.loginUserID,
Key: func(localFriend *model_struct.LocalFriend) string {
return localFriend.FriendUserID
},
Local: func() ([]*model_struct.LocalFriend, error) {
return f.db.GetAllFriendList(ctx)
},
Server: func(version *model_struct.LocalVersionSync) (*friend.GetIncrementalFriendsResp, error) {
return util.CallApi[friend.GetIncrementalFriendsResp](ctx, constant.GetIncrementalFriends, &friend.GetIncrementalFriendsReq{
UserID: f.loginUserID,
Version: version.Version,
VersionID: version.VersionID,
})
},
Full: func(resp *friend.GetIncrementalFriendsResp) bool {
return resp.Full
},
Version: func(resp *friend.GetIncrementalFriendsResp) (string, uint64) {
return resp.VersionID, resp.Version
},
Delete: func(resp *friend.GetIncrementalFriendsResp) []string {
return resp.Delete
},
Update: func(resp *friend.GetIncrementalFriendsResp) []*model_struct.LocalFriend {
return datautil.Batch(ServerFriendToLocalFriend, resp.Update)
},
Insert: func(resp *friend.GetIncrementalFriendsResp) []*model_struct.LocalFriend {
return datautil.Batch(ServerFriendToLocalFriend, resp.Insert)
},
Syncer: func(server, local []*model_struct.LocalFriend) error {
return f.friendSyncer.Sync(ctx, server, local, nil)
},
FullSyncer: func(ctx context.Context) error {
return f.friendSyncer.FullSync(ctx, f.loginUserID)
},
FullID: func(ctx context.Context) ([]string, error) {
resp, err := util.CallApi[friend.GetFullFriendUserIDsResp](ctx, constant.GetFullFriendUserIDs, &friend.GetFullFriendUserIDsReq{
UserID: f.loginUserID,
})
if err != nil {
return nil, err
}
return resp.UserIDs, nil
},
}
return friendSyncer.Sync()
}
func (f *Friend) friendListTableName() string {
return model_struct.LocalFriend{}.TableName()
}

View File

@@ -0,0 +1,13 @@
package friend
import (
"fmt"
"testing"
)
func Test_main(t *testing.T) {
a := []int{1, 2, 3, 4, 5}
fmt.Println(a[:3])
fmt.Println(a[3:])
fmt.Println(a[2:4])
}