复制项目

This commit is contained in:
kim.dev.6789
2026-01-14 22:16:44 +08:00
parent e2577b8cee
commit e50142a3b9
691 changed files with 97009 additions and 1 deletions

View File

@@ -0,0 +1,114 @@
// 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"
"git.imall.cloud/openim/open-im-server-deploy/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)
}
var (
_ Black = (*mgoImpl)(nil)
_ Black = (*redisImpl)(nil)
)
type mgoImpl struct {
}
func (m *mgoImpl) Create(ctx context.Context, blacks []*model.Black) (err error) {
//TODO implement me
panic("implement me")
}
func (m *mgoImpl) Delete(ctx context.Context, blacks []*model.Black) (err error) {
//TODO implement me
panic("implement me")
}
func (m *mgoImpl) Find(ctx context.Context, blacks []*model.Black) (blackList []*model.Black, err error) {
//TODO implement me
panic("implement me")
}
func (m *mgoImpl) Take(ctx context.Context, ownerUserID, blockUserID string) (black *model.Black, err error) {
//TODO implement me
panic("implement me")
}
func (m *mgoImpl) FindOwnerBlacks(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, blacks []*model.Black, err error) {
//TODO implement me
panic("implement me")
}
func (m *mgoImpl) FindOwnerBlackInfos(ctx context.Context, ownerUserID string, userIDs []string) (blacks []*model.Black, err error) {
//TODO implement me
panic("implement me")
}
func (m *mgoImpl) FindBlackUserIDs(ctx context.Context, ownerUserID string) (blackUserIDs []string, err error) {
//TODO implement me
panic("implement me")
}
type redisImpl struct {
}
func (r *redisImpl) Create(ctx context.Context, blacks []*model.Black) (err error) {
//TODO implement me
panic("implement me")
}
func (r *redisImpl) Delete(ctx context.Context, blacks []*model.Black) (err error) {
//TODO implement me
panic("implement me")
}
func (r *redisImpl) Find(ctx context.Context, blacks []*model.Black) (blackList []*model.Black, err error) {
//TODO implement me
panic("implement me")
}
func (r *redisImpl) Take(ctx context.Context, ownerUserID, blockUserID string) (black *model.Black, err error) {
//TODO implement me
panic("implement me")
}
func (r *redisImpl) FindOwnerBlacks(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, blacks []*model.Black, err error) {
//TODO implement me
panic("implement me")
}
func (r *redisImpl) FindOwnerBlackInfos(ctx context.Context, ownerUserID string, userIDs []string) (blacks []*model.Black, err error) {
//TODO implement me
panic("implement me")
}
func (r *redisImpl) FindBlackUserIDs(ctx context.Context, ownerUserID string) (blackUserIDs []string, err error) {
//TODO implement me
panic("implement me")
}

View File

@@ -0,0 +1,16 @@
package database
import (
"context"
"time"
)
type Cache interface {
Get(ctx context.Context, key []string) (map[string]string, error)
Prefix(ctx context.Context, prefix string) (map[string]string, error)
Set(ctx context.Context, key string, value string, expireAt time.Duration) error
Incr(ctx context.Context, key string, value int) (int, error)
Del(ctx context.Context, key []string) error
Lock(ctx context.Context, key string, duration time.Duration) (string, error)
Unlock(ctx context.Context, key string, value string) error
}

View File

@@ -0,0 +1,15 @@
package database
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/pagination"
)
type ClientConfig interface {
Set(ctx context.Context, userID string, config map[string]string) error
Get(ctx context.Context, userID string) (map[string]string, error)
Del(ctx context.Context, userID string, keys []string) error
GetPage(ctx context.Context, userID string, key string, pagination pagination.Pagination) (int64, []*model.ClientConfig, error)
}

View File

@@ -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"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/pagination"
)
type Conversation interface {
Create(ctx context.Context, conversations []*model.Conversation) (err error)
UpdateByMap(ctx context.Context, userIDs []string, conversationID string, args map[string]any) (rows int64, err error)
UpdateUserConversations(ctx context.Context, userID string, args map[string]any) ([]*model.Conversation, 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)
FindUserIDAllNotNotifyConversationID(ctx context.Context, userID string) ([]string, error)
FindUserIDAllPinnedConversationID(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)
FindConversationUserVersion(ctx context.Context, userID string, version uint, limit int) (*model.VersionLog, error)
FindRandConversation(ctx context.Context, ts int64, limit int) ([]*model.Conversation, error)
DeleteUsersConversations(ctx context.Context, userID string, conversationIDs []string) error
}

View File

@@ -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 "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model/relation"

View File

@@ -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 database
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/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)
FindIncrVersion(ctx context.Context, ownerUserID string, version uint, limit int) (*model.VersionLog, error)
FindFriendUserID(ctx context.Context, friendUserID string) ([]string, error)
//SearchFriend(ctx context.Context, ownerUserID, keyword string, pagination pagination.Pagination) (int64, []*model.Friend, error)
FindOwnerFriendUserIds(ctx context.Context, ownerUserID string, limit int) ([]string, error)
IncrVersion(ctx context.Context, ownerUserID string, friendUserIDs []string, state int32) error
}

View File

@@ -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"
"git.imall.cloud/openim/open-im-server-deploy/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, handleResults []int, pagination pagination.Pagination) (total int64, friendRequests []*model.FriendRequest, err error)
// Get list of friend requests sent by fromUserID
FindFromUserID(ctx context.Context, fromUserID string, handleResults []int, pagination pagination.Pagination) (total int64, friendRequests []*model.FriendRequest, err error)
FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*model.FriendRequest, err error)
GetUnhandledCount(ctx context.Context, userID string, ts int64) (int64, error)
}

View File

@@ -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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/pagination"
)
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)
FindJoinSortGroupID(ctx context.Context, groupIDs []string) ([]string, error)
SearchJoin(ctx context.Context, groupIDs []string, keyword string, pagination pagination.Pagination) (int64, []*model.Group, error)
}

View File

@@ -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"
"git.imall.cloud/openim/open-im-server-deploy/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
UpdateUserRoleLevels(ctx context.Context, groupID string, firstUserID string, firstUserRoleLevel int32, secondUserID string, secondUserRoleLevel 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)
Find(ctx context.Context, groupID string, userIDs []string) ([]*model.GroupMember, error)
FindInGroup(ctx context.Context, userID string, groupIDs []string) ([]*model.GroupMember, 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)
// SearchMemberByFields searches for group members by multiple fields: nickname, userID (account), and optionally phone
SearchMemberByFields(ctx context.Context, groupID string, nickname, userID, phone 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
JoinGroupIncrVersion(ctx context.Context, userID string, groupIDs []string, state int32) error
MemberGroupIncrVersion(ctx context.Context, groupID string, userIDs []string, state int32) error
FindMemberIncrVersion(ctx context.Context, groupID string, version uint, limit int) (*model.VersionLog, error)
BatchFindMemberIncrVersion(ctx context.Context, groupIDs []string, versions []uint, limits []int) ([]*model.VersionLog, error)
FindJoinIncrVersion(ctx context.Context, userID string, version uint, limit int) (*model.VersionLog, error)
}

View File

@@ -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 database
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/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, groupIDs []string, handleResults []int, pagination pagination.Pagination) (total int64, groups []*model.GroupRequest, err error)
PageGroup(ctx context.Context, groupIDs []string, handleResults []int, pagination pagination.Pagination) (total int64, groups []*model.GroupRequest, err error)
GetUnhandledCount(ctx context.Context, groupIDs []string, ts int64) (int64, error)
}

View File

@@ -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 database
import (
"context"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/pagination"
)
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)
}

View File

@@ -0,0 +1,52 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/pagination"
)
// Meeting defines the operations for managing meetings in MongoDB.
type Meeting interface {
// Create creates a new meeting record.
Create(ctx context.Context, meeting *model.Meeting) error
// Take retrieves a meeting by meeting ID. Returns an error if not found.
Take(ctx context.Context, meetingID string) (*model.Meeting, error)
// Update updates meeting information.
Update(ctx context.Context, meetingID string, data map[string]any) error
// UpdateStatus updates the status of a meeting.
UpdateStatus(ctx context.Context, meetingID string, status int32) error
// Find finds meetings by meeting IDs.
Find(ctx context.Context, meetingIDs []string) ([]*model.Meeting, error)
// FindByCreator finds meetings created by a specific user.
FindByCreator(ctx context.Context, creatorUserID string, pagination pagination.Pagination) (total int64, meetings []*model.Meeting, err error)
// FindAll finds all meetings with pagination.
FindAll(ctx context.Context, pagination pagination.Pagination) (total int64, meetings []*model.Meeting, err error)
// Search searches meetings by keyword (subject, description).
Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, meetings []*model.Meeting, err error)
// FindByStatus finds meetings by status.
FindByStatus(ctx context.Context, status int32, pagination pagination.Pagination) (total int64, meetings []*model.Meeting, err error)
// FindByScheduledTimeRange finds meetings within a scheduled time range.
FindByScheduledTimeRange(ctx context.Context, startTime, endTime int64, pagination pagination.Pagination) (total int64, meetings []*model.Meeting, err error)
// FindFinishedMeetingsBefore finds finished meetings that ended before the specified time.
// This is used for cleanup tasks like dismissing groups after meetings end.
FindFinishedMeetingsBefore(ctx context.Context, beforeTime time.Time) ([]*model.Meeting, error)
// Delete deletes a meeting by meeting ID.
Delete(ctx context.Context, meetingID string) error
}

View File

@@ -0,0 +1,41 @@
// 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"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/pagination"
)
// MeetingCheckIn defines the operations for managing meeting check-ins in MongoDB.
type MeetingCheckIn interface {
// Create creates a new meeting check-in record.
Create(ctx context.Context, checkIn *model.MeetingCheckIn) error
// Take retrieves a check-in by check-in ID. Returns an error if not found.
Take(ctx context.Context, checkInID string) (*model.MeetingCheckIn, error)
// FindByMeetingID finds all check-ins for a meeting with pagination.
FindByMeetingID(ctx context.Context, meetingID string, pagination pagination.Pagination) (total int64, checkIns []*model.MeetingCheckIn, err error)
// FindByUserAndMeetingID finds if a user has checked in for a specific meeting.
FindByUserAndMeetingID(ctx context.Context, userID, meetingID string) (*model.MeetingCheckIn, error)
// CountByMeetingID counts the number of check-ins for a meeting.
CountByMeetingID(ctx context.Context, meetingID string) (int64, error)
// FindByUser finds all check-ins by a user with pagination.
FindByUser(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, checkIns []*model.MeetingCheckIn, err error)
// Delete deletes a check-in by check-in ID.
Delete(ctx context.Context, checkInID string) error
}

View File

@@ -0,0 +1,106 @@
// 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"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/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(database.BlackName)
_, 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}))
}

View File

@@ -0,0 +1,183 @@
package mgo
import (
"context"
"strconv"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/google/uuid"
"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 NewCacheMgo(db *mongo.Database) (*CacheMgo, error) {
coll := db.Collection(database.CacheName)
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "key", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "expire_at", Value: 1},
},
Options: options.Index().SetExpireAfterSeconds(0),
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &CacheMgo{coll: coll}, nil
}
type CacheMgo struct {
coll *mongo.Collection
}
func (x *CacheMgo) findToMap(res []model.Cache, now time.Time) map[string]string {
kv := make(map[string]string)
for _, re := range res {
if re.ExpireAt != nil && re.ExpireAt.Before(now) {
continue
}
kv[re.Key] = re.Value
}
return kv
}
func (x *CacheMgo) Get(ctx context.Context, key []string) (map[string]string, error) {
if len(key) == 0 {
return nil, nil
}
now := time.Now()
res, err := mongoutil.Find[model.Cache](ctx, x.coll, bson.M{
"key": bson.M{"$in": key},
"$or": []bson.M{
{"expire_at": bson.M{"$gt": now}},
{"expire_at": nil},
},
})
if err != nil {
return nil, err
}
return x.findToMap(res, now), nil
}
func (x *CacheMgo) Prefix(ctx context.Context, prefix string) (map[string]string, error) {
now := time.Now()
res, err := mongoutil.Find[model.Cache](ctx, x.coll, bson.M{
"key": bson.M{"$regex": "^" + prefix},
"$or": []bson.M{
{"expire_at": bson.M{"$gt": now}},
{"expire_at": nil},
},
})
if err != nil {
return nil, err
}
return x.findToMap(res, now), nil
}
func (x *CacheMgo) Set(ctx context.Context, key string, value string, expireAt time.Duration) error {
cv := &model.Cache{
Key: key,
Value: value,
}
if expireAt > 0 {
now := time.Now().Add(expireAt)
cv.ExpireAt = &now
}
opt := options.Update().SetUpsert(true)
return mongoutil.UpdateOne(ctx, x.coll, bson.M{"key": key}, bson.M{"$set": cv}, false, opt)
}
func (x *CacheMgo) Incr(ctx context.Context, key string, value int) (int, error) {
pipeline := mongo.Pipeline{
{
{"$set", bson.M{
"value": bson.M{
"$toString": bson.M{
"$add": bson.A{
bson.M{"$toInt": "$value"},
value,
},
},
},
}},
},
}
opt := options.FindOneAndUpdate().SetReturnDocument(options.After)
res, err := mongoutil.FindOneAndUpdate[model.Cache](ctx, x.coll, bson.M{"key": key}, pipeline, opt)
if err != nil {
return 0, err
}
return strconv.Atoi(res.Value)
}
func (x *CacheMgo) Del(ctx context.Context, key []string) error {
if len(key) == 0 {
return nil
}
_, err := x.coll.DeleteMany(ctx, bson.M{"key": bson.M{"$in": key}})
return errs.Wrap(err)
}
func (x *CacheMgo) lockKey(key string) string {
return "LOCK_" + key
}
func (x *CacheMgo) Lock(ctx context.Context, key string, duration time.Duration) (string, error) {
tmp, err := uuid.NewUUID()
if err != nil {
return "", err
}
if duration <= 0 || duration > time.Minute*10 {
duration = time.Minute * 10
}
cv := &model.Cache{
Key: x.lockKey(key),
Value: tmp.String(),
ExpireAt: nil,
}
ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()
wait := func() error {
timeout := time.NewTimer(time.Millisecond * 100)
defer timeout.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-timeout.C:
return nil
}
}
for {
if err := mongoutil.DeleteOne(ctx, x.coll, bson.M{"key": key, "expire_at": bson.M{"$lt": time.Now()}}); err != nil {
return "", err
}
expireAt := time.Now().Add(duration)
cv.ExpireAt = &expireAt
if err := mongoutil.InsertMany[*model.Cache](ctx, x.coll, []*model.Cache{cv}); err != nil {
if mongo.IsDuplicateKeyError(err) {
if err := wait(); err != nil {
return "", err
}
continue
}
return "", err
}
return cv.Value, nil
}
}
func (x *CacheMgo) Unlock(ctx context.Context, key string, value string) error {
return mongoutil.DeleteOne(ctx, x.coll, bson.M{"key": x.lockKey(key), "value": value})
}

View File

