refactor: db refactor and cache key add. (#2320)
* refactor: db refactor and cache key add. * refactor: db refactor and cache key add. * refactor: go version update. * refactor: file name change.
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// BatchDeleter interface defines a set of methods for batch deleting cache and publishing deletion information.
|
||||
type BatchDeleter interface {
|
||||
//ChainExecDel method is used for chain calls and must call Clone to prevent memory pollution.
|
||||
ChainExecDel(ctx context.Context) error
|
||||
//ExecDelWithKeys method directly takes keys for deletion.
|
||||
ExecDelWithKeys(ctx context.Context, keys []string) error
|
||||
//Clone method creates a copy of the BatchDeleter to avoid modifying the original object.
|
||||
Clone() BatchDeleter
|
||||
//AddKeys method adds keys to be deleted.
|
||||
AddKeys(keys ...string)
|
||||
}
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright © 2023 OpenIM. 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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type BlackCache interface {
|
||||
BatchDeleter
|
||||
CloneBlackCache() BlackCache
|
||||
GetBlackIDs(ctx context.Context, userID string) (blackIDs []string, err error)
|
||||
// del user's blackIDs msgCache, exec when a user's black list changed
|
||||
DelBlackIDs(ctx context.Context, userID string) BlackCache
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright © 2024 OpenIM. 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 cachekey
|
||||
|
||||
const (
|
||||
BlackIDsKey = "BLACK_IDS:"
|
||||
IsBlackKey = "IS_BLACK:" // local cache
|
||||
)
|
||||
|
||||
func GetBlackIDsKey(ownerUserID string) string {
|
||||
return BlackIDsKey + ownerUserID
|
||||
|
||||
}
|
||||
|
||||
func GetIsBlackIDsKey(possibleBlackUserID, userID string) string {
|
||||
return IsBlackKey + userID + "-" + possibleBlackUserID
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright © 2024 OpenIM. 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 cachekey
|
||||
|
||||
const (
|
||||
ConversationKey = "CONVERSATION:"
|
||||
ConversationIDsKey = "CONVERSATION_IDS:"
|
||||
ConversationIDsHashKey = "CONVERSATION_IDS_HASH:"
|
||||
ConversationHasReadSeqKey = "CONVERSATION_HAS_READ_SEQ:"
|
||||
RecvMsgOptKey = "RECV_MSG_OPT:"
|
||||
SuperGroupRecvMsgNotNotifyUserIDsKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS:"
|
||||
SuperGroupRecvMsgNotNotifyUserIDsHashKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS_HASH:"
|
||||
ConversationNotReceiveMessageUserIDsKey = "CONVERSATION_NOT_RECEIVE_MESSAGE_USER_IDS:"
|
||||
)
|
||||
|
||||
func GetConversationKey(ownerUserID, conversationID string) string {
|
||||
return ConversationKey + ownerUserID + ":" + conversationID
|
||||
}
|
||||
|
||||
func GetConversationIDsKey(ownerUserID string) string {
|
||||
return ConversationIDsKey + ownerUserID
|
||||
}
|
||||
|
||||
func GetSuperGroupRecvNotNotifyUserIDsKey(groupID string) string {
|
||||
return SuperGroupRecvMsgNotNotifyUserIDsKey + groupID
|
||||
}
|
||||
|
||||
func GetRecvMsgOptKey(ownerUserID, conversationID string) string {
|
||||
return RecvMsgOptKey + ownerUserID + ":" + conversationID
|
||||
}
|
||||
|
||||
func GetSuperGroupRecvNotNotifyUserIDsHashKey(groupID string) string {
|
||||
return SuperGroupRecvMsgNotNotifyUserIDsHashKey + groupID
|
||||
}
|
||||
|
||||
func GetConversationHasReadSeqKey(ownerUserID, conversationID string) string {
|
||||
return ConversationHasReadSeqKey + ownerUserID + ":" + conversationID
|
||||
}
|
||||
|
||||
func GetConversationNotReceiveMessageUserIDsKey(conversationID string) string {
|
||||
return ConversationNotReceiveMessageUserIDsKey + conversationID
|
||||
}
|
||||
|
||||
func GetUserConversationIDsHashKey(ownerUserID string) string {
|
||||
return ConversationIDsHashKey + ownerUserID
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright © 2024 OpenIM. 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 cachekey // import "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cachekey"
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// Copyright © 2024 OpenIM. 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 cachekey
|
||||
|
||||
const (
|
||||
FriendIDsKey = "FRIEND_IDS:"
|
||||
TwoWayFriendsIDsKey = "COMMON_FRIENDS_IDS:"
|
||||
FriendKey = "FRIEND_INFO:"
|
||||
IsFriendKey = "IS_FRIEND:" // local cache key
|
||||
)
|
||||
|
||||
func GetFriendIDsKey(ownerUserID string) string {
|
||||
return FriendIDsKey + ownerUserID
|
||||
}
|
||||
|
||||
func GetTwoWayFriendsIDsKey(ownerUserID string) string {
|
||||
return TwoWayFriendsIDsKey + ownerUserID
|
||||
}
|
||||
|
||||
func GetFriendKey(ownerUserID, friendUserID string) string {
|
||||
return FriendKey + ownerUserID + "-" + friendUserID
|
||||
}
|
||||
|
||||
func GetIsFriendKey(possibleFriendUserID, userID string) string {
|
||||
return IsFriendKey + possibleFriendUserID + "-" + userID
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Copyright © 2024 OpenIM. 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 cachekey
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
groupExpireTime = time.Second * 60 * 60 * 12
|
||||
GroupInfoKey = "GROUP_INFO:"
|
||||
GroupMemberIDsKey = "GROUP_MEMBER_IDS:"
|
||||
GroupMembersHashKey = "GROUP_MEMBERS_HASH2:"
|
||||
GroupMemberInfoKey = "GROUP_MEMBER_INFO:"
|
||||
JoinedGroupsKey = "JOIN_GROUPS_KEY:"
|
||||
GroupMemberNumKey = "GROUP_MEMBER_NUM_CACHE:"
|
||||
GroupRoleLevelMemberIDsKey = "GROUP_ROLE_LEVEL_MEMBER_IDS:"
|
||||
)
|
||||
|
||||
func GetGroupInfoKey(groupID string) string {
|
||||
return GroupInfoKey + groupID
|
||||
}
|
||||
|
||||
func GetJoinedGroupsKey(userID string) string {
|
||||
return JoinedGroupsKey + userID
|
||||
}
|
||||
|
||||
func GetGroupMembersHashKey(groupID string) string {
|
||||
return GroupMembersHashKey + groupID
|
||||
}
|
||||
|
||||
func GetGroupMemberIDsKey(groupID string) string {
|
||||
return GroupMemberIDsKey + groupID
|
||||
}
|
||||
|
||||
func GetGroupMemberInfoKey(groupID, userID string) string {
|
||||
return GroupMemberInfoKey + groupID + "-" + userID
|
||||
}
|
||||
|
||||
func GetGroupMemberNumKey(groupID string) string {
|
||||
return GroupMemberNumKey + groupID
|
||||
}
|
||||
|
||||
func GetGroupRoleLevelMemberIDsKey(groupID string, roleLevel int32) string {
|
||||
return GroupRoleLevelMemberIDsKey + groupID + "-" + strconv.Itoa(int(roleLevel))
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright © 2024 OpenIM. 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 cachekey
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
messageCache = "MESSAGE_CACHE:"
|
||||
messageDelUserList = "MESSAGE_DEL_USER_LIST:"
|
||||
userDelMessagesList = "USER_DEL_MESSAGES_LIST:"
|
||||
sendMsgFailedFlag = "SEND_MSG_FAILED_FLAG:"
|
||||
exTypeKeyLocker = "EX_LOCK:"
|
||||
reactionExSingle = "EX_SINGLE_"
|
||||
reactionWriteGroup = "EX_GROUP_"
|
||||
reactionReadGroup = "EX_SUPER_GROUP_"
|
||||
reactionNotification = "EX_NOTIFICATION_"
|
||||
)
|
||||
|
||||
func GetAllMessageCacheKey(conversationID string) string {
|
||||
return messageCache + conversationID + "_*"
|
||||
}
|
||||
|
||||
func GetMessageCacheKey(conversationID string, seq int64) string {
|
||||
return messageCache + conversationID + "_" + strconv.Itoa(int(seq))
|
||||
}
|
||||
|
||||
func GetMessageDelUserListKey(conversationID string, seq int64) string {
|
||||
return messageDelUserList + conversationID + ":" + strconv.Itoa(int(seq))
|
||||
}
|
||||
|
||||
func GetUserDelListKey(conversationID, userID string) string {
|
||||
return userDelMessagesList + conversationID + ":" + userID
|
||||
}
|
||||
|
||||
func GetMessageReactionExKey(clientMsgID string, sessionType int32) string {
|
||||
switch sessionType {
|
||||
case constant.SingleChatType:
|
||||
return reactionExSingle + clientMsgID
|
||||
case constant.WriteGroupChatType:
|
||||
return reactionWriteGroup + clientMsgID
|
||||
case constant.ReadGroupChatType:
|
||||
return reactionReadGroup + clientMsgID
|
||||
case constant.NotificationChatType:
|
||||
return reactionNotification + clientMsgID
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
func GetLockMessageTypeKey(clientMsgID string, TypeKey string) string {
|
||||
return exTypeKeyLocker + clientMsgID + "_" + TypeKey
|
||||
}
|
||||
|
||||
func GetSendMsgKey(id string) string {
|
||||
return sendMsgFailedFlag + id
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// Copyright © 2024 OpenIM. 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 cachekey
|
||||
|
||||
import "strconv"
|
||||
|
||||
const (
|
||||
object = "OBJECT:"
|
||||
s3 = "S3:"
|
||||
minioImageInfo = "MINIO:IMAGE:"
|
||||
minioThumbnail = "MINIO:THUMBNAIL:"
|
||||
)
|
||||
|
||||
func GetObjectKey(engine string, name string) string {
|
||||
return object + engine + ":" + name
|
||||
}
|
||||
|
||||
func GetS3Key(engine string, name string) string {
|
||||
return s3 + engine + ":" + name
|
||||
}
|
||||
|
||||
func GetObjectImageInfoKey(key string) string {
|
||||
return minioImageInfo + key
|
||||
}
|
||||
|
||||
func GetMinioImageThumbnailKey(key string, format string, width int, height int) string {
|
||||
return minioThumbnail + format + ":w" + strconv.Itoa(width) + ":h" + strconv.Itoa(height) + ":" + key
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// Copyright © 2024 OpenIM. 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 cachekey
|
||||
|
||||
const (
|
||||
maxSeq = "MAX_SEQ:"
|
||||
minSeq = "MIN_SEQ:"
|
||||
conversationUserMinSeq = "CON_USER_MIN_SEQ:"
|
||||
hasReadSeq = "HAS_READ_SEQ:"
|
||||
)
|
||||
|
||||
func GetMaxSeqKey(conversationID string) string {
|
||||
return maxSeq + conversationID
|
||||
}
|
||||
|
||||
func GetMinSeqKey(conversationID string) string {
|
||||
return minSeq + conversationID
|
||||
}
|
||||
|
||||
func GetHasReadSeqKey(conversationID string, userID string) string {
|
||||
return hasReadSeq + userID + ":" + conversationID
|
||||
}
|
||||
|
||||
func GetConversationUserMinSeqKey(conversationID, userID string) string {
|
||||
return conversationUserMinSeq + conversationID + "u:" + userID
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// Copyright © 2024 OpenIM. 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 cachekey
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
getuiToken = "GETUI_TOKEN"
|
||||
getuiTaskID = "GETUI_TASK_ID"
|
||||
fmcToken = "FCM_TOKEN:"
|
||||
userBadgeUnreadCountSum = "USER_BADGE_UNREAD_COUNT_SUM:"
|
||||
)
|
||||
|
||||
func GetFcmAccountTokenKey(account string, platformID int) string {
|
||||
return fmcToken + account + ":" + strconv.Itoa(platformID)
|
||||
}
|
||||
|
||||
func GetUserBadgeUnreadCountSumKey(userID string) string {
|
||||
return userBadgeUnreadCountSum + userID
|
||||
}
|
||||
|
||||
func GetGetuiTokenKey() string {
|
||||
return getuiToken
|
||||
}
|
||||
func GetGetuiTaskIDKey() string {
|
||||
return getuiTaskID
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package cachekey
|
||||
|
||||
import "github.com/openimsdk/protocol/constant"
|
||||
|
||||
const (
|
||||
UidPidToken = "UID_PID_TOKEN_STATUS:"
|
||||
)
|
||||
|
||||
func GetTokenKey(userID string, platformID int) string {
|
||||
return UidPidToken + userID + ":" + constant.PlatformIDToName(platformID)
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// Copyright © 2024 OpenIM. 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 cachekey
|
||||
|
||||
const (
|
||||
UserInfoKey = "USER_INFO:"
|
||||
UserGlobalRecvMsgOptKey = "USER_GLOBAL_RECV_MSG_OPT_KEY:"
|
||||
olineStatusKey = "ONLINE_STATUS:"
|
||||
)
|
||||
|
||||
func GetUserInfoKey(userID string) string {
|
||||
return UserInfoKey + userID
|
||||
}
|
||||
|
||||
func GetUserGlobalRecvMsgOptKey(userID string) string {
|
||||
return UserGlobalRecvMsgOptKey + userID
|
||||
}
|
||||
|
||||
func GetOnlineStatusKey(modKey string) string {
|
||||
return olineStatusKey + modKey
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// Copyright © 2023 OpenIM. 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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
)
|
||||
|
||||
// arg fn will exec when no data in msgCache.
|
||||
type ConversationCache interface {
|
||||
BatchDeleter
|
||||
CloneConversationCache() ConversationCache
|
||||
// get user's conversationIDs from msgCache
|
||||
GetUserConversationIDs(ctx context.Context, ownerUserID string) ([]string, error)
|
||||
DelConversationIDs(userIDs ...string) ConversationCache
|
||||
|
||||
GetUserConversationIDsHash(ctx context.Context, ownerUserID string) (hash uint64, err error)
|
||||
DelUserConversationIDsHash(ownerUserIDs ...string) ConversationCache
|
||||
|
||||
// get one conversation from msgCache
|
||||
GetConversation(ctx context.Context, ownerUserID, conversationID string) (*relationtb.Conversation, error)
|
||||
DelConversations(ownerUserID string, conversationIDs ...string) ConversationCache
|
||||
DelUsersConversation(conversationID string, ownerUserIDs ...string) ConversationCache
|
||||
// get one conversation from msgCache
|
||||
GetConversations(ctx context.Context, ownerUserID string,
|
||||
conversationIDs []string) ([]*relationtb.Conversation, error)
|
||||
// get one user's all conversations from msgCache
|
||||
GetUserAllConversations(ctx context.Context, ownerUserID string) ([]*relationtb.Conversation, error)
|
||||
// get user conversation recv msg from msgCache
|
||||
GetUserRecvMsgOpt(ctx context.Context, ownerUserID, conversationID string) (opt int, err error)
|
||||
DelUserRecvMsgOpt(ownerUserID, conversationID string) ConversationCache
|
||||
// get one super group recv msg but do not notification userID list
|
||||
// GetSuperGroupRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) (userIDs []string, err error)
|
||||
DelSuperGroupRecvMsgNotNotifyUserIDs(groupID string) ConversationCache
|
||||
// get one super group recv msg but do not notification userID list hash
|
||||
// GetSuperGroupRecvMsgNotNotifyUserIDsHash(ctx context.Context, groupID string) (hash uint64, err error)
|
||||
DelSuperGroupRecvMsgNotNotifyUserIDsHash(groupID string) ConversationCache
|
||||
|
||||
// GetUserAllHasReadSeqs(ctx context.Context, ownerUserID string) (map[string]int64, error)
|
||||
DelUserAllHasReadSeqs(ownerUserID string, conversationIDs ...string) ConversationCache
|
||||
|
||||
GetConversationsByConversationID(ctx context.Context,
|
||||
conversationIDs []string) ([]*relationtb.Conversation, error)
|
||||
DelConversationByConversationID(conversationIDs ...string) ConversationCache
|
||||
GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error)
|
||||
DelConversationNotReceiveMessageUserIDs(conversationIDs ...string) ConversationCache
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright © 2024 OpenIM. 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 cache // import "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright © 2023 OpenIM. 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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
)
|
||||
|
||||
// FriendCache is an interface for caching friend-related data.
|
||||
type FriendCache interface {
|
||||
BatchDeleter
|
||||
CloneFriendCache() FriendCache
|
||||
GetFriendIDs(ctx context.Context, ownerUserID string) (friendIDs []string, err error)
|
||||
// Called when friendID list changed
|
||||
DelFriendIDs(ownerUserID ...string) FriendCache
|
||||
// Get single friendInfo from the cache
|
||||
GetFriend(ctx context.Context, ownerUserID, friendUserID string) (friend *relationtb.Friend, err error)
|
||||
// Delete friend when friend info changed
|
||||
DelFriend(ownerUserID, friendUserID string) FriendCache
|
||||
// Delete friends when friends' info changed
|
||||
DelFriends(ownerUserID string, friendUserIDs []string) FriendCache
|
||||
}
|
||||
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
// Copyright © 2023 OpenIM. 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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/common"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
)
|
||||
|
||||
type GroupHash interface {
|
||||
GetGroupHash(ctx context.Context, groupID string) (uint64, error)
|
||||
}
|
||||
|
||||
type GroupCache interface {
|
||||
BatchDeleter
|
||||
CloneGroupCache() GroupCache
|
||||
GetGroupsInfo(ctx context.Context, groupIDs []string) (groups []*model.Group, err error)
|
||||
GetGroupInfo(ctx context.Context, groupID string) (group *model.Group, err error)
|
||||
DelGroupsInfo(groupIDs ...string) GroupCache
|
||||
|
||||
GetGroupMembersHash(ctx context.Context, groupID string) (hashCode uint64, err error)
|
||||
GetGroupMemberHashMap(ctx context.Context, groupIDs []string) (map[string]*common.GroupSimpleUserID, error)
|
||||
DelGroupMembersHash(groupID string) GroupCache
|
||||
|
||||
GetGroupMemberIDs(ctx context.Context, groupID string) (groupMemberIDs []string, err error)
|
||||
GetGroupsMemberIDs(ctx context.Context, groupIDs []string) (groupMemberIDs map[string][]string, err error)
|
||||
|
||||
DelGroupMemberIDs(groupID string) GroupCache
|
||||
|
||||
GetJoinedGroupIDs(ctx context.Context, userID string) (joinedGroupIDs []string, err error)
|
||||
DelJoinedGroupID(userID ...string) GroupCache
|
||||
|
||||
GetGroupMemberInfo(ctx context.Context, groupID, userID string) (groupMember *model.GroupMember, err error)
|
||||
GetGroupMembersInfo(ctx context.Context, groupID string, userID []string) (groupMembers []*model.GroupMember, err error)
|
||||
GetAllGroupMembersInfo(ctx context.Context, groupID string) (groupMembers []*model.GroupMember, err error)
|
||||
GetGroupMembersPage(ctx context.Context, groupID string, userID []string, showNumber, pageNumber int32) (total uint32, groupMembers []*model.GroupMember, err error)
|
||||
FindGroupMemberUser(ctx context.Context, groupIDs []string, userID string) ([]*model.GroupMember, error)
|
||||
|
||||
GetGroupRoleLevelMemberIDs(ctx context.Context, groupID string, roleLevel int32) ([]string, error)
|
||||
GetGroupOwner(ctx context.Context, groupID string) (*model.GroupMember, error)
|
||||
GetGroupsOwner(ctx context.Context, groupIDs []string) ([]*model.GroupMember, error)
|
||||
DelGroupRoleLevel(groupID string, roleLevel []int32) GroupCache
|
||||
DelGroupAllRoleLevel(groupID string) GroupCache
|
||||
DelGroupMembersInfo(groupID string, userID ...string) GroupCache
|
||||
GetGroupRoleLevelMemberInfo(ctx context.Context, groupID string, roleLevel int32) ([]*model.GroupMember, error)
|
||||
GetGroupRolesLevelMemberInfo(ctx context.Context, groupID string, roleLevels []int32) ([]*model.GroupMember, error)
|
||||
GetGroupMemberNum(ctx context.Context, groupID string) (memberNum int64, err error)
|
||||
DelGroupsMemberNum(groupID ...string) GroupCache
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright © 2023 OpenIM. 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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
)
|
||||
|
||||
type MsgCache interface {
|
||||
GetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsg []*sdkws.MsgData, failedSeqList []int64, err error)
|
||||
SetMessageToCache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (int, error)
|
||||
UserDeleteMsgs(ctx context.Context, conversationID string, seqs []int64, userID string) error
|
||||
DelUserDeleteMsgsList(ctx context.Context, conversationID string, seqs []int64)
|
||||
DeleteMessages(ctx context.Context, conversationID string, seqs []int64) error
|
||||
GetUserDelList(ctx context.Context, userID, conversationID string) (seqs []int64, err error)
|
||||
CleanUpOneConversationAllMsg(ctx context.Context, conversationID string) error
|
||||
DelMsgFromCache(ctx context.Context, userID string, seqList []int64) error
|
||||
SetSendMsgStatus(ctx context.Context, id string, status int32) error
|
||||
GetSendMsgStatus(ctx context.Context, id string) (int32, error)
|
||||
JudgeMessageReactionExist(ctx context.Context, clientMsgID string, sessionType int32) (bool, error)
|
||||
GetOneMessageAllReactionList(ctx context.Context, clientMsgID string, sessionType int32) (map[string]string, error)
|
||||
DeleteOneMessageKey(ctx context.Context, clientMsgID string, sessionType int32, subKey string) error
|
||||
SetMessageReactionExpire(ctx context.Context, clientMsgID string, sessionType int32, expiration time.Duration) (bool, error)
|
||||
GetMessageTypeKeyValue(ctx context.Context, clientMsgID string, sessionType int32, typeKey string) (string, error)
|
||||
SetMessageTypeKeyValue(ctx context.Context, clientMsgID string, sessionType int32, typeKey, value string) error
|
||||
LockMessageTypeKey(ctx context.Context, clientMsgID string, TypeKey string) error
|
||||
UnLockMessageTypeKey(ctx context.Context, clientMsgID string, TypeKey string) error
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/localcache"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mw/specialerror"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BatchDeleterRedis is a concrete implementation of the BatchDeleter interface based on Redis and RocksCache.
|
||||
type BatchDeleterRedis struct {
|
||||
redisClient redis.UniversalClient
|
||||
keys []string
|
||||
rocksClient *rockscache.Client
|
||||
redisPubTopics []string
|
||||
}
|
||||
|
||||
// NewBatchDeleterRedis creates a new BatchDeleterRedis instance.
|
||||
func NewBatchDeleterRedis(redisClient redis.UniversalClient, options *rockscache.Options, redisPubTopics []string) *BatchDeleterRedis {
|
||||
return &BatchDeleterRedis{
|
||||
redisClient: redisClient,
|
||||
rocksClient: rockscache.NewClient(redisClient, *options),
|
||||
redisPubTopics: redisPubTopics,
|
||||
}
|
||||
}
|
||||
|
||||
// ExecDelWithKeys directly takes keys for batch deletion and publishes deletion information.
|
||||
func (c *BatchDeleterRedis) ExecDelWithKeys(ctx context.Context, keys []string) error {
|
||||
distinctKeys := datautil.Distinct(keys)
|
||||
return c.execDel(ctx, distinctKeys)
|
||||
}
|
||||
|
||||
// ChainExecDel is used for chain calls for batch deletion. It must call Clone to prevent memory pollution.
|
||||
func (c *BatchDeleterRedis) ChainExecDel(ctx context.Context) error {
|
||||
distinctKeys := datautil.Distinct(c.keys)
|
||||
return c.execDel(ctx, distinctKeys)
|
||||
}
|
||||
|
||||
// execDel performs batch deletion and publishes the keys that have been deleted to update the local cache information of other nodes.
|
||||
func (c *BatchDeleterRedis) execDel(ctx context.Context, keys []string) error {
|
||||
if len(keys) > 0 {
|
||||
log.ZDebug(ctx, "delete cache", "topic", c.redisPubTopics, "keys", keys)
|
||||
slotMapKeys, err := groupKeysBySlot(ctx, c.redisClient, keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Batch delete keys
|
||||
for slot, singleSlotKeys := range slotMapKeys {
|
||||
if err := c.rocksClient.TagAsDeletedBatch2(ctx, singleSlotKeys); err != nil {
|
||||
log.ZWarn(ctx, "Batch delete cache failed", err, "slot", slot, "keys", singleSlotKeys)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Publish the keys that have been deleted to Redis to update the local cache information of other nodes
|
||||
if len(c.redisPubTopics) > 0 && len(keys) > 0 {
|
||||
keysByTopic := localcache.GetPublishKeysByTopic(c.redisPubTopics, keys)
|
||||
for topic, keys := range keysByTopic {
|
||||
if len(keys) > 0 {
|
||||
data, err := json.Marshal(keys)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "keys json marshal failed", err, "topic", topic, "keys", keys)
|
||||
} else {
|
||||
if err := c.redisClient.Publish(ctx, topic, string(data)).Err(); err != nil {
|
||||
log.ZWarn(ctx, "redis publish cache delete error", err, "topic", topic, "keys", keys)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone creates a copy of BatchDeleterRedis for chain calls to prevent memory pollution.
|
||||
func (c *BatchDeleterRedis) Clone() cache.BatchDeleter {
|
||||
return &BatchDeleterRedis{
|
||||
redisClient: c.redisClient,
|
||||
keys: c.keys,
|
||||
rocksClient: c.rocksClient,
|
||||
redisPubTopics: c.redisPubTopics,
|
||||
}
|
||||
}
|
||||
|
||||
// AddKeys adds keys to be deleted.
|
||||
func (c *BatchDeleterRedis) AddKeys(keys ...string) {
|
||||
c.keys = append(c.keys, keys...)
|
||||
}
|
||||
|
||||
// GetRocksCacheOptions returns the default configuration options for RocksCache.
|
||||
func GetRocksCacheOptions() *rockscache.Options {
|
||||
opts := rockscache.NewDefaultOptions()
|
||||
opts.StrongConsistency = true
|
||||
opts.RandomExpireAdjustment = 0.2
|
||||
|
||||
return &opts
|
||||
}
|
||||
|
||||
// groupKeysBySlot groups keys by their Redis cluster hash slots.
|
||||
func groupKeysBySlot(ctx context.Context, redisClient redis.UniversalClient, keys []string) (map[int64][]string, error) {
|
||||
slots := make(map[int64][]string)
|
||||
clusterClient, isCluster := redisClient.(*redis.ClusterClient)
|
||||
if isCluster {
|
||||
pipe := clusterClient.Pipeline()
|
||||
cmds := make([]*redis.IntCmd, len(keys))
|
||||
for i, key := range keys {
|
||||
cmds[i] = pipe.ClusterKeySlot(ctx, key)
|
||||
}
|
||||
_, err := pipe.Exec(ctx)
|
||||
if err != nil {
|
||||
return nil, errs.WrapMsg(err, "get slot err")
|
||||
}
|
||||
|
||||
for i, cmd := range cmds {
|
||||
slot, err := cmd.Result()
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "some key get slot err", err, "key", keys[i])
|
||||
continue
|
||||
}
|
||||
slots[slot] = append(slots[slot], keys[i])
|
||||
}
|
||||
} else {
|
||||
// If not a cluster client, put all keys in the same slot (0)
|
||||
slots[0] = keys
|
||||
}
|
||||
|
||||
return slots, nil
|
||||
}
|
||||
|
||||
func getCache[T any](ctx context.Context, rcClient *rockscache.Client, key string, expire time.Duration, fn func(ctx context.Context) (T, error)) (T, error) {
|
||||
var t T
|
||||
var write bool
|
||||
v, err := rcClient.Fetch2(ctx, key, expire, func() (s string, err error) {
|
||||
t, err = fn(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
bs, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return "", errs.WrapMsg(err, "marshal failed")
|
||||
}
|
||||
write = true
|
||||
|
||||
return string(bs), nil
|
||||
})
|
||||
if err != nil {
|
||||
return t, errs.Wrap(err)
|
||||
}
|
||||
if write {
|
||||
return t, nil
|
||||
}
|
||||
if v == "" {
|
||||
return t, errs.ErrRecordNotFound.WrapMsg("cache is not found")
|
||||
}
|
||||
err = json.Unmarshal([]byte(v), &t)
|
||||
if err != nil {
|
||||
errInfo := fmt.Sprintf("cache json.Unmarshal failed, key:%s, value:%s, expire:%s", key, v, expire)
|
||||
return t, errs.WrapMsg(err, errInfo)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func batchGetCache[T any, K comparable](
|
||||
ctx context.Context,
|
||||
rcClient *rockscache.Client,
|
||||
expire time.Duration,
|
||||
keys []K,
|
||||
keyFn func(key K) string,
|
||||
fns func(ctx context.Context, key K) (T, error),
|
||||
) ([]T, error) {
|
||||
if len(keys) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
res := make([]T, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
val, err := getCache(ctx, rcClient, keyFn(key), expire, func(ctx context.Context) (T, error) {
|
||||
return fns(ctx, key)
|
||||
})
|
||||
if err != nil {
|
||||
if errs.ErrRecordNotFound.Is(specialerror.ErrCode(errs.Unwrap(err))) {
|
||||
continue
|
||||
}
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
res = append(res, val)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
blackExpireTime = time.Second * 60 * 60 * 12
|
||||
)
|
||||
|
||||
type BlackCacheRedis struct {
|
||||
cache.BatchDeleter
|
||||
expireTime time.Duration
|
||||
rcClient *rockscache.Client
|
||||
blackDB database.Black
|
||||
}
|
||||
|
||||
func NewBlackCacheRedis(rdb redis.UniversalClient, localCache *config.LocalCache, blackDB database.Black, options *rockscache.Options) cache.BlackCache {
|
||||
batchHandler := NewBatchDeleterRedis(rdb, options, []string{localCache.Friend.Topic})
|
||||
b := localCache.Friend
|
||||
log.ZDebug(context.Background(), "black local cache init", "Topic", b.Topic, "SlotNum", b.SlotNum, "SlotSize", b.SlotSize, "enable", b.Enable())
|
||||
return &BlackCacheRedis{
|
||||
BatchDeleter: batchHandler,
|
||||
expireTime: blackExpireTime,
|
||||
rcClient: rockscache.NewClient(rdb, *options),
|
||||
blackDB: blackDB,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BlackCacheRedis) CloneBlackCache() cache.BlackCache {
|
||||
return &BlackCacheRedis{
|
||||
BatchDeleter: b.BatchDeleter.Clone(),
|
||||
expireTime: b.expireTime,
|
||||
rcClient: b.rcClient,
|
||||
blackDB: b.blackDB,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BlackCacheRedis) getBlackIDsKey(ownerUserID string) string {
|
||||
return cachekey.GetBlackIDsKey(ownerUserID)
|
||||
}
|
||||
|
||||
func (b *BlackCacheRedis) GetBlackIDs(ctx context.Context, userID string) (blackIDs []string, err error) {
|
||||
return getCache(
|
||||
ctx,
|
||||
b.rcClient,
|
||||
b.getBlackIDsKey(userID),
|
||||
b.expireTime,
|
||||
func(ctx context.Context) ([]string, error) {
|
||||
return b.blackDB.FindBlackUserIDs(ctx, userID)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (b *BlackCacheRedis) DelBlackIDs(_ context.Context, userID string) cache.BlackCache {
|
||||
cache := b.CloneBlackCache()
|
||||
cache.AddKeys(b.getBlackIDsKey(userID))
|
||||
|
||||
return cache
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/encrypt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
conversationExpireTime = time.Second * 60 * 60 * 12
|
||||
)
|
||||
|
||||
func NewConversationRedis(rdb redis.UniversalClient, localCache *config.LocalCache, opts *rockscache.Options, db database.Conversation) cache.ConversationCache {
|
||||
batchHandler := NewBatchDeleterRedis(rdb, opts, []string{localCache.Conversation.Topic})
|
||||
c := localCache.Conversation
|
||||
log.ZDebug(context.Background(), "black local cache init", "Topic", c.Topic, "SlotNum", c.SlotNum, "SlotSize", c.SlotSize, "enable", c.Enable())
|
||||
return &ConversationRedisCache{
|
||||
BatchDeleter: batchHandler,
|
||||
rcClient: rockscache.NewClient(rdb, *opts),
|
||||
conversationDB: db,
|
||||
expireTime: conversationExpireTime,
|
||||
}
|
||||
}
|
||||
|
||||
type ConversationRedisCache struct {
|
||||
cache.BatchDeleter
|
||||
rcClient *rockscache.Client
|
||||
conversationDB database.Conversation
|
||||
expireTime time.Duration
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) CloneConversationCache() cache.ConversationCache {
|
||||
return &ConversationRedisCache{
|
||||
BatchDeleter: c.BatchDeleter.Clone(),
|
||||
rcClient: c.rcClient,
|
||||
conversationDB: c.conversationDB,
|
||||
expireTime: c.expireTime,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getConversationKey(ownerUserID, conversationID string) string {
|
||||
return cachekey.GetConversationKey(ownerUserID, conversationID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getConversationIDsKey(ownerUserID string) string {
|
||||
return cachekey.GetConversationIDsKey(ownerUserID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getSuperGroupRecvNotNotifyUserIDsKey(groupID string) string {
|
||||
return cachekey.GetSuperGroupRecvNotNotifyUserIDsKey(groupID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getRecvMsgOptKey(ownerUserID, conversationID string) string {
|
||||
return cachekey.GetRecvMsgOptKey(ownerUserID, conversationID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getSuperGroupRecvNotNotifyUserIDsHashKey(groupID string) string {
|
||||
return cachekey.GetSuperGroupRecvNotNotifyUserIDsHashKey(groupID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getConversationHasReadSeqKey(ownerUserID, conversationID string) string {
|
||||
return cachekey.GetConversationHasReadSeqKey(ownerUserID, conversationID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getConversationNotReceiveMessageUserIDsKey(conversationID string) string {
|
||||
return cachekey.GetConversationNotReceiveMessageUserIDsKey(conversationID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getUserConversationIDsHashKey(ownerUserID string) string {
|
||||
return cachekey.GetUserConversationIDsHashKey(ownerUserID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) GetUserConversationIDs(ctx context.Context, ownerUserID string) ([]string, error) {
|
||||
return getCache(ctx, c.rcClient, c.getConversationIDsKey(ownerUserID), c.expireTime, func(ctx context.Context) ([]string, error) {
|
||||
return c.conversationDB.FindUserIDAllConversationID(ctx, ownerUserID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelConversationIDs(userIDs ...string) cache.ConversationCache {
|
||||
keys := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
keys = append(keys, c.getConversationIDsKey(userID))
|
||||
}
|
||||
cache := c.CloneConversationCache()
|
||||
cache.AddKeys(keys...)
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) GetUserConversationIDsHash(ctx context.Context, ownerUserID string) (hash uint64, err error) {
|
||||
return getCache(
|
||||
ctx,
|
||||
c.rcClient,
|
||||
c.getUserConversationIDsHashKey(ownerUserID),
|
||||
c.expireTime,
|
||||
func(ctx context.Context) (uint64, error) {
|
||||
conversationIDs, err := c.GetUserConversationIDs(ctx, ownerUserID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
datautil.Sort(conversationIDs, true)
|
||||
bi := big.NewInt(0)
|
||||
bi.SetString(encrypt.Md5(strings.Join(conversationIDs, ";"))[0:8], 16)
|
||||
return bi.Uint64(), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelUserConversationIDsHash(ownerUserIDs ...string) cache.ConversationCache {
|
||||
keys := make([]string, 0, len(ownerUserIDs))
|
||||
for _, ownerUserID := range ownerUserIDs {
|
||||
keys = append(keys, c.getUserConversationIDsHashKey(ownerUserID))
|
||||
}
|
||||
cache := c.CloneConversationCache()
|
||||
cache.AddKeys(keys...)
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) GetConversation(ctx context.Context, ownerUserID, conversationID string) (*model.Conversation, error) {
|
||||
return getCache(ctx, c.rcClient, c.getConversationKey(ownerUserID, conversationID), c.expireTime, func(ctx context.Context) (*model.Conversation, error) {
|
||||
return c.conversationDB.Take(ctx, ownerUserID, conversationID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelConversations(ownerUserID string, conversationIDs ...string) cache.ConversationCache {
|
||||
keys := make([]string, 0, len(conversationIDs))
|
||||
for _, conversationID := range conversationIDs {
|
||||
keys = append(keys, c.getConversationKey(ownerUserID, conversationID))
|
||||
}
|
||||
cache := c.CloneConversationCache()
|
||||
cache.AddKeys(keys...)
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) GetConversations(ctx context.Context, ownerUserID string, conversationIDs []string) ([]*model.Conversation, error) {
|
||||
return batchGetCache(ctx, c.rcClient, c.expireTime, conversationIDs, func(conversationID string) string {
|
||||
return c.getConversationKey(ownerUserID, conversationID)
|
||||
}, func(ctx context.Context, conversationID string) (*model.Conversation, error) {
|
||||
return c.conversationDB.Take(ctx, ownerUserID, conversationID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) GetUserAllConversations(ctx context.Context, ownerUserID string) ([]*model.Conversation, error) {
|
||||
conversationIDs, err := c.GetUserConversationIDs(ctx, ownerUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.GetConversations(ctx, ownerUserID, conversationIDs)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) GetUserRecvMsgOpt(ctx context.Context, ownerUserID, conversationID string) (opt int, err error) {
|
||||
return getCache(ctx, c.rcClient, c.getRecvMsgOptKey(ownerUserID, conversationID), c.expireTime, func(ctx context.Context) (opt int, err error) {
|
||||
return c.conversationDB.GetUserRecvMsgOpt(ctx, ownerUserID, conversationID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelUsersConversation(conversationID string, ownerUserIDs ...string) cache.ConversationCache {
|
||||
keys := make([]string, 0, len(ownerUserIDs))
|
||||
for _, ownerUserID := range ownerUserIDs {
|
||||
keys = append(keys, c.getConversationKey(ownerUserID, conversationID))
|
||||
}
|
||||
cache := c.CloneConversationCache()
|
||||
cache.AddKeys(keys...)
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelUserRecvMsgOpt(ownerUserID, conversationID string) cache.ConversationCache {
|
||||
cache := c.CloneConversationCache()
|
||||
cache.AddKeys(c.getRecvMsgOptKey(ownerUserID, conversationID))
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelSuperGroupRecvMsgNotNotifyUserIDs(groupID string) cache.ConversationCache {
|
||||
cache := c.CloneConversationCache()
|
||||
cache.AddKeys(c.getSuperGroupRecvNotNotifyUserIDsKey(groupID))
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelSuperGroupRecvMsgNotNotifyUserIDsHash(groupID string) cache.ConversationCache {
|
||||
cache := c.CloneConversationCache()
|
||||
cache.AddKeys(c.getSuperGroupRecvNotNotifyUserIDsHashKey(groupID))
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelUserAllHasReadSeqs(ownerUserID string, conversationIDs ...string) cache.ConversationCache {
|
||||
cache := c.CloneConversationCache()
|
||||
for _, conversationID := range conversationIDs {
|
||||
cache.AddKeys(c.getConversationHasReadSeqKey(ownerUserID, conversationID))
|
||||
}
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) GetConversationsByConversationID(ctx context.Context, conversationIDs []string) ([]*model.Conversation, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelConversationByConversationID(conversationIDs ...string) cache.ConversationCache {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error) {
|
||||
return getCache(ctx, c.rcClient, c.getConversationNotReceiveMessageUserIDsKey(conversationID), c.expireTime, func(ctx context.Context) ([]string, error) {
|
||||
return c.conversationDB.GetConversationNotReceiveMessageUserIDs(ctx, conversationID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelConversationNotReceiveMessageUserIDs(conversationIDs ...string) cache.ConversationCache {
|
||||
cache := c.CloneConversationCache()
|
||||
for _, conversationID := range conversationIDs {
|
||||
cache.AddKeys(c.getConversationNotReceiveMessageUserIDsKey(conversationID))
|
||||
}
|
||||
|
||||
return cache
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
friendExpireTime = time.Second * 60 * 60 * 12
|
||||
)
|
||||
|
||||
// FriendCacheRedis is an implementation of the FriendCache interface using Redis.
|
||||
type FriendCacheRedis struct {
|
||||
cache.BatchDeleter
|
||||
friendDB database.Friend
|
||||
expireTime time.Duration
|
||||
rcClient *rockscache.Client
|
||||
}
|
||||
|
||||
// NewFriendCacheRedis creates a new instance of FriendCacheRedis.
|
||||
func NewFriendCacheRedis(rdb redis.UniversalClient, localCache *config.LocalCache, friendDB database.Friend,
|
||||
options *rockscache.Options) cache.FriendCache {
|
||||
batchHandler := NewBatchDeleterRedis(rdb, options, []string{localCache.Friend.Topic})
|
||||
f := localCache.Friend
|
||||
log.ZDebug(context.Background(), "friend local cache init", "Topic", f.Topic, "SlotNum", f.SlotNum, "SlotSize", f.SlotSize, "enable", f.Enable())
|
||||
return &FriendCacheRedis{
|
||||
BatchDeleter: batchHandler,
|
||||
friendDB: friendDB,
|
||||
expireTime: friendExpireTime,
|
||||
rcClient: rockscache.NewClient(rdb, *options),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FriendCacheRedis) CloneFriendCache() cache.FriendCache {
|
||||
return &FriendCacheRedis{
|
||||
BatchDeleter: f.BatchDeleter.Clone(),
|
||||
friendDB: f.friendDB,
|
||||
expireTime: f.expireTime,
|
||||
rcClient: f.rcClient,
|
||||
}
|
||||
}
|
||||
|
||||
// getFriendIDsKey returns the key for storing friend IDs in the cache.
|
||||
func (f *FriendCacheRedis) getFriendIDsKey(ownerUserID string) string {
|
||||
return cachekey.GetFriendIDsKey(ownerUserID)
|
||||
}
|
||||
|
||||
// getTwoWayFriendsIDsKey returns the key for storing two-way friend IDs in the cache.
|
||||
func (f *FriendCacheRedis) getTwoWayFriendsIDsKey(ownerUserID string) string {
|
||||
return cachekey.GetTwoWayFriendsIDsKey(ownerUserID)
|
||||
}
|
||||
|
||||
// getFriendKey returns the key for storing friend info in the cache.
|
||||
func (f *FriendCacheRedis) getFriendKey(ownerUserID, friendUserID string) string {
|
||||
return cachekey.GetFriendKey(ownerUserID, friendUserID)
|
||||
}
|
||||
|
||||
// GetFriendIDs retrieves friend IDs from the cache or the database if not found.
|
||||
func (f *FriendCacheRedis) GetFriendIDs(ctx context.Context, ownerUserID string) (friendIDs []string, err error) {
|
||||
return getCache(ctx, f.rcClient, f.getFriendIDsKey(ownerUserID), f.expireTime, func(ctx context.Context) ([]string, error) {
|
||||
return f.friendDB.FindFriendUserIDs(ctx, ownerUserID)
|
||||
})
|
||||
}
|
||||
|
||||
// DelFriendIDs deletes friend IDs from the cache.
|
||||
func (f *FriendCacheRedis) DelFriendIDs(ownerUserIDs ...string) cache.FriendCache {
|
||||
newFriendCache := f.CloneFriendCache()
|
||||
keys := make([]string, 0, len(ownerUserIDs))
|
||||
for _, userID := range ownerUserIDs {
|
||||
keys = append(keys, f.getFriendIDsKey(userID))
|
||||
}
|
||||
newFriendCache.AddKeys(keys...)
|
||||
|
||||
return newFriendCache
|
||||
}
|
||||
|
||||
// GetTwoWayFriendIDs retrieves two-way friend IDs from the cache.
|
||||
func (f *FriendCacheRedis) GetTwoWayFriendIDs(ctx context.Context, ownerUserID string) (twoWayFriendIDs []string, err error) {
|
||||
friendIDs, err := f.GetFriendIDs(ctx, ownerUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, friendID := range friendIDs {
|
||||
friendFriendID, err := f.GetFriendIDs(ctx, friendID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if datautil.Contain(ownerUserID, friendFriendID...) {
|
||||
twoWayFriendIDs = append(twoWayFriendIDs, ownerUserID)
|
||||
}
|
||||
}
|
||||
|
||||
return twoWayFriendIDs, nil
|
||||
}
|
||||
|
||||
// DelTwoWayFriendIDs deletes two-way friend IDs from the cache.
|
||||
func (f *FriendCacheRedis) DelTwoWayFriendIDs(ctx context.Context, ownerUserID string) cache.FriendCache {
|
||||
newFriendCache := f.CloneFriendCache()
|
||||
newFriendCache.AddKeys(f.getTwoWayFriendsIDsKey(ownerUserID))
|
||||
|
||||
return newFriendCache
|
||||
}
|
||||
|
||||
// GetFriend retrieves friend info from the cache or the database if not found.
|
||||
func (f *FriendCacheRedis) GetFriend(ctx context.Context, ownerUserID, friendUserID string) (friend *model.Friend, err error) {
|
||||
return getCache(ctx, f.rcClient, f.getFriendKey(ownerUserID,
|
||||
friendUserID), f.expireTime, func(ctx context.Context) (*model.Friend, error) {
|
||||
return f.friendDB.Take(ctx, ownerUserID, friendUserID)
|
||||
})
|
||||
}
|
||||
|
||||
// DelFriend deletes friend info from the cache.
|
||||
func (f *FriendCacheRedis) DelFriend(ownerUserID, friendUserID string) cache.FriendCache {
|
||||
newFriendCache := f.CloneFriendCache()
|
||||
newFriendCache.AddKeys(f.getFriendKey(ownerUserID, friendUserID))
|
||||
|
||||
return newFriendCache
|
||||
}
|
||||
|
||||
// DelFriends deletes multiple friend infos from the cache.
|
||||
func (f *FriendCacheRedis) DelFriends(ownerUserID string, friendUserIDs []string) cache.FriendCache {
|
||||
newFriendCache := f.CloneFriendCache()
|
||||
|
||||
for _, friendUserID := range friendUserIDs {
|
||||
key := f.getFriendKey(ownerUserID, friendUserID)
|
||||
newFriendCache.AddKeys(key) // Assuming AddKeys marks the keys for deletion
|
||||
}
|
||||
|
||||
return newFriendCache
|
||||
}
|
||||
+408
@@ -0,0 +1,408 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/common"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
groupExpireTime = time.Second * 60 * 60 * 12
|
||||
)
|
||||
|
||||
var errIndex = errs.New("err index")
|
||||
|
||||
type GroupCacheRedis struct {
|
||||
cache.BatchDeleter
|
||||
groupDB database.Group
|
||||
groupMemberDB database.GroupMember
|
||||
groupRequestDB database.GroupRequest
|
||||
expireTime time.Duration
|
||||
rcClient *rockscache.Client
|
||||
groupHash cache.GroupHash
|
||||
}
|
||||
|
||||
func NewGroupCacheRedis(
|
||||
rdb redis.UniversalClient,
|
||||
localCache *config.LocalCache,
|
||||
groupDB database.Group,
|
||||
groupMemberDB database.GroupMember,
|
||||
groupRequestDB database.GroupRequest,
|
||||
hashCode cache.GroupHash,
|
||||
opts *rockscache.Options,
|
||||
) cache.GroupCache {
|
||||
batchHandler := NewBatchDeleterRedis(rdb, opts, []string{localCache.Group.Topic})
|
||||
g := localCache.Group
|
||||
log.ZDebug(context.Background(), "group local cache init", "Topic", g.Topic, "SlotNum", g.SlotNum, "SlotSize", g.SlotSize, "enable", g.Enable())
|
||||
|
||||
return &GroupCacheRedis{
|
||||
BatchDeleter: batchHandler,
|
||||
rcClient: rockscache.NewClient(rdb, *opts),
|
||||
expireTime: groupExpireTime,
|
||||
groupDB: groupDB,
|
||||
groupMemberDB: groupMemberDB,
|
||||
groupRequestDB: groupRequestDB,
|
||||
groupHash: hashCode,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) CloneGroupCache() cache.GroupCache {
|
||||
return &GroupCacheRedis{
|
||||
BatchDeleter: g.BatchDeleter.Clone(),
|
||||
rcClient: g.rcClient,
|
||||
expireTime: g.expireTime,
|
||||
groupDB: g.groupDB,
|
||||
groupMemberDB: g.groupMemberDB,
|
||||
groupRequestDB: g.groupRequestDB,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) getGroupInfoKey(groupID string) string {
|
||||
return cachekey.GetGroupInfoKey(groupID)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) getJoinedGroupsKey(userID string) string {
|
||||
return cachekey.GetJoinedGroupsKey(userID)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) getGroupMembersHashKey(groupID string) string {
|
||||
return cachekey.GetGroupMembersHashKey(groupID)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) getGroupMemberIDsKey(groupID string) string {
|
||||
return cachekey.GetGroupMemberIDsKey(groupID)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) getGroupMemberInfoKey(groupID, userID string) string {
|
||||
return cachekey.GetGroupMemberInfoKey(groupID, userID)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) getGroupMemberNumKey(groupID string) string {
|
||||
return cachekey.GetGroupMemberNumKey(groupID)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) getGroupRoleLevelMemberIDsKey(groupID string, roleLevel int32) string {
|
||||
return cachekey.GetGroupRoleLevelMemberIDsKey(groupID, roleLevel)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupIndex(group *model.Group, keys []string) (int, error) {
|
||||
key := g.getGroupInfoKey(group.GroupID)
|
||||
for i, _key := range keys {
|
||||
if _key == key {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, errIndex
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupMemberIndex(groupMember *model.GroupMember, keys []string) (int, error) {
|
||||
key := g.getGroupMemberInfoKey(groupMember.GroupID, groupMember.UserID)
|
||||
for i, _key := range keys {
|
||||
if _key == key {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, errIndex
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupsInfo(ctx context.Context, groupIDs []string) (groups []*model.Group, err error) {
|
||||
return batchGetCache(ctx, g.rcClient, g.expireTime, groupIDs, func(groupID string) string {
|
||||
return g.getGroupInfoKey(groupID)
|
||||
}, func(ctx context.Context, groupID string) (*model.Group, error) {
|
||||
return g.groupDB.Take(ctx, groupID)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupInfo(ctx context.Context, groupID string) (group *model.Group, err error) {
|
||||
return getCache(ctx, g.rcClient, g.getGroupInfoKey(groupID), g.expireTime, func(ctx context.Context) (*model.Group, error) {
|
||||
return g.groupDB.Take(ctx, groupID)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) DelGroupsInfo(groupIDs ...string) cache.GroupCache {
|
||||
newGroupCache := g.CloneGroupCache()
|
||||
keys := make([]string, 0, len(groupIDs))
|
||||
for _, groupID := range groupIDs {
|
||||
keys = append(keys, g.getGroupInfoKey(groupID))
|
||||
}
|
||||
newGroupCache.AddKeys(keys...)
|
||||
|
||||
return newGroupCache
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) DelGroupsOwner(groupIDs ...string) cache.GroupCache {
|
||||
newGroupCache := g.CloneGroupCache()
|
||||
keys := make([]string, 0, len(groupIDs))
|
||||
for _, groupID := range groupIDs {
|
||||
keys = append(keys, g.getGroupRoleLevelMemberIDsKey(groupID, constant.GroupOwner))
|
||||
}
|
||||
newGroupCache.AddKeys(keys...)
|
||||
|
||||
return newGroupCache
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) DelGroupRoleLevel(groupID string, roleLevels []int32) cache.GroupCache {
|
||||
newGroupCache := g.CloneGroupCache()
|
||||
keys := make([]string, 0, len(roleLevels))
|
||||
for _, roleLevel := range roleLevels {
|
||||
keys = append(keys, g.getGroupRoleLevelMemberIDsKey(groupID, roleLevel))
|
||||
}
|
||||
newGroupCache.AddKeys(keys...)
|
||||
return newGroupCache
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) DelGroupAllRoleLevel(groupID string) cache.GroupCache {
|
||||
return g.DelGroupRoleLevel(groupID, []int32{constant.GroupOwner, constant.GroupAdmin, constant.GroupOrdinaryUsers})
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupMembersHash(ctx context.Context, groupID string) (hashCode uint64, err error) {
|
||||
if g.groupHash == nil {
|
||||
return 0, errs.ErrInternalServer.WrapMsg("group hash is nil")
|
||||
}
|
||||
return getCache(ctx, g.rcClient, g.getGroupMembersHashKey(groupID), g.expireTime, func(ctx context.Context) (uint64, error) {
|
||||
return g.groupHash.GetGroupHash(ctx, groupID)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupMemberHashMap(ctx context.Context, groupIDs []string) (map[string]*common.GroupSimpleUserID, error) {
|
||||
if g.groupHash == nil {
|
||||
return nil, errs.ErrInternalServer.WrapMsg("group hash is nil")
|
||||
}
|
||||
res := make(map[string]*common.GroupSimpleUserID)
|
||||
for _, groupID := range groupIDs {
|
||||
hash, err := g.GetGroupMembersHash(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.ZDebug(ctx, "GetGroupMemberHashMap", "groupID", groupID, "hash", hash)
|
||||
num, err := g.GetGroupMemberNum(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res[groupID] = &common.GroupSimpleUserID{Hash: hash, MemberNum: uint32(num)}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) DelGroupMembersHash(groupID string) cache.GroupCache {
|
||||
cache := g.CloneGroupCache()
|
||||
cache.AddKeys(g.getGroupMembersHashKey(groupID))
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupMemberIDs(ctx context.Context, groupID string) (groupMemberIDs []string, err error) {
|
||||
return getCache(ctx, g.rcClient, g.getGroupMemberIDsKey(groupID), g.expireTime, func(ctx context.Context) ([]string, error) {
|
||||
return g.groupMemberDB.FindMemberUserID(ctx, groupID)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupsMemberIDs(ctx context.Context, groupIDs []string) (map[string][]string, error) {
|
||||
m := make(map[string][]string)
|
||||
for _, groupID := range groupIDs {
|
||||
userIDs, err := g.GetGroupMemberIDs(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[groupID] = userIDs
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) DelGroupMemberIDs(groupID string) cache.GroupCache {
|
||||
cache := g.CloneGroupCache()
|
||||
cache.AddKeys(g.getGroupMemberIDsKey(groupID))
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetJoinedGroupIDs(ctx context.Context, userID string) (joinedGroupIDs []string, err error) {
|
||||
return getCache(ctx, g.rcClient, g.getJoinedGroupsKey(userID), g.expireTime, func(ctx context.Context) ([]string, error) {
|
||||
return g.groupMemberDB.FindUserJoinedGroupID(ctx, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) DelJoinedGroupID(userIDs ...string) cache.GroupCache {
|
||||
keys := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
keys = append(keys, g.getJoinedGroupsKey(userID))
|
||||
}
|
||||
cache := g.CloneGroupCache()
|
||||
cache.AddKeys(keys...)
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupMemberInfo(ctx context.Context, groupID, userID string) (groupMember *model.GroupMember, err error) {
|
||||
return getCache(ctx, g.rcClient, g.getGroupMemberInfoKey(groupID, userID), g.expireTime, func(ctx context.Context) (*model.GroupMember, error) {
|
||||
return g.groupMemberDB.Take(ctx, groupID, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupMembersInfo(ctx context.Context, groupID string, userIDs []string) ([]*model.GroupMember, error) {
|
||||
return batchGetCache(ctx, g.rcClient, g.expireTime, userIDs, func(userID string) string {
|
||||
return g.getGroupMemberInfoKey(groupID, userID)
|
||||
}, func(ctx context.Context, userID string) (*model.GroupMember, error) {
|
||||
return g.groupMemberDB.Take(ctx, groupID, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupMembersPage(
|
||||
ctx context.Context,
|
||||
groupID string,
|
||||
userIDs []string,
|
||||
showNumber, pageNumber int32,
|
||||
) (total uint32, groupMembers []*model.GroupMember, err error) {
|
||||
groupMemberIDs, err := g.GetGroupMemberIDs(ctx, groupID)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if userIDs != nil {
|
||||
userIDs = datautil.BothExist(userIDs, groupMemberIDs)
|
||||
} else {
|
||||
userIDs = groupMemberIDs
|
||||
}
|
||||
groupMembers, err = g.GetGroupMembersInfo(ctx, groupID, datautil.Paginate(userIDs, int(showNumber), int(showNumber)))
|
||||
|
||||
return uint32(len(userIDs)), groupMembers, err
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetAllGroupMembersInfo(ctx context.Context, groupID string) (groupMembers []*model.GroupMember, err error) {
|
||||
groupMemberIDs, err := g.GetGroupMemberIDs(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return g.GetGroupMembersInfo(ctx, groupID, groupMemberIDs)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetAllGroupMemberInfo(ctx context.Context, groupID string) ([]*model.GroupMember, error) {
|
||||
groupMemberIDs, err := g.GetGroupMemberIDs(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return g.GetGroupMembersInfo(ctx, groupID, groupMemberIDs)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) DelGroupMembersInfo(groupID string, userIDs ...string) cache.GroupCache {
|
||||
keys := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
keys = append(keys, g.getGroupMemberInfoKey(groupID, userID))
|
||||
}
|
||||
cache := g.CloneGroupCache()
|
||||
cache.AddKeys(keys...)
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupMemberNum(ctx context.Context, groupID string) (memberNum int64, err error) {
|
||||
return getCache(ctx, g.rcClient, g.getGroupMemberNumKey(groupID), g.expireTime, func(ctx context.Context) (int64, error) {
|
||||
return g.groupMemberDB.TakeGroupMemberNum(ctx, groupID)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) DelGroupsMemberNum(groupID ...string) cache.GroupCache {
|
||||
keys := make([]string, 0, len(groupID))
|
||||
for _, groupID := range groupID {
|
||||
keys = append(keys, g.getGroupMemberNumKey(groupID))
|
||||
}
|
||||
cache := g.CloneGroupCache()
|
||||
cache.AddKeys(keys...)
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupOwner(ctx context.Context, groupID string) (*model.GroupMember, error) {
|
||||
members, err := g.GetGroupRoleLevelMemberInfo(ctx, groupID, constant.GroupOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(members) == 0 {
|
||||
return nil, errs.ErrRecordNotFound.WrapMsg(fmt.Sprintf("group %s owner not found", groupID))
|
||||
}
|
||||
return members[0], nil
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupsOwner(ctx context.Context, groupIDs []string) ([]*model.GroupMember, error) {
|
||||
members := make([]*model.GroupMember, 0, len(groupIDs))
|
||||
for _, groupID := range groupIDs {
|
||||
items, err := g.GetGroupRoleLevelMemberInfo(ctx, groupID, constant.GroupOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(items) > 0 {
|
||||
members = append(members, items[0])
|
||||
}
|
||||
}
|
||||
return members, nil
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupRoleLevelMemberIDs(ctx context.Context, groupID string, roleLevel int32) ([]string, error) {
|
||||
return getCache(ctx, g.rcClient, g.getGroupRoleLevelMemberIDsKey(groupID, roleLevel), g.expireTime, func(ctx context.Context) ([]string, error) {
|
||||
return g.groupMemberDB.FindRoleLevelUserIDs(ctx, groupID, roleLevel)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupRoleLevelMemberInfo(ctx context.Context, groupID string, roleLevel int32) ([]*model.GroupMember, error) {
|
||||
userIDs, err := g.GetGroupRoleLevelMemberIDs(ctx, groupID, roleLevel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return g.GetGroupMembersInfo(ctx, groupID, userIDs)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) GetGroupRolesLevelMemberInfo(ctx context.Context, groupID string, roleLevels []int32) ([]*model.GroupMember, error) {
|
||||
var userIDs []string
|
||||
for _, roleLevel := range roleLevels {
|
||||
ids, err := g.GetGroupRoleLevelMemberIDs(ctx, groupID, roleLevel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIDs = append(userIDs, ids...)
|
||||
}
|
||||
return g.GetGroupMembersInfo(ctx, groupID, userIDs)
|
||||
}
|
||||
|
||||
func (g *GroupCacheRedis) FindGroupMemberUser(ctx context.Context, groupIDs []string, userID string) (_ []*model.GroupMember, err error) {
|
||||
if len(groupIDs) == 0 {
|
||||
groupIDs, err = g.GetJoinedGroupIDs(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return batchGetCache(ctx, g.rcClient, g.expireTime, groupIDs, func(groupID string) string {
|
||||
return g.getGroupMemberInfoKey(groupID, userID)
|
||||
}, func(ctx context.Context, groupID string) (*model.GroupMember, error) {
|
||||
return g.groupMemberDB.Take(ctx, groupID, userID)
|
||||
})
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/gogo/protobuf/jsonpb"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"time"
|
||||
)
|
||||
|
||||
const msgCacheTimeout = 86400 * time.Second
|
||||
|
||||
var concurrentLimit = 3
|
||||
|
||||
func NewMsgCache(client redis.UniversalClient, redisEnablePipeline bool) cache.MsgCache {
|
||||
return &msgCache{rdb: client, msgCacheTimeout: msgCacheTimeout, redisEnablePipeline: redisEnablePipeline}
|
||||
}
|
||||
|
||||
type msgCache struct {
|
||||
rdb redis.UniversalClient
|
||||
msgCacheTimeout time.Duration
|
||||
redisEnablePipeline bool
|
||||
}
|
||||
|
||||
func (c *msgCache) getAllMessageCacheKey(conversationID string) string {
|
||||
return cachekey.GetAllMessageCacheKey(conversationID)
|
||||
}
|
||||
|
||||
func (c *msgCache) getMessageCacheKey(conversationID string, seq int64) string {
|
||||
return cachekey.GetMessageCacheKey(conversationID, seq)
|
||||
}
|
||||
func (c *msgCache) getMessageDelUserListKey(conversationID string, seq int64) string {
|
||||
return cachekey.GetMessageDelUserListKey(conversationID, seq)
|
||||
}
|
||||
|
||||
func (c *msgCache) getUserDelList(conversationID, userID string) string {
|
||||
return cachekey.GetUserDelListKey(conversationID, userID)
|
||||
}
|
||||
|
||||
func (c *msgCache) getSendMsgKey(id string) string {
|
||||
return cachekey.GetSendMsgKey(id)
|
||||
}
|
||||
|
||||
func (c *msgCache) getLockMessageTypeKey(clientMsgID string, TypeKey string) string {
|
||||
return cachekey.GetLockMessageTypeKey(clientMsgID, TypeKey)
|
||||
}
|
||||
|
||||
func (c *msgCache) getMessageReactionExPrefix(clientMsgID string, sessionType int32) string {
|
||||
return cachekey.GetMessageReactionExKey(clientMsgID, sessionType)
|
||||
}
|
||||
|
||||
func (c *msgCache) SetMessageToCache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (int, error) {
|
||||
if c.redisEnablePipeline {
|
||||
return c.PipeSetMessageToCache(ctx, conversationID, msgs)
|
||||
}
|
||||
return c.ParallelSetMessageToCache(ctx, conversationID, msgs)
|
||||
}
|
||||
|
||||
func (c *msgCache) PipeSetMessageToCache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (int, error) {
|
||||
pipe := c.rdb.Pipeline()
|
||||
for _, msg := range msgs {
|
||||
s, err := msgprocessor.Pb2String(msg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
key := c.getMessageCacheKey(conversationID, msg.Seq)
|
||||
_ = pipe.Set(ctx, key, s, c.msgCacheTimeout)
|
||||
}
|
||||
|
||||
results, err := pipe.Exec(ctx)
|
||||
if err != nil {
|
||||
return 0, errs.Wrap(err)
|
||||
}
|
||||
|
||||
for _, res := range results {
|
||||
if res.Err() != nil {
|
||||
return 0, errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return len(msgs), nil
|
||||
}
|
||||
|
||||
func (c *msgCache) ParallelSetMessageToCache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (int, error) {
|
||||
wg := errgroup.Group{}
|
||||
wg.SetLimit(concurrentLimit)
|
||||
|
||||
for _, msg := range msgs {
|
||||
msg := msg // closure safe var
|
||||
wg.Go(func() error {
|
||||
s, err := msgprocessor.Pb2String(msg)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
key := c.getMessageCacheKey(conversationID, msg.Seq)
|
||||
if err := c.rdb.Set(ctx, key, s, c.msgCacheTimeout).Err(); err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
err := wg.Wait()
|
||||
if err != nil {
|
||||
return 0, errs.WrapMsg(err, "wg.Wait failed")
|
||||
}
|
||||
|
||||
return len(msgs), nil
|
||||
}
|
||||
|
||||
func (c *msgCache) UserDeleteMsgs(ctx context.Context, conversationID string, seqs []int64, userID string) error {
|
||||
for _, seq := range seqs {
|
||||
delUserListKey := c.getMessageDelUserListKey(conversationID, seq)
|
||||
userDelListKey := c.getUserDelList(conversationID, userID)
|
||||
err := c.rdb.SAdd(ctx, delUserListKey, userID).Err()
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
err = c.rdb.SAdd(ctx, userDelListKey, seq).Err()
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
if err := c.rdb.Expire(ctx, delUserListKey, c.msgCacheTimeout).Err(); err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
if err := c.rdb.Expire(ctx, userDelListKey, c.msgCacheTimeout).Err(); err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *msgCache) GetUserDelList(ctx context.Context, userID, conversationID string) (seqs []int64, err error) {
|
||||
result, err := c.rdb.SMembers(ctx, c.getUserDelList(conversationID, userID)).Result()
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
seqs = make([]int64, len(result))
|
||||
for i, v := range result {
|
||||
seqs[i] = stringutil.StringToInt64(v)
|
||||
}
|
||||
|
||||
return seqs, nil
|
||||
}
|
||||
|
||||
func (c *msgCache) DelUserDeleteMsgsList(ctx context.Context, conversationID string, seqs []int64) {
|
||||
for _, seq := range seqs {
|
||||
delUsers, err := c.rdb.SMembers(ctx, c.getMessageDelUserListKey(conversationID, seq)).Result()
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "DelUserDeleteMsgsList failed", err, "conversationID", conversationID, "seq", seq)
|
||||
|
||||
continue
|
||||
}
|
||||
if len(delUsers) > 0 {
|
||||
var failedFlag bool
|
||||
for _, userID := range delUsers {
|
||||
err = c.rdb.SRem(ctx, c.getUserDelList(conversationID, userID), seq).Err()
|
||||
if err != nil {
|
||||
failedFlag = true
|
||||
log.ZWarn(ctx, "DelUserDeleteMsgsList failed", err, "conversationID", conversationID, "seq", seq, "userID", userID)
|
||||
}
|
||||
}
|
||||
if !failedFlag {
|
||||
if err := c.rdb.Del(ctx, c.getMessageDelUserListKey(conversationID, seq)).Err(); err != nil {
|
||||
log.ZWarn(ctx, "DelUserDeleteMsgsList failed", err, "conversationID", conversationID, "seq", seq)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *msgCache) DeleteMessages(ctx context.Context, conversationID string, seqs []int64) error {
|
||||
if c.redisEnablePipeline {
|
||||
return c.PipeDeleteMessages(ctx, conversationID, seqs)
|
||||
}
|
||||
|
||||
return c.ParallelDeleteMessages(ctx, conversationID, seqs)
|
||||
}
|
||||
|
||||
func (c *msgCache) ParallelDeleteMessages(ctx context.Context, conversationID string, seqs []int64) error {
|
||||
wg := errgroup.Group{}
|
||||
wg.SetLimit(concurrentLimit)
|
||||
|
||||
for _, seq := range seqs {
|
||||
seq := seq
|
||||
wg.Go(func() error {
|
||||
err := c.rdb.Del(ctx, c.getMessageCacheKey(conversationID, seq)).Err()
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
func (c *msgCache) PipeDeleteMessages(ctx context.Context, conversationID string, seqs []int64) error {
|
||||
pipe := c.rdb.Pipeline()
|
||||
for _, seq := range seqs {
|
||||
_ = pipe.Del(ctx, c.getMessageCacheKey(conversationID, seq))
|
||||
}
|
||||
|
||||
results, err := pipe.Exec(ctx)
|
||||
if err != nil {
|
||||
return errs.WrapMsg(err, "pipe.del")
|
||||
}
|
||||
|
||||
for _, res := range results {
|
||||
if res.Err() != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *msgCache) CleanUpOneConversationAllMsg(ctx context.Context, conversationID string) error {
|
||||
vals, err := c.rdb.Keys(ctx, c.getAllMessageCacheKey(conversationID)).Result()
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
for _, v := range vals {
|
||||
if err := c.rdb.Del(ctx, v).Err(); err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *msgCache) DelMsgFromCache(ctx context.Context, userID string, seqs []int64) error {
|
||||
for _, seq := range seqs {
|
||||
key := c.getMessageCacheKey(userID, seq)
|
||||
result, err := c.rdb.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
continue
|
||||
}
|
||||
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
var msg sdkws.MsgData
|
||||
err = jsonpb.UnmarshalString(result, &msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg.Status = constant.MsgDeleted
|
||||
s, err := msgprocessor.Pb2String(&msg)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
if err := c.rdb.Set(ctx, key, s, c.msgCacheTimeout).Err(); err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *msgCache) SetSendMsgStatus(ctx context.Context, id string, status int32) error {
|
||||
return errs.Wrap(c.rdb.Set(ctx, c.getSendMsgKey(id), status, time.Hour*24).Err())
|
||||
}
|
||||
|
||||
func (c *msgCache) GetSendMsgStatus(ctx context.Context, id string) (int32, error) {
|
||||
result, err := c.rdb.Get(ctx, c.getSendMsgKey(id)).Int()
|
||||
return int32(result), errs.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *msgCache) LockMessageTypeKey(ctx context.Context, clientMsgID string, TypeKey string) error {
|
||||
key := c.getLockMessageTypeKey(clientMsgID, TypeKey)
|
||||
return errs.Wrap(c.rdb.SetNX(ctx, key, 1, time.Minute).Err())
|
||||
}
|
||||
|
||||
func (c *msgCache) UnLockMessageTypeKey(ctx context.Context, clientMsgID string, TypeKey string) error {
|
||||
key := c.getLockMessageTypeKey(clientMsgID, TypeKey)
|
||||
return errs.Wrap(c.rdb.Del(ctx, key).Err())
|
||||
}
|
||||
|
||||
func (c *msgCache) JudgeMessageReactionExist(ctx context.Context, clientMsgID string, sessionType int32) (bool, error) {
|
||||
n, err := c.rdb.Exists(ctx, c.getMessageReactionExPrefix(clientMsgID, sessionType)).Result()
|
||||
if err != nil {
|
||||
return false, errs.Wrap(err)
|
||||
}
|
||||
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
func (c *msgCache) SetMessageTypeKeyValue(ctx context.Context, clientMsgID string, sessionType int32, typeKey, value string) error {
|
||||
return errs.Wrap(c.rdb.HSet(ctx, c.getMessageReactionExPrefix(clientMsgID, sessionType), typeKey, value).Err())
|
||||
}
|
||||
|
||||
func (c *msgCache) SetMessageReactionExpire(ctx context.Context, clientMsgID string, sessionType int32, expiration time.Duration) (bool, error) {
|
||||
val, err := c.rdb.Expire(ctx, c.getMessageReactionExPrefix(clientMsgID, sessionType), expiration).Result()
|
||||
return val, errs.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *msgCache) GetMessageTypeKeyValue(ctx context.Context, clientMsgID string, sessionType int32, typeKey string) (string, error) {
|
||||
val, err := c.rdb.HGet(ctx, c.getMessageReactionExPrefix(clientMsgID, sessionType), typeKey).Result()
|
||||
return val, errs.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *msgCache) GetOneMessageAllReactionList(ctx context.Context, clientMsgID string, sessionType int32) (map[string]string, error) {
|
||||
val, err := c.rdb.HGetAll(ctx, c.getMessageReactionExPrefix(clientMsgID, sessionType)).Result()
|
||||
return val, errs.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *msgCache) DeleteOneMessageKey(ctx context.Context, clientMsgID string, sessionType int32, subKey string) error {
|
||||
return errs.Wrap(c.rdb.HDel(ctx, c.getMessageReactionExPrefix(clientMsgID, sessionType), subKey).Err())
|
||||
}
|
||||
|
||||
func (c *msgCache) GetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) {
|
||||
if c.redisEnablePipeline {
|
||||
return c.PipeGetMessagesBySeq(ctx, conversationID, seqs)
|
||||
}
|
||||
|
||||
return c.ParallelGetMessagesBySeq(ctx, conversationID, seqs)
|
||||
}
|
||||
|
||||
func (c *msgCache) PipeGetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) {
|
||||
pipe := c.rdb.Pipeline()
|
||||
|
||||
results := []*redis.StringCmd{}
|
||||
for _, seq := range seqs {
|
||||
results = append(results, pipe.Get(ctx, c.getMessageCacheKey(conversationID, seq)))
|
||||
}
|
||||
|
||||
_, err = pipe.Exec(ctx)
|
||||
if err != nil && err != redis.Nil {
|
||||
return seqMsgs, failedSeqs, errs.WrapMsg(err, "pipe.get")
|
||||
}
|
||||
|
||||
for idx, res := range results {
|
||||
seq := seqs[idx]
|
||||
if res.Err() != nil {
|
||||
log.ZError(ctx, "GetMessagesBySeq failed", err, "conversationID", conversationID, "seq", seq, "err", res.Err())
|
||||
failedSeqs = append(failedSeqs, seq)
|
||||
continue
|
||||
}
|
||||
|
||||
msg := sdkws.MsgData{}
|
||||
if err = msgprocessor.String2Pb(res.Val(), &msg); err != nil {
|
||||
log.ZError(ctx, "GetMessagesBySeq Unmarshal failed", err, "res", res, "conversationID", conversationID, "seq", seq)
|
||||
failedSeqs = append(failedSeqs, seq)
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.Status == constant.MsgDeleted {
|
||||
failedSeqs = append(failedSeqs, seq)
|
||||
continue
|
||||
}
|
||||
|
||||
seqMsgs = append(seqMsgs, &msg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *msgCache) ParallelGetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) {
|
||||
type entry struct {
|
||||
err error
|
||||
msg *sdkws.MsgData
|
||||
}
|
||||
|
||||
wg := errgroup.Group{}
|
||||
wg.SetLimit(concurrentLimit)
|
||||
|
||||
results := make([]entry, len(seqs)) // set slice len/cap to length of seqs.
|
||||
for idx, seq := range seqs {
|
||||
// closure safe var
|
||||
idx := idx
|
||||
seq := seq
|
||||
|
||||
wg.Go(func() error {
|
||||
res, err := c.rdb.Get(ctx, c.getMessageCacheKey(conversationID, seq)).Result()
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetMessagesBySeq failed", err, "conversationID", conversationID, "seq", seq)
|
||||
results[idx] = entry{err: err}
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := sdkws.MsgData{}
|
||||
if err = msgprocessor.String2Pb(res, &msg); err != nil {
|
||||
log.ZError(ctx, "GetMessagesBySeq Unmarshal failed", err, "res", res, "conversationID", conversationID, "seq", seq)
|
||||
results[idx] = entry{err: err}
|
||||
return nil
|
||||
}
|
||||
|
||||
if msg.Status == constant.MsgDeleted {
|
||||
results[idx] = entry{err: err}
|
||||
return nil
|
||||
}
|
||||
|
||||
results[idx] = entry{msg: &msg}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
_ = wg.Wait()
|
||||
|
||||
for idx, res := range results {
|
||||
if res.err != nil {
|
||||
failedSeqs = append(failedSeqs, seqs[idx])
|
||||
continue
|
||||
}
|
||||
|
||||
seqMsgs = append(seqMsgs, res.msg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParallelSetMessageToCache(t *testing.T) {
|
||||
var (
|
||||
cid = fmt.Sprintf("cid-%v", rand.Int63())
|
||||
seqFirst = rand.Int63()
|
||||
msgs = []*sdkws.MsgData{}
|
||||
)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
msgs = append(msgs, &sdkws.MsgData{
|
||||
Seq: seqFirst + int64(i),
|
||||
})
|
||||
}
|
||||
|
||||
testParallelSetMessageToCache(t, cid, msgs)
|
||||
}
|
||||
|
||||
func testParallelSetMessageToCache(t *testing.T, cid string, msgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
ret, err := cacher.ParallelSetMessageToCache(context.Background(), cid, msgs)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(msgs), ret)
|
||||
|
||||
// validate
|
||||
for _, msg := range msgs {
|
||||
key := cacher.getMessageCacheKey(cid, msg.Seq)
|
||||
val, err := rdb.Exists(context.Background(), key).Result()
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, 1, val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipeSetMessageToCache(t *testing.T) {
|
||||
var (
|
||||
cid = fmt.Sprintf("cid-%v", rand.Int63())
|
||||
seqFirst = rand.Int63()
|
||||
msgs = []*sdkws.MsgData{}
|
||||
)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
msgs = append(msgs, &sdkws.MsgData{
|
||||
Seq: seqFirst + int64(i),
|
||||
})
|
||||
}
|
||||
|
||||
testPipeSetMessageToCache(t, cid, msgs)
|
||||
}
|
||||
|
||||
func testPipeSetMessageToCache(t *testing.T, cid string, msgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
ret, err := cacher.PipeSetMessageToCache(context.Background(), cid, msgs)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(msgs), ret)
|
||||
|
||||
// validate
|
||||
for _, msg := range msgs {
|
||||
key := cacher.getMessageCacheKey(cid, msg.Seq)
|
||||
val, err := rdb.Exists(context.Background(), key).Result()
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, 1, val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMessagesBySeq(t *testing.T) {
|
||||
var (
|
||||
cid = fmt.Sprintf("cid-%v", rand.Int63())
|
||||
seqFirst = rand.Int63()
|
||||
msgs = []*sdkws.MsgData{}
|
||||
)
|
||||
|
||||
seqs := []int64{}
|
||||
for i := 0; i < 100; i++ {
|
||||
msgs = append(msgs, &sdkws.MsgData{
|
||||
Seq: seqFirst + int64(i),
|
||||
SendID: fmt.Sprintf("fake-sendid-%v", i),
|
||||
})
|
||||
seqs = append(seqs, seqFirst+int64(i))
|
||||
}
|
||||
|
||||
// set data to cache
|
||||
testPipeSetMessageToCache(t, cid, msgs)
|
||||
|
||||
// get data from cache with parallet mode
|
||||
testParallelGetMessagesBySeq(t, cid, seqs, msgs)
|
||||
|
||||
// get data from cache with pipeline mode
|
||||
testPipeGetMessagesBySeq(t, cid, seqs, msgs)
|
||||
}
|
||||
|
||||
func testParallelGetMessagesBySeq(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
respMsgs, failedSeqs, err := cacher.ParallelGetMessagesBySeq(context.Background(), cid, seqs)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(failedSeqs))
|
||||
assert.Equal(t, len(respMsgs), len(seqs))
|
||||
|
||||
// validate
|
||||
for idx, msg := range respMsgs {
|
||||
assert.Equal(t, msg.Seq, inputMsgs[idx].Seq)
|
||||
assert.Equal(t, msg.SendID, inputMsgs[idx].SendID)
|
||||
}
|
||||
}
|
||||
|
||||
func testPipeGetMessagesBySeq(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
respMsgs, failedSeqs, err := cacher.PipeGetMessagesBySeq(context.Background(), cid, seqs)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(failedSeqs))
|
||||
assert.Equal(t, len(respMsgs), len(seqs))
|
||||
|
||||
// validate
|
||||
for idx, msg := range respMsgs {
|
||||
assert.Equal(t, msg.Seq, inputMsgs[idx].Seq)
|
||||
assert.Equal(t, msg.SendID, inputMsgs[idx].SendID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMessagesBySeqWithEmptySeqs(t *testing.T) {
|
||||
var (
|
||||
cid = fmt.Sprintf("cid-%v", rand.Int63())
|
||||
seqFirst int64 = 0
|
||||
msgs = []*sdkws.MsgData{}
|
||||
)
|
||||
|
||||
seqs := []int64{}
|
||||
for i := 0; i < 100; i++ {
|
||||
msgs = append(msgs, &sdkws.MsgData{
|
||||
Seq: seqFirst + int64(i),
|
||||
SendID: fmt.Sprintf("fake-sendid-%v", i),
|
||||
})
|
||||
seqs = append(seqs, seqFirst+int64(i))
|
||||
}
|
||||
|
||||
// don't set cache, only get data from cache.
|
||||
|
||||
// get data from cache with parallet mode
|
||||
testParallelGetMessagesBySeqWithEmptry(t, cid, seqs, msgs)
|
||||
|
||||
// get data from cache with pipeline mode
|
||||
testPipeGetMessagesBySeqWithEmptry(t, cid, seqs, msgs)
|
||||
}
|
||||
|
||||
func testParallelGetMessagesBySeqWithEmptry(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
respMsgs, failedSeqs, err := cacher.ParallelGetMessagesBySeq(context.Background(), cid, seqs)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(seqs), len(failedSeqs))
|
||||
assert.Equal(t, 0, len(respMsgs))
|
||||
}
|
||||
|
||||
func testPipeGetMessagesBySeqWithEmptry(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
respMsgs, failedSeqs, err := cacher.PipeGetMessagesBySeq(context.Background(), cid, seqs)
|
||||
assert.Equal(t, err, redis.Nil)
|
||||
assert.Equal(t, len(seqs), len(failedSeqs))
|
||||
assert.Equal(t, 0, len(respMsgs))
|
||||
}
|
||||
|
||||
func TestGetMessagesBySeqWithLostHalfSeqs(t *testing.T) {
|
||||
var (
|
||||
cid = fmt.Sprintf("cid-%v", rand.Int63())
|
||||
seqFirst int64 = 0
|
||||
msgs = []*sdkws.MsgData{}
|
||||
)
|
||||
|
||||
seqs := []int64{}
|
||||
for i := 0; i < 100; i++ {
|
||||
msgs = append(msgs, &sdkws.MsgData{
|
||||
Seq: seqFirst + int64(i),
|
||||
SendID: fmt.Sprintf("fake-sendid-%v", i),
|
||||
})
|
||||
seqs = append(seqs, seqFirst+int64(i))
|
||||
}
|
||||
|
||||
// Only set half the number of messages.
|
||||
testParallelSetMessageToCache(t, cid, msgs[:50])
|
||||
|
||||
// get data from cache with parallet mode
|
||||
testParallelGetMessagesBySeqWithLostHalfSeqs(t, cid, seqs, msgs)
|
||||
|
||||
// get data from cache with pipeline mode
|
||||
testPipeGetMessagesBySeqWithLostHalfSeqs(t, cid, seqs, msgs)
|
||||
}
|
||||
|
||||
func testParallelGetMessagesBySeqWithLostHalfSeqs(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
respMsgs, failedSeqs, err := cacher.ParallelGetMessagesBySeq(context.Background(), cid, seqs)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(seqs)/2, len(failedSeqs))
|
||||
assert.Equal(t, len(seqs)/2, len(respMsgs))
|
||||
|
||||
for idx, msg := range respMsgs {
|
||||
assert.Equal(t, msg.Seq, seqs[idx])
|
||||
}
|
||||
}
|
||||
|
||||
func testPipeGetMessagesBySeqWithLostHalfSeqs(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
respMsgs, failedSeqs, err := cacher.PipeGetMessagesBySeq(context.Background(), cid, seqs)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(seqs)/2, len(failedSeqs))
|
||||
assert.Equal(t, len(seqs)/2, len(respMsgs))
|
||||
|
||||
for idx, msg := range respMsgs {
|
||||
assert.Equal(t, msg.Seq, seqs[idx])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipeDeleteMessages(t *testing.T) {
|
||||
var (
|
||||
cid = fmt.Sprintf("cid-%v", rand.Int63())
|
||||
seqFirst = rand.Int63()
|
||||
msgs = []*sdkws.MsgData{}
|
||||
)
|
||||
|
||||
var seqs []int64
|
||||
for i := 0; i < 100; i++ {
|
||||
msgs = append(msgs, &sdkws.MsgData{
|
||||
Seq: seqFirst + int64(i),
|
||||
})
|
||||
seqs = append(seqs, msgs[i].Seq)
|
||||
}
|
||||
|
||||
testPipeSetMessageToCache(t, cid, msgs)
|
||||
testPipeDeleteMessagesOK(t, cid, seqs, msgs)
|
||||
|
||||
// set again
|
||||
testPipeSetMessageToCache(t, cid, msgs)
|
||||
testPipeDeleteMessagesMix(t, cid, seqs[:90], msgs)
|
||||
}
|
||||
|
||||
func testPipeDeleteMessagesOK(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// validate
|
||||
for _, msg := range inputMsgs {
|
||||
key := cacher.getMessageCacheKey(cid, msg.Seq)
|
||||
val := rdb.Exists(context.Background(), key).Val()
|
||||
assert.EqualValues(t, 0, val)
|
||||
}
|
||||
}
|
||||
|
||||
func testPipeDeleteMessagesMix(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// validate
|
||||
for idx, msg := range inputMsgs {
|
||||
key := cacher.getMessageCacheKey(cid, msg.Seq)
|
||||
val, err := rdb.Exists(context.Background(), key).Result()
|
||||
assert.Nil(t, err)
|
||||
if idx < 90 {
|
||||
assert.EqualValues(t, 0, val) // not exists
|
||||
continue
|
||||
}
|
||||
|
||||
assert.EqualValues(t, 1, val) // exists
|
||||
}
|
||||
}
|
||||
|
||||
func TestParallelDeleteMessages(t *testing.T) {
|
||||
var (
|
||||
cid = fmt.Sprintf("cid-%v", rand.Int63())
|
||||
seqFirst = rand.Int63()
|
||||
msgs = []*sdkws.MsgData{}
|
||||
)
|
||||
|
||||
var seqs []int64
|
||||
for i := 0; i < 100; i++ {
|
||||
msgs = append(msgs, &sdkws.MsgData{
|
||||
Seq: seqFirst + int64(i),
|
||||
})
|
||||
seqs = append(seqs, msgs[i].Seq)
|
||||
}
|
||||
|
||||
randSeqs := []int64{}
|
||||
for i := seqFirst + 100; i < seqFirst+200; i++ {
|
||||
randSeqs = append(randSeqs, i)
|
||||
}
|
||||
|
||||
testParallelSetMessageToCache(t, cid, msgs)
|
||||
testParallelDeleteMessagesOK(t, cid, seqs, msgs)
|
||||
|
||||
// set again
|
||||
testParallelSetMessageToCache(t, cid, msgs)
|
||||
testParallelDeleteMessagesMix(t, cid, seqs[:90], msgs, 90)
|
||||
testParallelDeleteMessagesOK(t, cid, seqs[90:], msgs[:90])
|
||||
|
||||
// set again
|
||||
testParallelSetMessageToCache(t, cid, msgs)
|
||||
testParallelDeleteMessagesMix(t, cid, randSeqs, msgs, 0)
|
||||
}
|
||||
|
||||
func testParallelDeleteMessagesOK(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// validate
|
||||
for _, msg := range inputMsgs {
|
||||
key := cacher.getMessageCacheKey(cid, msg.Seq)
|
||||
val := rdb.Exists(context.Background(), key).Val()
|
||||
assert.EqualValues(t, 0, val)
|
||||
}
|
||||
}
|
||||
|
||||
func testParallelDeleteMessagesMix(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData, lessValNonExists int) {
|
||||
rdb := redis.NewClient(&redis.Options{})
|
||||
defer rdb.Close()
|
||||
|
||||
cacher := msgCache{rdb: rdb}
|
||||
|
||||
err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// validate
|
||||
for idx, msg := range inputMsgs {
|
||||
key := cacher.getMessageCacheKey(cid, msg.Seq)
|
||||
val, err := rdb.Exists(context.Background(), key).Result()
|
||||
assert.Nil(t, err)
|
||||
if idx < lessValNonExists {
|
||||
assert.EqualValues(t, 0, val) // not exists
|
||||
continue
|
||||
}
|
||||
|
||||
assert.EqualValues(t, 1, val) // exists
|
||||
}
|
||||
}
|
||||
Vendored
+161
@@ -0,0 +1,161 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/s3"
|
||||
"github.com/openimsdk/tools/s3/cont"
|
||||
"github.com/openimsdk/tools/s3/minio"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewObjectCacheRedis(rdb redis.UniversalClient, objDB database.ObjectInfo) cache.ObjectCache {
|
||||
opts := rockscache.NewDefaultOptions()
|
||||
batchHandler := NewBatchDeleterRedis(rdb, &opts, nil)
|
||||
return &objectCacheRedis{
|
||||
BatchDeleter: batchHandler,
|
||||
rcClient: rockscache.NewClient(rdb, opts),
|
||||
expireTime: time.Hour * 12,
|
||||
objDB: objDB,
|
||||
}
|
||||
}
|
||||
|
||||
type objectCacheRedis struct {
|
||||
cache.BatchDeleter
|
||||
objDB database.ObjectInfo
|
||||
rcClient *rockscache.Client
|
||||
expireTime time.Duration
|
||||
}
|
||||
|
||||
func (g *objectCacheRedis) getObjectKey(engine string, name string) string {
|
||||
return cachekey.GetObjectKey(engine, name)
|
||||
}
|
||||
|
||||
func (g *objectCacheRedis) CloneObjectCache() cache.ObjectCache {
|
||||
return &objectCacheRedis{
|
||||
BatchDeleter: g.BatchDeleter.Clone(),
|
||||
rcClient: g.rcClient,
|
||||
expireTime: g.expireTime,
|
||||
objDB: g.objDB,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *objectCacheRedis) DelObjectName(engine string, names ...string) cache.ObjectCache {
|
||||
objectCache := g.CloneObjectCache()
|
||||
keys := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
keys = append(keys, g.getObjectKey(name, engine))
|
||||
}
|
||||
objectCache.AddKeys(keys...)
|
||||
return objectCache
|
||||
}
|
||||
|
||||
func (g *objectCacheRedis) GetName(ctx context.Context, engine string, name string) (*model.Object, error) {
|
||||
return getCache(ctx, g.rcClient, g.getObjectKey(name, engine), g.expireTime, func(ctx context.Context) (*model.Object, error) {
|
||||
return g.objDB.Take(ctx, engine, name)
|
||||
})
|
||||
}
|
||||
|
||||
func NewS3Cache(rdb redis.UniversalClient, s3 s3.Interface) cont.S3Cache {
|
||||
opts := rockscache.NewDefaultOptions()
|
||||
batchHandler := NewBatchDeleterRedis(rdb, &opts, nil)
|
||||
return &s3CacheRedis{
|
||||
BatchDeleter: batchHandler,
|
||||
rcClient: rockscache.NewClient(rdb, opts),
|
||||
expireTime: time.Hour * 12,
|
||||
s3: s3,
|
||||
}
|
||||
}
|
||||
|
||||
type s3CacheRedis struct {
|
||||
cache.BatchDeleter
|
||||
s3 s3.Interface
|
||||
rcClient *rockscache.Client
|
||||
expireTime time.Duration
|
||||
}
|
||||
|
||||
func (g *s3CacheRedis) getS3Key(engine string, name string) string {
|
||||
return cachekey.GetS3Key(engine, name)
|
||||
}
|
||||
|
||||
func (g *s3CacheRedis) DelS3Key(ctx context.Context, engine string, keys ...string) error {
|
||||
ks := make([]string, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
ks = append(ks, g.getS3Key(engine, key))
|
||||
}
|
||||
return g.BatchDeleter.ExecDelWithKeys(ctx, ks)
|
||||
}
|
||||
|
||||
func (g *s3CacheRedis) GetKey(ctx context.Context, engine string, name string) (*s3.ObjectInfo, error) {
|
||||
return getCache(ctx, g.rcClient, g.getS3Key(engine, name), g.expireTime, func(ctx context.Context) (*s3.ObjectInfo, error) {
|
||||
return g.s3.StatObject(ctx, name)
|
||||
})
|
||||
}
|
||||
|
||||
func NewMinioCache(rdb redis.UniversalClient) minio.Cache {
|
||||
opts := rockscache.NewDefaultOptions()
|
||||
batchHandler := NewBatchDeleterRedis(rdb, &opts, nil)
|
||||
return &minioCacheRedis{
|
||||
BatchDeleter: batchHandler,
|
||||
rcClient: rockscache.NewClient(rdb, opts),
|
||||
expireTime: time.Hour * 24 * 7,
|
||||
}
|
||||
}
|
||||
|
||||
type minioCacheRedis struct {
|
||||
cache.BatchDeleter
|
||||
rcClient *rockscache.Client
|
||||
expireTime time.Duration
|
||||
}
|
||||
|
||||
func (g *minioCacheRedis) getObjectImageInfoKey(key string) string {
|
||||
return cachekey.GetObjectImageInfoKey(key)
|
||||
}
|
||||
|
||||
func (g *minioCacheRedis) getMinioImageThumbnailKey(key string, format string, width int, height int) string {
|
||||
return cachekey.GetMinioImageThumbnailKey(key, format, width, height)
|
||||
}
|
||||
|
||||
func (g *minioCacheRedis) DelObjectImageInfoKey(ctx context.Context, keys ...string) error {
|
||||
ks := make([]string, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
ks = append(ks, g.getObjectImageInfoKey(key))
|
||||
}
|
||||
return g.BatchDeleter.ExecDelWithKeys(ctx, ks)
|
||||
}
|
||||
|
||||
func (g *minioCacheRedis) DelImageThumbnailKey(ctx context.Context, key string, format string, width int, height int) error {
|
||||
return g.BatchDeleter.ExecDelWithKeys(ctx, []string{g.getMinioImageThumbnailKey(key, format, width, height)})
|
||||
|
||||
}
|
||||
|
||||
func (g *minioCacheRedis) GetImageObjectKeyInfo(ctx context.Context, key string, fn func(ctx context.Context) (*minio.ImageInfo, error)) (*minio.ImageInfo, error) {
|
||||
info, err := getCache(ctx, g.rcClient, g.getObjectImageInfoKey(key), g.expireTime, fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (g *minioCacheRedis) GetThumbnailKey(ctx context.Context, key string, format string, width int, height int, minioCache func(ctx context.Context) (string, error)) (string, error) {
|
||||
return getCache(ctx, g.rcClient, g.getMinioImageThumbnailKey(key, format, width, height), g.expireTime, minioCache)
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func NewSeqCache(rdb redis.UniversalClient) cache.SeqCache {
|
||||
return &seqCache{rdb: rdb}
|
||||
}
|
||||
|
||||
type seqCache struct {
|
||||
rdb redis.UniversalClient
|
||||
}
|
||||
|
||||
func (c *seqCache) getMaxSeqKey(conversationID string) string {
|
||||
return cachekey.GetMaxSeqKey(conversationID)
|
||||
}
|
||||
|
||||
func (c *seqCache) getMinSeqKey(conversationID string) string {
|
||||
return cachekey.GetMinSeqKey(conversationID)
|
||||
}
|
||||
|
||||
func (c *seqCache) getHasReadSeqKey(conversationID string, userID string) string {
|
||||
return cachekey.GetHasReadSeqKey(conversationID, userID)
|
||||
}
|
||||
|
||||
func (c *seqCache) getConversationUserMinSeqKey(conversationID, userID string) string {
|
||||
return cachekey.GetConversationUserMinSeqKey(conversationID, userID)
|
||||
}
|
||||
|
||||
func (c *seqCache) setSeq(ctx context.Context, conversationID string, seq int64, getkey func(conversationID string) string) error {
|
||||
return errs.Wrap(c.rdb.Set(ctx, getkey(conversationID), seq, 0).Err())
|
||||
}
|
||||
|
||||
func (c *seqCache) getSeq(ctx context.Context, conversationID string, getkey func(conversationID string) string) (int64, error) {
|
||||
val, err := c.rdb.Get(ctx, getkey(conversationID)).Int64()
|
||||
if err != nil {
|
||||
return 0, errs.Wrap(err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *seqCache) getSeqs(ctx context.Context, items []string, getkey func(s string) string) (m map[string]int64, err error) {
|
||||
m = make(map[string]int64, len(items))
|
||||
for i, v := range items {
|
||||
res, err := c.rdb.Get(ctx, getkey(v)).Result()
|
||||
if err != nil && err != redis.Nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
val := stringutil.StringToInt64(res)
|
||||
if val != 0 {
|
||||
m[items[i]] = val
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *seqCache) SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error {
|
||||
return c.setSeq(ctx, conversationID, maxSeq, c.getMaxSeqKey)
|
||||
}
|
||||
|
||||
func (c *seqCache) GetMaxSeqs(ctx context.Context, conversationIDs []string) (m map[string]int64, err error) {
|
||||
return c.getSeqs(ctx, conversationIDs, c.getMaxSeqKey)
|
||||
}
|
||||
|
||||
func (c *seqCache) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
|
||||
return c.getSeq(ctx, conversationID, c.getMaxSeqKey)
|
||||
}
|
||||
|
||||
func (c *seqCache) SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error {
|
||||
return c.setSeq(ctx, conversationID, minSeq, c.getMinSeqKey)
|
||||
}
|
||||
|
||||
func (c *seqCache) setSeqs(ctx context.Context, seqs map[string]int64, getkey func(key string) string) error {
|
||||
for conversationID, seq := range seqs {
|
||||
if err := c.rdb.Set(ctx, getkey(conversationID), seq, 0).Err(); err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *seqCache) SetMinSeqs(ctx context.Context, seqs map[string]int64) error {
|
||||
return c.setSeqs(ctx, seqs, c.getMinSeqKey)
|
||||
}
|
||||
|
||||
func (c *seqCache) GetMinSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) {
|
||||
return c.getSeqs(ctx, conversationIDs, c.getMinSeqKey)
|
||||
}
|
||||
|
||||
func (c *seqCache) GetMinSeq(ctx context.Context, conversationID string) (int64, error) {
|
||||
return c.getSeq(ctx, conversationID, c.getMinSeqKey)
|
||||
}
|
||||
|
||||
func (c *seqCache) GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error) {
|
||||
val, err := c.rdb.Get(ctx, c.getConversationUserMinSeqKey(conversationID, userID)).Int64()
|
||||
if err != nil {
|
||||
return 0, errs.Wrap(err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *seqCache) GetConversationUserMinSeqs(ctx context.Context, conversationID string, userIDs []string) (m map[string]int64, err error) {
|
||||
return c.getSeqs(ctx, userIDs, func(userID string) string {
|
||||
return c.getConversationUserMinSeqKey(conversationID, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *seqCache) SetConversationUserMinSeq(ctx context.Context, conversationID string, userID string, minSeq int64) error {
|
||||
return errs.Wrap(c.rdb.Set(ctx, c.getConversationUserMinSeqKey(conversationID, userID), minSeq, 0).Err())
|
||||
}
|
||||
|
||||
func (c *seqCache) SetConversationUserMinSeqs(ctx context.Context, conversationID string, seqs map[string]int64) (err error) {
|
||||
return c.setSeqs(ctx, seqs, func(userID string) string {
|
||||
return c.getConversationUserMinSeqKey(conversationID, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *seqCache) SetUserConversationsMinSeqs(ctx context.Context, userID string, seqs map[string]int64) (err error) {
|
||||
return c.setSeqs(ctx, seqs, func(conversationID string) string {
|
||||
return c.getConversationUserMinSeqKey(conversationID, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *seqCache) SetHasReadSeq(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error {
|
||||
return errs.Wrap(c.rdb.Set(ctx, c.getHasReadSeqKey(conversationID, userID), hasReadSeq, 0).Err())
|
||||
}
|
||||
|
||||
func (c *seqCache) SetHasReadSeqs(ctx context.Context, conversationID string, hasReadSeqs map[string]int64) error {
|
||||
return c.setSeqs(ctx, hasReadSeqs, func(userID string) string {
|
||||
return c.getHasReadSeqKey(conversationID, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *seqCache) UserSetHasReadSeqs(ctx context.Context, userID string, hasReadSeqs map[string]int64) error {
|
||||
return c.setSeqs(ctx, hasReadSeqs, func(conversationID string) string {
|
||||
return c.getHasReadSeqKey(conversationID, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *seqCache) GetHasReadSeqs(ctx context.Context, userID string, conversationIDs []string) (map[string]int64, error) {
|
||||
return c.getSeqs(ctx, conversationIDs, func(conversationID string) string {
|
||||
return c.getHasReadSeqKey(conversationID, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *seqCache) GetHasReadSeq(ctx context.Context, userID string, conversationID string) (int64, error) {
|
||||
val, err := c.rdb.Get(ctx, c.getHasReadSeqKey(conversationID, userID)).Int64()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewThirdCache(rdb redis.UniversalClient) cache.ThirdCache {
|
||||
return &thirdCache{rdb: rdb}
|
||||
}
|
||||
|
||||
type thirdCache struct {
|
||||
rdb redis.UniversalClient
|
||||
}
|
||||
|
||||
func (c *thirdCache) getGetuiTokenKey() string {
|
||||
return cachekey.GetGetuiTokenKey()
|
||||
}
|
||||
|
||||
func (c *thirdCache) getGetuiTaskIDKey() string {
|
||||
return cachekey.GetGetuiTaskIDKey()
|
||||
}
|
||||
|
||||
func (c *thirdCache) getUserBadgeUnreadCountSumKey(userID string) string {
|
||||
return cachekey.GetUserBadgeUnreadCountSumKey(userID)
|
||||
}
|
||||
|
||||
func (c *thirdCache) getFcmAccountTokenKey(account string, platformID int) string {
|
||||
return cachekey.GetFcmAccountTokenKey(account, platformID)
|
||||
}
|
||||
|
||||
func (c *thirdCache) SetFcmToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) (err error) {
|
||||
return errs.Wrap(c.rdb.Set(ctx, c.getFcmAccountTokenKey(account, platformID), fcmToken, time.Duration(expireTime)*time.Second).Err())
|
||||
}
|
||||
|
||||
func (c *thirdCache) GetFcmToken(ctx context.Context, account string, platformID int) (string, error) {
|
||||
val, err := c.rdb.Get(ctx, c.getFcmAccountTokenKey(account, platformID)).Result()
|
||||
if err != nil {
|
||||
return "", errs.Wrap(err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *thirdCache) DelFcmToken(ctx context.Context, account string, platformID int) error {
|
||||
return errs.Wrap(c.rdb.Del(ctx, c.getFcmAccountTokenKey(account, platformID)).Err())
|
||||
}
|
||||
|
||||
func (c *thirdCache) IncrUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) {
|
||||
seq, err := c.rdb.Incr(ctx, c.getUserBadgeUnreadCountSumKey(userID)).Result()
|
||||
|
||||
return int(seq), errs.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *thirdCache) SetUserBadgeUnreadCountSum(ctx context.Context, userID string, value int) error {
|
||||
return errs.Wrap(c.rdb.Set(ctx, c.getUserBadgeUnreadCountSumKey(userID), value, 0).Err())
|
||||
}
|
||||
|
||||
func (c *thirdCache) GetUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) {
|
||||
val, err := c.rdb.Get(ctx, c.getUserBadgeUnreadCountSumKey(userID)).Int()
|
||||
return val, errs.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *thirdCache) SetGetuiToken(ctx context.Context, token string, expireTime int64) error {
|
||||
return errs.Wrap(c.rdb.Set(ctx, c.getGetuiTokenKey(), token, time.Duration(expireTime)*time.Second).Err())
|
||||
}
|
||||
|
||||
func (c *thirdCache) GetGetuiToken(ctx context.Context) (string, error) {
|
||||
val, err := c.rdb.Get(ctx, c.getGetuiTokenKey()).Result()
|
||||
if err != nil {
|
||||
return "", errs.Wrap(err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *thirdCache) SetGetuiTaskID(ctx context.Context, taskID string, expireTime int64) error {
|
||||
return errs.Wrap(c.rdb.Set(ctx, c.getGetuiTaskIDKey(), taskID, time.Duration(expireTime)*time.Second).Err())
|
||||
}
|
||||
|
||||
func (c *thirdCache) GetGetuiTaskID(ctx context.Context) (string, error) {
|
||||
val, err := c.rdb.Get(ctx, c.getGetuiTaskIDKey()).Result()
|
||||
if err != nil {
|
||||
return "", errs.Wrap(err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type tokenCache struct {
|
||||
rdb redis.UniversalClient
|
||||
}
|
||||
|
||||
func NewTokenCacheModel(rdb redis.UniversalClient) cache.TokenModel {
|
||||
return &tokenCache{
|
||||
rdb: rdb,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *tokenCache) AddTokenFlag(ctx context.Context, userID string, platformID int, token string, flag int) error {
|
||||
return errs.Wrap(c.rdb.HSet(ctx, cachekey.GetTokenKey(userID, platformID), token, flag).Err())
|
||||
}
|
||||
|
||||
func (c *tokenCache) GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error) {
|
||||
m, err := c.rdb.HGetAll(ctx, cachekey.GetTokenKey(userID, platformID)).Result()
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
mm := make(map[string]int)
|
||||
for k, v := range m {
|
||||
mm[k] = stringutil.StringToInt(v)
|
||||
}
|
||||
|
||||
return mm, nil
|
||||
}
|
||||
|
||||
func (c *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error {
|
||||
mm := make(map[string]any)
|
||||
for k, v := range m {
|
||||
mm[k] = v
|
||||
}
|
||||
return errs.Wrap(c.rdb.HSet(ctx, cachekey.GetTokenKey(userID, platformID), mm).Err())
|
||||
}
|
||||
|
||||
func (c *tokenCache) DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error {
|
||||
return errs.Wrap(c.rdb.HDel(ctx, cachekey.GetTokenKey(userID, platformID), fields...).Err())
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
// Copyright © 2023 OpenIM. 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 redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/protocol/user"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"hash/crc32"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
userExpireTime = time.Second * 60 * 60 * 12
|
||||
userOlineStatusExpireTime = time.Second * 60 * 60 * 24
|
||||
statusMod = 501
|
||||
)
|
||||
|
||||
type UserCacheRedis struct {
|
||||
cache.BatchDeleter
|
||||
rdb redis.UniversalClient
|
||||
userDB database.User
|
||||
expireTime time.Duration
|
||||
rcClient *rockscache.Client
|
||||
}
|
||||
|
||||
func NewUserCacheRedis(rdb redis.UniversalClient, localCache *config.LocalCache, userDB database.User, options *rockscache.Options) cache.UserCache {
|
||||
batchHandler := NewBatchDeleterRedis(rdb, options, []string{localCache.User.Topic})
|
||||
u := localCache.User
|
||||
log.ZDebug(context.Background(), "user local cache init", "Topic", u.Topic, "SlotNum", u.SlotNum, "SlotSize", u.SlotSize, "enable", u.Enable())
|
||||
return &UserCacheRedis{
|
||||
BatchDeleter: batchHandler,
|
||||
rdb: rdb,
|
||||
userDB: userDB,
|
||||
expireTime: userExpireTime,
|
||||
rcClient: rockscache.NewClient(rdb, *options),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) getOnlineStatusKey(modKey string) string {
|
||||
return cachekey.GetOnlineStatusKey(modKey)
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) CloneUserCache() cache.UserCache {
|
||||
return &UserCacheRedis{
|
||||
BatchDeleter: u.BatchDeleter.Clone(),
|
||||
rdb: u.rdb,
|
||||
userDB: u.userDB,
|
||||
expireTime: u.expireTime,
|
||||
rcClient: u.rcClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) getUserInfoKey(userID string) string {
|
||||
return cachekey.GetUserInfoKey(userID)
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) getUserGlobalRecvMsgOptKey(userID string) string {
|
||||
return cachekey.GetUserGlobalRecvMsgOptKey(userID)
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) GetUserInfo(ctx context.Context, userID string) (userInfo *model.User, err error) {
|
||||
return getCache(ctx, u.rcClient, u.getUserInfoKey(userID), u.expireTime, func(ctx context.Context) (*model.User, error) {
|
||||
return u.userDB.Take(ctx, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) GetUsersInfo(ctx context.Context, userIDs []string) ([]*model.User, error) {
|
||||
return batchGetCache(ctx, u.rcClient, u.expireTime, userIDs, func(userID string) string {
|
||||
return u.getUserInfoKey(userID)
|
||||
}, func(ctx context.Context, userID string) (*model.User, error) {
|
||||
return u.userDB.Take(ctx, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) DelUsersInfo(userIDs ...string) cache.UserCache {
|
||||
keys := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
keys = append(keys, u.getUserInfoKey(userID))
|
||||
}
|
||||
cache := u.CloneUserCache()
|
||||
cache.AddKeys(keys...)
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) GetUserGlobalRecvMsgOpt(ctx context.Context, userID string) (opt int, err error) {
|
||||
return getCache(
|
||||
ctx,
|
||||
u.rcClient,
|
||||
u.getUserGlobalRecvMsgOptKey(userID),
|
||||
u.expireTime,
|
||||
func(ctx context.Context) (int, error) {
|
||||
return u.userDB.GetUserGlobalRecvMsgOpt(ctx, userID)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) DelUsersGlobalRecvMsgOpt(userIDs ...string) cache.UserCache {
|
||||
keys := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
keys = append(keys, u.getUserGlobalRecvMsgOptKey(userID))
|
||||
}
|
||||
cache := u.CloneUserCache()
|
||||
cache.AddKeys(keys...)
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
// GetUserStatus get user status.
|
||||
func (u *UserCacheRedis) GetUserStatus(ctx context.Context, userIDs []string) ([]*user.OnlineStatus, error) {
|
||||
userStatus := make([]*user.OnlineStatus, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
UserIDNum := crc32.ChecksumIEEE([]byte(userID))
|
||||
modKey := strconv.Itoa(int(UserIDNum % statusMod))
|
||||
var onlineStatus user.OnlineStatus
|
||||
key := u.getOnlineStatusKey(modKey)
|
||||
result, err := u.rdb.HGet(ctx, key, userID).Result()
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
// key or field does not exist
|
||||
userStatus = append(userStatus, &user.OnlineStatus{
|
||||
UserID: userID,
|
||||
Status: constant.Offline,
|
||||
PlatformIDs: nil,
|
||||
})
|
||||
|
||||
continue
|
||||
} else {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
err = json.Unmarshal([]byte(result), &onlineStatus)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
onlineStatus.UserID = userID
|
||||
onlineStatus.Status = constant.Online
|
||||
userStatus = append(userStatus, &onlineStatus)
|
||||
}
|
||||
|
||||
return userStatus, nil
|
||||
}
|
||||
|
||||
// SetUserStatus Set the user status and save it in redis.
|
||||
func (u *UserCacheRedis) SetUserStatus(ctx context.Context, userID string, status, platformID int32) error {
|
||||
UserIDNum := crc32.ChecksumIEEE([]byte(userID))
|
||||
modKey := strconv.Itoa(int(UserIDNum % statusMod))
|
||||
key := u.getOnlineStatusKey(modKey)
|
||||
log.ZDebug(ctx, "SetUserStatus args", "userID", userID, "status", status, "platformID", platformID, "modKey", modKey, "key", key)
|
||||
isNewKey, err := u.rdb.Exists(ctx, key).Result()
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
if isNewKey == 0 {
|
||||
if status == constant.Online {
|
||||
onlineStatus := user.OnlineStatus{
|
||||
UserID: userID,
|
||||
Status: constant.Online,
|
||||
PlatformIDs: []int32{platformID},
|
||||
}
|
||||
jsonData, err := json.Marshal(&onlineStatus)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
_, err = u.rdb.HSet(ctx, key, userID, string(jsonData)).Result()
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
u.rdb.Expire(ctx, key, userOlineStatusExpireTime)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
isNil := false
|
||||
result, err := u.rdb.HGet(ctx, key, userID).Result()
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
isNil = true
|
||||
} else {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
if status == constant.Offline {
|
||||
err = u.refreshStatusOffline(ctx, userID, status, platformID, isNil, err, result, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = u.refreshStatusOnline(ctx, userID, platformID, isNil, err, result, key)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) refreshStatusOffline(ctx context.Context, userID string, status, platformID int32, isNil bool, err error, result, key string) error {
|
||||
if isNil {
|
||||
log.ZWarn(ctx, "this user not online,maybe trigger order not right",
|
||||
err, "userStatus", status)
|
||||
|
||||
return nil
|
||||
}
|
||||
var onlineStatus user.OnlineStatus
|
||||
err = json.Unmarshal([]byte(result), &onlineStatus)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
var newPlatformIDs []int32
|
||||
for _, val := range onlineStatus.PlatformIDs {
|
||||
if val != platformID {
|
||||
newPlatformIDs = append(newPlatformIDs, val)
|
||||
}
|
||||
}
|
||||
if newPlatformIDs == nil {
|
||||
_, err = u.rdb.HDel(ctx, key, userID).Result()
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
} else {
|
||||
onlineStatus.PlatformIDs = newPlatformIDs
|
||||
newjsonData, err := json.Marshal(&onlineStatus)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
_, err = u.rdb.HSet(ctx, key, userID, string(newjsonData)).Result()
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserCacheRedis) refreshStatusOnline(ctx context.Context, userID string, platformID int32, isNil bool, err error, result, key string) error {
|
||||
var onlineStatus user.OnlineStatus
|
||||
if !isNil {
|
||||
err := json.Unmarshal([]byte(result), &onlineStatus)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
onlineStatus.PlatformIDs = RemoveRepeatedElementsInList(append(onlineStatus.PlatformIDs, platformID))
|
||||
} else {
|
||||
onlineStatus.PlatformIDs = append(onlineStatus.PlatformIDs, platformID)
|
||||
}
|
||||
onlineStatus.Status = constant.Online
|
||||
onlineStatus.UserID = userID
|
||||
newjsonData, err := json.Marshal(&onlineStatus)
|
||||
if err != nil {
|
||||
return errs.WrapMsg(err, "json.Marshal failed")
|
||||
}
|
||||
_, err = u.rdb.HSet(ctx, key, userID, string(newjsonData)).Result()
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Comparable interface {
|
||||
~int | ~string | ~float64 | ~int32
|
||||
}
|
||||
|
||||
func RemoveRepeatedElementsInList[T Comparable](slc []T) []T {
|
||||
var result []T
|
||||
tempMap := map[T]struct{}{}
|
||||
for _, e := range slc {
|
||||
if _, found := tempMap[e]; !found {
|
||||
tempMap[e] = struct{}{}
|
||||
result = append(result, e)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
// Copyright © 2023 OpenIM. 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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/s3"
|
||||
)
|
||||
|
||||
type ObjectCache interface {
|
||||
BatchDeleter
|
||||
CloneObjectCache() ObjectCache
|
||||
GetName(ctx context.Context, engine string, name string) (*relationtb.Object, error)
|
||||
DelObjectName(engine string, names ...string) ObjectCache
|
||||
}
|
||||
|
||||
type S3Cache interface {
|
||||
BatchDeleter
|
||||
GetKey(ctx context.Context, engine string, key string) (*s3.ObjectInfo, error)
|
||||
DelS3Key(engine string, keys ...string) S3Cache
|
||||
}
|
||||
|
||||
// TODO integrating minio.Cache and MinioCache interfaces.
|
||||
type MinioCache interface {
|
||||
BatchDeleter
|
||||
GetImageObjectKeyInfo(ctx context.Context, key string, fn func(ctx context.Context) (*MinioImageInfo, error)) (*MinioImageInfo, error)
|
||||
GetThumbnailKey(ctx context.Context, key string, format string, width int, height int, minioCache func(ctx context.Context) (string, error)) (string, error)
|
||||
DelObjectImageInfoKey(keys ...string) MinioCache
|
||||
DelImageThumbnailKey(key string, format string, width int, height int) MinioCache
|
||||
}
|
||||
|
||||
type MinioImageInfo struct {
|
||||
IsImg bool `json:"isImg"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Format string `json:"format"`
|
||||
Etag string `json:"etag"`
|
||||
}
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type SeqCache interface {
|
||||
SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error
|
||||
GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error)
|
||||
GetMaxSeq(ctx context.Context, conversationID string) (int64, error)
|
||||
SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error
|
||||
SetMinSeqs(ctx context.Context, seqs map[string]int64) error
|
||||
GetMinSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error)
|
||||
GetMinSeq(ctx context.Context, conversationID string) (int64, error)
|
||||
GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error)
|
||||
GetConversationUserMinSeqs(ctx context.Context, conversationID string, userIDs []string) (map[string]int64, error)
|
||||
SetConversationUserMinSeq(ctx context.Context, conversationID string, userID string, minSeq int64) error
|
||||
// seqs map: key userID value minSeq
|
||||
SetConversationUserMinSeqs(ctx context.Context, conversationID string, seqs map[string]int64) (err error)
|
||||
// seqs map: key conversationID value minSeq
|
||||
SetUserConversationsMinSeqs(ctx context.Context, userID string, seqs map[string]int64) error
|
||||
// has read seq
|
||||
SetHasReadSeq(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error
|
||||
// k: user, v: seq
|
||||
SetHasReadSeqs(ctx context.Context, conversationID string, hasReadSeqs map[string]int64) error
|
||||
// k: conversation, v :seq
|
||||
UserSetHasReadSeqs(ctx context.Context, userID string, hasReadSeqs map[string]int64) error
|
||||
GetHasReadSeqs(ctx context.Context, userID string, conversationIDs []string) (map[string]int64, error)
|
||||
GetHasReadSeq(ctx context.Context, userID string, conversationID string) (int64, error)
|
||||
}
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type ThirdCache interface {
|
||||
SetFcmToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) (err error)
|
||||
GetFcmToken(ctx context.Context, account string, platformID int) (string, error)
|
||||
DelFcmToken(ctx context.Context, account string, platformID int) error
|
||||
IncrUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error)
|
||||
SetUserBadgeUnreadCountSum(ctx context.Context, userID string, value int) error
|
||||
GetUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error)
|
||||
SetGetuiToken(ctx context.Context, token string, expireTime int64) error
|
||||
GetGetuiToken(ctx context.Context) (string, error)
|
||||
SetGetuiTaskID(ctx context.Context, taskID string, expireTime int64) error
|
||||
GetGetuiTaskID(ctx context.Context) (string, error)
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type TokenModel interface {
|
||||
AddTokenFlag(ctx context.Context, userID string, platformID int, token string, flag int) error
|
||||
GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error)
|
||||
SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error
|
||||
DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error
|
||||
}
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
// Copyright © 2023 OpenIM. 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 cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/protocol/user"
|
||||
)
|
||||
|
||||
type UserCache interface {
|
||||
BatchDeleter
|
||||
CloneUserCache() UserCache
|
||||
GetUserInfo(ctx context.Context, userID string) (userInfo *relationtb.User, err error)
|
||||
GetUsersInfo(ctx context.Context, userIDs []string) ([]*relationtb.User, error)
|
||||
DelUsersInfo(userIDs ...string) UserCache
|
||||
GetUserGlobalRecvMsgOpt(ctx context.Context, userID string) (opt int, err error)
|
||||
DelUsersGlobalRecvMsgOpt(userIDs ...string) UserCache
|
||||
GetUserStatus(ctx context.Context, userIDs []string) ([]*user.OnlineStatus, error)
|
||||
SetUserStatus(ctx context.Context, userID string, status, platformID int32) error
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright © 2023 OpenIM. 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 common
|
||||
|
||||
type BatchUpdateGroupMember struct {
|
||||
GroupID string
|
||||
UserID string
|
||||
Map map[string]any
|
||||
}
|
||||
|
||||
type GroupSimpleUserID struct {
|
||||
Hash uint64
|
||||
MemberNum uint32
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright © 2023 OpenIM. 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/tokenverify"
|
||||
)
|
||||
|
||||
type AuthDatabase interface {
|
||||
// If the result is empty, no error is returned.
|
||||
GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error)
|
||||
// Create token
|
||||
CreateToken(ctx context.Context, userID string, platformID int) (string, error)
|
||||
|
||||
SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error
|
||||
}
|
||||
|
||||
type authDatabase struct {
|
||||
cache cache.TokenModel
|
||||
accessSecret string
|
||||
accessExpire int64
|
||||
}
|
||||
|
||||
func NewAuthDatabase(cache cache.TokenModel, accessSecret string, accessExpire int64) AuthDatabase {
|
||||
return &authDatabase{cache: cache, accessSecret: accessSecret, accessExpire: accessExpire}
|
||||
}
|
||||
|
||||
// If the result is empty.
|
||||
func (a *authDatabase) GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error) {
|
||||
return a.cache.GetTokensWithoutError(ctx, userID, platformID)
|
||||
}
|
||||
|
||||
func (a *authDatabase) SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error {
|
||||
return a.cache.SetTokenMapByUidPid(ctx, userID, platformID, m)
|
||||
}
|
||||
|
||||
// Create Token.
|
||||
func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformID int) (string, error) {
|
||||
tokens, err := a.cache.GetTokensWithoutError(ctx, userID, platformID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var deleteTokenKey []string
|
||||
for k, v := range tokens {
|
||||
_, err = tokenverify.GetClaimFromToken(k, authverify.Secret(a.accessSecret))
|
||||
if err != nil || v != constant.NormalToken {
|
||||
deleteTokenKey = append(deleteTokenKey, k)
|
||||
}
|
||||
}
|
||||
if len(deleteTokenKey) != 0 {
|
||||
err = a.cache.DeleteTokenByUidPid(ctx, userID, platformID, deleteTokenKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
claims := tokenverify.BuildClaims(userID, platformID, a.accessExpire)
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte(a.accessSecret))
|
||||
if err != nil {
|
||||
return "", errs.WrapMsg(err, "token.SignedString")
|
||||
}
|
||||
return tokenString, a.cache.AddTokenFlag(ctx, userID, platformID, tokenString, constant.NormalToken)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright © 2023 OpenIM. 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
)
|
||||
|
||||
type BlackDatabase interface {
|
||||
// Create add BlackList
|
||||
Create(ctx context.Context, blacks []*model.Black) (err error)
|
||||
// Delete delete BlackList
|
||||
Delete(ctx context.Context, blacks []*model.Black) (err error)
|
||||
// FindOwnerBlacks get BlackList list
|
||||
FindOwnerBlacks(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, blacks []*model.Black, err error)
|
||||
FindBlackInfos(ctx context.Context, ownerUserID string, userIDs []string) (blacks []*model.Black, err error)
|
||||
// CheckIn Check whether user2 is in the black list of user1 (inUser1Blacks==true) Check whether user1 is in the black list of user2 (inUser2Blacks==true)
|
||||
CheckIn(ctx context.Context, userID1, userID2 string) (inUser1Blacks bool, inUser2Blacks bool, err error)
|
||||
}
|
||||
|
||||
type blackDatabase struct {
|
||||
black database.Black
|
||||
cache cache.BlackCache
|
||||
}
|
||||
|
||||
func NewBlackDatabase(black database.Black, cache cache.BlackCache) BlackDatabase {
|
||||
return &blackDatabase{black, cache}
|
||||
}
|
||||
|
||||
// Create Add Blacklist.
|
||||
func (b *blackDatabase) Create(ctx context.Context, blacks []*model.Black) (err error) {
|
||||
if err := b.black.Create(ctx, blacks); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.deleteBlackIDsCache(ctx, blacks)
|
||||
}
|
||||
|
||||
// Delete Delete Blacklist.
|
||||
func (b *blackDatabase) Delete(ctx context.Context, blacks []*model.Black) (err error) {
|
||||
if err := b.black.Delete(ctx, blacks); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.deleteBlackIDsCache(ctx, blacks)
|
||||
}
|
||||
|
||||
// FindOwnerBlacks Get Blacklist List.
|
||||
func (b *blackDatabase) deleteBlackIDsCache(ctx context.Context, blacks []*model.Black) (err error) {
|
||||
cache := b.cache.CloneBlackCache()
|
||||
for _, black := range blacks {
|
||||
cache = cache.DelBlackIDs(ctx, black.OwnerUserID)
|
||||
}
|
||||
return cache.ChainExecDel(ctx)
|
||||
}
|
||||
|
||||
// FindOwnerBlacks Get Blacklist List.
|
||||
func (b *blackDatabase) FindOwnerBlacks(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, blacks []*model.Black, err error) {
|
||||
return b.black.FindOwnerBlacks(ctx, ownerUserID, pagination)
|
||||
}
|
||||
|
||||
// FindOwnerBlacks Get Blacklist List.
|
||||
func (b *blackDatabase) CheckIn(ctx context.Context, userID1, userID2 string) (inUser1Blacks bool, inUser2Blacks bool, err error) {
|
||||
userID1BlackIDs, err := b.cache.GetBlackIDs(ctx, userID1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userID2BlackIDs, err := b.cache.GetBlackIDs(ctx, userID2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.ZDebug(ctx, "blackIDs", "user1BlackIDs", userID1BlackIDs, "user2BlackIDs", userID2BlackIDs)
|
||||
return datautil.Contain(userID2, userID1BlackIDs...), datautil.Contain(userID1, userID2BlackIDs...), nil
|
||||
}
|
||||
|
||||
// FindBlackIDs Get Blacklist List.
|
||||
func (b *blackDatabase) FindBlackIDs(ctx context.Context, ownerUserID string) (blackIDs []string, err error) {
|
||||
return b.cache.GetBlackIDs(ctx, ownerUserID)
|
||||
}
|
||||
|
||||
// FindBlackInfos Get Blacklist List.
|
||||
func (b *blackDatabase) FindBlackInfos(ctx context.Context, ownerUserID string, userIDs []string) (blacks []*model.Black, err error) {
|
||||
return b.black.FindOwnerBlackInfos(ctx, ownerUserID, userIDs)
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
// Copyright © 2023 OpenIM. 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/db/tx"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
)
|
||||
|
||||
type ConversationDatabase interface {
|
||||
// UpdateUsersConversationField updates the properties of a conversation for specified users.
|
||||
UpdateUsersConversationField(ctx context.Context, userIDs []string, conversationID string, args map[string]any) error
|
||||
// CreateConversation creates a batch of new conversations.
|
||||
CreateConversation(ctx context.Context, conversations []*relationtb.Conversation) error
|
||||
// SyncPeerUserPrivateConversationTx ensures transactional operation while syncing private conversations between peers.
|
||||
SyncPeerUserPrivateConversationTx(ctx context.Context, conversation []*relationtb.Conversation) error
|
||||
// FindConversations retrieves multiple conversations of a user by conversation IDs.
|
||||
FindConversations(ctx context.Context, ownerUserID string, conversationIDs []string) ([]*relationtb.Conversation, error)
|
||||
// GetUserAllConversation fetches all conversations of a user on the server.
|
||||
GetUserAllConversation(ctx context.Context, ownerUserID string) ([]*relationtb.Conversation, error)
|
||||
// SetUserConversations sets multiple conversation properties for a user, creates new conversations if they do not exist, or updates them otherwise. This operation is atomic.
|
||||
SetUserConversations(ctx context.Context, ownerUserID string, conversations []*relationtb.Conversation) error
|
||||
// SetUsersConversationFieldTx updates a specific field for multiple users' conversations, creating new conversations if they do not exist, or updates them otherwise. This operation is
|
||||
// transactional.
|
||||
SetUsersConversationFieldTx(ctx context.Context, userIDs []string, conversation *relationtb.Conversation, fieldMap map[string]any) error
|
||||
// CreateGroupChatConversation creates a group chat conversation for the specified group ID and user IDs.
|
||||
CreateGroupChatConversation(ctx context.Context, groupID string, userIDs []string) error
|
||||
// GetConversationIDs retrieves conversation IDs for a given user.
|
||||
GetConversationIDs(ctx context.Context, userID string) ([]string, error)
|
||||
// GetUserConversationIDsHash gets the hash of conversation IDs for a given user.
|
||||
GetUserConversationIDsHash(ctx context.Context, ownerUserID string) (hash uint64, err error)
|
||||
// GetAllConversationIDs fetches all conversation IDs.
|
||||
GetAllConversationIDs(ctx context.Context) ([]string, error)
|
||||
// GetAllConversationIDsNumber returns the number of all conversation IDs.
|
||||
GetAllConversationIDsNumber(ctx context.Context) (int64, error)
|
||||
// PageConversationIDs paginates through conversation IDs based on the specified pagination settings.
|
||||
PageConversationIDs(ctx context.Context, pagination pagination.Pagination) (conversationIDs []string, err error)
|
||||
// GetConversationsByConversationID retrieves conversations by their IDs.
|
||||
GetConversationsByConversationID(ctx context.Context, conversationIDs []string) ([]*relationtb.Conversation, error)
|
||||
// GetConversationIDsNeedDestruct fetches conversations that need to be destructed based on specific criteria.
|
||||
GetConversationIDsNeedDestruct(ctx context.Context) ([]*relationtb.Conversation, error)
|
||||
// GetConversationNotReceiveMessageUserIDs gets user IDs for users in a conversation who have not received messages.
|
||||
GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error)
|
||||
// GetUserAllHasReadSeqs(ctx context.Context, ownerUserID string) (map[string]int64, error)
|
||||
// FindRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) ([]string, error)
|
||||
}
|
||||
|
||||
func NewConversationDatabase(conversation database.Conversation, cache cache.ConversationCache, tx tx.Tx) ConversationDatabase {
|
||||
return &conversationDatabase{
|
||||
conversationDB: conversation,
|
||||
cache: cache,
|
||||
tx: tx,
|
||||
}
|
||||
}
|
||||
|
||||
type conversationDatabase struct {
|
||||
conversationDB database.Conversation
|
||||
cache cache.ConversationCache
|
||||
tx tx.Tx
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) SetUsersConversationFieldTx(ctx context.Context, userIDs []string, conversation *relationtb.Conversation, fieldMap map[string]any) (err error) {
|
||||
return c.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
cache := c.cache.CloneConversationCache()
|
||||
if conversation.GroupID != "" {
|
||||
cache = cache.DelSuperGroupRecvMsgNotNotifyUserIDs(conversation.GroupID).DelSuperGroupRecvMsgNotNotifyUserIDsHash(conversation.GroupID)
|
||||
}
|
||||
haveUserIDs, err := c.conversationDB.FindUserID(ctx, userIDs, []string{conversation.ConversationID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(haveUserIDs) > 0 {
|
||||
_, err = c.conversationDB.UpdateByMap(ctx, haveUserIDs, conversation.ConversationID, fieldMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache = cache.DelUsersConversation(conversation.ConversationID, haveUserIDs...)
|
||||
if _, ok := fieldMap["has_read_seq"]; ok {
|
||||
for _, userID := range haveUserIDs {
|
||||
cache = cache.DelUserAllHasReadSeqs(userID, conversation.ConversationID)
|
||||
}
|
||||
}
|
||||
if _, ok := fieldMap["recv_msg_opt"]; ok {
|
||||
cache = cache.DelConversationNotReceiveMessageUserIDs(conversation.ConversationID)
|
||||
}
|
||||
}
|
||||
NotUserIDs := stringutil.DifferenceString(haveUserIDs, userIDs)
|
||||
log.ZDebug(ctx, "SetUsersConversationFieldTx", "NotUserIDs", NotUserIDs, "haveUserIDs", haveUserIDs, "userIDs", userIDs)
|
||||
var conversations []*relationtb.Conversation
|
||||
now := time.Now()
|
||||
for _, v := range NotUserIDs {
|
||||
temp := new(relationtb.Conversation)
|
||||
if err = datautil.CopyStructFields(temp, conversation); err != nil {
|
||||
return err
|
||||
}
|
||||
temp.OwnerUserID = v
|
||||
temp.CreateTime = now
|
||||
conversations = append(conversations, temp)
|
||||
}
|
||||
if len(conversations) > 0 {
|
||||
err = c.conversationDB.Create(ctx, conversations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache = cache.DelConversationIDs(NotUserIDs...).DelUserConversationIDsHash(NotUserIDs...).DelConversations(conversation.ConversationID, NotUserIDs...)
|
||||
}
|
||||
return cache.ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) UpdateUsersConversationField(ctx context.Context, userIDs []string, conversationID string, args map[string]any) error {
|
||||
_, err := c.conversationDB.UpdateByMap(ctx, userIDs, conversationID, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache := c.cache.CloneConversationCache()
|
||||
cache = cache.DelUsersConversation(conversationID, userIDs...)
|
||||
if _, ok := args["recv_msg_opt"]; ok {
|
||||
cache = cache.DelConversationNotReceiveMessageUserIDs(conversationID)
|
||||
}
|
||||
return cache.ChainExecDel(ctx)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) CreateConversation(ctx context.Context, conversations []*relationtb.Conversation) error {
|
||||
if err := c.conversationDB.Create(ctx, conversations); err != nil {
|
||||
return err
|
||||
}
|
||||
var userIDs []string
|
||||
cache := c.cache.CloneConversationCache()
|
||||
for _, conversation := range conversations {
|
||||
cache = cache.DelConversations(conversation.OwnerUserID, conversation.ConversationID)
|
||||
cache = cache.DelConversationNotReceiveMessageUserIDs(conversation.ConversationID)
|
||||
userIDs = append(userIDs, conversation.OwnerUserID)
|
||||
}
|
||||
return cache.DelConversationIDs(userIDs...).DelUserConversationIDsHash(userIDs...).ChainExecDel(ctx)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) SyncPeerUserPrivateConversationTx(ctx context.Context, conversations []*relationtb.Conversation) error {
|
||||
return c.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
cache := c.cache.CloneConversationCache()
|
||||
for _, conversation := range conversations {
|
||||
for _, v := range [][2]string{{conversation.OwnerUserID, conversation.UserID}, {conversation.UserID, conversation.OwnerUserID}} {
|
||||
ownerUserID := v[0]
|
||||
userID := v[1]
|
||||
haveUserIDs, err := c.conversationDB.FindUserID(ctx, []string{ownerUserID}, []string{conversation.ConversationID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(haveUserIDs) > 0 {
|
||||
_, err := c.conversationDB.UpdateByMap(ctx, []string{ownerUserID}, conversation.ConversationID, map[string]any{"is_private_chat": conversation.IsPrivateChat})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache = cache.DelUsersConversation(conversation.ConversationID, ownerUserID)
|
||||
} else {
|
||||
newConversation := *conversation
|
||||
newConversation.OwnerUserID = ownerUserID
|
||||
newConversation.UserID = userID
|
||||
newConversation.ConversationID = conversation.ConversationID
|
||||
newConversation.IsPrivateChat = conversation.IsPrivateChat
|
||||
if err := c.conversationDB.Create(ctx, []*relationtb.Conversation{&newConversation}); err != nil {
|
||||
return err
|
||||
}
|
||||
cache = cache.DelConversationIDs(ownerUserID).DelUserConversationIDsHash(ownerUserID)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cache.ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) FindConversations(ctx context.Context, ownerUserID string, conversationIDs []string) ([]*relationtb.Conversation, error) {
|
||||
return c.cache.GetConversations(ctx, ownerUserID, conversationIDs)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetConversation(ctx context.Context, ownerUserID string, conversationID string) (*relationtb.Conversation, error) {
|
||||
return c.cache.GetConversation(ctx, ownerUserID, conversationID)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetUserAllConversation(ctx context.Context, ownerUserID string) ([]*relationtb.Conversation, error) {
|
||||
return c.cache.GetUserAllConversations(ctx, ownerUserID)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) SetUserConversations(ctx context.Context, ownerUserID string, conversations []*relationtb.Conversation) error {
|
||||
return c.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
cache := c.cache.CloneConversationCache()
|
||||
groupIDs := datautil.Distinct(datautil.Filter(conversations, func(e *relationtb.Conversation) (string, bool) {
|
||||
return e.GroupID, e.GroupID != ""
|
||||
}))
|
||||
for _, groupID := range groupIDs {
|
||||
cache = cache.DelSuperGroupRecvMsgNotNotifyUserIDs(groupID).DelSuperGroupRecvMsgNotNotifyUserIDsHash(groupID)
|
||||
}
|
||||
var conversationIDs []string
|
||||
for _, conversation := range conversations {
|
||||
conversationIDs = append(conversationIDs, conversation.ConversationID)
|
||||
cache = cache.DelConversations(conversation.OwnerUserID, conversation.ConversationID)
|
||||
}
|
||||
existConversations, err := c.conversationDB.Find(ctx, ownerUserID, conversationIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(existConversations) > 0 {
|
||||
for _, conversation := range conversations {
|
||||
err = c.conversationDB.Update(ctx, conversation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
var existConversationIDs []string
|
||||
for _, conversation := range existConversations {
|
||||
existConversationIDs = append(existConversationIDs, conversation.ConversationID)
|
||||
}
|
||||
|
||||
var notExistConversations []*relationtb.Conversation
|
||||
for _, conversation := range conversations {
|
||||
if !datautil.Contain(conversation.ConversationID, existConversationIDs...) {
|
||||
notExistConversations = append(notExistConversations, conversation)
|
||||
}
|
||||
}
|
||||
if len(notExistConversations) > 0 {
|
||||
err = c.conversationDB.Create(ctx, notExistConversations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache = cache.DelConversationIDs(ownerUserID).
|
||||
DelUserConversationIDsHash(ownerUserID).
|
||||
DelConversationNotReceiveMessageUserIDs(datautil.Slice(notExistConversations, func(e *relationtb.Conversation) string { return e.ConversationID })...)
|
||||
}
|
||||
return cache.ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
// func (c *conversationDatabase) FindRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) ([]string, error) {
|
||||
// return c.cache.GetSuperGroupRecvMsgNotNotifyUserIDs(ctx, groupID)
|
||||
//}
|
||||
|
||||
func (c *conversationDatabase) CreateGroupChatConversation(ctx context.Context, groupID string, userIDs []string) error {
|
||||
return c.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
cache := c.cache.CloneConversationCache()
|
||||
conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, groupID)
|
||||
existConversationUserIDs, err := c.conversationDB.FindUserID(ctx, userIDs, []string{conversationID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
notExistUserIDs := stringutil.DifferenceString(userIDs, existConversationUserIDs)
|
||||
var conversations []*relationtb.Conversation
|
||||
for _, v := range notExistUserIDs {
|
||||
conversation := relationtb.Conversation{ConversationType: constant.ReadGroupChatType, GroupID: groupID, OwnerUserID: v, ConversationID: conversationID}
|
||||
conversations = append(conversations, &conversation)
|
||||
cache = cache.DelConversations(v, conversationID).DelConversationNotReceiveMessageUserIDs(conversationID)
|
||||
}
|
||||
cache = cache.DelConversationIDs(notExistUserIDs...).DelUserConversationIDsHash(notExistUserIDs...)
|
||||
if len(conversations) > 0 {
|
||||
err = c.conversationDB.Create(ctx, conversations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = c.conversationDB.UpdateByMap(ctx, existConversationUserIDs, conversationID, map[string]any{"max_seq": 0})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range existConversationUserIDs {
|
||||
cache = cache.DelConversations(v, conversationID)
|
||||
}
|
||||
return cache.ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetConversationIDs(ctx context.Context, userID string) ([]string, error) {
|
||||
return c.cache.GetUserConversationIDs(ctx, userID)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetUserConversationIDsHash(ctx context.Context, ownerUserID string) (hash uint64, err error) {
|
||||
return c.cache.GetUserConversationIDsHash(ctx, ownerUserID)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetAllConversationIDs(ctx context.Context) ([]string, error) {
|
||||
return c.conversationDB.GetAllConversationIDs(ctx)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetAllConversationIDsNumber(ctx context.Context) (int64, error) {
|
||||
return c.conversationDB.GetAllConversationIDsNumber(ctx)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) PageConversationIDs(ctx context.Context, pagination pagination.Pagination) ([]string, error) {
|
||||
return c.conversationDB.PageConversationIDs(ctx, pagination)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetConversationsByConversationID(ctx context.Context, conversationIDs []string) ([]*relationtb.Conversation, error) {
|
||||
return c.conversationDB.GetConversationsByConversationID(ctx, conversationIDs)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetConversationIDsNeedDestruct(ctx context.Context) ([]*relationtb.Conversation, error) {
|
||||
return c.conversationDB.GetConversationIDsNeedDestruct(ctx)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error) {
|
||||
return c.cache.GetConversationNotReceiveMessageUserIDs(ctx, conversationID)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright © 2024 OpenIM. 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 controller // import "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
|
||||
@@ -0,0 +1,348 @@
|
||||
// Copyright © 2023 OpenIM. 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/db/tx"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
)
|
||||
|
||||
type FriendDatabase interface {
|
||||
// CheckIn checks if user2 is in user1's friend list (inUser1Friends==true) and if user1 is in user2's friend list (inUser2Friends==true)
|
||||
CheckIn(ctx context.Context, user1, user2 string) (inUser1Friends bool, inUser2Friends bool, err error)
|
||||
|
||||
// AddFriendRequest adds or updates a friend request
|
||||
AddFriendRequest(ctx context.Context, fromUserID, toUserID string, reqMsg string, ex string) (err error)
|
||||
|
||||
// BecomeFriends first checks if the users are already in the friends model; if not, it inserts them as friends
|
||||
BecomeFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, addSource int32) (err error)
|
||||
|
||||
// RefuseFriendRequest refuses a friend request
|
||||
RefuseFriendRequest(ctx context.Context, friendRequest *model.FriendRequest) (err error)
|
||||
|
||||
// AgreeFriendRequest accepts a friend request
|
||||
AgreeFriendRequest(ctx context.Context, friendRequest *model.FriendRequest) (err error)
|
||||
|
||||
// Delete removes a friend or friends from the owner's friend list
|
||||
Delete(ctx context.Context, ownerUserID string, friendUserIDs []string) (err error)
|
||||
|
||||
// UpdateRemark updates the remark for a friend
|
||||
UpdateRemark(ctx context.Context, ownerUserID, friendUserID, remark string) (err error)
|
||||
|
||||
// PageOwnerFriends retrieves the friend list of ownerUserID with pagination
|
||||
PageOwnerFriends(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, friends []*model.Friend, err error)
|
||||
|
||||
// PageInWhoseFriends finds the users who have friendUserID in their friend list with pagination
|
||||
PageInWhoseFriends(ctx context.Context, friendUserID string, pagination pagination.Pagination) (total int64, friends []*model.Friend, err error)
|
||||
|
||||
// PageFriendRequestFromMe retrieves the friend requests sent by the user with pagination
|
||||
PageFriendRequestFromMe(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, friends []*model.FriendRequest, err error)
|
||||
|
||||
// PageFriendRequestToMe retrieves the friend requests received by the user with pagination
|
||||
PageFriendRequestToMe(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, friends []*model.FriendRequest, err error)
|
||||
|
||||
// FindFriendsWithError fetches specified friends of a user and returns an error if any do not exist
|
||||
FindFriendsWithError(ctx context.Context, ownerUserID string, friendUserIDs []string) (friends []*model.Friend, err error)
|
||||
|
||||
// FindFriendUserIDs retrieves the friend IDs of a user
|
||||
FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error)
|
||||
|
||||
// FindBothFriendRequests finds friend requests sent and received
|
||||
FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*model.FriendRequest, err error)
|
||||
|
||||
// UpdateFriends updates fields for friends
|
||||
UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (err error)
|
||||
}
|
||||
|
||||
type friendDatabase struct {
|
||||
friend database.Friend
|
||||
friendRequest database.FriendRequest
|
||||
tx tx.Tx
|
||||
cache cache.FriendCache
|
||||
}
|
||||
|
||||
func NewFriendDatabase(friend database.Friend, friendRequest database.FriendRequest, cache cache.FriendCache, tx tx.Tx) FriendDatabase {
|
||||
return &friendDatabase{friend: friend, friendRequest: friendRequest, cache: cache, tx: tx}
|
||||
}
|
||||
|
||||
// CheckIn verifies if user2 is in user1's friend list (inUser1Friends returns true) and
|
||||
// if user1 is in user2's friend list (inUser2Friends returns true).
|
||||
func (f *friendDatabase) CheckIn(ctx context.Context, userID1, userID2 string) (inUser1Friends bool, inUser2Friends bool, err error) {
|
||||
// Retrieve friend IDs of userID1 from the cache
|
||||
userID1FriendIDs, err := f.cache.GetFriendIDs(ctx, userID1)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error retrieving friend IDs for user %s: %w", userID1, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve friend IDs of userID2 from the cache
|
||||
userID2FriendIDs, err := f.cache.GetFriendIDs(ctx, userID2)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error retrieving friend IDs for user %s: %w", userID2, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if userID2 is in userID1's friend list and vice versa
|
||||
inUser1Friends = datautil.Contain(userID2, userID1FriendIDs...)
|
||||
inUser2Friends = datautil.Contain(userID1, userID2FriendIDs...)
|
||||
return inUser1Friends, inUser2Friends, nil
|
||||
}
|
||||
|
||||
// AddFriendRequest adds or updates a friend request.
|
||||
func (f *friendDatabase) AddFriendRequest(ctx context.Context, fromUserID, toUserID string, reqMsg string, ex string) (err error) {
|
||||
return f.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
_, err := f.friendRequest.Take(ctx, fromUserID, toUserID)
|
||||
switch {
|
||||
case err == nil:
|
||||
m := make(map[string]any, 1)
|
||||
m["handle_result"] = 0
|
||||
m["handle_msg"] = ""
|
||||
m["req_msg"] = reqMsg
|
||||
m["ex"] = ex
|
||||
m["create_time"] = time.Now()
|
||||
return f.friendRequest.UpdateByMap(ctx, fromUserID, toUserID, m)
|
||||
case mgo.IsNotFound(err):
|
||||
return f.friendRequest.Create(
|
||||
ctx,
|
||||
[]*model.FriendRequest{{FromUserID: fromUserID, ToUserID: toUserID, ReqMsg: reqMsg, Ex: ex, CreateTime: time.Now(), HandleTime: time.Unix(0, 0)}},
|
||||
)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// (1) First determine whether it is in the friends list (in or out does not return an error) (2) for not in the friends list can be inserted.
|
||||
func (f *friendDatabase) BecomeFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, addSource int32) (err error) {
|
||||
return f.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
cache := f.cache.CloneFriendCache()
|
||||
// user find friends
|
||||
fs1, err := f.friend.FindFriends(ctx, ownerUserID, friendUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opUserID := mcontext.GetOperationID(ctx)
|
||||
for _, v := range friendUserIDs {
|
||||
fs1 = append(fs1, &model.Friend{OwnerUserID: ownerUserID, FriendUserID: v, AddSource: addSource, OperatorUserID: opUserID})
|
||||
}
|
||||
fs11 := datautil.DistinctAny(fs1, func(e *model.Friend) string {
|
||||
return e.FriendUserID
|
||||
})
|
||||
|
||||
err = f.friend.Create(ctx, fs11)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs2, err := f.friend.FindReversalFriends(ctx, ownerUserID, friendUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var newFriendIDs []string
|
||||
for _, v := range friendUserIDs {
|
||||
fs2 = append(fs2, &model.Friend{OwnerUserID: v, FriendUserID: ownerUserID, AddSource: addSource, OperatorUserID: opUserID})
|
||||
newFriendIDs = append(newFriendIDs, v)
|
||||
}
|
||||
fs22 := datautil.DistinctAny(fs2, func(e *model.Friend) string {
|
||||
return e.OwnerUserID
|
||||
})
|
||||
err = f.friend.Create(ctx, fs22)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newFriendIDs = append(newFriendIDs, ownerUserID)
|
||||
cache = cache.DelFriendIDs(newFriendIDs...)
|
||||
return cache.ChainExecDel(ctx)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// RefuseFriendRequest rejects a friend request. It first checks for an existing, unprocessed request.
|
||||
// If no such request exists, it returns an error. Otherwise, it marks the request as refused.
|
||||
func (f *friendDatabase) RefuseFriendRequest(ctx context.Context, friendRequest *model.FriendRequest) error {
|
||||
// Attempt to retrieve the friend request from the database.
|
||||
fr, err := f.friendRequest.Take(ctx, friendRequest.FromUserID, friendRequest.ToUserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve friend request from %s to %s: %w", friendRequest.FromUserID, friendRequest.ToUserID, err)
|
||||
}
|
||||
|
||||
// Check if the friend request has already been handled.
|
||||
if fr.HandleResult != 0 {
|
||||
return fmt.Errorf("friend request from %s to %s has already been processed", friendRequest.FromUserID, friendRequest.ToUserID)
|
||||
}
|
||||
|
||||
// Log the action of refusing the friend request for debugging and auditing purposes.
|
||||
log.ZDebug(ctx, "Refusing friend request", map[string]interface{}{
|
||||
"DB_FriendRequest": fr,
|
||||
"Arg_FriendRequest": friendRequest,
|
||||
})
|
||||
|
||||
// Mark the friend request as refused and update the handle time.
|
||||
friendRequest.HandleResult = constant.FriendResponseRefuse
|
||||
friendRequest.HandleTime = time.Now()
|
||||
if err := f.friendRequest.Update(ctx, friendRequest); err != nil {
|
||||
return fmt.Errorf("failed to update friend request from %s to %s as refused: %w", friendRequest.FromUserID, friendRequest.ToUserID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AgreeFriendRequest accepts a friend request. It first checks for an existing, unprocessed request.
|
||||
func (f *friendDatabase) AgreeFriendRequest(ctx context.Context, friendRequest *model.FriendRequest) (err error) {
|
||||
return f.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
now := time.Now()
|
||||
fr, err := f.friendRequest.Take(ctx, friendRequest.FromUserID, friendRequest.ToUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fr.HandleResult != 0 {
|
||||
return errs.ErrArgs.WrapMsg("the friend request has been processed")
|
||||
}
|
||||
friendRequest.HandlerUserID = mcontext.GetOpUserID(ctx)
|
||||
friendRequest.HandleResult = constant.FriendResponseAgree
|
||||
friendRequest.HandleTime = now
|
||||
err = f.friendRequest.Update(ctx, friendRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fr2, err := f.friendRequest.Take(ctx, friendRequest.ToUserID, friendRequest.FromUserID)
|
||||
if err == nil && fr2.HandleResult == constant.FriendResponseNotHandle {
|
||||
fr2.HandlerUserID = mcontext.GetOpUserID(ctx)
|
||||
fr2.HandleResult = constant.FriendResponseAgree
|
||||
fr2.HandleTime = now
|
||||
err = f.friendRequest.Update(ctx, fr2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil && (!mgo.IsNotFound(err)) {
|
||||
return err
|
||||
}
|
||||
|
||||
exists, err := f.friend.FindUserState(ctx, friendRequest.FromUserID, friendRequest.ToUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
existsMap := datautil.SliceSet(datautil.Slice(exists, func(friend *model.Friend) [2]string {
|
||||
return [...]string{friend.OwnerUserID, friend.FriendUserID} // My - Friend
|
||||
}))
|
||||
var adds []*model.Friend
|
||||
if _, ok := existsMap[[...]string{friendRequest.ToUserID, friendRequest.FromUserID}]; !ok { // My - Friend
|
||||
adds = append(
|
||||
adds,
|
||||
&model.Friend{
|
||||
OwnerUserID: friendRequest.ToUserID,
|
||||
FriendUserID: friendRequest.FromUserID,
|
||||
AddSource: int32(constant.BecomeFriendByApply),
|
||||
OperatorUserID: friendRequest.FromUserID,
|
||||
},
|
||||
)
|
||||
}
|
||||
if _, ok := existsMap[[...]string{friendRequest.FromUserID, friendRequest.ToUserID}]; !ok { // My - Friend
|
||||
adds = append(
|
||||
adds,
|
||||
&model.Friend{
|
||||
OwnerUserID: friendRequest.FromUserID,
|
||||
FriendUserID: friendRequest.ToUserID,
|
||||
AddSource: int32(constant.BecomeFriendByApply),
|
||||
OperatorUserID: friendRequest.FromUserID,
|
||||
},
|
||||
)
|
||||
}
|
||||
if len(adds) > 0 {
|
||||
if err := f.friend.Create(ctx, adds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return f.cache.DelFriendIDs(friendRequest.ToUserID, friendRequest.FromUserID).ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
// Delete removes a friend relationship. It is assumed that the external caller has verified the friendship status.
|
||||
func (f *friendDatabase) Delete(ctx context.Context, ownerUserID string, friendUserIDs []string) (err error) {
|
||||
if err := f.friend.Delete(ctx, ownerUserID, friendUserIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.cache.DelFriendIDs(append(friendUserIDs, ownerUserID)...).ChainExecDel(ctx)
|
||||
}
|
||||
|
||||
// UpdateRemark updates the remark for a friend. Zero value for remark is also supported.
|
||||
func (f *friendDatabase) UpdateRemark(ctx context.Context, ownerUserID, friendUserID, remark string) (err error) {
|
||||
if err := f.friend.UpdateRemark(ctx, ownerUserID, friendUserID, remark); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.cache.DelFriend(ownerUserID, friendUserID).ChainExecDel(ctx)
|
||||
}
|
||||
|
||||
// PageOwnerFriends retrieves the list of friends for the ownerUserID. It does not return an error if the result is empty.
|
||||
func (f *friendDatabase) PageOwnerFriends(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, friends []*model.Friend, err error) {
|
||||
return f.friend.FindOwnerFriends(ctx, ownerUserID, pagination)
|
||||
}
|
||||
|
||||
// PageInWhoseFriends identifies in whose friend lists the friendUserID appears.
|
||||
func (f *friendDatabase) PageInWhoseFriends(ctx context.Context, friendUserID string, pagination pagination.Pagination) (total int64, friends []*model.Friend, err error) {
|
||||
return f.friend.FindInWhoseFriends(ctx, friendUserID, pagination)
|
||||
}
|
||||
|
||||
// PageFriendRequestFromMe retrieves friend requests sent by me. It does not return an error if the result is empty.
|
||||
func (f *friendDatabase) PageFriendRequestFromMe(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, friends []*model.FriendRequest, err error) {
|
||||
return f.friendRequest.FindFromUserID(ctx, userID, pagination)
|
||||
}
|
||||
|
||||
// PageFriendRequestToMe retrieves friend requests received by me. It does not return an error if the result is empty.
|
||||
func (f *friendDatabase) PageFriendRequestToMe(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, friends []*model.FriendRequest, err error) {
|
||||
return f.friendRequest.FindToUserID(ctx, userID, pagination)
|
||||
}
|
||||
|
||||
// FindFriendsWithError retrieves specified friends' information for ownerUserID. Returns an error if any friend does not exist.
|
||||
func (f *friendDatabase) FindFriendsWithError(ctx context.Context, ownerUserID string, friendUserIDs []string) (friends []*model.Friend, err error) {
|
||||
friends, err = f.friend.FindFriends(ctx, ownerUserID, friendUserIDs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(friends) != len(friendUserIDs) {
|
||||
err = errs.ErrRecordNotFound.Wrap()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *friendDatabase) FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error) {
|
||||
return f.cache.GetFriendIDs(ctx, ownerUserID)
|
||||
}
|
||||
|
||||
func (f *friendDatabase) FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*model.FriendRequest, err error) {
|
||||
return f.friendRequest.FindBothFriendRequests(ctx, fromUserID, toUserID)
|
||||
}
|
||||
func (f *friendDatabase) UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (err error) {
|
||||
if len(val) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := f.friend.UpdateFriends(ctx, ownerUserID, friendUserIDs, val); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.cache.DelFriends(ownerUserID, friendUserIDs).ChainExecDel(ctx)
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
// Copyright © 2023 OpenIM. 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/common"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/db/tx"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type GroupDatabase interface {
|
||||
// CreateGroup creates new groups along with their members.
|
||||
CreateGroup(ctx context.Context, groups []*model.Group, groupMembers []*model.GroupMember) error
|
||||
// TakeGroup retrieves a single group by its ID.
|
||||
TakeGroup(ctx context.Context, groupID string) (group *model.Group, err error)
|
||||
// FindGroup retrieves multiple groups by their IDs.
|
||||
FindGroup(ctx context.Context, groupIDs []string) (groups []*model.Group, err error)
|
||||
// SearchGroup searches for groups based on a keyword and pagination settings, returns total count and groups.
|
||||
SearchGroup(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*model.Group, error)
|
||||
// UpdateGroup updates the properties of a group identified by its ID.
|
||||
UpdateGroup(ctx context.Context, groupID string, data map[string]any) error
|
||||
// DismissGroup disbands a group and optionally removes its members based on the deleteMember flag.
|
||||
DismissGroup(ctx context.Context, groupID string, deleteMember bool) error
|
||||
|
||||
// TakeGroupMember retrieves a specific group member by group ID and user ID.
|
||||
TakeGroupMember(ctx context.Context, groupID string, userID string) (groupMember *model.GroupMember, err error)
|
||||
// TakeGroupOwner retrieves the owner of a group by group ID.
|
||||
TakeGroupOwner(ctx context.Context, groupID string) (*model.GroupMember, error)
|
||||
// FindGroupMembers retrieves members of a group filtered by user IDs.
|
||||
FindGroupMembers(ctx context.Context, groupID string, userIDs []string) (groupMembers []*model.GroupMember, err error)
|
||||
// FindGroupMemberUser retrieves groups that a user is a member of, filtered by group IDs.
|
||||
FindGroupMemberUser(ctx context.Context, groupIDs []string, userID string) (groupMembers []*model.GroupMember, err error)
|
||||
// FindGroupMemberRoleLevels retrieves group members filtered by their role levels within a group.
|
||||
FindGroupMemberRoleLevels(ctx context.Context, groupID string, roleLevels []int32) (groupMembers []*model.GroupMember, err error)
|
||||
// FindGroupMemberAll retrieves all members of a group.
|
||||
FindGroupMemberAll(ctx context.Context, groupID string) (groupMembers []*model.GroupMember, err error)
|
||||
// FindGroupsOwner retrieves the owners for multiple groups.
|
||||
FindGroupsOwner(ctx context.Context, groupIDs []string) ([]*model.GroupMember, error)
|
||||
// FindGroupMemberUserID retrieves the user IDs of all members in a group.
|
||||
FindGroupMemberUserID(ctx context.Context, groupID string) ([]string, error)
|
||||
// FindGroupMemberNum retrieves the number of members in a group.
|
||||
FindGroupMemberNum(ctx context.Context, groupID string) (uint32, error)
|
||||
// FindUserManagedGroupID retrieves group IDs managed by a user.
|
||||
FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error)
|
||||
// PageGroupRequest paginates through group requests for specified groups.
|
||||
PageGroupRequest(ctx context.Context, groupIDs []string, pagination pagination.Pagination) (int64, []*model.GroupRequest, error)
|
||||
// GetGroupRoleLevelMemberIDs retrieves user IDs of group members with a specific role level.
|
||||
GetGroupRoleLevelMemberIDs(ctx context.Context, groupID string, roleLevel int32) ([]string, error)
|
||||
|
||||
// PageGetJoinGroup paginates through groups that a user has joined.
|
||||
PageGetJoinGroup(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, totalGroupMembers []*model.GroupMember, err error)
|
||||
// PageGetGroupMember paginates through members of a group.
|
||||
PageGetGroupMember(ctx context.Context, groupID string, pagination pagination.Pagination) (total int64, totalGroupMembers []*model.GroupMember, err error)
|
||||
// SearchGroupMember searches for group members based on a keyword, group ID, and pagination settings.
|
||||
SearchGroupMember(ctx context.Context, keyword string, groupID string, pagination pagination.Pagination) (int64, []*model.GroupMember, error)
|
||||
// HandlerGroupRequest processes a group join request with a specified result.
|
||||
HandlerGroupRequest(ctx context.Context, groupID string, userID string, handledMsg string, handleResult int32, member *model.GroupMember) error
|
||||
// DeleteGroupMember removes specified users from a group.
|
||||
DeleteGroupMember(ctx context.Context, groupID string, userIDs []string) error
|
||||
// MapGroupMemberUserID maps group IDs to their members' simplified user IDs.
|
||||
MapGroupMemberUserID(ctx context.Context, groupIDs []string) (map[string]*common.GroupSimpleUserID, error)
|
||||
// MapGroupMemberNum maps group IDs to their member count.
|
||||
MapGroupMemberNum(ctx context.Context, groupIDs []string) (map[string]uint32, error)
|
||||
// TransferGroupOwner transfers the ownership of a group to another user.
|
||||
TransferGroupOwner(ctx context.Context, groupID string, oldOwnerUserID, newOwnerUserID string, roleLevel int32) error
|
||||
// UpdateGroupMember updates properties of a group member.
|
||||
UpdateGroupMember(ctx context.Context, groupID string, userID string, data map[string]any) error
|
||||
// UpdateGroupMembers batch updates properties of group members.
|
||||
UpdateGroupMembers(ctx context.Context, data []*common.BatchUpdateGroupMember) error
|
||||
|
||||
// CreateGroupRequest creates new group join requests.
|
||||
CreateGroupRequest(ctx context.Context, requests []*model.GroupRequest) error
|
||||
// TakeGroupRequest retrieves a specific group join request.
|
||||
TakeGroupRequest(ctx context.Context, groupID string, userID string) (*model.GroupRequest, error)
|
||||
// FindGroupRequests retrieves multiple group join requests.
|
||||
FindGroupRequests(ctx context.Context, groupID string, userIDs []string) ([]*model.GroupRequest, error)
|
||||
// PageGroupRequestUser paginates through group join requests made by a user.
|
||||
PageGroupRequestUser(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*model.GroupRequest, error)
|
||||
|
||||
// CountTotal counts the total number of groups as of a certain date.
|
||||
CountTotal(ctx context.Context, before *time.Time) (count int64, err error)
|
||||
// CountRangeEverydayTotal counts the daily group creation total within a specified date range.
|
||||
CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error)
|
||||
// DeleteGroupMemberHash deletes the hash entries for group members in specified groups.
|
||||
DeleteGroupMemberHash(ctx context.Context, groupIDs []string) error
|
||||
}
|
||||
|
||||
func NewGroupDatabase(
|
||||
rdb redis.UniversalClient,
|
||||
localCache *config.LocalCache,
|
||||
groupDB database.Group,
|
||||
groupMemberDB database.GroupMember,
|
||||
groupRequestDB database.GroupRequest,
|
||||
ctxTx tx.Tx,
|
||||
groupHash cache.GroupHash,
|
||||
) GroupDatabase {
|
||||
return &groupDatabase{
|
||||
groupDB: groupDB,
|
||||
groupMemberDB: groupMemberDB,
|
||||
groupRequestDB: groupRequestDB,
|
||||
ctxTx: ctxTx,
|
||||
cache: redis2.NewGroupCacheRedis(rdb, localCache, groupDB, groupMemberDB, groupRequestDB, groupHash, redis2.GetRocksCacheOptions()),
|
||||
}
|
||||
}
|
||||
|
||||
type groupDatabase struct {
|
||||
groupDB database.Group
|
||||
groupMemberDB database.GroupMember
|
||||
groupRequestDB database.GroupRequest
|
||||
ctxTx tx.Tx
|
||||
cache cache.GroupCache
|
||||
}
|
||||
|
||||
func (g *groupDatabase) FindGroupMembers(ctx context.Context, groupID string, userIDs []string) ([]*model.GroupMember, error) {
|
||||
return g.cache.GetGroupMembersInfo(ctx, groupID, userIDs)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) FindGroupMemberUser(ctx context.Context, groupIDs []string, userID string) ([]*model.GroupMember, error) {
|
||||
return g.cache.FindGroupMemberUser(ctx, groupIDs, userID)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) FindGroupMemberRoleLevels(ctx context.Context, groupID string, roleLevels []int32) ([]*model.GroupMember, error) {
|
||||
return g.cache.GetGroupRolesLevelMemberInfo(ctx, groupID, roleLevels)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) FindGroupMemberAll(ctx context.Context, groupID string) ([]*model.GroupMember, error) {
|
||||
return g.cache.GetAllGroupMembersInfo(ctx, groupID)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) FindGroupsOwner(ctx context.Context, groupIDs []string) ([]*model.GroupMember, error) {
|
||||
return g.cache.GetGroupsOwner(ctx, groupIDs)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) GetGroupRoleLevelMemberIDs(ctx context.Context, groupID string, roleLevel int32) ([]string, error) {
|
||||
return g.cache.GetGroupRoleLevelMemberIDs(ctx, groupID, roleLevel)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) CreateGroup(ctx context.Context, groups []*model.Group, groupMembers []*model.GroupMember) error {
|
||||
if len(groups)+len(groupMembers) == 0 {
|
||||
return nil
|
||||
}
|
||||
return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
|
||||
c := g.cache.CloneGroupCache()
|
||||
if len(groups) > 0 {
|
||||
if err := g.groupDB.Create(ctx, groups); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, group := range groups {
|
||||
c = c.DelGroupsInfo(group.GroupID).
|
||||
DelGroupMembersHash(group.GroupID).
|
||||
DelGroupMembersHash(group.GroupID).
|
||||
DelGroupsMemberNum(group.GroupID).
|
||||
DelGroupMemberIDs(group.GroupID).
|
||||
DelGroupAllRoleLevel(group.GroupID)
|
||||
}
|
||||
}
|
||||
if len(groupMembers) > 0 {
|
||||
if err := g.groupMemberDB.Create(ctx, groupMembers); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, groupMember := range groupMembers {
|
||||
c = c.DelGroupMembersHash(groupMember.GroupID).
|
||||
DelGroupsMemberNum(groupMember.GroupID).
|
||||
DelGroupMemberIDs(groupMember.GroupID).
|
||||
DelJoinedGroupID(groupMember.UserID).
|
||||
DelGroupMembersInfo(groupMember.GroupID, groupMember.UserID).
|
||||
DelGroupAllRoleLevel(groupMember.GroupID)
|
||||
}
|
||||
}
|
||||
return c.ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *groupDatabase) FindGroupMemberUserID(ctx context.Context, groupID string) ([]string, error) {
|
||||
return g.cache.GetGroupMemberIDs(ctx, groupID)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) FindGroupMemberNum(ctx context.Context, groupID string) (uint32, error) {
|
||||
num, err := g.cache.GetGroupMemberNum(ctx, groupID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(num), nil
|
||||
}
|
||||
|
||||
func (g *groupDatabase) TakeGroup(ctx context.Context, groupID string) (*model.Group, error) {
|
||||
return g.cache.GetGroupInfo(ctx, groupID)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) FindGroup(ctx context.Context, groupIDs []string) ([]*model.Group, error) {
|
||||
return g.cache.GetGroupsInfo(ctx, groupIDs)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) SearchGroup(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*model.Group, error) {
|
||||
return g.groupDB.Search(ctx, keyword, pagination)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) UpdateGroup(ctx context.Context, groupID string, data map[string]any) error {
|
||||
if err := g.groupDB.UpdateMap(ctx, groupID, data); err != nil {
|
||||
return err
|
||||
}
|
||||
return g.cache.DelGroupsInfo(groupID).ChainExecDel(ctx)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) DismissGroup(ctx context.Context, groupID string, deleteMember bool) error {
|
||||
return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
|
||||
c := g.cache.CloneGroupCache()
|
||||
if err := g.groupDB.UpdateStatus(ctx, groupID, constant.GroupStatusDismissed); err != nil {
|
||||
return err
|
||||
}
|
||||
if deleteMember {
|
||||
userIDs, err := g.cache.GetGroupMemberIDs(ctx, groupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.groupMemberDB.Delete(ctx, groupID, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
c = c.DelJoinedGroupID(userIDs...).
|
||||
DelGroupMemberIDs(groupID).
|
||||
DelGroupsMemberNum(groupID).
|
||||
DelGroupMembersHash(groupID).
|
||||
DelGroupAllRoleLevel(groupID).
|
||||
DelGroupMembersInfo(groupID, userIDs...)
|
||||
}
|
||||
return c.DelGroupsInfo(groupID).ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *groupDatabase) TakeGroupMember(ctx context.Context, groupID string, userID string) (*model.GroupMember, error) {
|
||||
return g.cache.GetGroupMemberInfo(ctx, groupID, userID)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) TakeGroupOwner(ctx context.Context, groupID string) (*model.GroupMember, error) {
|
||||
return g.cache.GetGroupOwner(ctx, groupID)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) {
|
||||
return g.groupMemberDB.FindUserManagedGroupID(ctx, userID)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) PageGroupRequest(ctx context.Context, groupIDs []string, pagination pagination.Pagination) (int64, []*model.GroupRequest, error) {
|
||||
return g.groupRequestDB.PageGroup(ctx, groupIDs, pagination)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) PageGetJoinGroup(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, totalGroupMembers []*model.GroupMember, err error) {
|
||||
groupIDs, err := g.cache.GetJoinedGroupIDs(ctx, userID)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for _, groupID := range datautil.Paginate(groupIDs, int(pagination.GetPageNumber()), int(pagination.GetShowNumber())) {
|
||||
groupMembers, err := g.cache.GetGroupMembersInfo(ctx, groupID, []string{userID})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
totalGroupMembers = append(totalGroupMembers, groupMembers...)
|
||||
}
|
||||
return int64(len(groupIDs)), totalGroupMembers, nil
|
||||
}
|
||||
|
||||
func (g *groupDatabase) PageGetGroupMember(ctx context.Context, groupID string, pagination pagination.Pagination) (total int64, totalGroupMembers []*model.GroupMember, err error) {
|
||||
groupMemberIDs, err := g.cache.GetGroupMemberIDs(ctx, groupID)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
pageIDs := datautil.Paginate(groupMemberIDs, int(pagination.GetPageNumber()), int(pagination.GetShowNumber()))
|
||||
if len(pageIDs) == 0 {
|
||||
return int64(len(groupMemberIDs)), nil, nil
|
||||
}
|
||||
members, err := g.cache.GetGroupMembersInfo(ctx, groupID, pageIDs)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return int64(len(groupMemberIDs)), members, nil
|
||||
}
|
||||
|
||||
func (g *groupDatabase) SearchGroupMember(ctx context.Context, keyword string, groupID string, pagination pagination.Pagination) (int64, []*model.GroupMember, error) {
|
||||
return g.groupMemberDB.SearchMember(ctx, keyword, groupID, pagination)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) HandlerGroupRequest(ctx context.Context, groupID string, userID string, handledMsg string, handleResult int32, member *model.GroupMember) error {
|
||||
return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
|
||||
if err := g.groupRequestDB.UpdateHandler(ctx, groupID, userID, handledMsg, handleResult); err != nil {
|
||||
return err
|
||||
}
|
||||
if member != nil {
|
||||
c := g.cache.CloneGroupCache()
|
||||
if err := g.groupMemberDB.Create(ctx, []*model.GroupMember{member}); err != nil {
|
||||
return err
|
||||
}
|
||||
c = c.DelGroupMembersHash(groupID).
|
||||
DelGroupMembersInfo(groupID, member.UserID).
|
||||
DelGroupMemberIDs(groupID).
|
||||
DelGroupsMemberNum(groupID).
|
||||
DelJoinedGroupID(member.UserID).
|
||||
DelGroupRoleLevel(groupID, []int32{member.RoleLevel})
|
||||
if err := c.ChainExecDel(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (g *groupDatabase) DeleteGroupMember(ctx context.Context, groupID string, userIDs []string) error {
|
||||
if err := g.groupMemberDB.Delete(ctx, groupID, userIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
c := g.cache.CloneGroupCache()
|
||||
return c.DelGroupMembersHash(groupID).
|
||||
DelGroupMemberIDs(groupID).
|
||||
DelGroupsMemberNum(groupID).
|
||||
DelJoinedGroupID(userIDs...).
|
||||
DelGroupMembersInfo(groupID, userIDs...).
|
||||
DelGroupAllRoleLevel(groupID).
|
||||
ChainExecDel(ctx)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) MapGroupMemberUserID(ctx context.Context, groupIDs []string) (map[string]*common.GroupSimpleUserID, error) {
|
||||
return g.cache.GetGroupMemberHashMap(ctx, groupIDs)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) MapGroupMemberNum(ctx context.Context, groupIDs []string) (m map[string]uint32, err error) {
|
||||
m = make(map[string]uint32)
|
||||
for _, groupID := range groupIDs {
|
||||
num, err := g.cache.GetGroupMemberNum(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[groupID] = uint32(num)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (g *groupDatabase) TransferGroupOwner(ctx context.Context, groupID string, oldOwnerUserID, newOwnerUserID string, roleLevel int32) error {
|
||||
return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
|
||||
if err := g.groupMemberDB.UpdateRoleLevel(ctx, groupID, oldOwnerUserID, roleLevel); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.groupMemberDB.UpdateRoleLevel(ctx, groupID, newOwnerUserID, constant.GroupOwner); err != nil {
|
||||
return err
|
||||
}
|
||||
c := g.cache.CloneGroupCache()
|
||||
return c.DelGroupMembersInfo(groupID, oldOwnerUserID, newOwnerUserID).
|
||||
DelGroupAllRoleLevel(groupID).
|
||||
DelGroupMembersHash(groupID).ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *groupDatabase) UpdateGroupMember(ctx context.Context, groupID string, userID string, data map[string]any) error {
|
||||
if err := g.groupMemberDB.Update(ctx, groupID, userID, data); err != nil {
|
||||
return err
|
||||
}
|
||||
c := g.cache.CloneGroupCache()
|
||||
c = c.DelGroupMembersInfo(groupID, userID)
|
||||
if g.groupMemberDB.IsUpdateRoleLevel(data) {
|
||||
c = c.DelGroupAllRoleLevel(groupID)
|
||||
}
|
||||
return c.ChainExecDel(ctx)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) UpdateGroupMembers(ctx context.Context, data []*common.BatchUpdateGroupMember) error {
|
||||
return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
|
||||
c := g.cache.CloneGroupCache()
|
||||
for _, item := range data {
|
||||
if err := g.groupMemberDB.Update(ctx, item.GroupID, item.UserID, item.Map); err != nil {
|
||||
return err
|
||||
}
|
||||
if g.groupMemberDB.IsUpdateRoleLevel(item.Map) {
|
||||
c = c.DelGroupAllRoleLevel(item.GroupID)
|
||||
}
|
||||
c = c.DelGroupMembersInfo(item.GroupID, item.UserID).DelGroupMembersHash(item.GroupID)
|
||||
}
|
||||
return c.ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *groupDatabase) CreateGroupRequest(ctx context.Context, requests []*model.GroupRequest) error {
|
||||
return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
|
||||
for _, request := range requests {
|
||||
if err := g.groupRequestDB.Delete(ctx, request.GroupID, request.UserID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return g.groupRequestDB.Create(ctx, requests)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *groupDatabase) TakeGroupRequest(
|
||||
ctx context.Context,
|
||||
groupID string,
|
||||
userID string,
|
||||
) (*model.GroupRequest, error) {
|
||||
return g.groupRequestDB.Take(ctx, groupID, userID)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) PageGroupRequestUser(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*model.GroupRequest, error) {
|
||||
return g.groupRequestDB.Page(ctx, userID, pagination)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
|
||||
return g.groupDB.CountTotal(ctx, before)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) {
|
||||
return g.groupDB.CountRangeEverydayTotal(ctx, start, end)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) FindGroupRequests(ctx context.Context, groupID string, userIDs []string) ([]*model.GroupRequest, error) {
|
||||
return g.groupRequestDB.FindGroupRequests(ctx, groupID, userIDs)
|
||||
}
|
||||
|
||||
func (g *groupDatabase) DeleteGroupMemberHash(ctx context.Context, groupIDs []string) error {
|
||||
if len(groupIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
c := g.cache.CloneGroupCache()
|
||||
for _, groupID := range groupIDs {
|
||||
c = c.DelGroupMembersHash(groupID)
|
||||
}
|
||||
return c.ChainExecDel(ctx)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
// Copyright © 2023 OpenIM. 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
)
|
||||
|
||||
type PushDatabase interface {
|
||||
DelFcmToken(ctx context.Context, userID string, platformID int) error
|
||||
}
|
||||
|
||||
type pushDataBase struct {
|
||||
cache cache.ThirdCache
|
||||
}
|
||||
|
||||
func NewPushDatabase(cache cache.ThirdCache) PushDatabase {
|
||||
return &pushDataBase{cache: cache}
|
||||
}
|
||||
|
||||
func (p *pushDataBase) DelFcmToken(ctx context.Context, userID string, platformID int) error {
|
||||
return p.cache.DelFcmToken(ctx, userID, platformID)
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright © 2023 OpenIM. 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/tools/s3"
|
||||
"github.com/openimsdk/tools/s3/cont"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type S3Database interface {
|
||||
PartLimit() *s3.PartLimit
|
||||
PartSize(ctx context.Context, size int64) (int64, error)
|
||||
AuthSign(ctx context.Context, uploadID string, partNumbers []int) (*s3.AuthSignResult, error)
|
||||
InitiateMultipartUpload(ctx context.Context, hash string, size int64, expire time.Duration, maxParts int) (*cont.InitiateUploadResult, error)
|
||||
CompleteMultipartUpload(ctx context.Context, uploadID string, parts []string) (*cont.UploadResult, error)
|
||||
AccessURL(ctx context.Context, name string, expire time.Duration, opt *s3.AccessURLOption) (time.Time, string, error)
|
||||
SetObject(ctx context.Context, info *model.Object) error
|
||||
StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error)
|
||||
FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error)
|
||||
}
|
||||
|
||||
func NewS3Database(rdb redis.UniversalClient, s3 s3.Interface, obj database.ObjectInfo) S3Database {
|
||||
return &s3Database{
|
||||
s3: cont.New(redis2.NewS3Cache(rdb, s3), s3),
|
||||
cache: redis2.NewObjectCacheRedis(rdb, obj),
|
||||
db: obj,
|
||||
}
|
||||
}
|
||||
|
||||
type s3Database struct {
|
||||
s3 *cont.Controller
|
||||
cache cache.ObjectCache
|
||||
db database.ObjectInfo
|
||||
}
|
||||
|
||||
func (s *s3Database) PartSize(ctx context.Context, size int64) (int64, error) {
|
||||
return s.s3.PartSize(ctx, size)
|
||||
}
|
||||
|
||||
func (s *s3Database) PartLimit() *s3.PartLimit {
|
||||
return s.s3.PartLimit()
|
||||
}
|
||||
|
||||
func (s *s3Database) AuthSign(ctx context.Context, uploadID string, partNumbers []int) (*s3.AuthSignResult, error) {
|
||||
return s.s3.AuthSign(ctx, uploadID, partNumbers)
|
||||
}
|
||||
|
||||
func (s *s3Database) InitiateMultipartUpload(ctx context.Context, hash string, size int64, expire time.Duration, maxParts int) (*cont.InitiateUploadResult, error) {
|
||||
return s.s3.InitiateUpload(ctx, hash, size, expire, maxParts)
|
||||
}
|
||||
|
||||
func (s *s3Database) CompleteMultipartUpload(ctx context.Context, uploadID string, parts []string) (*cont.UploadResult, error) {
|
||||
return s.s3.CompleteUpload(ctx, uploadID, parts)
|
||||
}
|
||||
|
||||
func (s *s3Database) SetObject(ctx context.Context, info *model.Object) error {
|
||||
info.Engine = s.s3.Engine()
|
||||
if err := s.db.SetObject(ctx, info); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.cache.DelObjectName(info.Engine, info.Name).ChainExecDel(ctx)
|
||||
}
|
||||
|
||||
func (s *s3Database) AccessURL(ctx context.Context, name string, expire time.Duration, opt *s3.AccessURLOption) (time.Time, string, error) {
|
||||
obj, err := s.cache.GetName(ctx, s.s3.Engine(), name)
|
||||
if err != nil {
|
||||
return time.Time{}, "", err
|
||||
}
|
||||
if opt == nil {
|
||||
opt = &s3.AccessURLOption{}
|
||||
}
|
||||
if opt.ContentType == "" {
|
||||
opt.ContentType = obj.ContentType
|
||||
}
|
||||
if opt.Filename == "" {
|
||||
opt.Filename = filepath.Base(obj.Name)
|
||||
}
|
||||
expireTime := time.Now().Add(expire)
|
||||
rawURL, err := s.s3.AccessURL(ctx, obj.Key, expire, opt)
|
||||
if err != nil {
|
||||
return time.Time{}, "", err
|
||||
}
|
||||
return expireTime, rawURL, nil
|
||||
}
|
||||
|
||||
func (s *s3Database) StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error) {
|
||||
return s.s3.StatObject(ctx, name)
|
||||
}
|
||||
|
||||
func (s *s3Database) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) {
|
||||
return s.s3.FormData(ctx, name, size, contentType, duration)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright © 2023 OpenIM. 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
)
|
||||
|
||||
type ThirdDatabase interface {
|
||||
FcmUpdateToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) error
|
||||
SetAppBadge(ctx context.Context, userID string, value int) error
|
||||
// about log for debug
|
||||
UploadLogs(ctx context.Context, logs []*model.Log) error
|
||||
DeleteLogs(ctx context.Context, logID []string, userID string) error
|
||||
SearchLogs(ctx context.Context, keyword string, start time.Time, end time.Time, pagination pagination.Pagination) (int64, []*model.Log, error)
|
||||
GetLogs(ctx context.Context, LogIDs []string, userID string) ([]*model.Log, error)
|
||||
}
|
||||
|
||||
type thirdDatabase struct {
|
||||
cache cache.ThirdCache
|
||||
logdb database.Log
|
||||
}
|
||||
|
||||
// DeleteLogs implements ThirdDatabase.
|
||||
func (t *thirdDatabase) DeleteLogs(ctx context.Context, logID []string, userID string) error {
|
||||
return t.logdb.Delete(ctx, logID, userID)
|
||||
}
|
||||
|
||||
// GetLogs implements ThirdDatabase.
|
||||
func (t *thirdDatabase) GetLogs(ctx context.Context, LogIDs []string, userID string) ([]*model.Log, error) {
|
||||
return t.logdb.Get(ctx, LogIDs, userID)
|
||||
}
|
||||
|
||||
// SearchLogs implements ThirdDatabase.
|
||||
func (t *thirdDatabase) SearchLogs(ctx context.Context, keyword string, start time.Time, end time.Time, pagination pagination.Pagination) (int64, []*model.Log, error) {
|
||||
return t.logdb.Search(ctx, keyword, start, end, pagination)
|
||||
}
|
||||
|
||||
// UploadLogs implements ThirdDatabase.
|
||||
func (t *thirdDatabase) UploadLogs(ctx context.Context, logs []*model.Log) error {
|
||||
return t.logdb.Create(ctx, logs)
|
||||
}
|
||||
|
||||
func NewThirdDatabase(cache cache.ThirdCache, logdb database.Log) ThirdDatabase {
|
||||
return &thirdDatabase{cache: cache, logdb: logdb}
|
||||
}
|
||||
|
||||
func (t *thirdDatabase) FcmUpdateToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) error {
|
||||
return t.cache.SetFcmToken(ctx, account, platformID, fcmToken, expireTime)
|
||||
}
|
||||
|
||||
func (t *thirdDatabase) SetAppBadge(ctx context.Context, userID string, value int) error {
|
||||
return t.cache.SetUserBadgeUnreadCountSum(ctx, userID, value)
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// Copyright © 2023 OpenIM. 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/db/tx"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/protocol/user"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
)
|
||||
|
||||
type UserDatabase interface {
|
||||
// FindWithError Get the information of the specified user. If the userID is not found, it will also return an error
|
||||
FindWithError(ctx context.Context, userIDs []string) (users []*model.User, err error)
|
||||
// Find Get the information of the specified user If the userID is not found, no error will be returned
|
||||
Find(ctx context.Context, userIDs []string) (users []*model.User, err error)
|
||||
// Find userInfo By Nickname
|
||||
FindByNickname(ctx context.Context, nickname string) (users []*model.User, err error)
|
||||
// Find notificationAccounts
|
||||
FindNotification(ctx context.Context, level int64) (users []*model.User, err error)
|
||||
// Create Insert multiple external guarantees that the userID is not repeated and does not exist in the storage
|
||||
Create(ctx context.Context, users []*model.User) (err error)
|
||||
// UpdateByMap update (zero value) external guarantee userID exists
|
||||
UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error)
|
||||
// FindUser
|
||||
PageFindUser(ctx context.Context, level1 int64, level2 int64, pagination pagination.Pagination) (count int64, users []*model.User, err error)
|
||||
// FindUser with keyword
|
||||
PageFindUserWithKeyword(ctx context.Context, level1 int64, level2 int64, userID string, nickName string, pagination pagination.Pagination) (count int64, users []*model.User, err error)
|
||||
// Page If not found, no error is returned
|
||||
Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*model.User, err error)
|
||||
// IsExist true as long as one exists
|
||||
IsExist(ctx context.Context, userIDs []string) (exist bool, err error)
|
||||
// GetAllUserID Get all user IDs
|
||||
GetAllUserID(ctx context.Context, pagination pagination.Pagination) (int64, []string, error)
|
||||
// Get user by userID
|
||||
GetUserByID(ctx context.Context, userID string) (user *model.User, err error)
|
||||
// InitOnce Inside the function, first query whether it exists in the storage, if it exists, do nothing; if it does not exist, insert it
|
||||
InitOnce(ctx context.Context, users []*model.User) (err error)
|
||||
// CountTotal Get the total number of users
|
||||
CountTotal(ctx context.Context, before *time.Time) (int64, error)
|
||||
// CountRangeEverydayTotal Get the user increment in the range
|
||||
CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error)
|
||||
// SubscribeUsersStatus Subscribe a user's presence status
|
||||
SubscribeUsersStatus(ctx context.Context, userID string, userIDs []string) error
|
||||
// UnsubscribeUsersStatus unsubscribe a user's presence status
|
||||
UnsubscribeUsersStatus(ctx context.Context, userID string, userIDs []string) error
|
||||
// GetAllSubscribeList Get a list of all subscriptions
|
||||
GetAllSubscribeList(ctx context.Context, userID string) ([]string, error)
|
||||
// GetSubscribedList Get all subscribed lists
|
||||
GetSubscribedList(ctx context.Context, userID string) ([]string, error)
|
||||
// GetUserStatus Get the online status of the user
|
||||
GetUserStatus(ctx context.Context, userIDs []string) ([]*user.OnlineStatus, error)
|
||||
// SetUserStatus Set the user status and store the user status in redis
|
||||
SetUserStatus(ctx context.Context, userID string, status, platformID int32) error
|
||||
|
||||
// CRUD user command
|
||||
AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error
|
||||
DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error
|
||||
UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error
|
||||
GetUserCommands(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error)
|
||||
GetAllUserCommands(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error)
|
||||
}
|
||||
|
||||
type userDatabase struct {
|
||||
tx tx.Tx
|
||||
userDB database.User
|
||||
cache cache.UserCache
|
||||
mongoDB database.SubscribeUser
|
||||
}
|
||||
|
||||
func NewUserDatabase(userDB database.User, cache cache.UserCache, tx tx.Tx, mongoDB database.SubscribeUser) UserDatabase {
|
||||
return &userDatabase{userDB: userDB, cache: cache, tx: tx, mongoDB: mongoDB}
|
||||
}
|
||||
|
||||
func (u *userDatabase) InitOnce(ctx context.Context, users []*model.User) error {
|
||||
// Extract user IDs from the given user models.
|
||||
userIDs := datautil.Slice(users, func(e *model.User) string {
|
||||
return e.UserID
|
||||
})
|
||||
|
||||
// Find existing users in the database.
|
||||
existingUsers, err := u.userDB.Find(ctx, userIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine which users are missing from the database.
|
||||
missingUsers := datautil.SliceAnySub(users, existingUsers, func(e *model.User) string {
|
||||
return e.UserID
|
||||
})
|
||||
|
||||
// Create records for missing users.
|
||||
if len(missingUsers) > 0 {
|
||||
if err := u.userDB.Create(ctx, missingUsers); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindWithError Get the information of the specified user and return an error if the userID is not found.
|
||||
func (u *userDatabase) FindWithError(ctx context.Context, userIDs []string) (users []*model.User, err error) {
|
||||
users, err = u.cache.GetUsersInfo(ctx, userIDs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(users) != len(userIDs) {
|
||||
err = errs.ErrRecordNotFound.WrapMsg("userID not found")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Find Get the information of the specified user. If the userID is not found, no error will be returned.
|
||||
func (u *userDatabase) Find(ctx context.Context, userIDs []string) (users []*model.User, err error) {
|
||||
return u.cache.GetUsersInfo(ctx, userIDs)
|
||||
}
|
||||
|
||||
func (u *userDatabase) FindByNickname(ctx context.Context, nickname string) (users []*model.User, err error) {
|
||||
return u.userDB.TakeByNickname(ctx, nickname)
|
||||
}
|
||||
|
||||
func (u *userDatabase) FindNotification(ctx context.Context, level int64) (users []*model.User, err error) {
|
||||
return u.userDB.TakeNotification(ctx, level)
|
||||
}
|
||||
|
||||
// Create Insert multiple external guarantees that the userID is not repeated and does not exist in the storage.
|
||||
func (u *userDatabase) Create(ctx context.Context, users []*model.User) (err error) {
|
||||
return u.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
if err = u.userDB.Create(ctx, users); err != nil {
|
||||
return err
|
||||
}
|
||||
return u.cache.DelUsersInfo(datautil.Slice(users, func(e *model.User) string {
|
||||
return e.UserID
|
||||
})...).ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateByMap update (zero value) externally guarantees that userID exists.
|
||||
func (u *userDatabase) UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error) {
|
||||
return u.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
if err := u.userDB.UpdateByMap(ctx, userID, args); err != nil {
|
||||
return err
|
||||
}
|
||||
return u.cache.DelUsersInfo(userID).ChainExecDel(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
// Page Gets, returns no error if not found.
|
||||
func (u *userDatabase) Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*model.User, err error) {
|
||||
return u.userDB.Page(ctx, pagination)
|
||||
}
|
||||
|
||||
func (u *userDatabase) PageFindUser(ctx context.Context, level1 int64, level2 int64, pagination pagination.Pagination) (count int64, users []*model.User, err error) {
|
||||
return u.userDB.PageFindUser(ctx, level1, level2, pagination)
|
||||
}
|
||||
|
||||
func (u *userDatabase) PageFindUserWithKeyword(ctx context.Context, level1 int64, level2 int64, userID, nickName string, pagination pagination.Pagination) (count int64, users []*model.User, err error) {
|
||||
return u.userDB.PageFindUserWithKeyword(ctx, level1, level2, userID, nickName, pagination)
|
||||
}
|
||||
|
||||
// IsExist Does userIDs exist? As long as there is one, it will be true.
|
||||
func (u *userDatabase) IsExist(ctx context.Context, userIDs []string) (exist bool, err error) {
|
||||
users, err := u.userDB.Find(ctx, userIDs)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(users) > 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetAllUserID Get all user IDs.
|
||||
func (u *userDatabase) GetAllUserID(ctx context.Context, pagination pagination.Pagination) (total int64, userIDs []string, err error) {
|
||||
return u.userDB.GetAllUserID(ctx, pagination)
|
||||
}
|
||||
|
||||
func (u *userDatabase) GetUserByID(ctx context.Context, userID string) (user *model.User, err error) {
|
||||
return u.userDB.Take(ctx, userID)
|
||||
}
|
||||
|
||||
// CountTotal Get the total number of users.
|
||||
func (u *userDatabase) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
|
||||
return u.userDB.CountTotal(ctx, before)
|
||||
}
|
||||
|
||||
// CountRangeEverydayTotal Get the user increment in the range.
|
||||
func (u *userDatabase) CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) {
|
||||
return u.userDB.CountRangeEverydayTotal(ctx, start, end)
|
||||
}
|
||||
|
||||
// SubscribeUsersStatus Subscribe or unsubscribe a user's presence status.
|
||||
func (u *userDatabase) SubscribeUsersStatus(ctx context.Context, userID string, userIDs []string) error {
|
||||
err := u.mongoDB.AddSubscriptionList(ctx, userID, userIDs)
|
||||
return err
|
||||
}
|
||||
|
||||
// UnsubscribeUsersStatus unsubscribe a user's presence status.
|
||||
func (u *userDatabase) UnsubscribeUsersStatus(ctx context.Context, userID string, userIDs []string) error {
|
||||
err := u.mongoDB.UnsubscriptionList(ctx, userID, userIDs)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAllSubscribeList Get a list of all subscriptions.
|
||||
func (u *userDatabase) GetAllSubscribeList(ctx context.Context, userID string) ([]string, error) {
|
||||
list, err := u.mongoDB.GetAllSubscribeList(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// GetSubscribedList Get all subscribed lists.
|
||||
func (u *userDatabase) GetSubscribedList(ctx context.Context, userID string) ([]string, error) {
|
||||
list, err := u.mongoDB.GetSubscribedList(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// GetUserStatus get user status.
|
||||
func (u *userDatabase) GetUserStatus(ctx context.Context, userIDs []string) ([]*user.OnlineStatus, error) {
|
||||
onlineStatusList, err := u.cache.GetUserStatus(ctx, userIDs)
|
||||
return onlineStatusList, err
|
||||
}
|
||||
|
||||
// SetUserStatus Set the user status and save it in redis.
|
||||
func (u *userDatabase) SetUserStatus(ctx context.Context, userID string, status, platformID int32) error {
|
||||
return u.cache.SetUserStatus(ctx, userID, status, platformID)
|
||||
}
|
||||
|
||||
func (u *userDatabase) AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error {
|
||||
return u.userDB.AddUserCommand(ctx, userID, Type, UUID, value, ex)
|
||||
}
|
||||
|
||||
func (u *userDatabase) DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error {
|
||||
return u.userDB.DeleteUserCommand(ctx, userID, Type, UUID)
|
||||
}
|
||||
|
||||
func (u *userDatabase) UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error {
|
||||
return u.userDB.UpdateUserCommand(ctx, userID, Type, UUID, val)
|
||||
}
|
||||
|
||||
func (u *userDatabase) GetUserCommands(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error) {
|
||||
commands, err := u.userDB.GetUserCommand(ctx, userID, Type)
|
||||
return commands, err
|
||||
}
|
||||
|
||||
func (u *userDatabase) GetAllUserCommands(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error) {
|
||||
commands, err := u.userDB.GetAllUserCommand(ctx, userID)
|
||||
return commands, err
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
)
|
||||
|
||||
type Black interface {
|
||||
Create(ctx context.Context, blacks []*model.Black) (err error)
|
||||
Delete(ctx context.Context, blacks []*model.Black) (err error)
|
||||
Find(ctx context.Context, blacks []*model.Black) (blackList []*model.Black, err error)
|
||||
Take(ctx context.Context, ownerUserID, blockUserID string) (black *model.Black, err error)
|
||||
FindOwnerBlacks(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, blacks []*model.Black, err error)
|
||||
FindOwnerBlackInfos(ctx context.Context, ownerUserID string, userIDs []string) (blacks []*model.Black, err error)
|
||||
FindBlackUserIDs(ctx context.Context, ownerUserID string) (blackUserIDs []string, err error)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
)
|
||||
|
||||
type Conversation interface {
|
||||
Create(ctx context.Context, conversations []*model.Conversation) (err error)
|
||||
Delete(ctx context.Context, groupIDs []string) (err error)
|
||||
UpdateByMap(ctx context.Context, userIDs []string, conversationID string, args map[string]any) (rows int64, err error)
|
||||
Update(ctx context.Context, conversation *model.Conversation) (err error)
|
||||
Find(ctx context.Context, ownerUserID string, conversationIDs []string) (conversations []*model.Conversation, err error)
|
||||
FindUserID(ctx context.Context, userIDs []string, conversationIDs []string) ([]string, error)
|
||||
FindUserIDAllConversationID(ctx context.Context, userID string) ([]string, error)
|
||||
Take(ctx context.Context, userID, conversationID string) (conversation *model.Conversation, err error)
|
||||
FindConversationID(ctx context.Context, userID string, conversationIDs []string) (existConversationID []string, err error)
|
||||
FindUserIDAllConversations(ctx context.Context, userID string) (conversations []*model.Conversation, err error)
|
||||
FindRecvMsgUserIDs(ctx context.Context, conversationID string, recvOpts []int) ([]string, error)
|
||||
GetUserRecvMsgOpt(ctx context.Context, ownerUserID, conversationID string) (opt int, err error)
|
||||
GetAllConversationIDs(ctx context.Context) ([]string, error)
|
||||
GetAllConversationIDsNumber(ctx context.Context) (int64, error)
|
||||
PageConversationIDs(ctx context.Context, pagination pagination.Pagination) (conversationIDs []string, err error)
|
||||
GetConversationsByConversationID(ctx context.Context, conversationIDs []string) ([]*model.Conversation, error)
|
||||
GetConversationIDsNeedDestruct(ctx context.Context) ([]*model.Conversation, error)
|
||||
GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright © 2024 OpenIM. 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 database // import "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model/relation"
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
)
|
||||
|
||||
// Friend defines the operations for managing friends in MongoDB.
|
||||
type Friend interface {
|
||||
// Create inserts multiple friend records.
|
||||
Create(ctx context.Context, friends []*model.Friend) (err error)
|
||||
// Delete removes specified friends of the owner user.
|
||||
Delete(ctx context.Context, ownerUserID string, friendUserIDs []string) (err error)
|
||||
// UpdateByMap updates specific fields of a friend document using a map.
|
||||
UpdateByMap(ctx context.Context, ownerUserID string, friendUserID string, args map[string]any) (err error)
|
||||
// UpdateRemark modify remarks.
|
||||
UpdateRemark(ctx context.Context, ownerUserID, friendUserID, remark string) (err error)
|
||||
// Take retrieves a single friend document. Returns an error if not found.
|
||||
Take(ctx context.Context, ownerUserID, friendUserID string) (friend *model.Friend, err error)
|
||||
// FindUserState finds the friendship status between two users.
|
||||
FindUserState(ctx context.Context, userID1, userID2 string) (friends []*model.Friend, err error)
|
||||
// FindFriends retrieves a list of friends for a given owner. Missing friends do not cause an error.
|
||||
FindFriends(ctx context.Context, ownerUserID string, friendUserIDs []string) (friends []*model.Friend, err error)
|
||||
// FindReversalFriends finds users who have added the specified user as a friend.
|
||||
FindReversalFriends(ctx context.Context, friendUserID string, ownerUserIDs []string) (friends []*model.Friend, err error)
|
||||
// FindOwnerFriends retrieves a paginated list of friends for a given owner.
|
||||
FindOwnerFriends(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, friends []*model.Friend, err error)
|
||||
// FindInWhoseFriends finds users who have added the specified user as a friend, with pagination.
|
||||
FindInWhoseFriends(ctx context.Context, friendUserID string, pagination pagination.Pagination) (total int64, friends []*model.Friend, err error)
|
||||
// FindFriendUserIDs retrieves a list of friend user IDs for a given owner.
|
||||
FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error)
|
||||
// UpdateFriends update friends' fields
|
||||
UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (err error)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
)
|
||||
|
||||
type FriendRequest interface {
|
||||
// Insert multiple records
|
||||
Create(ctx context.Context, friendRequests []*model.FriendRequest) (err error)
|
||||
// Delete record
|
||||
Delete(ctx context.Context, fromUserID, toUserID string) (err error)
|
||||
// Update with zero values
|
||||
UpdateByMap(ctx context.Context, formUserID string, toUserID string, args map[string]any) (err error)
|
||||
// Update multiple records (non-zero values)
|
||||
Update(ctx context.Context, friendRequest *model.FriendRequest) (err error)
|
||||
// Get friend requests sent to a specific user, no error returned if not found
|
||||
Find(ctx context.Context, fromUserID, toUserID string) (friendRequest *model.FriendRequest, err error)
|
||||
Take(ctx context.Context, fromUserID, toUserID string) (friendRequest *model.FriendRequest, err error)
|
||||
// Get list of friend requests received by toUserID
|
||||
FindToUserID(ctx context.Context, toUserID string, pagination pagination.Pagination) (total int64, friendRequests []*model.FriendRequest, err error)
|
||||
// Get list of friend requests sent by fromUserID
|
||||
FindFromUserID(ctx context.Context, fromUserID string, pagination pagination.Pagination) (total int64, friendRequests []*model.FriendRequest, err error)
|
||||
FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*model.FriendRequest, err error)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Group interface {
|
||||
Create(ctx context.Context, groups []*model.Group) (err error)
|
||||
UpdateMap(ctx context.Context, groupID string, args map[string]any) (err error)
|
||||
UpdateStatus(ctx context.Context, groupID string, status int32) (err error)
|
||||
Find(ctx context.Context, groupIDs []string) (groups []*model.Group, err error)
|
||||
Take(ctx context.Context, groupID string) (group *model.Group, err error)
|
||||
Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*model.Group, err error)
|
||||
// Get Group total quantity
|
||||
CountTotal(ctx context.Context, before *time.Time) (count int64, err error)
|
||||
// Get Group total quantity every day
|
||||
CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
)
|
||||
|
||||
type GroupMember interface {
|
||||
Create(ctx context.Context, groupMembers []*model.GroupMember) (err error)
|
||||
Delete(ctx context.Context, groupID string, userIDs []string) (err error)
|
||||
Update(ctx context.Context, groupID string, userID string, data map[string]any) (err error)
|
||||
UpdateRoleLevel(ctx context.Context, groupID string, userID string, roleLevel int32) error
|
||||
FindMemberUserID(ctx context.Context, groupID string) (userIDs []string, err error)
|
||||
Take(ctx context.Context, groupID string, userID string) (groupMember *model.GroupMember, err error)
|
||||
TakeOwner(ctx context.Context, groupID string) (groupMember *model.GroupMember, err error)
|
||||
SearchMember(ctx context.Context, keyword string, groupID string, pagination pagination.Pagination) (total int64, groupList []*model.GroupMember, err error)
|
||||
FindRoleLevelUserIDs(ctx context.Context, groupID string, roleLevel int32) ([]string, error)
|
||||
FindUserJoinedGroupID(ctx context.Context, userID string) (groupIDs []string, err error)
|
||||
TakeGroupMemberNum(ctx context.Context, groupID string) (count int64, err error)
|
||||
FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error)
|
||||
IsUpdateRoleLevel(data map[string]any) bool
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
)
|
||||
|
||||
type GroupRequest interface {
|
||||
Create(ctx context.Context, groupRequests []*model.GroupRequest) (err error)
|
||||
Delete(ctx context.Context, groupID string, userID string) (err error)
|
||||
UpdateHandler(ctx context.Context, groupID string, userID string, handledMsg string, handleResult int32) (err error)
|
||||
Take(ctx context.Context, groupID string, userID string) (groupRequest *model.GroupRequest, err error)
|
||||
FindGroupRequests(ctx context.Context, groupID string, userIDs []string) ([]*model.GroupRequest, error)
|
||||
Page(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, groups []*model.GroupRequest, err error)
|
||||
PageGroup(ctx context.Context, groupIDs []string, pagination pagination.Pagination) (total int64, groups []*model.GroupRequest, err error)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Log interface {
|
||||
Create(ctx context.Context, log []*model.Log) error
|
||||
Search(ctx context.Context, keyword string, start time.Time, end time.Time, pagination pagination.Pagination) (int64, []*model.Log, error)
|
||||
Delete(ctx context.Context, logID []string, userID string) error
|
||||
Get(ctx context.Context, logIDs []string, userID string) ([]*model.Log, error)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewBlackMongo(db *mongo.Database) (database.Black, error) {
|
||||
coll := db.Collection("black")
|
||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
Keys: bson.D{
|
||||
{Key: "owner_user_id", Value: 1},
|
||||
{Key: "block_user_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BlackMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
type BlackMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (b *BlackMgo) blackFilter(ownerUserID, blockUserID string) bson.M {
|
||||
return bson.M{
|
||||
"owner_user_id": ownerUserID,
|
||||
"block_user_id": blockUserID,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BlackMgo) blacksFilter(blacks []*model.Black) bson.M {
|
||||
if len(blacks) == 0 {
|
||||
return nil
|
||||
}
|
||||
or := make(bson.A, 0, len(blacks))
|
||||
for _, black := range blacks {
|
||||
or = append(or, b.blackFilter(black.OwnerUserID, black.BlockUserID))
|
||||
}
|
||||
return bson.M{"$or": or}
|
||||
}
|
||||
|
||||
func (b *BlackMgo) Create(ctx context.Context, blacks []*model.Black) (err error) {
|
||||
return mongoutil.InsertMany(ctx, b.coll, blacks)
|
||||
}
|
||||
|
||||
func (b *BlackMgo) Delete(ctx context.Context, blacks []*model.Black) (err error) {
|
||||
if len(blacks) == 0 {
|
||||
return nil
|
||||
}
|
||||
return mongoutil.DeleteMany(ctx, b.coll, b.blacksFilter(blacks))
|
||||
}
|
||||
|
||||
func (b *BlackMgo) UpdateByMap(ctx context.Context, ownerUserID, blockUserID string, args map[string]any) (err error) {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
return mongoutil.UpdateOne(ctx, b.coll, b.blackFilter(ownerUserID, blockUserID), bson.M{"$set": args}, false)
|
||||
}
|
||||
|
||||
func (b *BlackMgo) Find(ctx context.Context, blacks []*model.Black) (blackList []*model.Black, err error) {
|
||||
return mongoutil.Find[*model.Black](ctx, b.coll, b.blacksFilter(blacks))
|
||||
}
|
||||
|
||||
func (b *BlackMgo) Take(ctx context.Context, ownerUserID, blockUserID string) (black *model.Black, err error) {
|
||||
return mongoutil.FindOne[*model.Black](ctx, b.coll, b.blackFilter(ownerUserID, blockUserID))
|
||||
}
|
||||
|
||||
func (b *BlackMgo) FindOwnerBlacks(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, blacks []*model.Black, err error) {
|
||||
return mongoutil.FindPage[*model.Black](ctx, b.coll, bson.M{"owner_user_id": ownerUserID}, pagination)
|
||||
}
|
||||
|
||||
func (b *BlackMgo) FindOwnerBlackInfos(ctx context.Context, ownerUserID string, userIDs []string) (blacks []*model.Black, err error) {
|
||||
if len(userIDs) == 0 {
|
||||
return mongoutil.Find[*model.Black](ctx, b.coll, bson.M{"owner_user_id": ownerUserID})
|
||||
}
|
||||
return mongoutil.Find[*model.Black](ctx, b.coll, bson.M{"owner_user_id": ownerUserID, "block_user_id": bson.M{"$in": userIDs}})
|
||||
}
|
||||
|
||||
func (b *BlackMgo) FindBlackUserIDs(ctx context.Context, ownerUserID string) (blackUserIDs []string, err error) {
|
||||
return mongoutil.Find[string](ctx, b.coll, bson.M{"owner_user_id": ownerUserID}, options.Find().SetProjection(bson.M{"_id": 0, "block_user_id": 1}))
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewConversationMongo(db *mongo.Database) (*ConversationMgo, error) {
|
||||
coll := db.Collection("conversation")
|
||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
Keys: bson.D{
|
||||
{Key: "owner_user_id", Value: 1},
|
||||
{Key: "conversation_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
return &ConversationMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
type ConversationMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) Create(ctx context.Context, conversations []*model.Conversation) (err error) {
|
||||
return mongoutil.InsertMany(ctx, c.coll, conversations)
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) Delete(ctx context.Context, groupIDs []string) (err error) {
|
||||
return mongoutil.DeleteMany(ctx, c.coll, bson.M{"group_id": bson.M{"$in": groupIDs}})
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) UpdateByMap(ctx context.Context, userIDs []string, conversationID string, args map[string]any) (rows int64, err error) {
|
||||
if len(args) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
filter := bson.M{
|
||||
"conversation_id": conversationID,
|
||||
}
|
||||
if len(userIDs) > 0 {
|
||||
filter["owner_user_id"] = bson.M{"$in": userIDs}
|
||||
}
|
||||
res, err := mongoutil.UpdateMany(ctx, c.coll, filter, bson.M{"$set": args})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.ModifiedCount, nil
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) Update(ctx context.Context, conversation *model.Conversation) (err error) {
|
||||
return mongoutil.UpdateOne(ctx, c.coll, bson.M{"owner_user_id": conversation.OwnerUserID, "conversation_id": conversation.ConversationID}, bson.M{"$set": conversation}, true)
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) Find(ctx context.Context, ownerUserID string, conversationIDs []string) (conversations []*model.Conversation, err error) {
|
||||
return mongoutil.Find[*model.Conversation](ctx, c.coll, bson.M{"owner_user_id": ownerUserID, "conversation_id": bson.M{"$in": conversationIDs}})
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) FindUserID(ctx context.Context, userIDs []string, conversationIDs []string) ([]string, error) {
|
||||
return mongoutil.Find[string](
|
||||
ctx,
|
||||
c.coll,
|
||||
bson.M{"owner_user_id": bson.M{"$in": userIDs}, "conversation_id": bson.M{"$in": conversationIDs}},
|
||||
options.Find().SetProjection(bson.M{"_id": 0, "owner_user_id": 1}),
|
||||
)
|
||||
}
|
||||
func (c *ConversationMgo) FindUserIDAllConversationID(ctx context.Context, userID string) ([]string, error) {
|
||||
return mongoutil.Find[string](ctx, c.coll, bson.M{"owner_user_id": userID}, options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1}))
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) Take(ctx context.Context, userID, conversationID string) (conversation *model.Conversation, err error) {
|
||||
return mongoutil.FindOne[*model.Conversation](ctx, c.coll, bson.M{"owner_user_id": userID, "conversation_id": conversationID})
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) FindConversationID(ctx context.Context, userID string, conversationIDs []string) (existConversationID []string, err error) {
|
||||
return mongoutil.Find[string](ctx, c.coll, bson.M{"owner_user_id": userID, "conversation_id": bson.M{"$in": conversationIDs}}, options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1}))
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) FindUserIDAllConversations(ctx context.Context, userID string) (conversations []*model.Conversation, err error) {
|
||||
return mongoutil.Find[*model.Conversation](ctx, c.coll, bson.M{"owner_user_id": userID})
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) FindRecvMsgUserIDs(ctx context.Context, conversationID string, recvOpts []int) ([]string, error) {
|
||||
var filter any
|
||||
if len(recvOpts) == 0 {
|
||||
filter = bson.M{"conversation_id": conversationID}
|
||||
} else {
|
||||
filter = bson.M{"conversation_id": conversationID, "recv_msg_opt": bson.M{"$in": recvOpts}}
|
||||
}
|
||||
return mongoutil.Find[string](ctx, c.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "owner_user_id": 1}))
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) GetUserRecvMsgOpt(ctx context.Context, ownerUserID, conversationID string) (opt int, err error) {
|
||||
return mongoutil.FindOne[int](ctx, c.coll, bson.M{"owner_user_id": ownerUserID, "conversation_id": conversationID}, options.FindOne().SetProjection(bson.M{"recv_msg_opt": 1}))
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) GetAllConversationIDs(ctx context.Context) ([]string, error) {
|
||||
return mongoutil.Aggregate[string](ctx, c.coll, []bson.M{
|
||||
{"$group": bson.M{"_id": "$conversation_id"}},
|
||||
{"$project": bson.M{"_id": 0, "conversation_id": "$_id"}},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) GetAllConversationIDsNumber(ctx context.Context) (int64, error) {
|
||||
counts, err := mongoutil.Aggregate[int64](ctx, c.coll, []bson.M{
|
||||
{"$group": bson.M{"_id": "$conversation_id"}},
|
||||
{"$group": bson.M{"_id": nil, "count": bson.M{"$sum": 1}}},
|
||||
{"$project": bson.M{"_id": 0}},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(counts) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return counts[0], nil
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) PageConversationIDs(ctx context.Context, pagination pagination.Pagination) (conversationIDs []string, err error) {
|
||||
return mongoutil.FindPageOnly[string](ctx, c.coll, bson.M{}, pagination, options.Find().SetProjection(bson.M{"conversation_id": 1}))
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) GetConversationsByConversationID(ctx context.Context, conversationIDs []string) ([]*model.Conversation, error) {
|
||||
return mongoutil.Find[*model.Conversation](ctx, c.coll, bson.M{"conversation_id": bson.M{"$in": conversationIDs}})
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) GetConversationIDsNeedDestruct(ctx context.Context) ([]*model.Conversation, error) {
|
||||
// "is_msg_destruct = 1 && msg_destruct_time != 0 && (UNIX_TIMESTAMP(NOW()) > (msg_destruct_time + UNIX_TIMESTAMP(latest_msg_destruct_time)) || latest_msg_destruct_time is NULL)"
|
||||
return mongoutil.Find[*model.Conversation](ctx, c.coll, bson.M{
|
||||
"is_msg_destruct": 1,
|
||||
"msg_destruct_time": bson.M{"$ne": 0},
|
||||
"$or": []bson.M{
|
||||
{
|
||||
"$expr": bson.M{
|
||||
"$gt": []any{
|
||||
time.Now(),
|
||||
bson.M{"$add": []any{"$msg_destruct_time", "$latest_msg_destruct_time"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"latest_msg_destruct_time": nil,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error) {
|
||||
return mongoutil.Find[string](
|
||||
ctx,
|
||||
c.coll,
|
||||
bson.M{"conversation_id": conversationID, "recv_msg_opt": bson.M{"$ne": constant.ReceiveMessage}},
|
||||
options.Find().SetProjection(bson.M{"_id": 0, "owner_user_id": 1}),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright © 2024 OpenIM. 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 mgo // import "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// FriendMgo implements Friend using MongoDB as the storage backend.
|
||||
type FriendMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
// NewFriendMongo creates a new instance of FriendMgo with the provided MongoDB database.
|
||||
func NewFriendMongo(db *mongo.Database) (database.Friend, error) {
|
||||
coll := db.Collection("friend")
|
||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
Keys: bson.D{
|
||||
{Key: "owner_user_id", Value: 1},
|
||||
{Key: "friend_user_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FriendMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
// Create inserts multiple friend records.
|
||||
func (f *FriendMgo) Create(ctx context.Context, friends []*model.Friend) error {
|
||||
return mongoutil.InsertMany(ctx, f.coll, friends)
|
||||
}
|
||||
|
||||
// Delete removes specified friends of the owner user.
|
||||
func (f *FriendMgo) Delete(ctx context.Context, ownerUserID string, friendUserIDs []string) error {
|
||||
filter := bson.M{
|
||||
"owner_user_id": ownerUserID,
|
||||
"friend_user_id": bson.M{"$in": friendUserIDs},
|
||||
}
|
||||
return mongoutil.DeleteOne(ctx, f.coll, filter)
|
||||
}
|
||||
|
||||
// UpdateByMap updates specific fields of a friend document using a map.
|
||||
func (f *FriendMgo) UpdateByMap(ctx context.Context, ownerUserID string, friendUserID string, args map[string]interface{}) error {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
filter := bson.M{
|
||||
"owner_user_id": ownerUserID,
|
||||
"friend_user_id": friendUserID,
|
||||
}
|
||||
return mongoutil.UpdateOne(ctx, f.coll, filter, bson.M{"$set": args}, true)
|
||||
}
|
||||
|
||||
// Update modifies multiple friend documents.
|
||||
// func (f *FriendMgo) Update(ctx context.Context, friends []*relation.Friend) error {
|
||||
// filter := bson.M{
|
||||
// "owner_user_id": ownerUserID,
|
||||
// "friend_user_id": friendUserID,
|
||||
// }
|
||||
// return mgotool.UpdateMany(ctx, f.coll, filter, friends)
|
||||
// }
|
||||
|
||||
// UpdateRemark updates the remark for a specific friend.
|
||||
func (f *FriendMgo) UpdateRemark(ctx context.Context, ownerUserID, friendUserID, remark string) error {
|
||||
return f.UpdateByMap(ctx, ownerUserID, friendUserID, map[string]any{"remark": remark})
|
||||
}
|
||||
|
||||
// Take retrieves a single friend document. Returns an error if not found.
|
||||
func (f *FriendMgo) Take(ctx context.Context, ownerUserID, friendUserID string) (*model.Friend, error) {
|
||||
filter := bson.M{
|
||||
"owner_user_id": ownerUserID,
|
||||
"friend_user_id": friendUserID,
|
||||
}
|
||||
return mongoutil.FindOne[*model.Friend](ctx, f.coll, filter)
|
||||
}
|
||||
|
||||
// FindUserState finds the friendship status between two users.
|
||||
func (f *FriendMgo) FindUserState(ctx context.Context, userID1, userID2 string) ([]*model.Friend, error) {
|
||||
filter := bson.M{
|
||||
"$or": []bson.M{
|
||||
{"owner_user_id": userID1, "friend_user_id": userID2},
|
||||
{"owner_user_id": userID2, "friend_user_id": userID1},
|
||||
},
|
||||
}
|
||||
return mongoutil.Find[*model.Friend](ctx, f.coll, filter)
|
||||
}
|
||||
|
||||
// FindFriends retrieves a list of friends for a given owner. Missing friends do not cause an error.
|
||||
func (f *FriendMgo) FindFriends(ctx context.Context, ownerUserID string, friendUserIDs []string) ([]*model.Friend, error) {
|
||||
filter := bson.M{
|
||||
"owner_user_id": ownerUserID,
|
||||
"friend_user_id": bson.M{"$in": friendUserIDs},
|
||||
}
|
||||
return mongoutil.Find[*model.Friend](ctx, f.coll, filter)
|
||||
}
|
||||
|
||||
// FindReversalFriends finds users who have added the specified user as a friend.
|
||||
func (f *FriendMgo) FindReversalFriends(ctx context.Context, friendUserID string, ownerUserIDs []string) ([]*model.Friend, error) {
|
||||
filter := bson.M{
|
||||
"owner_user_id": bson.M{"$in": ownerUserIDs},
|
||||
"friend_user_id": friendUserID,
|
||||
}
|
||||
return mongoutil.Find[*model.Friend](ctx, f.coll, filter)
|
||||
}
|
||||
|
||||
// FindOwnerFriends retrieves a paginated list of friends for a given owner.
|
||||
func (f *FriendMgo) FindOwnerFriends(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (int64, []*model.Friend, error) {
|
||||
filter := bson.M{"owner_user_id": ownerUserID}
|
||||
return mongoutil.FindPage[*model.Friend](ctx, f.coll, filter, pagination)
|
||||
}
|
||||
|
||||
// FindInWhoseFriends finds users who have added the specified user as a friend, with pagination.
|
||||
func (f *FriendMgo) FindInWhoseFriends(ctx context.Context, friendUserID string, pagination pagination.Pagination) (int64, []*model.Friend, error) {
|
||||
filter := bson.M{"friend_user_id": friendUserID}
|
||||
return mongoutil.FindPage[*model.Friend](ctx, f.coll, filter, pagination)
|
||||
}
|
||||
|
||||
// FindFriendUserIDs retrieves a list of friend user IDs for a given owner.
|
||||
func (f *FriendMgo) FindFriendUserIDs(ctx context.Context, ownerUserID string) ([]string, error) {
|
||||
filter := bson.M{"owner_user_id": ownerUserID}
|
||||
return mongoutil.Find[string](ctx, f.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "friend_user_id": 1}))
|
||||
}
|
||||
|
||||
func (f *FriendMgo) UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) error {
|
||||
// Ensure there are IDs to update
|
||||
if len(friendUserIDs) == 0 {
|
||||
return nil // Or return an error if you expect there to always be IDs
|
||||
}
|
||||
|
||||
// Create a filter to match documents with the specified ownerUserID and any of the friendUserIDs
|
||||
filter := bson.M{
|
||||
"owner_user_id": ownerUserID,
|
||||
"friend_user_id": bson.M{"$in": friendUserIDs},
|
||||
}
|
||||
|
||||
// Create an update document
|
||||
update := bson.M{"$set": val}
|
||||
|
||||
// Perform the update operation for all matching documents
|
||||
_, err := mongoutil.UpdateMany(ctx, f.coll, filter, update)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewFriendRequestMongo(db *mongo.Database) (database.FriendRequest, error) {
|
||||
coll := db.Collection("friend_request")
|
||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
Keys: bson.D{
|
||||
{Key: "from_user_id", Value: 1},
|
||||
{Key: "to_user_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FriendRequestMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
type FriendRequestMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (f *FriendRequestMgo) FindToUserID(ctx context.Context, toUserID string, pagination pagination.Pagination) (total int64, friendRequests []*model.FriendRequest, err error) {
|
||||
return mongoutil.FindPage[*model.FriendRequest](ctx, f.coll, bson.M{"to_user_id": toUserID}, pagination)
|
||||
}
|
||||
|
||||
func (f *FriendRequestMgo) FindFromUserID(ctx context.Context, fromUserID string, pagination pagination.Pagination) (total int64, friendRequests []*model.FriendRequest, err error) {
|
||||
return mongoutil.FindPage[*model.FriendRequest](ctx, f.coll, bson.M{"from_user_id": fromUserID}, pagination)
|
||||
}
|
||||
|
||||
func (f *FriendRequestMgo) FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*model.FriendRequest, err error) {
|
||||
filter := bson.M{"$or": []bson.M{
|
||||
{"from_user_id": fromUserID, "to_user_id": toUserID},
|
||||
{"from_user_id": toUserID, "to_user_id": fromUserID},
|
||||
}}
|
||||
return mongoutil.Find[*model.FriendRequest](ctx, f.coll, filter)
|
||||
}
|
||||
|
||||
func (f *FriendRequestMgo) Create(ctx context.Context, friendRequests []*model.FriendRequest) error {
|
||||
return mongoutil.InsertMany(ctx, f.coll, friendRequests)
|
||||
}
|
||||
|
||||
func (f *FriendRequestMgo) Delete(ctx context.Context, fromUserID, toUserID string) (err error) {
|
||||
return mongoutil.DeleteOne(ctx, f.coll, bson.M{"from_user_id": fromUserID, "to_user_id": toUserID})
|
||||
}
|
||||
|
||||
func (f *FriendRequestMgo) UpdateByMap(ctx context.Context, formUserID, toUserID string, args map[string]any) (err error) {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
return mongoutil.UpdateOne(ctx, f.coll, bson.M{"from_user_id": formUserID, "to_user_id": toUserID}, bson.M{"$set": args}, true)
|
||||
}
|
||||
|
||||
func (f *FriendRequestMgo) Update(ctx context.Context, friendRequest *model.FriendRequest) (err error) {
|
||||
updater := bson.M{}
|
||||
if friendRequest.HandleResult != 0 {
|
||||
updater["handle_result"] = friendRequest.HandleResult
|
||||
}
|
||||
if friendRequest.ReqMsg != "" {
|
||||
updater["req_msg"] = friendRequest.ReqMsg
|
||||
}
|
||||
if friendRequest.HandlerUserID != "" {
|
||||
updater["handler_user_id"] = friendRequest.HandlerUserID
|
||||
}
|
||||
if friendRequest.HandleMsg != "" {
|
||||
updater["handle_msg"] = friendRequest.HandleMsg
|
||||
}
|
||||
if !friendRequest.HandleTime.IsZero() {
|
||||
updater["handle_time"] = friendRequest.HandleTime
|
||||
}
|
||||
if friendRequest.Ex != "" {
|
||||
updater["ex"] = friendRequest.Ex
|
||||
}
|
||||
if len(updater) == 0 {
|
||||
return nil
|
||||
}
|
||||
filter := bson.M{"from_user_id": friendRequest.FromUserID, "to_user_id": friendRequest.ToUserID}
|
||||
return mongoutil.UpdateOne(ctx, f.coll, filter, bson.M{"$set": updater}, true)
|
||||
}
|
||||
|
||||
func (f *FriendRequestMgo) Find(ctx context.Context, fromUserID, toUserID string) (friendRequest *model.FriendRequest, err error) {
|
||||
return mongoutil.FindOne[*model.FriendRequest](ctx, f.coll, bson.M{"from_user_id": fromUserID, "to_user_id": toUserID})
|
||||
}
|
||||
|
||||
func (f *FriendRequestMgo) Take(ctx context.Context, fromUserID, toUserID string) (friendRequest *model.FriendRequest, err error) {
|
||||
return f.Find(ctx, fromUserID, toUserID)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewGroupMongo(db *mongo.Database) (database.Group, error) {
|
||||
coll := db.Collection("group")
|
||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
Keys: bson.D{
|
||||
{Key: "group_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
return &GroupMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
type GroupMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (g *GroupMgo) Create(ctx context.Context, groups []*model.Group) (err error) {
|
||||
return mongoutil.InsertMany(ctx, g.coll, groups)
|
||||
}
|
||||
|
||||
func (g *GroupMgo) UpdateStatus(ctx context.Context, groupID string, status int32) (err error) {
|
||||
return g.UpdateMap(ctx, groupID, map[string]any{"status": status})
|
||||
}
|
||||
|
||||
func (g *GroupMgo) UpdateMap(ctx context.Context, groupID string, args map[string]any) (err error) {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID}, bson.M{"$set": args}, true)
|
||||
}
|
||||
|
||||
func (g *GroupMgo) Find(ctx context.Context, groupIDs []string) (groups []*model.Group, err error) {
|
||||
return mongoutil.Find[*model.Group](ctx, g.coll, bson.M{"group_id": bson.M{"$in": groupIDs}})
|
||||
}
|
||||
|
||||
func (g *GroupMgo) Take(ctx context.Context, groupID string) (group *model.Group, err error) {
|
||||
return mongoutil.FindOne[*model.Group](ctx, g.coll, bson.M{"group_id": groupID})
|
||||
}
|
||||
|
||||
func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*model.Group, err error) {
|
||||
// Define the sorting options
|
||||
opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}})
|
||||
|
||||
// Perform the search with pagination and sorting
|
||||
return mongoutil.FindPage[*model.Group](ctx, g.coll, bson.M{
|
||||
"group_name": bson.M{"$regex": keyword},
|
||||
"status": bson.M{"$ne": constant.GroupStatusDismissed},
|
||||
}, pagination, opts)
|
||||
}
|
||||
|
||||
func (g *GroupMgo) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
|
||||
if before == nil {
|
||||
return mongoutil.Count(ctx, g.coll, bson.M{})
|
||||
}
|
||||
return mongoutil.Count(ctx, g.coll, bson.M{"create_time": bson.M{"$lt": before}})
|
||||
}
|
||||
|
||||
func (g *GroupMgo) CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) {
|
||||
pipeline := bson.A{
|
||||
bson.M{
|
||||
"$match": bson.M{
|
||||
"create_time": bson.M{
|
||||
"$gte": start,
|
||||
"$lt": end,
|
||||
},
|
||||
},
|
||||
},
|
||||
bson.M{
|
||||
"$group": bson.M{
|
||||
"_id": bson.M{
|
||||
"$dateToString": bson.M{
|
||||
"format": "%Y-%m-%d",
|
||||
"date": "$create_time",
|
||||
},
|
||||
},
|
||||
"count": bson.M{
|
||||
"$sum": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
type Item struct {
|
||||
Date string `bson:"_id"`
|
||||
Count int64 `bson:"count"`
|
||||
}
|
||||
items, err := mongoutil.Aggregate[Item](ctx, g.coll, pipeline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make(map[string]int64, len(items))
|
||||
for _, item := range items {
|
||||
res[item.Date] = item.Count
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewGroupMember(db *mongo.Database) (database.GroupMember, error) {
|
||||
coll := db.Collection("group_member")
|
||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
Keys: bson.D{
|
||||
{Key: "group_id", Value: 1},
|
||||
{Key: "user_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
return &GroupMemberMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
type GroupMemberMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) Create(ctx context.Context, groupMembers []*model.GroupMember) (err error) {
|
||||
return mongoutil.InsertMany(ctx, g.coll, groupMembers)
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) Delete(ctx context.Context, groupID string, userIDs []string) (err error) {
|
||||
filter := bson.M{"group_id": groupID}
|
||||
if len(userIDs) > 0 {
|
||||
filter["user_id"] = bson.M{"$in": userIDs}
|
||||
}
|
||||
return mongoutil.DeleteMany(ctx, g.coll, filter)
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) UpdateRoleLevel(ctx context.Context, groupID string, userID string, roleLevel int32) error {
|
||||
return g.Update(ctx, groupID, userID, bson.M{"role_level": roleLevel})
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) Update(ctx context.Context, groupID string, userID string, data map[string]any) (err error) {
|
||||
return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}, bson.M{"$set": data}, true)
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) Find(ctx context.Context, groupIDs []string, userIDs []string, roleLevels []int32) (groupMembers []*model.GroupMember, err error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) FindMemberUserID(ctx context.Context, groupID string) (userIDs []string, err error) {
|
||||
return mongoutil.Find[string](ctx, g.coll, bson.M{"group_id": groupID}, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}))
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) Take(ctx context.Context, groupID string, userID string) (groupMember *model.GroupMember, err error) {
|
||||
return mongoutil.FindOne[*model.GroupMember](ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID})
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) TakeOwner(ctx context.Context, groupID string) (groupMember *model.GroupMember, err error) {
|
||||
return mongoutil.FindOne[*model.GroupMember](ctx, g.coll, bson.M{"group_id": groupID, "role_level": constant.GroupOwner})
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) FindRoleLevelUserIDs(ctx context.Context, groupID string, roleLevel int32) ([]string, error) {
|
||||
return mongoutil.Find[string](ctx, g.coll, bson.M{"group_id": groupID, "role_level": roleLevel}, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}))
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) SearchMember(ctx context.Context, keyword string, groupID string, pagination pagination.Pagination) (total int64, groupList []*model.GroupMember, err error) {
|
||||
filter := bson.M{"group_id": groupID, "nickname": bson.M{"$regex": keyword}}
|
||||
return mongoutil.FindPage[*model.GroupMember](ctx, g.coll, filter, pagination)
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) FindUserJoinedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) {
|
||||
return mongoutil.Find[string](ctx, g.coll, bson.M{"user_id": userID}, options.Find().SetProjection(bson.M{"_id": 0, "group_id": 1}))
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) TakeGroupMemberNum(ctx context.Context, groupID string) (count int64, err error) {
|
||||
return mongoutil.Count(ctx, g.coll, bson.M{"group_id": groupID})
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) {
|
||||
filter := bson.M{
|
||||
"user_id": userID,
|
||||
"role_level": bson.M{
|
||||
"$in": []int{constant.GroupOwner, constant.GroupAdmin},
|
||||
},
|
||||
}
|
||||
return mongoutil.Find[string](ctx, g.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "group_id": 1}))
|
||||
}
|
||||
|
||||
func (g *GroupMemberMgo) IsUpdateRoleLevel(data map[string]any) bool {
|
||||
if len(data) == 0 {
|
||||
return false
|
||||
}
|
||||
_, ok := data["role_level"]
|
||||
return ok
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewGroupRequestMgo(db *mongo.Database) (database.GroupRequest, error) {
|
||||
coll := db.Collection("group_request")
|
||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
Keys: bson.D{
|
||||
{Key: "group_id", Value: 1},
|
||||
{Key: "user_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
return &GroupRequestMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
type GroupRequestMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (g *GroupRequestMgo) Create(ctx context.Context, groupRequests []*model.GroupRequest) (err error) {
|
||||
return mongoutil.InsertMany(ctx, g.coll, groupRequests)
|
||||
}
|
||||
|
||||
func (g *GroupRequestMgo) Delete(ctx context.Context, groupID string, userID string) (err error) {
|
||||
return mongoutil.DeleteOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID})
|
||||
}
|
||||
|
||||
func (g *GroupRequestMgo) UpdateHandler(ctx context.Context, groupID string, userID string, handledMsg string, handleResult int32) (err error) {
|
||||
return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}, bson.M{"$set": bson.M{"handle_msg": handledMsg, "handle_result": handleResult}}, true)
|
||||
}
|
||||
|
||||
func (g *GroupRequestMgo) Take(ctx context.Context, groupID string, userID string) (groupRequest *model.GroupRequest, err error) {
|
||||
return mongoutil.FindOne[*model.GroupRequest](ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID})
|
||||
}
|
||||
|
||||
func (g *GroupRequestMgo) FindGroupRequests(ctx context.Context, groupID string, userIDs []string) ([]*model.GroupRequest, error) {
|
||||
return mongoutil.Find[*model.GroupRequest](ctx, g.coll, bson.M{"group_id": groupID, "user_id": bson.M{"$in": userIDs}})
|
||||
}
|
||||
|
||||
func (g *GroupRequestMgo) Page(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, groups []*model.GroupRequest, err error) {
|
||||
return mongoutil.FindPage[*model.GroupRequest](ctx, g.coll, bson.M{"user_id": userID}, pagination)
|
||||
}
|
||||
|
||||
func (g *GroupRequestMgo) PageGroup(ctx context.Context, groupIDs []string, pagination pagination.Pagination) (total int64, groups []*model.GroupRequest, err error) {
|
||||
return mongoutil.FindPage[*model.GroupRequest](ctx, g.coll, bson.M{"group_id": bson.M{"$in": groupIDs}}, pagination)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
func IsNotFound(err error) bool {
|
||||
return errs.Unwrap(err) == mongo.ErrNoDocuments
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewLogMongo(db *mongo.Database) (database.Log, error) {
|
||||
coll := db.Collection("log")
|
||||
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "log_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "user_id", Value: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "create_time", Value: -1},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &LogMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
type LogMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (l *LogMgo) Create(ctx context.Context, log []*model.Log) error {
|
||||
return mongoutil.InsertMany(ctx, l.coll, log)
|
||||
}
|
||||
|
||||
func (l *LogMgo) Search(ctx context.Context, keyword string, start time.Time, end time.Time, pagination pagination.Pagination) (int64, []*model.Log, error) {
|
||||
filter := bson.M{"create_time": bson.M{"$gte": start, "$lte": end}}
|
||||
if keyword != "" {
|
||||
filter["user_id"] = bson.M{"$regex": keyword}
|
||||
}
|
||||
return mongoutil.FindPage[*model.Log](ctx, l.coll, filter, pagination, options.Find().SetSort(bson.M{"create_time": -1}))
|
||||
}
|
||||
|
||||
func (l *LogMgo) Delete(ctx context.Context, logID []string, userID string) error {
|
||||
if userID == "" {
|
||||
return mongoutil.DeleteMany(ctx, l.coll, bson.M{"log_id": bson.M{"$in": logID}})
|
||||
}
|
||||
return mongoutil.DeleteMany(ctx, l.coll, bson.M{"log_id": bson.M{"$in": logID}, "user_id": userID})
|
||||
}
|
||||
|
||||
func (l *LogMgo) Get(ctx context.Context, logIDs []string, userID string) ([]*model.Log, error) {
|
||||
if userID == "" {
|
||||
return mongoutil.Find[*model.Log](ctx, l.coll, bson.M{"log_id": bson.M{"$in": logIDs}})
|
||||
}
|
||||
return mongoutil.Find[*model.Log](ctx, l.coll, bson.M{"log_id": bson.M{"$in": logIDs}, "user_id": userID})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewS3Mongo(db *mongo.Database) (database.ObjectInfo, error) {
|
||||
coll := db.Collection("s3")
|
||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
Keys: bson.D{
|
||||
{Key: "name", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
return &S3Mongo{coll: coll}, nil
|
||||
}
|
||||
|
||||
type S3Mongo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (o *S3Mongo) SetObject(ctx context.Context, obj *model.Object) error {
|
||||
filter := bson.M{"name": obj.Name, "engine": obj.Engine}
|
||||
update := bson.M{
|
||||
"name": obj.Name,
|
||||
"engine": obj.Engine,
|
||||
"key": obj.Key,
|
||||
"size": obj.Size,
|
||||
"content_type": obj.ContentType,
|
||||
"group": obj.Group,
|
||||
"create_time": obj.CreateTime,
|
||||
}
|
||||
return mongoutil.UpdateOne(ctx, o.coll, filter, bson.M{"$set": update}, false, options.Update().SetUpsert(true))
|
||||
}
|
||||
|
||||
func (o *S3Mongo) Take(ctx context.Context, engine string, name string) (*model.Object, error) {
|
||||
if engine == "" {
|
||||
return mongoutil.FindOne[*model.Object](ctx, o.coll, bson.M{"name": name})
|
||||
}
|
||||
return mongoutil.FindOne[*model.Object](ctx, o.coll, bson.M{"name": name, "engine": engine})
|
||||
}
|
||||
|
||||
func (o *S3Mongo) Delete(ctx context.Context, engine string, name string) error {
|
||||
return mongoutil.DeleteOne(ctx, o.coll, bson.M{"name": name, "engine": engine})
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// prefixes and suffixes.
|
||||
const (
|
||||
SubscriptionPrefix = "subscription_prefix"
|
||||
SubscribedPrefix = "subscribed_prefix"
|
||||
)
|
||||
|
||||
// MaximumSubscription Maximum number of subscriptions.
|
||||
const (
|
||||
MaximumSubscription = 3000
|
||||
)
|
||||
|
||||
func NewUserMongoDriver(database *mongo.Database) database.SubscribeUser {
|
||||
return &UserMongoDriver{
|
||||
userCollection: database.Collection(model.SubscribeUserTableName),
|
||||
}
|
||||
}
|
||||
|
||||
type UserMongoDriver struct {
|
||||
userCollection *mongo.Collection
|
||||
}
|
||||
|
||||
// AddSubscriptionList Subscriber's handling of thresholds.
|
||||
func (u *UserMongoDriver) AddSubscriptionList(ctx context.Context, userID string, userIDList []string) error {
|
||||
// Check the number of lists in the key.
|
||||
pipeline := mongo.Pipeline{
|
||||
{{"$match", bson.D{{"user_id", SubscriptionPrefix + userID}}}},
|
||||
{{"$project", bson.D{{"count", bson.D{{"$size", "$user_id_list"}}}}}},
|
||||
}
|
||||
// perform aggregate operations
|
||||
cursor, err := u.userCollection.Aggregate(ctx, pipeline)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
var cnt struct {
|
||||
Count int `bson:"count"`
|
||||
}
|
||||
// iterate over aggregated results
|
||||
for cursor.Next(ctx) {
|
||||
err = cursor.Decode(&cnt)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
var newUserIDList []string
|
||||
// If the threshold is exceeded, pop out the previous MaximumSubscription - len(userIDList) and insert it.
|
||||
if cnt.Count+len(userIDList) > MaximumSubscription {
|
||||
newUserIDList, err = u.GetAllSubscribeList(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newUserIDList = newUserIDList[MaximumSubscription-len(userIDList):]
|
||||
_, err = u.userCollection.UpdateOne(
|
||||
ctx,
|
||||
bson.M{"user_id": SubscriptionPrefix + userID},
|
||||
bson.M{"$set": bson.M{"user_id_list": newUserIDList}},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Another way to subscribe to N before pop,Delete after testing
|
||||
/*for i := 1; i <= MaximumSubscription-len(userIDList); i++ {
|
||||
_, err := u.userCollection.UpdateOne(
|
||||
ctx,
|
||||
bson.M{"user_id": SubscriptionPrefix + userID},
|
||||
bson.M{SubscriptionPrefix + userID: bson.M{"$pop": -1}},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}*/
|
||||
}
|
||||
upsert := true
|
||||
opts := &options.UpdateOptions{
|
||||
Upsert: &upsert,
|
||||
}
|
||||
_, err = u.userCollection.UpdateOne(
|
||||
ctx,
|
||||
bson.M{"user_id": SubscriptionPrefix + userID},
|
||||
bson.M{"$addToSet": bson.M{"user_id_list": bson.M{"$each": userIDList}}},
|
||||
opts,
|
||||
)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
for _, user := range userIDList {
|
||||
_, err = u.userCollection.UpdateOne(
|
||||
ctx,
|
||||
bson.M{"user_id": SubscribedPrefix + user},
|
||||
bson.M{"$addToSet": bson.M{"user_id_list": userID}},
|
||||
opts,
|
||||
)
|
||||
if err != nil {
|
||||
return errs.WrapMsg(err, "transaction failed")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsubscriptionList Handling of unsubscribe.
|
||||
func (u *UserMongoDriver) UnsubscriptionList(ctx context.Context, userID string, userIDList []string) error {
|
||||
_, err := u.userCollection.UpdateOne(
|
||||
ctx,
|
||||
bson.M{"user_id": SubscriptionPrefix + userID},
|
||||
bson.M{"$pull": bson.M{"user_id_list": bson.M{"$in": userIDList}}},
|
||||
)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
err = u.RemoveSubscribedListFromUser(ctx, userID, userIDList)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveSubscribedListFromUser Among the unsubscribed users, delete the user from the subscribed list.
|
||||
func (u *UserMongoDriver) RemoveSubscribedListFromUser(ctx context.Context, userID string, userIDList []string) error {
|
||||
var err error
|
||||
for _, userIDTemp := range userIDList {
|
||||
_, err = u.userCollection.UpdateOne(
|
||||
ctx,
|
||||
bson.M{"user_id": SubscribedPrefix + userIDTemp},
|
||||
bson.M{"$pull": bson.M{"user_id_list": userID}},
|
||||
)
|
||||
}
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
// GetAllSubscribeList Get all users subscribed by this user.
|
||||
func (u *UserMongoDriver) GetAllSubscribeList(ctx context.Context, userID string) (userIDList []string, err error) {
|
||||
var user model.SubscribeUser
|
||||
cursor := u.userCollection.FindOne(
|
||||
ctx,
|
||||
bson.M{"user_id": SubscriptionPrefix + userID})
|
||||
err = cursor.Decode(&user)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return []string{}, nil
|
||||
} else {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
return user.UserIDList, nil
|
||||
}
|
||||
|
||||
// GetSubscribedList Get the user subscribed by those users.
|
||||
func (u *UserMongoDriver) GetSubscribedList(ctx context.Context, userID string) (userIDList []string, err error) {
|
||||
var user model.SubscribeUser
|
||||
cursor := u.userCollection.FindOne(
|
||||
ctx,
|
||||
bson.M{"user_id": SubscribedPrefix + userID})
|
||||
err = cursor.Decode(&user)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return []string{}, nil
|
||||
} else {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
}
|
||||
return user.UserIDList, nil
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
// Copyright © 2023 OpenIM. 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 mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/protocol/user"
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewUserMongo(db *mongo.Database) (database.User, error) {
|
||||
coll := db.Collection("user")
|
||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
Keys: bson.D{
|
||||
{Key: "user_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
return &UserMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
type UserMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (u *UserMgo) Create(ctx context.Context, users []*model.User) error {
|
||||
return mongoutil.InsertMany(ctx, u.coll, users)
|
||||
}
|
||||
|
||||
func (u *UserMgo) UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error) {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
return mongoutil.UpdateOne(ctx, u.coll, bson.M{"user_id": userID}, bson.M{"$set": args}, true)
|
||||
}
|
||||
|
||||
func (u *UserMgo) Find(ctx context.Context, userIDs []string) (users []*model.User, err error) {
|
||||
return mongoutil.Find[*model.User](ctx, u.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
|
||||
}
|
||||
|
||||
func (u *UserMgo) Take(ctx context.Context, userID string) (user *model.User, err error) {
|
||||
return mongoutil.FindOne[*model.User](ctx, u.coll, bson.M{"user_id": userID})
|
||||
}
|
||||
|
||||
func (u *UserMgo) TakeNotification(ctx context.Context, level int64) (user []*model.User, err error) {
|
||||
return mongoutil.Find[*model.User](ctx, u.coll, bson.M{"app_manger_level": level})
|
||||
}
|
||||
|
||||
func (u *UserMgo) TakeByNickname(ctx context.Context, nickname string) (user []*model.User, err error) {
|
||||
return mongoutil.Find[*model.User](ctx, u.coll, bson.M{"nickname": nickname})
|
||||
}
|
||||
|
||||
func (u *UserMgo) Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*model.User, err error) {
|
||||
return mongoutil.FindPage[*model.User](ctx, u.coll, bson.M{}, pagination)
|
||||
}
|
||||
|
||||
func (u *UserMgo) PageFindUser(ctx context.Context, level1 int64, level2 int64, pagination pagination.Pagination) (count int64, users []*model.User, err error) {
|
||||
query := bson.M{
|
||||
"$or": []bson.M{
|
||||
{"app_manger_level": level1},
|
||||
{"app_manger_level": level2},
|
||||
},
|
||||
}
|
||||
|
||||
return mongoutil.FindPage[*model.User](ctx, u.coll, query, pagination)
|
||||
}
|
||||
|
||||
func (u *UserMgo) PageFindUserWithKeyword(
|
||||
ctx context.Context,
|
||||
level1 int64,
|
||||
level2 int64,
|
||||
userID string,
|
||||
nickName string,
|
||||
pagination pagination.Pagination,
|
||||
) (count int64, users []*model.User, err error) {
|
||||
// Initialize the base query with level conditions
|
||||
query := bson.M{
|
||||
"$and": []bson.M{
|
||||
{"app_manger_level": bson.M{"$in": []int64{level1, level2}}},
|
||||
},
|
||||
}
|
||||
|
||||
// Add userID and userName conditions to the query if they are provided
|
||||
if userID != "" || nickName != "" {
|
||||
userConditions := []bson.M{}
|
||||
if userID != "" {
|
||||
// Use regex for userID
|
||||
regexPattern := primitive.Regex{Pattern: userID, Options: "i"} // 'i' for case-insensitive matching
|
||||
userConditions = append(userConditions, bson.M{"user_id": regexPattern})
|
||||
}
|
||||
if nickName != "" {
|
||||
// Use regex for userName
|
||||
regexPattern := primitive.Regex{Pattern: nickName, Options: "i"} // 'i' for case-insensitive matching
|
||||
userConditions = append(userConditions, bson.M{"nickname": regexPattern})
|
||||
}
|
||||
query["$and"] = append(query["$and"].([]bson.M), bson.M{"$or": userConditions})
|
||||
}
|
||||
|
||||
// Perform the paginated search
|
||||
return mongoutil.FindPage[*model.User](ctx, u.coll, query, pagination)
|
||||
}
|
||||
|
||||
func (u *UserMgo) GetAllUserID(ctx context.Context, pagination pagination.Pagination) (int64, []string, error) {
|
||||
return mongoutil.FindPage[string](ctx, u.coll, bson.M{}, pagination, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}))
|
||||
}
|
||||
|
||||
func (u *UserMgo) Exist(ctx context.Context, userID string) (exist bool, err error) {
|
||||
return mongoutil.Exist(ctx, u.coll, bson.M{"user_id": userID})
|
||||
}
|
||||
|
||||
func (u *UserMgo) GetUserGlobalRecvMsgOpt(ctx context.Context, userID string) (opt int, err error) {
|
||||
return mongoutil.FindOne[int](ctx, u.coll, bson.M{"user_id": userID}, options.FindOne().SetProjection(bson.M{"_id": 0, "global_recv_msg_opt": 1}))
|
||||
}
|
||||
|
||||
func (u *UserMgo) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
|
||||
if before == nil {
|
||||
return mongoutil.Count(ctx, u.coll, bson.M{})
|
||||
}
|
||||
return mongoutil.Count(ctx, u.coll, bson.M{"create_time": bson.M{"$lt": before}})
|
||||
}
|
||||
|
||||
func (u *UserMgo) AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error {
|
||||
collection := u.coll.Database().Collection("userCommands")
|
||||
|
||||
// Create a new document instead of updating an existing one
|
||||
doc := bson.M{
|
||||
"userID": userID,
|
||||
"type": Type,
|
||||
"uuid": UUID,
|
||||
"createTime": time.Now().Unix(), // assuming you want the creation time in Unix timestamp
|
||||
"value": value,
|
||||
"ex": ex,
|
||||
}
|
||||
|
||||
_, err := collection.InsertOne(ctx, doc)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
func (u *UserMgo) DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error {
|
||||
collection := u.coll.Database().Collection("userCommands")
|
||||
|
||||
filter := bson.M{"userID": userID, "type": Type, "uuid": UUID}
|
||||
|
||||
result, err := collection.DeleteOne(ctx, filter)
|
||||
if result.DeletedCount == 0 {
|
||||
// No records found to update
|
||||
return errs.Wrap(errs.ErrRecordNotFound)
|
||||
}
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
func (u *UserMgo) UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error {
|
||||
if len(val) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
collection := u.coll.Database().Collection("userCommands")
|
||||
|
||||
filter := bson.M{"userID": userID, "type": Type, "uuid": UUID}
|
||||
update := bson.M{"$set": val}
|
||||
|
||||
result, err := collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
// No records found to update
|
||||
return errs.Wrap(errs.ErrRecordNotFound)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserMgo) GetUserCommand(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error) {
|
||||
collection := u.coll.Database().Collection("userCommands")
|
||||
filter := bson.M{"userID": userID, "type": Type}
|
||||
|
||||
cursor, err := collection.Find(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
// Initialize commands as a slice of pointers
|
||||
commands := []*user.CommandInfoResp{}
|
||||
|
||||
for cursor.Next(ctx) {
|
||||
var document struct {
|
||||
Type int32 `bson:"type"`
|
||||
UUID string `bson:"uuid"`
|
||||
Value string `bson:"value"`
|
||||
CreateTime int64 `bson:"createTime"`
|
||||
Ex string `bson:"ex"`
|
||||
}
|
||||
|
||||
if err := cursor.Decode(&document); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commandInfo := &user.CommandInfoResp{
|
||||
Type: document.Type,
|
||||
Uuid: document.UUID,
|
||||
Value: document.Value,
|
||||
CreateTime: document.CreateTime,
|
||||
Ex: document.Ex,
|
||||
}
|
||||
|
||||
commands = append(commands, commandInfo)
|
||||
}
|
||||
|
||||
if err := cursor.Err(); err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
return commands, nil
|
||||
}
|
||||
func (u *UserMgo) GetAllUserCommand(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error) {
|
||||
collection := u.coll.Database().Collection("userCommands")
|
||||
filter := bson.M{"userID": userID}
|
||||
|
||||
cursor, err := collection.Find(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
// Initialize commands as a slice of pointers
|
||||
commands := []*user.AllCommandInfoResp{}
|
||||
|
||||
for cursor.Next(ctx) {
|
||||
var document struct {
|
||||
Type int32 `bson:"type"`
|
||||
UUID string `bson:"uuid"`
|
||||
Value string `bson:"value"`
|
||||
CreateTime int64 `bson:"createTime"`
|
||||
Ex string `bson:"ex"`
|
||||
}
|
||||
|
||||
if err := cursor.Decode(&document); err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
commandInfo := &user.AllCommandInfoResp{
|
||||
Type: document.Type,
|
||||
Uuid: document.UUID,
|
||||
Value: document.Value,
|
||||
CreateTime: document.CreateTime,
|
||||
Ex: document.Ex,
|
||||
}
|
||||
|
||||
commands = append(commands, commandInfo)
|
||||
}
|
||||
|
||||
if err := cursor.Err(); err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
return commands, nil
|
||||
}
|
||||
func (u *UserMgo) CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) {
|
||||
pipeline := bson.A{
|
||||
bson.M{
|
||||
"$match": bson.M{
|
||||
"create_time": bson.M{
|
||||
"$gte": start,
|
||||
"$lt": end,
|
||||
},
|
||||
},
|
||||
},
|
||||
bson.M{
|
||||
"$group": bson.M{
|
||||
"_id": bson.M{
|
||||
"$dateToString": bson.M{
|
||||
"format": "%Y-%m-%d",
|
||||
"date": "$create_time",
|
||||
},
|
||||
},
|
||||
"count": bson.M{
|
||||
"$sum": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
type Item struct {
|
||||
Date string `bson:"_id"`
|
||||
Count int64 `bson:"count"`
|
||||
}
|
||||
items, err := mongoutil.Aggregate[Item](ctx, u.coll, pipeline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make(map[string]int64, len(items))
|
||||
for _, item := range items {
|
||||
res[item.Date] = item.Count
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/protocol/msg"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Msg interface {
|
||||
PushMsgsToDoc(ctx context.Context, docID string, msgsToMongo []model.MsgInfoModel) error
|
||||
Create(ctx context.Context, model *model.MsgDocModel) error
|
||||
UpdateMsg(ctx context.Context, docID string, index int64, key string, value any) (*mongo.UpdateResult, error)
|
||||
PushUnique(ctx context.Context, docID string, index int64, key string, value any) (*mongo.UpdateResult, error)
|
||||
UpdateMsgContent(ctx context.Context, docID string, index int64, msg []byte) error
|
||||
IsExistDocID(ctx context.Context, docID string) (bool, error)
|
||||
FindOneByDocID(ctx context.Context, docID string) (*model.MsgDocModel, error)
|
||||
GetMsgBySeqIndexIn1Doc(ctx context.Context, userID, docID string, seqs []int64) ([]*model.MsgInfoModel, error)
|
||||
GetNewestMsg(ctx context.Context, conversationID string) (*model.MsgInfoModel, error)
|
||||
GetOldestMsg(ctx context.Context, conversationID string) (*model.MsgInfoModel, error)
|
||||
DeleteDocs(ctx context.Context, docIDs []string) error
|
||||
GetMsgDocModelByIndex(ctx context.Context, conversationID string, index, sort int64) (*model.MsgDocModel, error)
|
||||
DeleteMsgsInOneDocByIndex(ctx context.Context, docID string, indexes []int) error
|
||||
MarkSingleChatMsgsAsRead(ctx context.Context, userID string, docID string, indexes []int64) error
|
||||
SearchMessage(ctx context.Context, req *msg.SearchMessageReq) (int32, []*model.MsgInfoModel, error)
|
||||
RangeUserSendCount(ctx context.Context, start time.Time, end time.Time, group bool, ase bool, pageNumber int32, showNumber int32) (msgCount int64, userCount int64, users []*model.UserCount, dateCount map[string]int64, err error)
|
||||
RangeGroupSendCount(ctx context.Context, start time.Time, end time.Time, ase bool, pageNumber int32, showNumber int32) (msgCount int64, userCount int64, groups []*model.GroupCount, dateCount map[string]int64, err error)
|
||||
ConvertMsgsDocLen(ctx context.Context, conversationIDs []string)
|
||||
|
||||
DeleteDoc(ctx context.Context, docID string) error
|
||||
DeleteMsgByIndex(ctx context.Context, docID string, index []int) error
|
||||
GetBeforeMsg(ctx context.Context, ts int64, limit int) ([]*model.MsgDocModel, error)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
)
|
||||
|
||||
type ObjectInfo interface {
|
||||
SetObject(ctx context.Context, obj *model.Object) error
|
||||
Take(ctx context.Context, engine string, name string) (*model.Object, error)
|
||||
Delete(ctx context.Context, engine string, name string) error
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import "context"
|
||||
|
||||
// SubscribeUser Operation interface of user mongodb.
|
||||
type SubscribeUser interface {
|
||||
// AddSubscriptionList Subscriber's handling of thresholds.
|
||||
AddSubscriptionList(ctx context.Context, userID string, userIDList []string) error
|
||||
// UnsubscriptionList Handling of unsubscribe.
|
||||
UnsubscriptionList(ctx context.Context, userID string, userIDList []string) error
|
||||
// RemoveSubscribedListFromUser Among the unsubscribed users, delete the user from the subscribed list.
|
||||
RemoveSubscribedListFromUser(ctx context.Context, userID string, userIDList []string) error
|
||||
// GetAllSubscribeList Get all users subscribed by this user
|
||||
GetAllSubscribeList(ctx context.Context, id string) (userIDList []string, err error)
|
||||
// GetSubscribedList Get the user subscribed by those users
|
||||
GetSubscribedList(ctx context.Context, id string) (userIDList []string, err error)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright © 2023 OpenIM. 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 database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/protocol/user"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User interface {
|
||||
Create(ctx context.Context, users []*model.User) (err error)
|
||||
UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error)
|
||||
Find(ctx context.Context, userIDs []string) (users []*model.User, err error)
|
||||
Take(ctx context.Context, userID string) (user *model.User, err error)
|
||||
TakeNotification(ctx context.Context, level int64) (user []*model.User, err error)
|
||||
TakeByNickname(ctx context.Context, nickname string) (user []*model.User, err error)
|
||||
Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*model.User, err error)
|
||||
PageFindUser(ctx context.Context, level1 int64, level2 int64, pagination pagination.Pagination) (count int64, users []*model.User, err error)
|
||||
PageFindUserWithKeyword(ctx context.Context, level1 int64, level2 int64, userID, nickName string, pagination pagination.Pagination) (count int64, users []*model.User, err error)
|
||||
Exist(ctx context.Context, userID string) (exist bool, err error)
|
||||
GetAllUserID(ctx context.Context, pagination pagination.Pagination) (count int64, userIDs []string, err error)
|
||||
GetUserGlobalRecvMsgOpt(ctx context.Context, userID string) (opt int, err error)
|
||||
// Get user total quantity
|
||||
CountTotal(ctx context.Context, before *time.Time) (count int64, err error)
|
||||
// Get user total quantity every day
|
||||
CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error)
|
||||
// CRUD user command
|
||||
AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error
|
||||
DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error
|
||||
UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error
|
||||
GetUserCommand(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error)
|
||||
GetAllUserCommand(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Black struct {
|
||||
OwnerUserID string `bson:"owner_user_id"`
|
||||
BlockUserID string `bson:"block_user_id"`
|
||||
CreateTime time.Time `bson:"create_time"`
|
||||
AddSource int32 `bson:"add_source"`
|
||||
OperatorUserID string `bson:"operator_user_id"`
|
||||
Ex string `bson:"ex"`
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Conversation struct {
|
||||
OwnerUserID string `bson:"owner_user_id"`
|
||||
ConversationID string `bson:"conversation_id"`
|
||||
ConversationType int32 `bson:"conversation_type"`
|
||||
UserID string `bson:"user_id"`
|
||||
GroupID string `bson:"group_id"`
|
||||
RecvMsgOpt int32 `bson:"recv_msg_opt"`
|
||||
IsPinned bool `bson:"is_pinned"`
|
||||
IsPrivateChat bool `bson:"is_private_chat"`
|
||||
BurnDuration int32 `bson:"burn_duration"`
|
||||
GroupAtType int32 `bson:"group_at_type"`
|
||||
AttachedInfo string `bson:"attached_info"`
|
||||
Ex string `bson:"ex"`
|
||||
MaxSeq int64 `bson:"max_seq"`
|
||||
MinSeq int64 `bson:"min_seq"`
|
||||
CreateTime time.Time `bson:"create_time"`
|
||||
IsMsgDestruct bool `bson:"is_msg_destruct"`
|
||||
MsgDestructTime int64 `bson:"msg_destruct_time"`
|
||||
LatestMsgDestructTime time.Time `bson:"latest_msg_destruct_time"`
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright © 2024 OpenIM. 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 model // import "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model/relation"
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Friend represents the data structure for a friend relationship in MongoDB.
|
||||
type Friend struct {
|
||||
OwnerUserID string `bson:"owner_user_id"`
|
||||
FriendUserID string `bson:"friend_user_id"`
|
||||
Remark string `bson:"remark"`
|
||||
CreateTime time.Time `bson:"create_time"`
|
||||
AddSource int32 `bson:"add_source"`
|
||||
OperatorUserID string `bson:"operator_user_id"`
|
||||
Ex string `bson:"ex"`
|
||||
IsPinned bool `bson:"is_pinned"`
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type FriendRequest struct {
|
||||
FromUserID string `bson:"from_user_id"`
|
||||
ToUserID string `bson:"to_user_id"`
|
||||
HandleResult int32 `bson:"handle_result"`
|
||||
ReqMsg string `bson:"req_msg"`
|
||||
CreateTime time.Time `bson:"create_time"`
|
||||
HandlerUserID string `bson:"handler_user_id"`
|
||||
HandleMsg string `bson:"handle_msg"`
|
||||
HandleTime time.Time `bson:"handle_time"`
|
||||
Ex string `bson:"ex"`
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Group struct {
|
||||
GroupID string `bson:"group_id"`
|
||||
GroupName string `bson:"group_name"`
|
||||
Notification string `bson:"notification"`
|
||||
Introduction string `bson:"introduction"`
|
||||
FaceURL string `bson:"face_url"`
|
||||
CreateTime time.Time `bson:"create_time"`
|
||||
Ex string `bson:"ex"`
|
||||
Status int32 `bson:"status"`
|
||||
CreatorUserID string `bson:"creator_user_id"`
|
||||
GroupType int32 `bson:"group_type"`
|
||||
NeedVerification int32 `bson:"need_verification"`
|
||||
LookMemberInfo int32 `bson:"look_member_info"`
|
||||
ApplyMemberFriend int32 `bson:"apply_member_friend"`
|
||||
NotificationUpdateTime time.Time `bson:"notification_update_time"`
|
||||
NotificationUserID string `bson:"notification_user_id"`
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type GroupMember struct {
|
||||
GroupID string `bson:"group_id"`
|
||||
UserID string `bson:"user_id"`
|
||||
Nickname string `bson:"nickname"`
|
||||
FaceURL string `bson:"face_url"`
|
||||
RoleLevel int32 `bson:"role_level"`
|
||||
JoinTime time.Time `bson:"join_time"`
|
||||
JoinSource int32 `bson:"join_source"`
|
||||
InviterUserID string `bson:"inviter_user_id"`
|
||||
OperatorUserID string `bson:"operator_user_id"`
|
||||
MuteEndTime time.Time `bson:"mute_end_time"`
|
||||
Ex string `bson:"ex"`
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type GroupRequest struct {
|
||||
UserID string `bson:"user_id"`
|
||||
GroupID string `bson:"group_id"`
|
||||
HandleResult int32 `bson:"handle_result"`
|
||||
ReqMsg string `bson:"req_msg"`
|
||||
HandledMsg string `bson:"handled_msg"`
|
||||
ReqTime time.Time `bson:"req_time"`
|
||||
HandleUserID string `bson:"handle_user_id"`
|
||||
HandledTime time.Time `bson:"handled_time"`
|
||||
JoinSource int32 `bson:"join_source"`
|
||||
InviterUserID string `bson:"inviter_user_id"`
|
||||
Ex string `bson:"ex"`
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Log struct {
|
||||
LogID string `bson:"log_id"`
|
||||
Platform string `bson:"platform"`
|
||||
UserID string `bson:"user_id"`
|
||||
CreateTime time.Time `bson:"create_time"`
|
||||
Url string `bson:"url"`
|
||||
FileName string `bson:"file_name"`
|
||||
SystemType string `bson:"system_type"`
|
||||
Version string `bson:"version"`
|
||||
Ex string `bson:"ex"`
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
singleGocMsgNum = 100
|
||||
singleGocMsgNum5000 = 5000
|
||||
MsgTableName = "msg"
|
||||
OldestList = 0
|
||||
NewestList = -1
|
||||
)
|
||||
|
||||
var ErrMsgListNotExist = errs.New("user not have msg in mongoDB")
|
||||
|
||||
type MsgDocModel struct {
|
||||
DocID string `bson:"doc_id"`
|
||||
Msg []*MsgInfoModel `bson:"msgs"`
|
||||
}
|
||||
|
||||
type RevokeModel struct {
|
||||
Role int32 `bson:"role"`
|
||||
UserID string `bson:"user_id"`
|
||||
Nickname string `bson:"nickname"`
|
||||
Time int64 `bson:"time"`
|
||||
}
|
||||
|
||||
type OfflinePushModel struct {
|
||||
Title string `bson:"title"`
|
||||
Desc string `bson:"desc"`
|
||||
Ex string `bson:"ex"`
|
||||
IOSPushSound string `bson:"ios_push_sound"`
|
||||
IOSBadgeCount bool `bson:"ios_badge_count"`
|
||||
}
|
||||
|
||||
type MsgDataModel struct {
|
||||
SendID string `bson:"send_id"`
|
||||
RecvID string `bson:"recv_id"`
|
||||
GroupID string `bson:"group_id"`
|
||||
ClientMsgID string `bson:"client_msg_id"`
|
||||
ServerMsgID string `bson:"server_msg_id"`
|
||||
SenderPlatformID int32 `bson:"sender_platform_id"`
|
||||
SenderNickname string `bson:"sender_nickname"`
|
||||
SenderFaceURL string `bson:"sender_face_url"`
|
||||
SessionType int32 `bson:"session_type"`
|
||||
MsgFrom int32 `bson:"msg_from"`
|
||||
ContentType int32 `bson:"content_type"`
|
||||
Content string `bson:"content"`
|
||||
Seq int64 `bson:"seq"`
|
||||
SendTime int64 `bson:"send_time"`
|
||||
CreateTime int64 `bson:"create_time"`
|
||||
Status int32 `bson:"status"`
|
||||
IsRead bool `bson:"is_read"`
|
||||
Options map[string]bool `bson:"options"`
|
||||
OfflinePush *OfflinePushModel `bson:"offline_push"`
|
||||
AtUserIDList []string `bson:"at_user_id_list"`
|
||||
AttachedInfo string `bson:"attached_info"`
|
||||
Ex string `bson:"ex"`
|
||||
}
|
||||
|
||||
type MsgInfoModel struct {
|
||||
Msg *MsgDataModel `bson:"msg"`
|
||||
Revoke *RevokeModel `bson:"revoke"`
|
||||
DelList []string `bson:"del_list"`
|
||||
IsRead bool `bson:"is_read"`
|
||||
}
|
||||
|
||||
type UserCount struct {
|
||||
UserID string `bson:"user_id"`
|
||||
Count int64 `bson:"count"`
|
||||
}
|
||||
|
||||
type GroupCount struct {
|
||||
GroupID string `bson:"group_id"`
|
||||
Count int64 `bson:"count"`
|
||||
}
|
||||
|
||||
func (MsgDocModel) TableName() string {
|
||||
return MsgTableName
|
||||
}
|
||||
|
||||
func (MsgDocModel) GetSingleGocMsgNum() int64 {
|
||||
return singleGocMsgNum
|
||||
}
|
||||
|
||||
func (MsgDocModel) GetSingleGocMsgNum5000() int64 {
|
||||
return singleGocMsgNum5000
|
||||
}
|
||||
|
||||
func (m *MsgDocModel) IsFull() bool {
|
||||
return m.Msg[len(m.Msg)-1].Msg != nil
|
||||
}
|
||||
|
||||
func (m MsgDocModel) GetDocID(conversationID string, seq int64) string {
|
||||
seqSuffix := (seq - 1) / singleGocMsgNum
|
||||
return m.indexGen(conversationID, seqSuffix)
|
||||
}
|
||||
|
||||
func (m MsgDocModel) GetDocIDSeqsMap(conversationID string, seqs []int64) map[string][]int64 {
|
||||
t := make(map[string][]int64)
|
||||
for i := 0; i < len(seqs); i++ {
|
||||
docID := m.GetDocID(conversationID, seqs[i])
|
||||
if value, ok := t[docID]; !ok {
|
||||
var temp []int64
|
||||
t[docID] = append(temp, seqs[i])
|
||||
} else {
|
||||
t[docID] = append(value, seqs[i])
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (MsgDocModel) GetMsgIndex(seq int64) int64 {
|
||||
return (seq - 1) % singleGocMsgNum
|
||||
}
|
||||
|
||||
func (MsgDocModel) indexGen(conversationID string, seqSuffix int64) string {
|
||||
return conversationID + ":" + strconv.FormatInt(seqSuffix, 10)
|
||||
}
|
||||
|
||||
func (MsgDocModel) GenExceptionMessageBySeqs(seqs []int64) (exceptionMsg []*sdkws.MsgData) {
|
||||
for _, v := range seqs {
|
||||
msgModel := new(sdkws.MsgData)
|
||||
msgModel.Seq = v
|
||||
exceptionMsg = append(exceptionMsg, msgModel)
|
||||
}
|
||||
return exceptionMsg
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Object struct {
|
||||
Name string `bson:"name"`
|
||||
UserID string `bson:"user_id"`
|
||||
Hash string `bson:"hash"`
|
||||
Engine string `bson:"engine"`
|
||||
Key string `bson:"key"`
|
||||
Size int64 `bson:"size"`
|
||||
ContentType string `bson:"content_type"`
|
||||
Group string `bson:"group"`
|
||||
CreateTime time.Time `bson:"create_time"`
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
// SubscribeUserTableName collection constant.
|
||||
const (
|
||||
SubscribeUserTableName = "subscribe_user"
|
||||
)
|
||||
|
||||
// SubscribeUser collection structure.
|
||||
type SubscribeUser struct {
|
||||
UserID string `bson:"user_id" json:"userID"`
|
||||
UserIDList []string `bson:"user_id_list" json:"userIDList"`
|
||||
}
|
||||
|
||||
func (SubscribeUser) TableName() string {
|
||||
return SubscribeUserTableName
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright © 2023 OpenIM. 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 model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
UserID string `bson:"user_id"`
|
||||
Nickname string `bson:"nickname"`
|
||||
FaceURL string `bson:"face_url"`
|
||||
Ex string `bson:"ex"`
|
||||
AppMangerLevel int32 `bson:"app_manger_level"`
|
||||
GlobalRecvMsgOpt int32 `bson:"global_recv_msg_opt"`
|
||||
CreateTime time.Time `bson:"create_time"`
|
||||
}
|
||||
|
||||
func (u *User) GetNickname() string {
|
||||
return u.Nickname
|
||||
}
|
||||
|
||||
func (u *User) GetFaceURL() string {
|
||||
return u.FaceURL
|
||||
}
|
||||
|
||||
func (u User) GetUserID() string {
|
||||
return u.UserID
|
||||
}
|
||||
|
||||
func (u User) GetEx() string {
|
||||
return u.Ex
|
||||
}
|
||||
Reference in New Issue
Block a user