@@ -0,0 +1,133 @@
package mgo
import (
"context"
"strings"
"sync"
"testing"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/mongoutil"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func TestName1111(t *testing.T) {
coll := Mongodb().Collection("temp")
//updatePipeline := mongo.Pipeline{
// {
// {"$set", bson.M{
// "age": bson.M{
// "$toString": bson.M{
// "$add": bson.A{
// bson.M{"$toInt": "$age"},
// 1,
// },
// },
// },
// }},
// },
//}
pipeline := mongo.Pipeline{
{
{"$set", bson.M{
"value": bson.M{
"$toString": bson.M{
"$add": bson.A{
bson.M{"$toInt": "$value"},
1,
},
},
},
}},
},
}
opt := options.FindOneAndUpdate().SetUpsert(false).SetReturnDocument(options.After)
res, err := mongoutil.FindOneAndUpdate[model.Cache](context.Background(), coll, bson.M{"key": "123456"}, pipeline, opt)
if err != nil {
panic(err)
}
t.Log(res)
}
func TestName33333(t *testing.T) {
c, err := NewCacheMgo(Mongodb())
if err != nil {
panic(err)
}
if err := c.Set(context.Background(), "123456", "123456", time.Hour); err != nil {
panic(err)
}
if err := c.Set(context.Background(), "123666", "123666", time.Hour); err != nil {
panic(err)
}
res1, err := c.Get(context.Background(), []string{"123456"})
if err != nil {
panic(err)
}
t.Log(res1)
res2, err := c.Prefix(context.Background(), "123")
if err != nil {
panic(err)
}
t.Log(res2)
}
func TestName1111aa(t *testing.T) {
c, err := NewCacheMgo(Mongodb())
if err != nil {
panic(err)
}
var count int
key := "123456"
doFunc := func() {
value, err := c.Lock(context.Background(), key, time.Second*30)
if err != nil {
t.Log("Lock error", err)
return
}
tmp := count
tmp++
count = tmp
t.Log("count", tmp)
if err := c.Unlock(context.Background(), key, value); err != nil {
t.Log("Unlock error", err)
return
}
}
if _, err := c.Lock(context.Background(), key, time.Second*10); err != nil {
t.Log(err)
return
}
var wg sync.WaitGroup
for i := 0; i < 32; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
doFunc()
}
}()
}
wg.Wait()
}
func TestName111111a(t *testing.T) {
arr := strings.SplitN("1:testkakskdask:1111", ":", 2)
t.Log(arr)
}

View File

@@ -0,0 +1,99 @@
// Copyright © 2023 OpenIM open source community. 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"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/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"
"github.com/openimsdk/tools/errs"
)
func NewClientConfig(db *mongo.Database) (database.ClientConfig, error) {
coll := db.Collection("config")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "key", Value: 1},
{Key: "user_id", Value: 1},
},
Options: options.Index().SetUnique(true),
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &ClientConfig{
coll: coll,
}, nil
}
type ClientConfig struct {
coll *mongo.Collection
}
func (x *ClientConfig) Set(ctx context.Context, userID string, config map[string]string) error {
if len(config) == 0 {
return nil
}
for key, value := range config {
filter := bson.M{"key": key, "user_id": userID}
update := bson.M{
"value": value,
}
err := mongoutil.UpdateOne(ctx, x.coll, filter, bson.M{"$set": update}, false, options.Update().SetUpsert(true))
if err != nil {
return err
}
}
return nil
}
func (x *ClientConfig) Get(ctx context.Context, userID string) (map[string]string, error) {
cs, err := mongoutil.Find[*model.ClientConfig](ctx, x.coll, bson.M{"user_id": userID})
if err != nil {
return nil, err
}
cm := make(map[string]string)
for _, config := range cs {
cm[config.Key] = config.Value
}
return cm, nil
}
func (x *ClientConfig) Del(ctx context.Context, userID string, keys []string) error {
if len(keys) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, x.coll, bson.M{"key": bson.M{"$in": keys}, "user_id": userID})
}
func (x *ClientConfig) GetPage(ctx context.Context, userID string, key string, pagination pagination.Pagination) (int64, []*model.ClientConfig, error) {
filter := bson.M{}
if userID != "" {
filter["user_id"] = userID
}
if key != "" {
filter["key"] = key
}
return mongoutil.FindPage[*model.ClientConfig](ctx, x.coll, filter, pagination)
}

View File

@@ -0,0 +1,325 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/protocol/constant"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
)
func NewConversationMongo(db *mongo.Database) (*ConversationMgo, error) {
coll := db.Collection(database.ConversationName)
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "owner_user_id", Value: 1},
{Key: "conversation_id", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "user_id", Value: 1},
},
Options: options.Index(),
},
})
if err != nil {
return nil, errs.Wrap(err)
}
version, err := NewVersionLog(db.Collection(database.ConversationVersionName))
if err != nil {
return nil, err
}
return &ConversationMgo{version: version, coll: coll}, nil
}
type ConversationMgo struct {
version database.VersionLog
coll *mongo.Collection
}
func (c *ConversationMgo) Create(ctx context.Context, conversations []*model.Conversation) (err error) {
return mongoutil.IncrVersion(func() error {
return mongoutil.InsertMany(ctx, c.coll, conversations)
}, func() error {
userConversation := make(map[string][]string)
for _, conversation := range conversations {
userConversation[conversation.OwnerUserID] = append(userConversation[conversation.OwnerUserID], conversation.ConversationID)
}
for userID, conversationIDs := range userConversation {
if err := c.version.IncrVersion(ctx, userID, conversationIDs, model.VersionStateInsert); err != nil {
return err
}
}
return nil
})
}
func (c *ConversationMgo) UpdateByMap(ctx context.Context, userIDs []string, conversationID string, args map[string]any) (int64, error) {
if len(args) == 0 || len(userIDs) == 0 {
return 0, nil
}
filter := bson.M{
"conversation_id": conversationID,
"owner_user_id": bson.M{"$in": userIDs},
}
var rows int64
err := mongoutil.IncrVersion(func() error {
res, err := mongoutil.UpdateMany(ctx, c.coll, filter, bson.M{"$set": args})
if err != nil {
return err
}
rows = res.ModifiedCount
return nil
}, func() error {
for _, userID := range userIDs {
if err := c.version.IncrVersion(ctx, userID, []string{conversationID}, model.VersionStateUpdate); err != nil {
return err
}
}
return nil
})
if err != nil {
return 0, err
}
return rows, nil
}
func (c *ConversationMgo) UpdateUserConversations(ctx context.Context, userID string, args map[string]any) ([]*model.Conversation, error) {
if len(args) == 0 {
return nil, nil
}
filter := bson.M{
"user_id": userID,
}
conversations, err := mongoutil.Find[*model.Conversation](ctx, c.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "owner_user_id": 1, "conversation_id": 1}))
if err != nil {
return nil, err
}
err = mongoutil.IncrVersion(func() error {
_, err := mongoutil.UpdateMany(ctx, c.coll, filter, bson.M{"$set": args})
if err != nil {
return err
}
return nil
}, func() error {
for _, conversation := range conversations {
if err := c.version.IncrVersion(ctx, conversation.OwnerUserID, []string{conversation.ConversationID}, model.VersionStateUpdate); err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, err
}
return conversations, nil
}
func (c *ConversationMgo) Update(ctx context.Context, conversation *model.Conversation) (err error) {
return mongoutil.IncrVersion(func() error {
return mongoutil.UpdateOne(ctx, c.coll, bson.M{"owner_user_id": conversation.OwnerUserID, "conversation_id": conversation.ConversationID}, bson.M{"$set": conversation}, true)
}, func() error {
return c.version.IncrVersion(ctx, conversation.OwnerUserID, []string{conversation.ConversationID}, model.VersionStateUpdate)
})
}
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) FindUserIDAllNotNotifyConversationID(ctx context.Context, userID string) ([]string, error) {
return mongoutil.Find[string](ctx, c.coll, bson.M{
"owner_user_id": userID,
"recv_msg_opt": constant.ReceiveNotNotifyMessage,
}, options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1}))
}
func (c *ConversationMgo) FindUserIDAllPinnedConversationID(ctx context.Context, userID string) ([]string, error) {
return mongoutil.Find[string](ctx, c.coll, bson.M{
"owner_user_id": userID,
"is_pinned": true,
}, 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}),
)
}
func (c *ConversationMgo) FindConversationUserVersion(ctx context.Context, userID string, version uint, limit int) (*model.VersionLog, error) {
return c.version.FindChangeLog(ctx, userID, version, limit)
}
func (c *ConversationMgo) FindRandConversation(ctx context.Context, ts int64, limit int) ([]*model.Conversation, error) {
pipeline := []bson.M{
{
"$match": bson.M{
"is_msg_destruct": true,
"msg_destruct_time": bson.M{"$ne": 0},
},
},
{
"$addFields": bson.M{
"next_msg_destruct_timestamp": bson.M{
"$add": []any{
bson.M{
"$toLong": "$latest_msg_destruct_time",
},
bson.M{
"$multiply": []any{
"$msg_destruct_time",
1000, // convert to milliseconds
},
},
},
},
},
},
{
"$match": bson.M{
"next_msg_destruct_timestamp": bson.M{"$lt": ts},
},
},
{
"$sample": bson.M{
"size": limit,
},
},
}
return mongoutil.Aggregate[*model.Conversation](ctx, c.coll, pipeline)
}
func (c *ConversationMgo) DeleteUsersConversations(ctx context.Context, userID string, conversationIDs []string) error {
if len(conversationIDs) == 0 {
return nil
}
filter := bson.M{
"owner_user_id": userID,
"conversation_id": bson.M{"$in": conversationIDs},
}
return mongoutil.IncrVersion(func() error {
return mongoutil.DeleteMany(ctx, c.coll, filter)
}, func() error {
return c.version.IncrVersion(ctx, userID, conversationIDs, model.VersionStateDelete)
})
}

View File

@@ -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 "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"

View File

@@ -0,0 +1,271 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"go.mongodb.org/mongo-driver/bson/primitive"
"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
owner database.VersionLog
}
// NewFriendMongo creates a new instance of FriendMgo with the provided MongoDB database.
func NewFriendMongo(db *mongo.Database) (database.Friend, error) {
coll := db.Collection(database.FriendName)
_, 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
}
owner, err := NewVersionLog(db.Collection(database.FriendVersionName))
if err != nil {
return nil, err
}
return &FriendMgo{coll: coll, owner: owner}, nil
}
func (f *FriendMgo) friendSort() any {
return bson.D{{"is_pinned", -1}, {"_id", 1}}
}
// Create inserts multiple friend records.
func (f *FriendMgo) Create(ctx context.Context, friends []*model.Friend) error {
for i, friend := range friends {
if friend.ID.IsZero() {
friends[i].ID = primitive.NewObjectID()
}
if friend.CreateTime.IsZero() {
friends[i].CreateTime = time.Now()
}
}
return mongoutil.IncrVersion(func() error {
return mongoutil.InsertMany(ctx, f.coll, friends)
}, func() error {
mp := make(map[string][]string)
for _, friend := range friends {
mp[friend.OwnerUserID] = append(mp[friend.OwnerUserID], friend.FriendUserID)
}
for ownerUserID, friendUserIDs := range mp {
if err := f.owner.IncrVersion(ctx, ownerUserID, friendUserIDs, model.VersionStateInsert); err != nil {
return err
}
}
return nil
})
}
// 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.IncrVersion(func() error {
return mongoutil.DeleteOne(ctx, f.coll, filter)
}, func() error {
return f.owner.IncrVersion(ctx, ownerUserID, friendUserIDs, model.VersionStateDelete)
})
}
// 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]any) error {
if len(args) == 0 {
return nil
}
filter := bson.M{
"owner_user_id": ownerUserID,
"friend_user_id": friendUserID,
}
return mongoutil.IncrVersion(func() error {
return mongoutil.UpdateOne(ctx, f.coll, filter, bson.M{"$set": args}, true)
}, func() error {
var friendUserIDs []string
if f.IsUpdateIsPinned(args) {
friendUserIDs = []string{model.VersionSortChangeID, friendUserID}
} else {
friendUserIDs = []string{friendUserID}
}
return f.owner.IncrVersion(ctx, ownerUserID, friendUserIDs, model.VersionStateUpdate)
})
}
// 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})
}
func (f *FriendMgo) fillTime(friends ...*model.Friend) {
for i, friend := range friends {
if friend.CreateTime.IsZero() {
friends[i].CreateTime = friend.ID.Timestamp()
}
}
}
func (f *FriendMgo) findOne(ctx context.Context, filter any) (*model.Friend, error) {
friend, err := mongoutil.FindOne[*model.Friend](ctx, f.coll, filter)
if err != nil {
return nil, err
}
f.fillTime(friend)
return friend, nil
}
func (f *FriendMgo) find(ctx context.Context, filter any) ([]*model.Friend, error) {
friends, err := mongoutil.Find[*model.Friend](ctx, f.coll, filter)
if err != nil {
return nil, err
}
f.fillTime(friends...)
return friends, nil
}
func (f *FriendMgo) findPage(ctx context.Context, filter any, pagination pagination.Pagination, opts ...*options.FindOptions) (int64, []*model.Friend, error) {
return mongoutil.FindPage[*model.Friend](ctx, f.coll, filter, pagination, opts...)
}
// 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 f.findOne(ctx, 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 f.find(ctx, 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 f.find(ctx, 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 f.find(ctx, 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}
opt := options.Find().SetSort(f.friendSort())
return f.findPage(ctx, filter, pagination, opt)
}
func (f *FriendMgo) FindOwnerFriendUserIds(ctx context.Context, ownerUserID string, limit int) ([]string, error) {
filter := bson.M{"owner_user_id": ownerUserID}
opt := options.Find().SetProjection(bson.M{"_id": 0, "friend_user_id": 1}).SetSort(f.friendSort()).SetLimit(int64(limit))
return mongoutil.Find[string](ctx, f.coll, filter, opt)
}
// 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}
opt := options.Find().SetSort(f.friendSort())
return f.findPage(ctx, filter, pagination, opt)
}
// 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}).SetSort(f.friendSort()))
}
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 || len(val) == 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}
return mongoutil.IncrVersion(func() error {
return mongoutil.Ignore(mongoutil.UpdateMany(ctx, f.coll, filter, update))
}, func() error {
var userIDs []string
if f.IsUpdateIsPinned(val) {
userIDs = append([]string{model.VersionSortChangeID}, friendUserIDs...)
} else {
userIDs = friendUserIDs
}
return f.owner.IncrVersion(ctx, ownerUserID, userIDs, model.VersionStateUpdate)
})
}
func (f *FriendMgo) FindIncrVersion(ctx context.Context, ownerUserID string, version uint, limit int) (*model.VersionLog, error) {
return f.owner.FindChangeLog(ctx, ownerUserID, version, limit)
}
func (f *FriendMgo) FindFriendUserID(ctx context.Context, friendUserID string) ([]string, error) {
filter := bson.M{
"friend_user_id": friendUserID,
}
return mongoutil.Find[string](ctx, f.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "owner_user_id": 1}).SetSort(f.friendSort()))
}
func (f *FriendMgo) IncrVersion(ctx context.Context, ownerUserID string, friendUserIDs []string, state int32) error {
return f.owner.IncrVersion(ctx, ownerUserID, friendUserIDs, state)
}
func (f *FriendMgo) IsUpdateIsPinned(data map[string]any) bool {
if data == nil {
return false
}
_, ok := data["is_pinned"]
return ok
}

View File

@@ -0,0 +1,143 @@
// 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"
"time"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
)
func NewFriendRequestMongo(db *mongo.Database) (database.FriendRequest, error) {
coll := db.Collection(database.FriendRequestName)
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "from_user_id", Value: 1},
{Key: "to_user_id", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "create_time", Value: -1},
},
},
})
if err != nil {
return nil, err
}
return &FriendRequestMgo{coll: coll}, nil
}
type FriendRequestMgo struct {
coll *mongo.Collection
}
func (f *FriendRequestMgo) sort() any {
return bson.D{{Key: "create_time", Value: -1}}
}
func (f *FriendRequestMgo) FindToUserID(ctx context.Context, toUserID string, handleResults []int, pagination pagination.Pagination) (total int64, friendRequests []*model.FriendRequest, err error) {
filter := bson.M{"to_user_id": toUserID}
if len(handleResults) > 0 {
filter["handle_result"] = bson.M{"$in": handleResults}
}
return mongoutil.FindPage[*model.FriendRequest](ctx, f.coll, filter, pagination, options.Find().SetSort(f.sort()))
}
func (f *FriendRequestMgo) FindFromUserID(ctx context.Context, fromUserID string, handleResults []int, pagination pagination.Pagination) (total int64, friendRequests []*model.FriendRequest, err error) {
filter := bson.M{"from_user_id": fromUserID}
if len(handleResults) > 0 {
filter["handle_result"] = bson.M{"$in": handleResults}
}
return mongoutil.FindPage[*model.FriendRequest](ctx, f.coll, filter, pagination, options.Find().SetSort(f.sort()))
}
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)
}
func (f *FriendRequestMgo) GetUnhandledCount(ctx context.Context, userID string, ts int64) (int64, error) {
filter := bson.M{"to_user_id": userID, "handle_result": 0}
if ts != 0 {
filter["create_time"] = bson.M{"$gt": time.UnixMilli(ts)}
}
return mongoutil.Count(ctx, f.coll, filter)
}

View File

@@ -0,0 +1,162 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/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(database.GroupName)
_, 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) sortGroup() any {
return bson.D{{"group_name", 1}, {"create_time", 1}}
}
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: "create_time", 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
}
func (g *GroupMgo) FindJoinSortGroupID(ctx context.Context, groupIDs []string) ([]string, error) {
if len(groupIDs) < 2 {
return groupIDs, nil
}
filter := bson.M{
"group_id": bson.M{"$in": groupIDs},
"status": bson.M{"$ne": constant.GroupStatusDismissed},
}
opt := options.Find().SetSort(g.sortGroup()).SetProjection(bson.M{"_id": 0, "group_id": 1})
return mongoutil.Find[string](ctx, g.coll, filter, opt)
}
func (g *GroupMgo) SearchJoin(ctx context.Context, groupIDs []string, keyword string, pagination pagination.Pagination) (int64, []*model.Group, error) {
if len(groupIDs) == 0 {
return 0, nil, nil
}
filter := bson.M{
"group_id": bson.M{"$in": groupIDs},
"status": bson.M{"$ne": constant.GroupStatusDismissed},
}
if keyword != "" {
filter["group_name"] = bson.M{"$regex": keyword}
}
// Define the sorting options
opts := options.Find().SetSort(g.sortGroup())
// Perform the search with pagination and sorting
return mongoutil.FindPage[*model.Group](ctx, g.coll, filter, pagination, opts)
}

View File

@@ -0,0 +1,282 @@
// 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"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/log"
"git.imall.cloud/openim/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(database.GroupMemberName)
_, 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)
}
member, err := NewVersionLog(db.Collection(database.GroupMemberVersionName))
if err != nil {
return nil, err
}
join, err := NewVersionLog(db.Collection(database.GroupJoinVersionName))
if err != nil {
return nil, err
}
return &GroupMemberMgo{coll: coll, member: member, join: join}, nil
}
type GroupMemberMgo struct {
coll *mongo.Collection
member database.VersionLog
join database.VersionLog
}
func (g *GroupMemberMgo) memberSort() any {
return bson.D{{Key: "role_level", Value: -1}, {Key: "create_time", Value: 1}}
}
func (g *GroupMemberMgo) Create(ctx context.Context, groupMembers []*model.GroupMember) (err error) {
return mongoutil.IncrVersion(func() error {
return mongoutil.InsertMany(ctx, g.coll, groupMembers)
}, func() error {
gms := make(map[string][]string)
for _, member := range groupMembers {
gms[member.GroupID] = append(gms[member.GroupID], member.UserID)
}
for groupID, userIDs := range gms {
if err := g.member.IncrVersion(ctx, groupID, userIDs, model.VersionStateInsert); err != nil {
return err
}
}
return nil
}, func() error {
gms := make(map[string][]string)
for _, member := range groupMembers {
gms[member.UserID] = append(gms[member.UserID], member.GroupID)
}
for userID, groupIDs := range gms {
if err := g.join.IncrVersion(ctx, userID, groupIDs, model.VersionStateInsert); err != nil {
return err
}
}
return nil
})
}
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.IncrVersion(func() error {
return mongoutil.DeleteMany(ctx, g.coll, filter)
}, func() error {
if len(userIDs) == 0 {
return g.member.Delete(ctx, groupID)
} else {
return g.member.IncrVersion(ctx, groupID, userIDs, model.VersionStateDelete)
}
}, func() error {
for _, userID := range userIDs {
if err := g.join.IncrVersion(ctx, userID, []string{groupID}, model.VersionStateDelete); err != nil {
return err
}
}
return nil
})
}
func (g *GroupMemberMgo) UpdateRoleLevel(ctx context.Context, groupID string, userID string, roleLevel int32) error {
return mongoutil.IncrVersion(func() error {
return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID},
bson.M{"$set": bson.M{"role_level": roleLevel}}, true)
}, func() error {
return g.member.IncrVersion(ctx, groupID, []string{model.VersionSortChangeID, userID}, model.VersionStateUpdate)
})
}
func (g *GroupMemberMgo) UpdateUserRoleLevels(ctx context.Context, groupID string, firstUserID string, firstUserRoleLevel int32, secondUserID string, secondUserRoleLevel int32) error {
return mongoutil.IncrVersion(func() error {
if err := mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": firstUserID},
bson.M{"$set": bson.M{"role_level": firstUserRoleLevel}}, true); err != nil {
return err
}
if err := mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": secondUserID},
bson.M{"$set": bson.M{"role_level": secondUserRoleLevel}}, true); err != nil {
return err
}
return nil
}, func() error {
return g.member.IncrVersion(ctx, groupID, []string{model.VersionSortChangeID, firstUserID, secondUserID}, model.VersionStateUpdate)
})
}
func (g *GroupMemberMgo) Update(ctx context.Context, groupID string, userID string, data map[string]any) (err error) {
if len(data) == 0 {
return nil
}
return mongoutil.IncrVersion(func() error {
return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}, bson.M{"$set": data}, true)
}, func() error {
var userIDs []string
if g.IsUpdateRoleLevel(data) {
userIDs = []string{model.VersionSortChangeID, userID}
} else {
userIDs = []string{userID}
}
return g.member.IncrVersion(ctx, groupID, userIDs, model.VersionStateUpdate)
})
}
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}).SetSort(g.memberSort()))
}
func (g *GroupMemberMgo) Find(ctx context.Context, groupID string, userIDs []string) ([]*model.GroupMember, error) {
filter := bson.M{"group_id": groupID}
if len(userIDs) > 0 {
filter["user_id"] = bson.M{"$in": userIDs}
}
return mongoutil.Find[*model.GroupMember](ctx, g.coll, filter)
}
func (g *GroupMemberMgo) FindInGroup(ctx context.Context, userID string, groupIDs []string) ([]*model.GroupMember, error) {
filter := bson.M{"user_id": userID}
if len(groupIDs) > 0 {
filter["group_id"] = bson.M{"$in": groupIDs}
}
return mongoutil.Find[*model.GroupMember](ctx, g.coll, filter)
}
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) (int64, []*model.GroupMember, error) {
// 支持通过昵称、user_id账号搜索
// 使用 $or 条件,匹配昵称或 user_id
filter := bson.M{
"group_id": groupID,
"$or": []bson.M{
{"nickname": bson.M{"$regex": keyword, "$options": "i"}}, // 昵称模糊匹配,不区分大小写
{"user_id": bson.M{"$regex": keyword, "$options": "i"}}, // user_id账号模糊匹配不区分大小写
},
}
return mongoutil.FindPage[*model.GroupMember](ctx, g.coll, filter, pagination, options.Find().SetSort(g.memberSort()))
}
// SearchMemberByFields 支持通过多个独立字段搜索群成员昵称、账号userID、手机号
// nickname: 用户昵称(群内昵称)
// userID: 用户账号user_id
// phone: 手机号(如果群成员表中有相关字段,或通过 Ex 字段存储)
func (g *GroupMemberMgo) SearchMemberByFields(ctx context.Context, groupID string, nickname, userID, phone string, pagination pagination.Pagination) (int64, []*model.GroupMember, error) {
filter := bson.M{"group_id": groupID}
// 构建多个搜索条件,使用 $and 确保所有提供的条件都满足
conditions := []bson.M{}
if nickname != "" {
conditions = append(conditions, bson.M{"nickname": bson.M{"$regex": nickname, "$options": "i"}})
}
if userID != "" {
conditions = append(conditions, bson.M{"user_id": bson.M{"$regex": userID, "$options": "i"}})
}
if phone != "" {
// 手机号可能存储在 Ex 字段中,使用正则表达式匹配
// 如果 Ex 字段是 JSON 格式,可能需要更复杂的查询
conditions = append(conditions, bson.M{"ex": bson.M{"$regex": phone, "$options": "i"}})
}
// 如果有搜索条件,添加到 filter 中
if len(conditions) > 0 {
filter["$and"] = conditions
}
return mongoutil.FindPage[*model.GroupMember](ctx, g.coll, filter, pagination, options.Find().SetSort(g.memberSort()))
}
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}).SetSort(g.memberSort()))
}
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
}
func (g *GroupMemberMgo) JoinGroupIncrVersion(ctx context.Context, userID string, groupIDs []string, state int32) error {
return g.join.IncrVersion(ctx, userID, groupIDs, state)
}
func (g *GroupMemberMgo) MemberGroupIncrVersion(ctx context.Context, groupID string, userIDs []string, state int32) error {
return g.member.IncrVersion(ctx, groupID, userIDs, state)
}
func (g *GroupMemberMgo) FindMemberIncrVersion(ctx context.Context, groupID string, version uint, limit int) (*model.VersionLog, error) {
log.ZDebug(ctx, "find member incr version", "groupID", groupID, "version", version)
return g.member.FindChangeLog(ctx, groupID, version, limit)
}
func (g *GroupMemberMgo) BatchFindMemberIncrVersion(ctx context.Context, groupIDs []string, versions []uint, limits []int) ([]*model.VersionLog, error) {
log.ZDebug(ctx, "Batch find member incr version", "groupIDs", groupIDs, "versions", versions)
return g.member.BatchFindChangeLog(ctx, groupIDs, versions, limits)
}
func (g *GroupMemberMgo) FindJoinIncrVersion(ctx context.Context, userID string, version uint, limit int) (*model.VersionLog, error) {
log.ZDebug(ctx, "find join incr version", "userID", userID, "version", version)
return g.join.FindChangeLog(ctx, userID, version, limit)
}

View File

@@ -0,0 +1,115 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/utils/datautil"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
)
func NewGroupRequestMgo(db *mongo.Database) (database.GroupRequest, error) {
coll := db.Collection(database.GroupRequestName)
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "group_id", Value: 1},
{Key: "user_id", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "req_time", Value: -1},
},
},
})
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) sort() any {
return bson.D{{Key: "req_time", Value: -1}}
}
func (g *GroupRequestMgo) Page(ctx context.Context, userID string, groupIDs []string, handleResults []int, pagination pagination.Pagination) (total int64, groups []*model.GroupRequest, err error) {
filter := bson.M{"user_id": userID}
if len(groupIDs) > 0 {
filter["group_id"] = bson.M{"$in": datautil.Distinct(groupIDs)}
}
if len(handleResults) > 0 {
filter["handle_result"] = bson.M{"$in": handleResults}
}
return mongoutil.FindPage[*model.GroupRequest](ctx, g.coll, filter, pagination, options.Find().SetSort(g.sort()))
}
func (g *GroupRequestMgo) PageGroup(ctx context.Context, groupIDs []string, handleResults []int, pagination pagination.Pagination) (total int64, groups []*model.GroupRequest, err error) {
if len(groupIDs) == 0 {
return 0, nil, nil
}
filter := bson.M{"group_id": bson.M{"$in": groupIDs}}
if len(handleResults) > 0 {
filter["handle_result"] = bson.M{"$in": handleResults}
}
return mongoutil.FindPage[*model.GroupRequest](ctx, g.coll, filter, pagination, options.Find().SetSort(g.sort()))
}
func (g *GroupRequestMgo) GetUnhandledCount(ctx context.Context, groupIDs []string, ts int64) (int64, error) {
if len(groupIDs) == 0 {
return 0, nil
}
filter := bson.M{"group_id": bson.M{"$in": groupIDs}, "handle_result": 0}
if ts != 0 {
filter["req_time"] = bson.M{"$gt": time.UnixMilli(ts)}
}
return mongoutil.Count(ctx, g.coll, filter)
}

View File

@@ -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
}

View File

@@ -0,0 +1,85 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/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 NewLogMongo(db *mongo.Database) (database.Log, error) {
coll := db.Collection(database.LogName)
_, 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})
}

View File

@@ -0,0 +1,183 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/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"
)
// MeetingMgo implements Meeting using MongoDB as the storage backend.
type MeetingMgo struct {
coll *mongo.Collection
}
// NewMeetingMongo creates a new instance of MeetingMgo with the provided MongoDB database.
func NewMeetingMongo(db *mongo.Database) (database.Meeting, error) {
coll := db.Collection(database.MeetingName)
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{{Key: "meeting_id", Value: 1}},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{{Key: "creator_user_id", Value: 1}},
},
{
Keys: bson.D{{Key: "status", Value: 1}},
},
{
Keys: bson.D{{Key: "scheduled_time", Value: 1}},
},
{
Keys: bson.D{{Key: "create_time", Value: -1}},
},
{
Keys: bson.D{{Key: "update_time", Value: -1}},
},
{
Keys: bson.D{{Key: "subject", Value: "text"}, {Key: "description", Value: "text"}},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &MeetingMgo{coll: coll}, nil
}
// Create creates a new meeting record.
func (m *MeetingMgo) Create(ctx context.Context, meeting *model.Meeting) error {
if meeting.CreateTime.IsZero() {
meeting.CreateTime = time.Now()
}
if meeting.UpdateTime.IsZero() {
meeting.UpdateTime = time.Now()
}
return mongoutil.InsertOne(ctx, m.coll, meeting)
}
// Take retrieves a meeting by meeting ID. Returns an error if not found.
func (m *MeetingMgo) Take(ctx context.Context, meetingID string) (*model.Meeting, error) {
return mongoutil.FindOne[*model.Meeting](ctx, m.coll, bson.M{"meeting_id": meetingID})
}
// Update updates meeting information.
func (m *MeetingMgo) Update(ctx context.Context, meetingID string, data map[string]any) error {
data["update_time"] = time.Now()
update := bson.M{"$set": data}
return mongoutil.UpdateOne(ctx, m.coll, bson.M{"meeting_id": meetingID}, update, false)
}
// UpdateStatus updates the status of a meeting.
func (m *MeetingMgo) UpdateStatus(ctx context.Context, meetingID string, status int32) error {
return m.Update(ctx, meetingID, map[string]any{"status": status})
}
// Find finds meetings by meeting IDs.
func (m *MeetingMgo) Find(ctx context.Context, meetingIDs []string) ([]*model.Meeting, error) {
if len(meetingIDs) == 0 {
return []*model.Meeting{}, nil
}
filter := bson.M{"meeting_id": bson.M{"$in": meetingIDs}}
return mongoutil.Find[*model.Meeting](ctx, m.coll, filter)
}
// FindByCreator finds meetings created by a specific user.
func (m *MeetingMgo) FindByCreator(ctx context.Context, creatorUserID string, pagination pagination.Pagination) (total int64, meetings []*model.Meeting, err error) {
filter := bson.M{"creator_user_id": creatorUserID}
return mongoutil.FindPage[*model.Meeting](ctx, m.coll, filter, pagination, &options.FindOptions{
Sort: bson.D{{Key: "scheduled_time", Value: -1}},
})
}
// FindAll finds all meetings with pagination.
func (m *MeetingMgo) FindAll(ctx context.Context, pagination pagination.Pagination) (total int64, meetings []*model.Meeting, err error) {
return mongoutil.FindPage[*model.Meeting](ctx, m.coll, bson.M{}, pagination, &options.FindOptions{
Sort: bson.D{{Key: "scheduled_time", Value: -1}},
})
}
// Search searches meetings by keyword (subject, description).
func (m *MeetingMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, meetings []*model.Meeting, err error) {
filter := bson.M{
"$or": []bson.M{
{"subject": bson.M{"$regex": keyword, "$options": "i"}},
{"description": bson.M{"$regex": keyword, "$options": "i"}},
},
}
return mongoutil.FindPage[*model.Meeting](ctx, m.coll, filter, pagination, &options.FindOptions{
Sort: bson.D{{Key: "scheduled_time", Value: -1}},
})
}
// FindByStatus finds meetings by status.
func (m *MeetingMgo) FindByStatus(ctx context.Context, status int32, pagination pagination.Pagination) (total int64, meetings []*model.Meeting, err error) {
filter := bson.M{"status": status}
return mongoutil.FindPage[*model.Meeting](ctx, m.coll, filter, pagination, &options.FindOptions{
Sort: bson.D{{Key: "scheduled_time", Value: -1}},
})
}
// FindByScheduledTimeRange finds meetings within a scheduled time range.
func (m *MeetingMgo) FindByScheduledTimeRange(ctx context.Context, startTime, endTime int64, pagination pagination.Pagination) (total int64, meetings []*model.Meeting, err error) {
filter := bson.M{
"scheduled_time": bson.M{
"$gte": time.UnixMilli(startTime),
"$lte": time.UnixMilli(endTime),
},
}
return mongoutil.FindPage[*model.Meeting](ctx, m.coll, filter, pagination, &options.FindOptions{
Sort: bson.D{{Key: "scheduled_time", Value: 1}},
})
}
// FindFinishedMeetingsBefore finds finished meetings that ended before the specified time.
// A meeting is considered finished if its status is 3 (Finished) and its end time (scheduledTime + duration) is before beforeTime.
// Only returns meetings with a non-empty group_id to avoid processing meetings that have already been handled.
func (m *MeetingMgo) FindFinishedMeetingsBefore(ctx context.Context, beforeTime time.Time) ([]*model.Meeting, error) {
// 查询状态为3已结束且group_id不为空的会议
// 结束时间 = scheduledTime + duration分钟
// 需要计算scheduledTime + duration * 60秒 <= beforeTime
filter := bson.M{
"status": 3, // 已结束
"group_id": bson.M{"$ne": ""}, // 只查询group_id不为空的会议避免重复处理已清空groupID的会议
"$expr": bson.M{
"$lte": []interface{}{
bson.M{
"$add": []interface{}{
"$scheduled_time",
bson.M{"$multiply": []interface{}{"$duration", int64(60)}}, // duration是分钟转换为秒
},
},
beforeTime,
},
},
}
return mongoutil.Find[*model.Meeting](ctx, m.coll, filter)
}
// Delete deletes a meeting by meeting ID.
func (m *MeetingMgo) Delete(ctx context.Context, meetingID string) error {
return mongoutil.DeleteOne(ctx, m.coll, bson.M{"meeting_id": meetingID})
}

View File

@@ -0,0 +1,110 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/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"
)
// MeetingCheckInMgo implements MeetingCheckIn using MongoDB as the storage backend.
type MeetingCheckInMgo struct {
coll *mongo.Collection
}
// NewMeetingCheckInMongo creates a new instance of MeetingCheckInMgo with the provided MongoDB database.
func NewMeetingCheckInMongo(db *mongo.Database) (database.MeetingCheckIn, error) {
coll := db.Collection(database.MeetingCheckInName)
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{{Key: "check_in_id", Value: 1}},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{{Key: "meeting_id", Value: 1}, {Key: "user_id", Value: 1}},
Options: options.Index().SetUnique(true), // 一个用户在一个会议中只能签到一次
},
{
Keys: bson.D{{Key: "meeting_id", Value: 1}, {Key: "check_in_time", Value: -1}},
},
{
Keys: bson.D{{Key: "user_id", Value: 1}, {Key: "check_in_time", Value: -1}},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &MeetingCheckInMgo{coll: coll}, nil
}
// Create creates a new meeting check-in record.
func (m *MeetingCheckInMgo) Create(ctx context.Context, checkIn *model.MeetingCheckIn) error {
if checkIn.CreateTime.IsZero() {
checkIn.CreateTime = time.Now()
}
if checkIn.CheckInTime.IsZero() {
checkIn.CheckInTime = time.Now()
}
return mongoutil.InsertOne(ctx, m.coll, checkIn)
}
// Take retrieves a check-in by check-in ID. Returns an error if not found.
func (m *MeetingCheckInMgo) Take(ctx context.Context, checkInID string) (*model.MeetingCheckIn, error) {
return mongoutil.FindOne[*model.MeetingCheckIn](ctx, m.coll, bson.M{"check_in_id": checkInID})
}
// FindByMeetingID finds all check-ins for a meeting with pagination.
func (m *MeetingCheckInMgo) FindByMeetingID(ctx context.Context, meetingID string, pagination pagination.Pagination) (total int64, checkIns []*model.MeetingCheckIn, err error) {
filter := bson.M{"meeting_id": meetingID}
return mongoutil.FindPage[*model.MeetingCheckIn](ctx, m.coll, filter, pagination, &options.FindOptions{
Sort: bson.D{{Key: "check_in_time", Value: -1}},
})
}
// FindByUserAndMeetingID finds if a user has checked in for a specific meeting.
func (m *MeetingCheckInMgo) FindByUserAndMeetingID(ctx context.Context, userID, meetingID string) (*model.MeetingCheckIn, error) {
return mongoutil.FindOne[*model.MeetingCheckIn](ctx, m.coll, bson.M{
"user_id": userID,
"meeting_id": meetingID,
})
}
// CountByMeetingID counts the number of check-ins for a meeting.
func (m *MeetingCheckInMgo) CountByMeetingID(ctx context.Context, meetingID string) (int64, error) {
return mongoutil.Count(ctx, m.coll, bson.M{"meeting_id": meetingID})
}
// FindByUser finds all check-ins by a user with pagination.
func (m *MeetingCheckInMgo) FindByUser(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, checkIns []*model.MeetingCheckIn, err error) {
filter := bson.M{"user_id": userID}
return mongoutil.FindPage[*model.MeetingCheckIn](ctx, m.coll, filter, pagination, &options.FindOptions{
Sort: bson.D{{Key: "check_in_time", Value: -1}},
})
}
// Delete deletes a check-in by check-in ID.
func (m *MeetingCheckInMgo) Delete(ctx context.Context, checkInID string) error {
return mongoutil.DeleteOne(ctx, m.coll, bson.M{"check_in_id": checkInID})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,178 @@
package mgo
import (
"context"
"math"
"math/rand"
"strconv"
"testing"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/protocol/msg"
"git.imall.cloud/openim/protocol/sdkws"
"github.com/openimsdk/tools/db/mongoutil"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func TestName1(t *testing.T) {
//ctx, cancel := context.WithTimeout(context.Background(), time.Second*300)
//defer cancel()
//cli := Result(mongo.Connect(ctx, options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.66:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second)))
//
//v := &MsgMgo{
// coll: cli.Database("openim_v3").Collection("msg3"),
//}
//
//req := &msg.SearchMessageReq{
// //RecvID: "3187706596",
// //SendID: "7009965934",
// ContentType: 101,
// //SendTime: "2024-05-06",
// //SessionType: 3,
// Pagination: &sdkws.RequestPagination{
// PageNumber: 1,
// ShowNumber: 10,
// },
//}
//total, res, err := v.SearchMessage(ctx, req)
//if err != nil {
// panic(err)
//}
//
//for i, re := range res {
// t.Logf("%d => %d | %+v", i+1, re.Msg.Seq, re.Msg.Content)
//}
//
//t.Log(total)
//
//msg, err := NewMsgMongo(cli.Database("openim_v3"))
//if err != nil {
// panic(err)
//}
//res, err := msg.GetBeforeMsg(ctx, time.Now().UnixMilli(), []string{"1:0"}, 1000)
//if err != nil {
// panic(err)
//}
//t.Log(len(res))
}
func TestName10(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
cli := Result(mongo.Connect(ctx, options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.48:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second)))
v := &MsgMgo{
coll: cli.Database("openim_v3").Collection("msg3"),
}
opt := options.Find().SetLimit(1000)
res, err := mongoutil.Find[model.MsgDocModel](ctx, v.coll, bson.M{}, opt)
if err != nil {
panic(err)
}
ctx = context.Background()
for i := 0; i < 100000; i++ {
for j := range res {
res[j].DocID = strconv.FormatUint(rand.Uint64(), 10) + ":0"
}
if err := mongoutil.InsertMany(ctx, v.coll, res); err != nil {
panic(err)
}
t.Log("====>", time.Now(), i)
}
}
func TestName3(t *testing.T) {
t.Log(uint64(math.MaxUint64))
t.Log(int64(math.MaxInt64))
t.Log(int64(math.MinInt64))
}
func TestName4(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*300)
defer cancel()
cli := Result(mongo.Connect(ctx, options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.135:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second)))
msg, err := NewMsgMongo(cli.Database("openim_v3"))
if err != nil {
panic(err)
}
ts := time.Now().Add(-time.Hour * 24 * 5).UnixMilli()
t.Log(ts)
res, err := msg.GetLastMessageSeqByTime(ctx, "sg_1523453548", ts)
if err != nil {
panic(err)
}
t.Log(res)
}
func TestName5(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*300)
defer cancel()
cli := Result(mongo.Connect(ctx, options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.135:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second)))
tmp, err := NewMsgMongo(cli.Database("openim_v3"))
if err != nil {
panic(err)
}
msg := tmp.(*MsgMgo)
ts := time.Now().Add(-time.Hour * 24 * 5).UnixMilli()
t.Log(ts)
var seqs []int64
for i := 1; i < 256; i++ {
seqs = append(seqs, int64(i))
}
res, err := msg.FindSeqs(ctx, "si_4924054191_9511766539", seqs)
if err != nil {
panic(err)
}
t.Log(res)
}
//func TestName6(t *testing.T) {
// ctx, cancel := context.WithTimeout(context.Background(), time.Second*300)
// defer cancel()
// cli := Result(mongo.Connect(ctx, options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.135:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second)))
//
// tmp, err := NewMsgMongo(cli.Database("openim_v3"))
// if err != nil {
// panic(err)
// }
// msg := tmp.(*MsgMgo)
// seq, sendTime, err := msg.findBeforeSendTime(ctx, "si_4924054191_9511766539", 1144)
// if err != nil {
// panic(err)
// }
// t.Log(seq, sendTime)
//}
func TestSearchMessage(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*300)
defer cancel()
cli := Result(mongo.Connect(ctx, options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.135:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second)))
msgMongo, err := NewMsgMongo(cli.Database("openim_v3"))
if err != nil {
panic(err)
}
ts := time.Now().Add(-time.Hour * 24 * 5).UnixMilli()
t.Log(ts)
req := &msg.SearchMessageReq{
//SendID: "yjz",
//RecvID: "aibot",
Pagination: &sdkws.RequestPagination{
PageNumber: 1,
ShowNumber: 20,
},
}
count, resp, err := msgMongo.SearchMessage(ctx, req)
if err != nil {
panic(err)
}
t.Log(resp, count)
}

View File

@@ -0,0 +1,126 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/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(database.ObjectName)
// Create index for name
_, 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)
}
// Create index for create_time
_, err = coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "create_time", Value: 1},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
// Create index for key
_, err = coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "key", Value: 1},
},
})
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 {
if len(name) == 0 {
return nil
}
return mongoutil.DeleteOne(ctx, o.coll, bson.M{"engine": engine, "name": bson.M{"$in": name}})
}
func (o *S3Mongo) FindExpirationObject(ctx context.Context, engine string, expiration time.Time, needDelType []string, count int64) ([]*model.Object, error) {
opt := options.Find()
if count > 0 {
opt.SetLimit(count)
}
return mongoutil.Find[*model.Object](ctx, o.coll, bson.M{
"engine": engine,
"create_time": bson.M{"$lt": expiration},
"group": bson.M{"$in": needDelType},
}, opt)
}
func (o *S3Mongo) GetKeyCount(ctx context.Context, engine string, key string) (int64, error) {
return mongoutil.Count(ctx, o.coll, bson.M{"engine": engine, "key": key})
}
func (o *S3Mongo) GetEngineCount(ctx context.Context, engine string) (int64, error) {
return mongoutil.Count(ctx, o.coll, bson.M{"engine": engine})
}
func (o *S3Mongo) GetEngineInfo(ctx context.Context, engine string, limit int, skip int) ([]*model.Object, error) {
return mongoutil.Find[*model.Object](ctx, o.coll, bson.M{"engine": engine}, options.Find().SetLimit(int64(limit)).SetSkip(int64(skip)))
}
func (o *S3Mongo) UpdateEngine(ctx context.Context, oldEngine, oldName string, newEngine string) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"engine": oldEngine, "name": oldName}, bson.M{"$set": bson.M{"engine": newEngine}}, false)
}

View File

@@ -0,0 +1,234 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/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"
)
// RedPacketMgo implements RedPacket using MongoDB as the storage backend.
type RedPacketMgo struct {
coll *mongo.Collection
}
// NewRedPacketMongo creates a new instance of RedPacketMgo with the provided MongoDB database.
func NewRedPacketMongo(db *mongo.Database) (database.RedPacket, error) {
coll := db.Collection(database.RedPacketName)
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{{Key: "red_packet_id", Value: 1}},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{{Key: "send_user_id", Value: 1}, {Key: "create_time", Value: -1}},
},
{
Keys: bson.D{{Key: "group_id", Value: 1}, {Key: "create_time", Value: -1}},
},
{
Keys: bson.D{{Key: "expire_time", Value: 1}},
},
})
if err != nil {
return nil, err
}
return &RedPacketMgo{coll: coll}, nil
}
// Create creates a new red packet record.
func (r *RedPacketMgo) Create(ctx context.Context, redPacket *model.RedPacket) error {
if redPacket.CreateTime.IsZero() {
redPacket.CreateTime = time.Now()
}
return mongoutil.InsertOne(ctx, r.coll, redPacket)
}
// Take retrieves a red packet by ID. Returns an error if not found.
func (r *RedPacketMgo) Take(ctx context.Context, redPacketID string) (*model.RedPacket, error) {
return mongoutil.FindOne[*model.RedPacket](ctx, r.coll, bson.M{"red_packet_id": redPacketID})
}
// UpdateStatus updates the status of a red packet.
func (r *RedPacketMgo) UpdateStatus(ctx context.Context, redPacketID string, status int32) error {
return mongoutil.UpdateOne(ctx, r.coll, bson.M{"red_packet_id": redPacketID}, bson.M{"$set": bson.M{"status": status}}, false)
}
// UpdateRemain updates the remain amount and count of a red packet.
func (r *RedPacketMgo) UpdateRemain(ctx context.Context, redPacketID string, remainAmount int64, remainCount int32) error {
update := bson.M{
"$set": bson.M{
"remain_amount": remainAmount,
"remain_count": remainCount,
},
}
// If remain count is 0, update status to finished
if remainCount == 0 {
update["$set"].(bson.M)["status"] = model.RedPacketStatusFinished
}
return mongoutil.UpdateOne(ctx, r.coll, bson.M{"red_packet_id": redPacketID}, update, false)
}
// DecreaseRemainAtomic 原子性地减少红包剩余数量和金额(防止并发问题)
// 只有在 remain_count > 0 且状态为 Active 时才会更新
func (r *RedPacketMgo) DecreaseRemainAtomic(ctx context.Context, redPacketID string, amount int64) (*model.RedPacket, error) {
// 过滤条件红包ID匹配、剩余数量>0、状态为Active
filter := bson.M{
"red_packet_id": redPacketID,
"remain_count": bson.M{"$gt": 0},
"status": model.RedPacketStatusActive,
}
// 使用 $inc 原子性地减少剩余数量和金额
update := bson.M{
"$inc": bson.M{
"remain_amount": -amount,
"remain_count": -1,
},
}
// 使用 findOneAndUpdate 返回更新后的文档
opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
var updatedRedPacket model.RedPacket
err := r.coll.FindOneAndUpdate(ctx, filter, update, opts).Decode(&updatedRedPacket)
if err != nil {
if err == mongo.ErrNoDocuments {
// 红包不存在、已领完或状态不正确
return nil, errs.ErrArgs.WrapMsg("red packet not available (already finished or expired)")
}
return nil, err
}
// 如果剩余数量为0更新状态为已完成
if updatedRedPacket.RemainCount == 0 {
statusUpdate := bson.M{"$set": bson.M{"status": model.RedPacketStatusFinished}}
_ = mongoutil.UpdateOne(ctx, r.coll, bson.M{"red_packet_id": redPacketID}, statusUpdate, false)
updatedRedPacket.Status = model.RedPacketStatusFinished
}
return &updatedRedPacket, nil
}
// FindExpiredRedPackets finds red packets that have expired.
func (r *RedPacketMgo) FindExpiredRedPackets(ctx context.Context, beforeTime time.Time) ([]*model.RedPacket, error) {
filter := bson.M{
"expire_time": bson.M{"$lt": beforeTime},
"status": model.RedPacketStatusActive,
}
return mongoutil.Find[*model.RedPacket](ctx, r.coll, filter)
}
// FindRedPacketsByUser finds red packets sent by a user with pagination.
func (r *RedPacketMgo) FindRedPacketsByUser(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, redPackets []*model.RedPacket, err error) {
filter := bson.M{"send_user_id": userID}
return mongoutil.FindPage[*model.RedPacket](ctx, r.coll, filter, pagination, &options.FindOptions{
Sort: bson.D{{Key: "create_time", Value: -1}},
})
}
// FindRedPacketsByGroup finds red packets in a group with pagination.
func (r *RedPacketMgo) FindRedPacketsByGroup(ctx context.Context, groupID string, pagination pagination.Pagination) (total int64, redPackets []*model.RedPacket, err error) {
filter := bson.M{"group_id": groupID}
return mongoutil.FindPage[*model.RedPacket](ctx, r.coll, filter, pagination, &options.FindOptions{
Sort: bson.D{{Key: "create_time", Value: -1}},
})
}
// FindAllRedPackets finds all red packets with pagination.
func (r *RedPacketMgo) FindAllRedPackets(ctx context.Context, pagination pagination.Pagination) (total int64, redPackets []*model.RedPacket, err error) {
return mongoutil.FindPage[*model.RedPacket](ctx, r.coll, bson.M{}, pagination, &options.FindOptions{
Sort: bson.D{{Key: "create_time", Value: -1}},
})
}
// RedPacketReceiveMgo implements RedPacketReceive using MongoDB as the storage backend.
type RedPacketReceiveMgo struct {
coll *mongo.Collection
}
// NewRedPacketReceiveMongo creates a new instance of RedPacketReceiveMgo with the provided MongoDB database.
func NewRedPacketReceiveMongo(db *mongo.Database) (database.RedPacketReceive, error) {
coll := db.Collection(database.RedPacketReceiveName)
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{{Key: "receive_id", Value: 1}},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{{Key: "red_packet_id", Value: 1}, {Key: "receive_time", Value: -1}},
},
{
Keys: bson.D{{Key: "receive_user_id", Value: 1}, {Key: "red_packet_id", Value: 1}},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{{Key: "receive_user_id", Value: 1}, {Key: "receive_time", Value: -1}},
},
})
if err != nil {
return nil, err
}
return &RedPacketReceiveMgo{coll: coll}, nil
}
// Create creates a new red packet receive record.
func (r *RedPacketReceiveMgo) Create(ctx context.Context, receive *model.RedPacketReceive) error {
if receive.ReceiveTime.IsZero() {
receive.ReceiveTime = time.Now()
}
return mongoutil.InsertOne(ctx, r.coll, receive)
}
// Take retrieves a receive record by ID. Returns an error if not found.
func (r *RedPacketReceiveMgo) Take(ctx context.Context, receiveID string) (*model.RedPacketReceive, error) {
return mongoutil.FindOne[*model.RedPacketReceive](ctx, r.coll, bson.M{"receive_id": receiveID})
}
// FindByRedPacketID finds all receive records for a red packet.
func (r *RedPacketReceiveMgo) FindByRedPacketID(ctx context.Context, redPacketID string) ([]*model.RedPacketReceive, error) {
return mongoutil.Find[*model.RedPacketReceive](ctx, r.coll, bson.M{"red_packet_id": redPacketID}, &options.FindOptions{
Sort: bson.D{{Key: "receive_time", Value: 1}},
})
}
// FindByUserAndRedPacketID finds if a user has received a specific red packet.
func (r *RedPacketReceiveMgo) FindByUserAndRedPacketID(ctx context.Context, userID, redPacketID string) (*model.RedPacketReceive, error) {
return mongoutil.FindOne[*model.RedPacketReceive](ctx, r.coll, bson.M{
"receive_user_id": userID,
"red_packet_id": redPacketID,
})
}
// FindByUser finds all red packets received by a user with pagination.
func (r *RedPacketReceiveMgo) FindByUser(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, receives []*model.RedPacketReceive, err error) {
filter := bson.M{"receive_user_id": userID}
return mongoutil.FindPage[*model.RedPacketReceive](ctx, r.coll, filter, pagination, &options.FindOptions{
Sort: bson.D{{Key: "receive_time", Value: -1}},
})
}
// DeleteByReceiveID deletes a receive record by receive ID (for cleanup on failure).
func (r *RedPacketReceiveMgo) DeleteByReceiveID(ctx context.Context, receiveID string) error {
return mongoutil.DeleteOne(ctx, r.coll, bson.M{"receive_id": receiveID})
}

View File

@@ -0,0 +1,104 @@
package mgo
import (
"context"
"errors"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/mongoutil"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewSeqConversationMongo(db *mongo.Database) (database.SeqConversation, error) {
coll := db.Collection(database.SeqConversationName)
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "conversation_id", Value: 1},
},
})
if err != nil {
return nil, err
}
return &seqConversationMongo{coll: coll}, nil
}
type seqConversationMongo struct {
coll *mongo.Collection
}
func (s *seqConversationMongo) setSeq(ctx context.Context, conversationID string, seq int64, field string) error {
filter := map[string]any{
"conversation_id": conversationID,
}
insert := bson.M{
"conversation_id": conversationID,
"min_seq": 0,
"max_seq": 0,
}
delete(insert, field)
update := map[string]any{
"$set": bson.M{
field: seq,
},
"$setOnInsert": insert,
}
opt := options.Update().SetUpsert(true)
return mongoutil.UpdateOne(ctx, s.coll, filter, update, false, opt)
}
func (s *seqConversationMongo) Malloc(ctx context.Context, conversationID string, size int64) (int64, error) {
if size < 0 {
return 0, errors.New("size must be greater than 0")
}
if size == 0 {
return s.GetMaxSeq(ctx, conversationID)
}
filter := map[string]any{"conversation_id": conversationID}
update := map[string]any{
"$inc": map[string]any{"max_seq": size},
"$set": map[string]any{"min_seq": int64(0)},
}
opt := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After).SetProjection(map[string]any{"_id": 0, "max_seq": 1})
lastSeq, err := mongoutil.FindOneAndUpdate[int64](ctx, s.coll, filter, update, opt)
if err != nil {
return 0, err
}
return lastSeq - size, nil
}
func (s *seqConversationMongo) SetMaxSeq(ctx context.Context, conversationID string, seq int64) error {
return s.setSeq(ctx, conversationID, seq, "max_seq")
}
func (s *seqConversationMongo) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
seq, err := mongoutil.FindOne[int64](ctx, s.coll, bson.M{"conversation_id": conversationID}, options.FindOne().SetProjection(map[string]any{"_id": 0, "max_seq": 1}))
if err == nil {
return seq, nil
} else if IsNotFound(err) {
return 0, nil
} else {
return 0, err
}
}
func (s *seqConversationMongo) GetMinSeq(ctx context.Context, conversationID string) (int64, error) {
seq, err := mongoutil.FindOne[int64](ctx, s.coll, bson.M{"conversation_id": conversationID}, options.FindOne().SetProjection(map[string]any{"_id": 0, "min_seq": 1}))
if err == nil {
return seq, nil
} else if IsNotFound(err) {
return 0, nil
} else {
return 0, err
}
}
func (s *seqConversationMongo) SetMinSeq(ctx context.Context, conversationID string, seq int64) error {
return s.setSeq(ctx, conversationID, seq, "min_seq")
}
func (s *seqConversationMongo) GetConversation(ctx context.Context, conversationID string) (*model.SeqConversation, error) {
return mongoutil.FindOne[*model.SeqConversation](ctx, s.coll, bson.M{"conversation_id": conversationID})
}

View File

@@ -0,0 +1,43 @@
package mgo
import (
"context"
"testing"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func Result[V any](val V, err error) V {
if err != nil {
panic(err)
}
return val
}
func Mongodb() *mongo.Database {
return Result(
mongo.Connect(context.Background(),
options.Client().
ApplyURI("mongodb://openIM:openIM123@172.16.8.135:37017/openim_v3?maxPoolSize=100").
SetConnectTimeout(5*time.Second)),
).Database("openim_v3")
}
func TestUserSeq(t *testing.T) {
uSeq := Result(NewSeqUserMongo(Mongodb())).(*seqUserMongo)
t.Log(uSeq.SetUserMinSeq(context.Background(), "1000", "2000", 4))
}
func TestConversationSeq(t *testing.T) {
cSeq := Result(NewSeqConversationMongo(Mongodb())).(*seqConversationMongo)
t.Log(cSeq.SetMaxSeq(context.Background(), "2000", 10))
t.Log(cSeq.Malloc(context.Background(), "2000", 10))
t.Log(cSeq.GetMaxSeq(context.Background(), "2000"))
}
func TestUserGetUserReadSeqs(t *testing.T) {
uSeq := Result(NewSeqUserMongo(Mongodb())).(*seqUserMongo)
t.Log(uSeq.GetUserReadSeqs(context.Background(), "2110910952", []string{"sg_345762580", "2000", "3000"}))
}

View File

@@ -0,0 +1,127 @@
package mgo
import (
"context"
"errors"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/mongoutil"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewSeqUserMongo(db *mongo.Database) (database.SeqUser, error) {
coll := db.Collection(database.SeqUserName)
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "conversation_id", Value: 1},
},
})
if err != nil {
return nil, err
}
return &seqUserMongo{coll: coll}, nil
}
type seqUserMongo struct {
coll *mongo.Collection
}
func (s *seqUserMongo) setSeq(ctx context.Context, conversationID string, userID string, seq int64, field string) error {
filter := map[string]any{
"user_id": userID,
"conversation_id": conversationID,
}
insert := bson.M{
"user_id": userID,
"conversation_id": conversationID,
"min_seq": 0,
"max_seq": 0,
"read_seq": 0,
}
delete(insert, field)
update := map[string]any{
"$set": bson.M{
field: seq,
},
"$setOnInsert": insert,
}
opt := options.Update().SetUpsert(true)
return mongoutil.UpdateOne(ctx, s.coll, filter, update, false, opt)
}
func (s *seqUserMongo) getSeq(ctx context.Context, conversationID string, userID string, failed string) (int64, error) {
filter := map[string]any{
"user_id": userID,
"conversation_id": conversationID,
}
opt := options.FindOne().SetProjection(bson.M{"_id": 0, failed: 1})
seq, err := mongoutil.FindOne[int64](ctx, s.coll, filter, opt)
if err == nil {
return seq, nil
} else if errors.Is(err, mongo.ErrNoDocuments) {
return 0, nil
} else {
return 0, err
}
}
func (s *seqUserMongo) GetUserMaxSeq(ctx context.Context, conversationID string, userID string) (int64, error) {
return s.getSeq(ctx, conversationID, userID, "max_seq")
}
func (s *seqUserMongo) SetUserMaxSeq(ctx context.Context, conversationID string, userID string, seq int64) error {
return s.setSeq(ctx, conversationID, userID, seq, "max_seq")
}
func (s *seqUserMongo) GetUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error) {
return s.getSeq(ctx, conversationID, userID, "min_seq")
}
func (s *seqUserMongo) SetUserMinSeq(ctx context.Context, conversationID string, userID string, seq int64) error {
return s.setSeq(ctx, conversationID, userID, seq, "min_seq")
}
func (s *seqUserMongo) GetUserReadSeq(ctx context.Context, conversationID string, userID string) (int64, error) {
return s.getSeq(ctx, conversationID, userID, "read_seq")
}
func (s *seqUserMongo) notFoundSet0(seq map[string]int64, conversationIDs []string) {
for _, conversationID := range conversationIDs {
if _, ok := seq[conversationID]; !ok {
seq[conversationID] = 0
}
}
}
func (s *seqUserMongo) GetUserReadSeqs(ctx context.Context, userID string, conversationID []string) (map[string]int64, error) {
if len(conversationID) == 0 {
return map[string]int64{}, nil
}
filter := bson.M{"user_id": userID, "conversation_id": bson.M{"$in": conversationID}}
opt := options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1, "read_seq": 1})
seqs, err := mongoutil.Find[*model.SeqUser](ctx, s.coll, filter, opt)
if err != nil {
return nil, err
}
res := make(map[string]int64)
for _, seq := range seqs {
res[seq.ConversationID] = seq.ReadSeq
}
s.notFoundSet0(res, conversationID)
return res, nil
}
func (s *seqUserMongo) SetUserReadSeq(ctx context.Context, conversationID string, userID string, seq int64) error {
dbSeq, err := s.GetUserReadSeq(ctx, conversationID, userID)
if err != nil {
return err
}
if dbSeq > seq {
return nil
}
return s.setSeq(ctx, conversationID, userID, seq, "read_seq")
}

View File

@@ -0,0 +1,99 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/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"
)
// SystemConfigMgo implements SystemConfig using MongoDB as the storage backend.
type SystemConfigMgo struct {
coll *mongo.Collection
}
// NewSystemConfigMongo creates a new instance of SystemConfigMgo with the provided MongoDB database.
func NewSystemConfigMongo(db *mongo.Database) (database.SystemConfig, error) {
coll := db.Collection(database.SystemConfigName)
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{{Key: "key", Value: 1}},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{{Key: "enabled", Value: 1}},
},
{
Keys: bson.D{{Key: "create_time", Value: -1}},
},
})
if err != nil {
return nil, err
}
return &SystemConfigMgo{coll: coll}, nil
}
// Create creates a new system config record.
func (s *SystemConfigMgo) Create(ctx context.Context, config *model.SystemConfig) error {
config.CreateTime = time.Now()
config.UpdateTime = time.Now()
return mongoutil.InsertOne(ctx, s.coll, config)
}
// Take retrieves a system config by key. Returns an error if not found.
func (s *SystemConfigMgo) Take(ctx context.Context, key string) (*model.SystemConfig, error) {
return mongoutil.FindOne[*model.SystemConfig](ctx, s.coll, bson.M{"key": key})
}
// Update updates system config information.
func (s *SystemConfigMgo) Update(ctx context.Context, key string, data map[string]any) error {
data["update_time"] = time.Now()
return mongoutil.UpdateOne(ctx, s.coll, bson.M{"key": key}, bson.M{"$set": data}, true)
}
// Find finds system configs by keys.
func (s *SystemConfigMgo) Find(ctx context.Context, keys []string) ([]*model.SystemConfig, error) {
return mongoutil.Find[*model.SystemConfig](ctx, s.coll, bson.M{"key": bson.M{"$in": keys}})
}
// FindEnabled finds all enabled system configs.
func (s *SystemConfigMgo) FindEnabled(ctx context.Context) ([]*model.SystemConfig, error) {
return mongoutil.Find[*model.SystemConfig](ctx, s.coll, bson.M{"enabled": true})
}
// FindByKey finds a system config by key (returns nil if not found, no error).
func (s *SystemConfigMgo) FindByKey(ctx context.Context, key string) (*model.SystemConfig, error) {
config, err := mongoutil.FindOne[*model.SystemConfig](ctx, s.coll, bson.M{"key": key})
if err != nil {
if errs.ErrRecordNotFound.Is(err) || err == mongo.ErrNoDocuments {
return nil, nil
}
return nil, err
}
return config, nil
}
// Delete deletes a system config by key.
func (s *SystemConfigMgo) Delete(ctx context.Context, key string) error {
return mongoutil.DeleteOne(ctx, s.coll, bson.M{"key": key})
}

View File

@@ -0,0 +1,699 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/protocol/user"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"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(database.UserName)
_, 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) {
query := bson.M{"user_id": bson.M{"$in": userIDs}}
log.ZInfo(ctx, "UserMongo Find query", "collection", u.coll.Name(), "query", query)
users, err = mongoutil.Find[*model.User](ctx, u.coll, query)
log.ZInfo(ctx, "UserMongo Find result", "userCount", len(users), "err", err)
return users, err
}
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) TakeGTEAppManagerLevel(ctx context.Context, level int64) (user []*model.User, err error) {
return mongoutil.Find[*model.User](ctx, u.coll, bson.M{"app_manger_level": bson.M{"$gte": 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}))
}
// SearchUsersByFields 根据多个字段搜索用户accountuserID、phone、nickname
// 返回匹配的用户ID列表
// 使用 MongoDB $lookup 实现连表查询attribute 集合和 user 集合
func (u *UserMgo) SearchUsersByFields(ctx context.Context, account, phone, nickname string) (userIDs []string, err error) {
log.ZInfo(ctx, "SearchUsersByFields START", "account", account, "phone", phone, "nickname", nickname)
// 获取 attribute 集合
attributeColl := u.coll.Database().Collection("attribute")
log.ZInfo(ctx, "SearchUsersByFields collections", "attributeCollection", "attribute", "userCollection", u.coll.Name(), "database", u.coll.Database().Name())
// 构建聚合管道,使用 $lookup 实现连表查询
pipeline := bson.A{}
// 第一步:从 attribute 集合开始,构建匹配条件
attributeMatch := bson.M{}
attributeOrConditions := []bson.M{}
if account != "" {
attributeOrConditions = append(attributeOrConditions, bson.M{"account": bson.M{"$regex": account, "$options": "i"}})
log.ZInfo(ctx, "SearchUsersByFields add account condition", "account", account)
}
if phone != "" {
// phone_number 在 attribute 集合中搜索(注意:字段名是 phone_number不是 phone
attributeOrConditions = append(attributeOrConditions, bson.M{"phone_number": bson.M{"$regex": phone, "$options": "i"}})
log.ZInfo(ctx, "SearchUsersByFields add phone condition", "phone", phone, "field", "phone_number")
}
// 判断查询策略:如果有 account 或 phone从 attribute 开始;如果只有 nickname从 user 开始
hasAttributeSearch := len(attributeOrConditions) > 0
hasUserSearch := nickname != ""
var startCollection *mongo.Collection // 记录起始集合
if hasAttributeSearch {
// 从 attribute 集合开始查询
startCollection = attributeColl
// 先直接查询 attribute 集合,看看实际的数据结构
type FullAttributeDoc map[string]interface{}
allDocs, err := mongoutil.Find[*FullAttributeDoc](ctx, attributeColl, bson.M{}, options.Find().SetLimit(5).SetProjection(bson.M{"_id": 0}))
if err == nil && len(allDocs) > 0 {
log.ZInfo(ctx, "SearchUsersByFields attribute sample documents (all fields)", "sampleCount", len(allDocs), "samples", allDocs)
}
// 尝试精确匹配手机号(使用正确的字段名 phone_number
exactPhoneMatch := bson.M{"phone_number": phone}
exactDocs, err := mongoutil.Find[*FullAttributeDoc](ctx, attributeColl, exactPhoneMatch, options.Find().SetLimit(5).SetProjection(bson.M{"_id": 0}))
if err == nil {
log.ZInfo(ctx, "SearchUsersByFields exact phone_number match", "phone", phone, "matchCount", len(exactDocs), "docs", exactDocs)
}
// 尝试正则匹配(不区分大小写,使用正确的字段名 phone_number
regexPhoneMatch := bson.M{"phone_number": bson.M{"$regex": phone, "$options": "i"}}
regexDocs, err := mongoutil.Find[*FullAttributeDoc](ctx, attributeColl, regexPhoneMatch, options.Find().SetLimit(5).SetProjection(bson.M{"_id": 0}))
if err == nil {
log.ZInfo(ctx, "SearchUsersByFields regex phone_number match", "phone", phone, "matchCount", len(regexDocs), "docs", regexDocs)
}
// 尝试查询包含 phone_number 字段的所有记录
hasPhoneFieldMatch := bson.M{"phone_number": bson.M{"$exists": true, "$ne": ""}}
hasPhoneDocs, err := mongoutil.Find[*FullAttributeDoc](ctx, attributeColl, hasPhoneFieldMatch, options.Find().SetLimit(5).SetProjection(bson.M{"_id": 0}))
if err == nil {
log.ZInfo(ctx, "SearchUsersByFields documents with phone_number field", "matchCount", len(hasPhoneDocs), "docs", hasPhoneDocs)
}
attributeMatch["$or"] = attributeOrConditions
pipeline = append(pipeline, bson.M{"$match": attributeMatch})
log.ZInfo(ctx, "SearchUsersByFields attribute match stage", "match", attributeMatch)
// 使用 $lookup 关联 user 集合
lookupStage := bson.M{
"$lookup": bson.M{
"from": u.coll.Name(), // user 集合名称
"localField": "user_id", // attribute 集合的字段
"foreignField": "user_id", // user 集合的字段
"as": "userInfo", // 关联后的字段名
},
}
pipeline = append(pipeline, lookupStage)
log.ZInfo(ctx, "SearchUsersByFields add lookup stage", "from", u.coll.Name(), "localField", "user_id", "foreignField", "user_id")
// 展开 userInfo 数组
pipeline = append(pipeline, bson.M{"$unwind": bson.M{
"path": "$userInfo",
"preserveNullAndEmptyArrays": true,
}})
// 如果有 nickname 条件,需要匹配 user 集合的 nickname
if hasUserSearch {
userMatch := bson.M{
"$or": []bson.M{
{"userInfo.nickname": bson.M{"$regex": nickname, "$options": "i"}},
{"userInfo": bson.M{"$exists": false}}, // 如果没有关联到 user也保留
},
}
pipeline = append(pipeline, bson.M{"$match": userMatch})
log.ZInfo(ctx, "SearchUsersByFields add nickname match", "nickname", nickname)
}
// 从 attribute 集合开始user_id 字段在根级别
pipeline = append(pipeline, bson.M{
"$project": bson.M{
"_id": 0,
"user_id": 1,
},
})
} else if hasUserSearch {
// 只有 nickname 条件,从 user 集合开始查询
startCollection = u.coll
userMatch := bson.M{"nickname": bson.M{"$regex": nickname, "$options": "i"}}
pipeline = append(pipeline, bson.M{"$match": userMatch})
log.ZInfo(ctx, "SearchUsersByFields user match stage", "match", userMatch)
// 从 user 集合开始user_id 字段在根级别
pipeline = append(pipeline, bson.M{
"$project": bson.M{
"_id": 0,
"user_id": 1,
},
})
} else {
// 没有任何搜索条件,返回空
log.ZInfo(ctx, "SearchUsersByFields no search conditions", "returning empty")
return []string{}, nil
}
// 第五步:去重
pipeline = append(pipeline, bson.M{
"$group": bson.M{
"_id": "$user_id",
"user_id": bson.M{"$first": "$user_id"},
},
})
// 第六步:只返回 user_id
pipeline = append(pipeline, bson.M{
"$project": bson.M{
"_id": 0,
"user_id": 1,
},
})
log.ZInfo(ctx, "SearchUsersByFields pipeline", "pipeline", pipeline, "startCollection", startCollection.Name())
// 执行聚合查询
type ResultDoc struct {
UserID string `bson:"user_id"`
}
results, err := mongoutil.Aggregate[*ResultDoc](ctx, startCollection, pipeline)
if err != nil {
log.ZError(ctx, "SearchUsersByFields Aggregate failed", err, "pipeline", pipeline)
return nil, err
}
log.ZInfo(ctx, "SearchUsersByFields Aggregate result", "resultCount", len(results), "results", results)
// 提取 user_id 列表
userIDs = make([]string, 0, len(results))
for _, result := range results {
if result.UserID != "" {
userIDs = append(userIDs, result.UserID)
}
}
log.ZInfo(ctx, "SearchUsersByFields FINAL result", "totalUserIDs", len(userIDs), "userIDs", userIDs)
return userIDs, nil
}
// 旧版本的实现(保留作为备份)
func (u *UserMgo) SearchUsersByFields_old(ctx context.Context, account, phone, nickname string) (userIDs []string, err error) {
log.ZInfo(ctx, "SearchUsersByFields START", "account", account, "phone", phone, "nickname", nickname)
userIDMap := make(map[string]bool) // 用于去重
// 获取 attribute 集合
attributeColl := u.coll.Database().Collection("attribute")
log.ZInfo(ctx, "SearchUsersByFields attribute collection", "collectionName", "attribute", "database", u.coll.Database().Name())
// 从 attribute 集合查询 account 和 phone
if account != "" || phone != "" {
attributeFilter := bson.M{}
attributeConditions := []bson.M{}
if account != "" {
// account 在 attribute 集合中搜索
attributeConditions = append(attributeConditions, bson.M{"account": bson.M{"$regex": account, "$options": "i"}})
log.ZInfo(ctx, "SearchUsersByFields add account condition", "account", account)
}
if phone != "" {
// phone 在 attribute 集合中搜索
attributeConditions = append(attributeConditions, bson.M{"phone": bson.M{"$regex": phone, "$options": "i"}})
log.ZInfo(ctx, "SearchUsersByFields add phone condition", "phone", phone)
}
if len(attributeConditions) > 0 {
attributeFilter["$or"] = attributeConditions
log.ZInfo(ctx, "SearchUsersByFields query attribute", "filter", attributeFilter, "account", account, "phone", phone, "conditionsCount", len(attributeConditions))
// attribute 集合的结构包含user_id, account, phone 等字段
type AttributeDoc struct {
UserID string `bson:"user_id"`
}
// 先尝试查询,看看集合是否存在数据
count, err := mongoutil.Count(ctx, attributeColl, bson.M{})
log.ZInfo(ctx, "SearchUsersByFields attribute collection total count", "count", count, "err", err)
// 尝试查询多条记录看看结构,特别是包含 phone 数据的记录
type SampleDoc struct {
UserID string `bson:"user_id"`
Account string `bson:"account"`
Phone string `bson:"phone"`
}
// 查询所有记录,看看实际的数据结构
samples, err := mongoutil.Find[*SampleDoc](ctx, attributeColl, bson.M{}, options.Find().SetLimit(10).SetProjection(bson.M{"_id": 0, "user_id": 1, "account": 1, "phone": 1}))
if err == nil && len(samples) > 0 {
log.ZInfo(ctx, "SearchUsersByFields attribute sample documents", "sampleCount", len(samples), "samples", samples)
// 尝试查询包含 phone 字段不为空的记录
phoneFilter := bson.M{"phone": bson.M{"$exists": true, "$ne": ""}}
phoneSamples, err := mongoutil.Find[*SampleDoc](ctx, attributeColl, phoneFilter, options.Find().SetLimit(5).SetProjection(bson.M{"_id": 0, "user_id": 1, "account": 1, "phone": 1}))
if err == nil {
log.ZInfo(ctx, "SearchUsersByFields attribute documents with phone", "phoneSampleCount", len(phoneSamples), "phoneSamples", phoneSamples)
} else {
log.ZWarn(ctx, "SearchUsersByFields cannot find documents with phone", err)
}
// 尝试查询所有字段,看看实际的数据结构(只查一条)
type FullDoc map[string]interface{}
fullSample, err := mongoutil.FindOne[*FullDoc](ctx, attributeColl, bson.M{}, options.FindOne().SetProjection(bson.M{"_id": 0}))
if err == nil && fullSample != nil {
log.ZInfo(ctx, "SearchUsersByFields attribute full document structure", "fullSample", fullSample)
}
} else {
log.ZWarn(ctx, "SearchUsersByFields cannot get samples from attribute", err, "sampleCount", len(samples))
}
// 尝试精确匹配手机号,看看是否有数据
exactPhoneFilter := bson.M{"phone": phone}
exactCount, err := mongoutil.Count(ctx, attributeColl, exactPhoneFilter)
log.ZInfo(ctx, "SearchUsersByFields exact phone match count", "phone", phone, "count", exactCount, "err", err)
attributeDocs, err := mongoutil.Find[*AttributeDoc](ctx, attributeColl, attributeFilter, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}))
if err != nil {
log.ZError(ctx, "SearchUsersByFields Find failed in attribute collection", err, "filter", attributeFilter)
return nil, err
}
log.ZInfo(ctx, "SearchUsersByFields Find result from attribute", "userCount", len(attributeDocs), "userIDs", attributeDocs)
for i, doc := range attributeDocs {
log.ZDebug(ctx, "SearchUsersByFields processing attribute doc", "index", i, "userID", doc.UserID)
if doc.UserID != "" && !userIDMap[doc.UserID] {
userIDMap[doc.UserID] = true
log.ZDebug(ctx, "SearchUsersByFields added userID from attribute", "userID", doc.UserID)
}
}
}
}
// 从 user 集合查询 nickname
if nickname != "" {
userFilter := bson.M{"nickname": bson.M{"$regex": nickname, "$options": "i"}}
log.ZInfo(ctx, "SearchUsersByFields query user", "filter", userFilter, "nickname", nickname)
users, err := mongoutil.Find[*model.User](ctx, u.coll, userFilter, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}))
if err != nil {
log.ZError(ctx, "SearchUsersByFields Find failed in user collection", err, "filter", userFilter)
return nil, err
}
log.ZInfo(ctx, "SearchUsersByFields Find result from user", "userCount", len(users))
for i, user := range users {
log.ZDebug(ctx, "SearchUsersByFields processing user doc", "index", i, "userID", user.UserID)
if user.UserID != "" && !userIDMap[user.UserID] {
userIDMap[user.UserID] = true
log.ZDebug(ctx, "SearchUsersByFields added userID from user", "userID", user.UserID)
}
}
}
// 将 map 转换为 slice
userIDs = make([]string, 0, len(userIDMap))
for userID := range userIDMap {
userIDs = append(userIDs, userID)
}
log.ZInfo(ctx, "SearchUsersByFields FINAL result", "totalUserIDs", len(userIDs), "userIDs", userIDs)
return userIDs, nil
}
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)
// when err is not nil, result might be nil
if err != nil {
return errs.Wrap(err)
}
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
}
func (u *UserMgo) SortQuery(ctx context.Context, userIDName map[string]string, asc bool) ([]*model.User, error) {
if len(userIDName) == 0 {
return nil, nil
}
userIDs := make([]string, 0, len(userIDName))
attached := make(map[string]string)
for userID, name := range userIDName {
userIDs = append(userIDs, userID)
if name == "" {
continue
}
attached[userID] = name
}
var sortValue int
if asc {
sortValue = 1
} else {
sortValue = -1
}
if len(attached) == 0 {
filter := bson.M{"user_id": bson.M{"$in": userIDs}}
opt := options.Find().SetSort(bson.M{"nickname": sortValue})
return mongoutil.Find[*model.User](ctx, u.coll, filter, opt)
}
pipeline := []bson.M{
{
"$match": bson.M{
"user_id": bson.M{"$in": userIDs},
},
},
{
"$addFields": bson.M{
"_query_sort_name": bson.M{
"$arrayElemAt": []any{
bson.M{
"$filter": bson.M{
"input": bson.M{
"$objectToArray": attached,
},
"as": "item",
"cond": bson.M{
"$eq": []any{"$$item.k", "$user_id"},
},
},
},
0,
},
},
},
},
{
"$addFields": bson.M{
"_query_sort_name": bson.M{
"$ifNull": []any{"$_query_sort_name.v", "$nickname"},
},
},
},
{
"$sort": bson.M{
"_query_sort_name": sortValue,
},
},
}
return mongoutil.Aggregate[*model.User](ctx, u.coll, pipeline)
}

View File

@@ -0,0 +1,304 @@
package mgo
import (
"context"
"errors"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/versionctx"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"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 NewVersionLog(coll *mongo.Collection) (database.VersionLog, error) {
lm := &VersionLogMgo{coll: coll}
if err := lm.initIndex(context.Background()); err != nil {
return nil, errs.WrapMsg(err, "init version log index failed", "coll", coll.Name())
}
return lm, nil
}
type VersionLogMgo struct {
coll *mongo.Collection
}
func (l *VersionLogMgo) initIndex(ctx context.Context) error {
_, err := l.coll.Indexes().CreateOne(ctx, mongo.IndexModel{
Keys: bson.M{
"d_id": 1,
},
Options: options.Index().SetUnique(true),
})
return err
}
func (l *VersionLogMgo) IncrVersion(ctx context.Context, dId string, eIds []string, state int32) error {
_, err := l.IncrVersionResult(ctx, dId, eIds, state)
return err
}
func (l *VersionLogMgo) IncrVersionResult(ctx context.Context, dId string, eIds []string, state int32) (*model.VersionLog, error) {
vl, err := l.incrVersionResult(ctx, dId, eIds, state)
if err != nil {
return nil, err
}
versionctx.GetVersionLog(ctx).Append(versionctx.Collection{
Name: l.coll.Name(),
Doc: vl,
})
return vl, nil
}
func (l *VersionLogMgo) incrVersionResult(ctx context.Context, dId string, eIds []string, state int32) (*model.VersionLog, error) {
if len(eIds) == 0 {
return nil, errs.ErrArgs.WrapMsg("elem id is empty", "dId", dId)
}
now := time.Now()
if res, err := l.writeLogBatch2(ctx, dId, eIds, state, now); err == nil {
return res, nil
} else if !errors.Is(err, mongo.ErrNoDocuments) {
return nil, err
}
if res, err := l.initDoc(ctx, dId, eIds, state, now); err == nil {
return res, nil
} else if !mongo.IsDuplicateKeyError(err) {
return nil, err
}
return l.writeLogBatch2(ctx, dId, eIds, state, now)
}
func (l *VersionLogMgo) initDoc(ctx context.Context, dId string, eIds []string, state int32, now time.Time) (*model.VersionLog, error) {
wl := model.VersionLogTable{
ID: primitive.NewObjectID(),
DID: dId,
Logs: make([]model.VersionLogElem, 0, len(eIds)),
Version: database.FirstVersion,
Deleted: database.DefaultDeleteVersion,
LastUpdate: now,
}
for _, eId := range eIds {
wl.Logs = append(wl.Logs, model.VersionLogElem{
EID: eId,
State: state,
Version: database.FirstVersion,
LastUpdate: now,
})
}
if _, err := l.coll.InsertOne(ctx, &wl); err != nil {
return nil, err
}
return wl.VersionLog(), nil
}
func (l *VersionLogMgo) writeLogBatch2(ctx context.Context, dId string, eIds []string, state int32, now time.Time) (*model.VersionLog, error) {
if eIds == nil {
eIds = []string{}
}
filter := bson.M{
"d_id": dId,
}
elems := make([]bson.M, 0, len(eIds))
for _, eId := range eIds {
elems = append(elems, bson.M{
"e_id": eId,
"version": "$version",
"state": state,
"last_update": now,
})
}
pipeline := []bson.M{
{
"$addFields": bson.M{
"delete_e_ids": eIds,
},
},
{
"$set": bson.M{
"version": bson.M{"$add": []any{"$version", 1}},
"last_update": now,
},
},
{
"$set": bson.M{
"logs": bson.M{
"$filter": bson.M{
"input": "$logs",
"as": "log",
"cond": bson.M{
"$not": bson.M{
"$in": []any{"$$log.e_id", "$delete_e_ids"},
},
},
},
},
},
},
{
"$set": bson.M{
"logs": bson.M{
"$concatArrays": []any{
"$logs",
elems,
},
},
},
},
{
"$unset": "delete_e_ids",
},
}
projection := bson.M{
"logs": 0,
}
opt := options.FindOneAndUpdate().SetUpsert(false).SetReturnDocument(options.After).SetProjection(projection)
res, err := mongoutil.FindOneAndUpdate[*model.VersionLog](ctx, l.coll, filter, pipeline, opt)
if err != nil {
return nil, err
}
res.Logs = make([]model.VersionLogElem, 0, len(eIds))
for _, id := range eIds {
res.Logs = append(res.Logs, model.VersionLogElem{
EID: id,
State: state,
Version: res.Version,
LastUpdate: res.LastUpdate,
})
}
return res, nil
}
func (l *VersionLogMgo) findDoc(ctx context.Context, dId string) (*model.VersionLog, error) {
vl, err := mongoutil.FindOne[*model.VersionLogTable](ctx, l.coll, bson.M{"d_id": dId}, options.FindOne().SetProjection(bson.M{"logs": 0}))
if err != nil {
return nil, err
}
return vl.VersionLog(), nil
}
func (l *VersionLogMgo) FindChangeLog(ctx context.Context, dId string, version uint, limit int) (*model.VersionLog, error) {
if wl, err := l.findChangeLog(ctx, dId, version, limit); err == nil {
return wl, nil
} else if !errors.Is(err, mongo.ErrNoDocuments) {
return nil, err
}
log.ZDebug(ctx, "init doc", "dId", dId)
if res, err := l.initDoc(ctx, dId, nil, 0, time.Now()); err == nil {
log.ZDebug(ctx, "init doc success", "dId", dId)
return res, nil
} else if mongo.IsDuplicateKeyError(err) {
return l.findChangeLog(ctx, dId, version, limit)
} else {
return nil, err
}
}
func (l *VersionLogMgo) BatchFindChangeLog(ctx context.Context, dIds []string, versions []uint, limits []int) (vLogs []*model.VersionLog, err error) {
for i := 0; i < len(dIds); i++ {
if vLog, err := l.findChangeLog(ctx, dIds[i], versions[i], limits[i]); err == nil {
vLogs = append(vLogs, vLog)
} else if !errors.Is(err, mongo.ErrNoDocuments) {
log.ZError(ctx, "findChangeLog error:", errs.Wrap(err))
}
log.ZDebug(ctx, "init doc", "dId", dIds[i])
if res, err := l.initDoc(ctx, dIds[i], nil, 0, time.Now()); err == nil {
log.ZDebug(ctx, "init doc success", "dId", dIds[i])
vLogs = append(vLogs, res)
} else if mongo.IsDuplicateKeyError(err) {
l.findChangeLog(ctx, dIds[i], versions[i], limits[i])
} else {
log.ZError(ctx, "init doc error:", errs.Wrap(err))
}
}
return vLogs, errs.Wrap(err)
}
func (l *VersionLogMgo) findChangeLog(ctx context.Context, dId string, version uint, limit int) (*model.VersionLog, error) {
if version == 0 && limit == 0 {
return l.findDoc(ctx, dId)
}
pipeline := []bson.M{
{
"$match": bson.M{
"d_id": dId,
},
},
{
"$addFields": bson.M{
"logs": bson.M{
"$cond": bson.M{
"if": bson.M{
"$or": []bson.M{
{"$lt": []any{"$version", version}},
{"$gte": []any{"$deleted", version}},
},
},
"then": []any{},
"else": "$logs",
},
},
},
},
{
"$addFields": bson.M{
"logs": bson.M{
"$filter": bson.M{
"input": "$logs",
"as": "l",
"cond": bson.M{
"$gt": []any{"$$l.version", version},
},
},
},
},
},
{
"$addFields": bson.M{
"log_len": bson.M{"$size": "$logs"},
},
},
{
"$addFields": bson.M{
"logs": bson.M{
"$cond": bson.M{
"if": bson.M{
"$gt": []any{"$log_len", limit},
},
"then": []any{},
"else": "$logs",
},
},
},
},
}
if limit <= 0 {
pipeline = pipeline[:len(pipeline)-1]
}
vl, err := mongoutil.Aggregate[*model.VersionLog](ctx, l.coll, pipeline)
if err != nil {
return nil, err
}
if len(vl) == 0 {
return nil, mongo.ErrNoDocuments
}
return vl[0], nil
}
func (l *VersionLogMgo) DeleteAfterUnchangedLog(ctx context.Context, deadline time.Time) error {
return mongoutil.DeleteMany(ctx, l.coll, bson.M{
"last_update": bson.M{
"$lt": deadline,
},
})
}
func (l *VersionLogMgo) Delete(ctx context.Context, dId string) error {
return mongoutil.DeleteOne(ctx, l.coll, bson.M{"d_id": dId})
}

View File

@@ -0,0 +1,40 @@
package mgo
import (
"context"
"testing"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
//func Result[V any](val V, err error) V {
// if err != nil {
// panic(err)
// }
// return val
//}
func Check(err error) {
if err != nil {
panic(err)
}
}
func TestName(t *testing.T) {
cli := Result(mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.48:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second)))
coll := cli.Database("openim_v3").Collection("version_test")
tmp, err := NewVersionLog(coll)
if err != nil {
panic(err)
}
vl := tmp.(*VersionLogMgo)
res, err := vl.incrVersionResult(context.Background(), "100", []string{"1000", "1001", "1003"}, model.VersionStateInsert)
if err != nil {
t.Log(err)
return
}
t.Logf("%+v", res)
}

View File

@@ -0,0 +1,231 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/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"
)
// WalletMgo implements Wallet using MongoDB as the storage backend.
type WalletMgo struct {
coll *mongo.Collection
}
// NewWalletMongo creates a new instance of WalletMgo with the provided MongoDB database.
func NewWalletMongo(db *mongo.Database) (database.Wallet, error) {
coll := db.Collection(database.WalletName)
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{{Key: "user_id", Value: 1}},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{{Key: "create_time", Value: -1}},
},
{
Keys: bson.D{{Key: "update_time", Value: -1}},
},
})
if err != nil {
return nil, err
}
return &WalletMgo{coll: coll}, nil
}
// Create creates a new wallet record.
func (w *WalletMgo) Create(ctx context.Context, wallet *model.Wallet) error {
if wallet.CreateTime.IsZero() {
wallet.CreateTime = time.Now()
}
if wallet.UpdateTime.IsZero() {
wallet.UpdateTime = time.Now()
}
if wallet.Version == 0 {
wallet.Version = 1
}
return mongoutil.InsertOne(ctx, w.coll, wallet)
}
// Take retrieves a wallet by user ID. Returns an error if not found.
func (w *WalletMgo) Take(ctx context.Context, userID string) (*model.Wallet, error) {
return mongoutil.FindOne[*model.Wallet](ctx, w.coll, bson.M{"user_id": userID})
}
// UpdateBalance updates the balance of a wallet.
func (w *WalletMgo) UpdateBalance(ctx context.Context, userID string, balance int64) error {
update := bson.M{
"$set": bson.M{
"balance": balance,
"update_time": time.Now(),
},
}
return mongoutil.UpdateOne(ctx, w.coll, bson.M{"user_id": userID}, update, false)
}
// UpdateBalanceByAmount updates the balance by adding/subtracting an amount.
func (w *WalletMgo) UpdateBalanceByAmount(ctx context.Context, userID string, amount int64) error {
update := bson.M{
"$inc": bson.M{
"balance": amount,
},
"$set": bson.M{
"update_time": time.Now(),
},
}
return mongoutil.UpdateOne(ctx, w.coll, bson.M{"user_id": userID}, update, false)
}
// UpdateBalanceWithVersion 使用版本号更新余额(防止并发覆盖)
func (w *WalletMgo) UpdateBalanceWithVersion(ctx context.Context, params *database.WalletUpdateParams) (*database.WalletUpdateResult, error) {
// 基于单文档原子操作,避免并发覆盖
if params.Amount < 0 {
return nil, errs.ErrArgs.WrapMsg("amount cannot be negative")
}
// 兼容旧数据(无 version 字段或 version=0
filter := bson.M{
"user_id": params.UserID,
"$or": []bson.M{
{"version": params.OldVersion},
{"version": bson.M{"$exists": false}},
{"version": 0},
},
}
// 默认更新:更新时间、版本号自增
update := bson.M{
"$set": bson.M{
"update_time": time.Now(),
},
"$inc": bson.M{
"version": 1,
},
}
switch params.Operation {
case "add":
update["$inc"].(bson.M)["balance"] = params.Amount
case "subtract":
update["$inc"].(bson.M)["balance"] = -params.Amount
// 防止出现负数,过滤条件要求当前余额 >= amount
filter["balance"] = bson.M{"$gte": params.Amount}
case "set":
// 直接设置余额,同时递增版本
delete(update, "$inc")
update["$set"].(bson.M)["balance"] = params.Amount
update["$set"].(bson.M)["version"] = params.OldVersion + 1
default:
return nil, errs.ErrArgs.WrapMsg("invalid operation: " + params.Operation)
}
// 如果 set 分支删除了 $inc需要补上版本递增
if _, ok := update["$inc"]; !ok {
update["$inc"] = bson.M{"version": 1}
} else if _, ok := update["$inc"].(bson.M)["version"]; !ok {
update["$inc"].(bson.M)["version"] = 1
}
// 使用 findOneAndUpdate 返回更新后的文档
opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
var updatedWallet model.Wallet
err := w.coll.FindOneAndUpdate(ctx, filter, update, opts).Decode(&updatedWallet)
if err != nil {
if err == mongo.ErrNoDocuments {
// 版本号或余额不匹配,说明有并发修改
return nil, errs.ErrInternalServer.WrapMsg("concurrent modification detected: version or balance mismatch")
}
return nil, err
}
return &database.WalletUpdateResult{
NewBalance: updatedWallet.Balance,
NewVersion: updatedWallet.Version,
Success: true,
}, nil
}
// FindAllWallets finds all wallets with pagination.
func (w *WalletMgo) FindAllWallets(ctx context.Context, pagination pagination.Pagination) (total int64, wallets []*model.Wallet, err error) {
return mongoutil.FindPage[*model.Wallet](ctx, w.coll, bson.M{}, pagination, &options.FindOptions{
Sort: bson.D{{Key: "create_time", Value: -1}},
})
}
// FindWalletsByUserIDs finds wallets by user IDs.
func (w *WalletMgo) FindWalletsByUserIDs(ctx context.Context, userIDs []string) ([]*model.Wallet, error) {
if len(userIDs) == 0 {
return []*model.Wallet{}, nil
}
filter := bson.M{"user_id": bson.M{"$in": userIDs}}
return mongoutil.Find[*model.Wallet](ctx, w.coll, filter)
}
// WalletBalanceRecordMgo implements WalletBalanceRecord using MongoDB as the storage backend.
type WalletBalanceRecordMgo struct {
coll *mongo.Collection
}
// NewWalletBalanceRecordMongo creates a new instance of WalletBalanceRecordMgo with the provided MongoDB database.
func NewWalletBalanceRecordMongo(db *mongo.Database) (database.WalletBalanceRecord, error) {
coll := db.Collection(database.WalletBalanceRecordName)
// 先尝试删除可能存在的旧 record_id 索引(如果不是稀疏索引)
// 忽略删除失败的错误(索引可能不存在)
_, _ = coll.Indexes().DropOne(context.Background(), "record_id_1")
// 创建索引,使用稀疏索引允许 record_id 为 null
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{{Key: "user_id", Value: 1}, {Key: "create_time", Value: -1}},
},
{
Keys: bson.D{{Key: "record_id", Value: 1}},
Options: options.Index().SetUnique(true).SetSparse(true),
},
{
Keys: bson.D{{Key: "create_time", Value: -1}},
},
})
if err != nil {
return nil, err
}
return &WalletBalanceRecordMgo{coll: coll}, nil
}
// Create creates a new wallet balance record.
func (w *WalletBalanceRecordMgo) Create(ctx context.Context, record *model.WalletBalanceRecord) error {
if record.CreateTime.IsZero() {
record.CreateTime = time.Now()
}
return mongoutil.InsertOne(ctx, w.coll, record)
}
// FindByUserID finds all balance records for a user with pagination.
func (w *WalletBalanceRecordMgo) FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, records []*model.WalletBalanceRecord, err error) {
filter := bson.M{"user_id": userID}
return mongoutil.FindPage[*model.WalletBalanceRecord](ctx, w.coll, filter, pagination, &options.FindOptions{
Sort: bson.D{{Key: "create_time", Value: -1}},
})
}

View File

@@ -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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/protocol/msg"
"go.mongodb.org/mongo-driver/mongo"
)
type Msg interface {
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)
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)
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) (int64, []*model.MsgInfoModel, error)
CountUserSendMessages(ctx context.Context, sendID string, startTime int64, endTime int64, content string) (int64, error)
SearchUserMessages(ctx context.Context, sendID string, startTime int64, endTime int64, content string, pageNumber int32, showNumber int32) (int64, []*model.MsgInfoModel, error)
// CountUserSendMessagesTrend 按时间区间统计用户发送消息走势数据
CountUserSendMessagesTrend(ctx context.Context, sendID string, sessionTypes []int32, startTime int64, endTime int64, intervalMillis int64) (map[int64]int64, 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)
DeleteDoc(ctx context.Context, docID string) error
GetRandBeforeMsg(ctx context.Context, ts int64, limit int) ([]*model.MsgDocModel, error)
GetLastMessageSeqByTime(ctx context.Context, conversationID string, time int64) (int64, error)
GetLastMessage(ctx context.Context, conversationID string) (*model.MsgInfoModel, error)
FindSeqs(ctx context.Context, conversationID string, seqs []int64) ([]*model.MsgInfoModel, error)
}

View File

@@ -0,0 +1,29 @@
package database
const (
BlackName = "black"
ConversationName = "conversation"
FriendName = "friend"
FriendVersionName = "friend_version"
FriendRequestName = "friend_request"
GroupName = "group"
GroupMemberName = "group_member"
GroupMemberVersionName = "group_member_version"
GroupJoinVersionName = "group_join_version"
ConversationVersionName = "conversation_version"
GroupRequestName = "group_request"
LogName = "log"
ObjectName = "s3"
UserName = "user"
SeqConversationName = "seq"
SeqUserName = "seq_user"
StreamMsgName = "stream_msg"
CacheName = "cache"
RedPacketName = "red_packet"
RedPacketReceiveName = "red_packet_receive"
WalletName = "wallets"
WalletBalanceRecordName = "wallet_balance_records"
MeetingName = "meetings"
MeetingCheckInName = "meeting_checkins"
SystemConfigName = "system_configs"
)

View File

@@ -0,0 +1,34 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/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
FindExpirationObject(ctx context.Context, engine string, expiration time.Time, needDelType []string, count int64) ([]*model.Object, error)
GetKeyCount(ctx context.Context, engine string, key string) (int64, error)
GetEngineCount(ctx context.Context, engine string) (int64, error)
GetEngineInfo(ctx context.Context, engine string, limit int, skip int) ([]*model.Object, error)
UpdateEngine(ctx context.Context, oldEngine, oldName string, newEngine string) error
}

View File

@@ -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 database
import (
"context"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/pagination"
)
// RedPacket defines the operations for managing red packets in MongoDB.
type RedPacket interface {
// Create creates a new red packet record.
Create(ctx context.Context, redPacket *model.RedPacket) error
// Take retrieves a red packet by ID. Returns an error if not found.
Take(ctx context.Context, redPacketID string) (*model.RedPacket, error)
// UpdateStatus updates the status of a red packet.
UpdateStatus(ctx context.Context, redPacketID string, status int32) error
// UpdateRemain updates the remain amount and count of a red packet.
UpdateRemain(ctx context.Context, redPacketID string, remainAmount int64, remainCount int32) error
// DecreaseRemainAtomic 原子性地减少红包剩余数量和金额(防止并发问题)
// 只有在 remain_count > 0 时才会更新,返回更新后的红包信息
DecreaseRemainAtomic(ctx context.Context, redPacketID string, amount int64) (*model.RedPacket, error)
// FindExpiredRedPackets finds red packets that have expired.
FindExpiredRedPackets(ctx context.Context, beforeTime time.Time) ([]*model.RedPacket, error)
// FindRedPacketsByUser finds red packets sent by a user with pagination.
FindRedPacketsByUser(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, redPackets []*model.RedPacket, err error)
// FindRedPacketsByGroup finds red packets in a group with pagination.
FindRedPacketsByGroup(ctx context.Context, groupID string, pagination pagination.Pagination) (total int64, redPackets []*model.RedPacket, err error)
// FindAllRedPackets finds all red packets with pagination.
FindAllRedPackets(ctx context.Context, pagination pagination.Pagination) (total int64, redPackets []*model.RedPacket, err error)
}
// RedPacketReceive defines the operations for managing red packet receives in MongoDB.
type RedPacketReceive interface {
// Create creates a new red packet receive record.
Create(ctx context.Context, receive *model.RedPacketReceive) error
// Take retrieves a receive record by ID. Returns an error if not found.
Take(ctx context.Context, receiveID string) (*model.RedPacketReceive, error)
// FindByRedPacketID finds all receive records for a red packet.
FindByRedPacketID(ctx context.Context, redPacketID string) ([]*model.RedPacketReceive, error)
// FindByUserAndRedPacketID finds if a user has received a specific red packet.
FindByUserAndRedPacketID(ctx context.Context, userID, redPacketID string) (*model.RedPacketReceive, error)
// FindByUser finds all red packets received by a user with pagination.
FindByUser(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, receives []*model.RedPacketReceive, err error)
// DeleteByReceiveID deletes a receive record by receive ID (for cleanup on failure).
DeleteByReceiveID(ctx context.Context, receiveID string) error
}

View File

@@ -0,0 +1,16 @@
package database
import "context"
type SeqTime struct {
Seq int64
Time int64
}
type SeqConversation interface {
Malloc(ctx context.Context, conversationID string, size int64) (int64, error)
GetMaxSeq(ctx context.Context, conversationID string) (int64, error)
SetMaxSeq(ctx context.Context, conversationID string, seq int64) error
GetMinSeq(ctx context.Context, conversationID string) (int64, error)
SetMinSeq(ctx context.Context, conversationID string, seq int64) error
}

View File

@@ -0,0 +1,13 @@
package database
import "context"
type SeqUser interface {
GetUserMaxSeq(ctx context.Context, conversationID string, userID string) (int64, error)
SetUserMaxSeq(ctx context.Context, conversationID string, userID string, seq int64) error
GetUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error)
SetUserMinSeq(ctx context.Context, conversationID string, userID string, seq int64) error
GetUserReadSeq(ctx context.Context, conversationID string, userID string) (int64, error)
SetUserReadSeq(ctx context.Context, conversationID string, userID string, seq int64) error
GetUserReadSeqs(ctx context.Context, userID string, conversationID []string) (map[string]int64, error)
}

View File

@@ -0,0 +1,39 @@
// 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"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
)
// SystemConfig defines the operations for managing system configurations in MongoDB.
type SystemConfig interface {
// Create creates a new system config record.
Create(ctx context.Context, config *model.SystemConfig) error
// Take retrieves a system config by key. Returns an error if not found.
Take(ctx context.Context, key string) (*model.SystemConfig, error)
// Update updates system config information.
Update(ctx context.Context, key string, data map[string]any) error
// Find finds system configs by keys.
Find(ctx context.Context, keys []string) ([]*model.SystemConfig, error)
// FindEnabled finds all enabled system configs.
FindEnabled(ctx context.Context) ([]*model.SystemConfig, error)
// FindByKey finds a system config by key (returns nil if not found, no error).
FindByKey(ctx context.Context, key string) (*model.SystemConfig, error)
// Delete deletes a system config by key.
Delete(ctx context.Context, key string) error
}

View File

@@ -0,0 +1,56 @@
// 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"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/protocol/user"
"github.com/openimsdk/tools/db/pagination"
)
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)
TakeGTEAppManagerLevel(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)
// SearchUsersByFields searches users by multiple fields: account (userID), phone, nickname
// Returns userIDs that match the search criteria
SearchUsersByFields(ctx context.Context, account, phone, nickname string) (userIDs []string, 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)
SortQuery(ctx context.Context, userIDName map[string]string, asc bool) ([]*model.User, 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)
}

View File

@@ -0,0 +1,21 @@
package database
import (
"context"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
)
const (
FirstVersion = 1
DefaultDeleteVersion = 0
)
type VersionLog interface {
IncrVersion(ctx context.Context, dId string, eIds []string, state int32) error
FindChangeLog(ctx context.Context, dId string, version uint, limit int) (*model.VersionLog, error)
BatchFindChangeLog(ctx context.Context, dIds []string, versions []uint, limits []int) ([]*model.VersionLog, error)
DeleteAfterUnchangedLog(ctx context.Context, deadline time.Time) error
Delete(ctx context.Context, dId string) error
}

View File

@@ -0,0 +1,65 @@
// 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"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/db/pagination"
)
// WalletUpdateParams 钱包更新参数
type WalletUpdateParams struct {
UserID string // 用户ID
Operation string // 操作类型set设置为指定金额、add增加金额、subtract减少金额
Amount int64 // 金额(分)
OldBalance int64 // 旧余额(用于乐观锁检查)
OldVersion int64 // 旧版本号(用于乐观锁检查)
}
// WalletUpdateResult 钱包更新结果
type WalletUpdateResult struct {
NewBalance int64 // 新余额
NewVersion int64 // 新版本号
Success bool // 是否成功
}
// Wallet defines the operations for managing user wallets in MongoDB.
type Wallet interface {
// Create creates a new wallet record.
Create(ctx context.Context, wallet *model.Wallet) error
// Take retrieves a wallet by user ID. Returns an error if not found.
Take(ctx context.Context, userID string) (*model.Wallet, error)
// UpdateBalance updates the balance of a wallet.
UpdateBalance(ctx context.Context, userID string, balance int64) error
// UpdateBalanceByAmount updates the balance by adding/subtracting an amount.
UpdateBalanceByAmount(ctx context.Context, userID string, amount int64) error
// UpdateBalanceWithVersion 使用版本号更新余额(防止并发覆盖)
// 如果 oldVersion 与当前版本号不匹配,返回错误
UpdateBalanceWithVersion(ctx context.Context, params *WalletUpdateParams) (*WalletUpdateResult, error)
// FindAllWallets finds all wallets with pagination.
FindAllWallets(ctx context.Context, pagination pagination.Pagination) (total int64, wallets []*model.Wallet, err error)
// FindWalletsByUserIDs finds wallets by user IDs.
FindWalletsByUserIDs(ctx context.Context, userIDs []string) ([]*model.Wallet, error)
}
// WalletBalanceRecord defines the operations for managing wallet balance records in MongoDB.
type WalletBalanceRecord interface {
// Create creates a new wallet balance record.
Create(ctx context.Context, record *model.WalletBalanceRecord) error
// FindByUserID finds all balance records for a user with pagination.
FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, records []*model.WalletBalanceRecord, err error)
}