pull/1427/head
withchao 2 years ago
parent 358f70895d
commit d99e3d93e3

@ -20,6 +20,8 @@ import (
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/OpenIMSDK/tools/tx"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/newmgo"
"math/big" "math/big"
"math/rand" "math/rand"
"strconv" "strconv"
@ -73,11 +75,23 @@ func Start(client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) e
if err != nil { if err != nil {
return err return err
} }
groupDB, err := newmgo.NewGroupMongo(mongo.GetDatabase())
if err != nil {
return err
}
groupMemberDB, err := newmgo.NewGroupMember(mongo.GetDatabase())
if err != nil {
return err
}
groupRequestDB, err := newmgo.NewGroupRequestMgo(mongo.GetDatabase())
if err != nil {
return err
}
userRpcClient := rpcclient.NewUserRpcClient(client) userRpcClient := rpcclient.NewUserRpcClient(client)
msgRpcClient := rpcclient.NewMessageRpcClient(client) msgRpcClient := rpcclient.NewMessageRpcClient(client)
conversationRpcClient := rpcclient.NewConversationRpcClient(client) conversationRpcClient := rpcclient.NewConversationRpcClient(client)
var gs groupServer var gs groupServer
database := controller.InitGroupDatabase(db, rdb, mongo.GetDatabase(), gs.groupMemberHashCode) database := controller.NewGroupDatabase(rdb, groupDB, groupMemberDB, groupRequestDB, tx.NewMongo(mongo.GetClient()), gs.groupMemberHashCode)
gs.GroupDatabase = database gs.GroupDatabase = database
gs.User = userRpcClient gs.User = userRpcClient
gs.Notification = notification.NewGroupNotificationSender(database, &msgRpcClient, &userRpcClient, func(ctx context.Context, userIDs []string) ([]notification.CommonUser, error) { gs.Notification = notification.NewGroupNotificationSender(database, &msgRpcClient, &userRpcClient, func(ctx context.Context, userIDs []string) ([]notification.CommonUser, error) {

@ -26,7 +26,6 @@ import (
"github.com/OpenIMSDK/tools/utils" "github.com/OpenIMSDK/tools/utils"
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
unrelationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation"
) )
const ( const (
@ -35,8 +34,6 @@ const (
groupMemberIDsKey = "GROUP_MEMBER_IDS:" groupMemberIDsKey = "GROUP_MEMBER_IDS:"
groupMembersHashKey = "GROUP_MEMBERS_HASH2:" groupMembersHashKey = "GROUP_MEMBERS_HASH2:"
groupMemberInfoKey = "GROUP_MEMBER_INFO:" groupMemberInfoKey = "GROUP_MEMBER_INFO:"
joinedSuperGroupsKey = "JOIN_SUPER_GROUPS:"
SuperGroupMemberIDsKey = "SUPER_GROUP_MEMBER_IDS:"
joinedGroupsKey = "JOIN_GROUPS_KEY:" joinedGroupsKey = "JOIN_GROUPS_KEY:"
groupMemberNumKey = "GROUP_MEMBER_NUM_CACHE:" groupMemberNumKey = "GROUP_MEMBER_NUM_CACHE:"
) )
@ -48,11 +45,6 @@ type GroupCache interface {
GetGroupInfo(ctx context.Context, groupID string) (group *relationtb.GroupModel, err error) GetGroupInfo(ctx context.Context, groupID string) (group *relationtb.GroupModel, err error)
DelGroupsInfo(groupIDs ...string) GroupCache DelGroupsInfo(groupIDs ...string) GroupCache
GetJoinedSuperGroupIDs(ctx context.Context, userID string) (joinedSuperGroupIDs []string, err error)
DelJoinedSuperGroupIDs(userIDs ...string) GroupCache
GetSuperGroupMemberIDs(ctx context.Context, groupIDs ...string) (models []*unrelationtb.SuperGroupModel, err error)
DelSuperGroupMemberIDs(groupIDs ...string) GroupCache
GetGroupMembersHash(ctx context.Context, groupID string) (hashCode uint64, err error) GetGroupMembersHash(ctx context.Context, groupID string) (hashCode uint64, err error)
GetGroupMemberHashMap(ctx context.Context, groupIDs []string) (map[string]*relationtb.GroupSimpleUserID, error) GetGroupMemberHashMap(ctx context.Context, groupIDs []string) (map[string]*relationtb.GroupSimpleUserID, error)
DelGroupMembersHash(groupID string) GroupCache DelGroupMembersHash(groupID string) GroupCache
@ -81,7 +73,6 @@ type GroupCacheRedis struct {
groupDB relationtb.GroupModelInterface groupDB relationtb.GroupModelInterface
groupMemberDB relationtb.GroupMemberModelInterface groupMemberDB relationtb.GroupMemberModelInterface
groupRequestDB relationtb.GroupRequestModelInterface groupRequestDB relationtb.GroupRequestModelInterface
mongoDB unrelationtb.SuperGroupModelInterface
expireTime time.Duration expireTime time.Duration
rcClient *rockscache.Client rcClient *rockscache.Client
hashCode func(ctx context.Context, groupID string) (uint64, error) hashCode func(ctx context.Context, groupID string) (uint64, error)
@ -92,7 +83,6 @@ func NewGroupCacheRedis(
groupDB relationtb.GroupModelInterface, groupDB relationtb.GroupModelInterface,
groupMemberDB relationtb.GroupMemberModelInterface, groupMemberDB relationtb.GroupMemberModelInterface,
groupRequestDB relationtb.GroupRequestModelInterface, groupRequestDB relationtb.GroupRequestModelInterface,
mongoClient unrelationtb.SuperGroupModelInterface,
hashCode func(ctx context.Context, groupID string) (uint64, error), hashCode func(ctx context.Context, groupID string) (uint64, error),
opts rockscache.Options, opts rockscache.Options,
) GroupCache { ) GroupCache {
@ -101,7 +91,6 @@ func NewGroupCacheRedis(
return &GroupCacheRedis{ return &GroupCacheRedis{
rcClient: rcClient, expireTime: groupExpireTime, rcClient: rcClient, expireTime: groupExpireTime,
groupDB: groupDB, groupMemberDB: groupMemberDB, groupRequestDB: groupRequestDB, groupDB: groupDB, groupMemberDB: groupMemberDB, groupRequestDB: groupRequestDB,
mongoDB: mongoClient,
hashCode: hashCode, hashCode: hashCode,
metaCache: NewMetaCacheRedis(rcClient), metaCache: NewMetaCacheRedis(rcClient),
} }
@ -114,7 +103,6 @@ func (g *GroupCacheRedis) NewCache() GroupCache {
groupDB: g.groupDB, groupDB: g.groupDB,
groupMemberDB: g.groupMemberDB, groupMemberDB: g.groupMemberDB,
groupRequestDB: g.groupRequestDB, groupRequestDB: g.groupRequestDB,
mongoDB: g.mongoDB,
metaCache: NewMetaCacheRedis(g.rcClient, g.metaCache.GetPreDelKeys()...), metaCache: NewMetaCacheRedis(g.rcClient, g.metaCache.GetPreDelKeys()...),
} }
} }
@ -123,18 +111,10 @@ func (g *GroupCacheRedis) getGroupInfoKey(groupID string) string {
return groupInfoKey + groupID return groupInfoKey + groupID
} }
func (g *GroupCacheRedis) getJoinedSuperGroupsIDKey(userID string) string {
return joinedSuperGroupsKey + userID
}
func (g *GroupCacheRedis) getJoinedGroupsKey(userID string) string { func (g *GroupCacheRedis) getJoinedGroupsKey(userID string) string {
return joinedGroupsKey + userID return joinedGroupsKey + userID
} }
func (g *GroupCacheRedis) getSuperGroupMemberIDsKey(groupID string) string {
return SuperGroupMemberIDsKey + groupID
}
func (g *GroupCacheRedis) getGroupMembersHashKey(groupID string) string { func (g *GroupCacheRedis) getGroupMembersHashKey(groupID string) string {
return groupMembersHashKey + groupID return groupMembersHashKey + groupID
} }
@ -175,13 +155,6 @@ func (g *GroupCacheRedis) GetGroupMemberIndex(groupMember *relationtb.GroupMembe
// / groupInfo. // / groupInfo.
func (g *GroupCacheRedis) GetGroupsInfo(ctx context.Context, groupIDs []string) (groups []*relationtb.GroupModel, err error) { func (g *GroupCacheRedis) GetGroupsInfo(ctx context.Context, groupIDs []string) (groups []*relationtb.GroupModel, err error) {
//var keys []string
//for _, group := range groupIDs {
// keys = append(keys, g.getGroupInfoKey(group))
//}
//return batchGetCache(ctx, g.rcClient, keys, g.expireTime, g.GetGroupIndex, func(ctx context.Context) ([]*relationtb.GroupModel, error) {
// return g.groupDB.Find(ctx, groupIDs)
//})
return batchGetCache2(ctx, g.rcClient, g.expireTime, groupIDs, func(groupID string) string { return batchGetCache2(ctx, g.rcClient, g.expireTime, groupIDs, func(groupID string) string {
return g.getGroupInfoKey(groupID) return g.getGroupInfoKey(groupID)
}, func(ctx context.Context, groupID string) (*relationtb.GroupModel, error) { }, func(ctx context.Context, groupID string) (*relationtb.GroupModel, error) {
@ -206,120 +179,11 @@ func (g *GroupCacheRedis) DelGroupsInfo(groupIDs ...string) GroupCache {
return newGroupCache return newGroupCache
} }
func (g *GroupCacheRedis) GetJoinedSuperGroupIDs(ctx context.Context, userID string) (joinedSuperGroupIDs []string, err error) {
return getCache(ctx, g.rcClient, g.getJoinedSuperGroupsIDKey(userID), g.expireTime, func(ctx context.Context) ([]string, error) {
userGroup, err := g.mongoDB.GetSuperGroupByUserID(ctx, userID)
if err != nil {
return nil, err
}
return userGroup.GroupIDs, nil
},
)
}
func (g *GroupCacheRedis) GetSuperGroupMemberIDs(ctx context.Context, groupIDs ...string) (models []*unrelationtb.SuperGroupModel, err error) {
//var keys []string
//for _, group := range groupIDs {
// keys = append(keys, g.getSuperGroupMemberIDsKey(group))
//}
//return batchGetCache(ctx, g.rcClient, keys, g.expireTime, func(model *unrelationtb.SuperGroupModel, keys []string) (int, error) {
// for i, key := range keys {
// if g.getSuperGroupMemberIDsKey(model.GroupID) == key {
// return i, nil
// }
// }
// return 0, errIndex
//},
// func(ctx context.Context) ([]*unrelationtb.SuperGroupModel, error) {
// return g.mongoDB.FindSuperGroup(ctx, groupIDs)
// })
return batchGetCache2(ctx, g.rcClient, g.expireTime, groupIDs, func(groupID string) string {
return g.getSuperGroupMemberIDsKey(groupID)
}, func(ctx context.Context, groupID string) (*unrelationtb.SuperGroupModel, error) {
return g.mongoDB.TakeSuperGroup(ctx, groupID)
})
}
// userJoinSuperGroup.
func (g *GroupCacheRedis) DelJoinedSuperGroupIDs(userIDs ...string) GroupCache {
newGroupCache := g.NewCache()
keys := make([]string, 0, len(userIDs))
for _, userID := range userIDs {
keys = append(keys, g.getJoinedSuperGroupsIDKey(userID))
}
newGroupCache.AddKeys(keys...)
return newGroupCache
}
func (g *GroupCacheRedis) DelSuperGroupMemberIDs(groupIDs ...string) GroupCache {
newGroupCache := g.NewCache()
keys := make([]string, 0, len(groupIDs))
for _, groupID := range groupIDs {
keys = append(keys, g.getSuperGroupMemberIDsKey(groupID))
}
newGroupCache.AddKeys(keys...)
return newGroupCache
}
// groupMembersHash. // groupMembersHash.
func (g *GroupCacheRedis) GetGroupMembersHash(ctx context.Context, groupID string) (hashCode uint64, err error) { func (g *GroupCacheRedis) GetGroupMembersHash(ctx context.Context, groupID string) (hashCode uint64, err error) {
return getCache(ctx, g.rcClient, g.getGroupMembersHashKey(groupID), g.expireTime, func(ctx context.Context) (uint64, error) { return getCache(ctx, g.rcClient, g.getGroupMembersHashKey(groupID), g.expireTime, func(ctx context.Context) (uint64, error) {
return g.hashCode(ctx, groupID) return g.hashCode(ctx, groupID)
}) })
//return getCache(ctx, g.rcClient, g.getGroupMembersHashKey(groupID), g.expireTime,
// func(ctx context.Context) (uint64, error) {
// userIDs, err := g.GetGroupMemberIDs(ctx, groupID)
// if err != nil {
// return 0, err
// }
// log.ZInfo(ctx, "GetGroupMembersHash", "groupID", groupID, "userIDs", userIDs)
// var members []*relationtb.GroupMemberModel
// if len(userIDs) > 0 {
// members, err = g.GetGroupMembersInfo(ctx, groupID, userIDs)
// if err != nil {
// return 0, err
// }
// utils.Sort(userIDs, true)
// }
// memberMap := make(map[string]*relationtb.GroupMemberModel)
// for i, member := range members {
// memberMap[member.UserID] = members[i]
// }
// data := make([]string, 0, len(members)*11)
// for _, userID := range userIDs {
// member, ok := memberMap[userID]
// if !ok {
// continue
// }
// data = append(data,
// member.GroupID,
// member.UserID,
// member.Nickname,
// member.FaceURL,
// strconv.Itoa(int(member.RoleLevel)),
// strconv.FormatInt(member.JoinTime.UnixMilli(), 10),
// strconv.Itoa(int(member.JoinSource)),
// member.InviterUserID,
// member.OperatorUserID,
// strconv.FormatInt(member.MuteEndTime.UnixMilli(), 10),
// member.Ex,
// )
// }
// log.ZInfo(ctx, "hash data info", "userIDs.len", len(userIDs), "hash.data.len", len(data))
// log.ZInfo(ctx, "json hash data", "groupID", groupID, "data", data)
// val, err := json.Marshal(data)
// if err != nil {
// return 0, err
// }
// sum := md5.Sum(val)
// code := binary.BigEndian.Uint64(sum[:])
// log.ZInfo(ctx, "GetGroupMembersHash", "groupID", groupID, "hashCode", code, "num", len(members))
// return code, nil
// },
//)
} }
func (g *GroupCacheRedis) GetGroupMemberHashMap(ctx context.Context, groupIDs []string) (map[string]*relationtb.GroupSimpleUserID, error) { func (g *GroupCacheRedis) GetGroupMemberHashMap(ctx context.Context, groupIDs []string) (map[string]*relationtb.GroupSimpleUserID, error) {
@ -398,13 +262,6 @@ func (g *GroupCacheRedis) GetGroupMemberInfo(ctx context.Context, groupID, userI
} }
func (g *GroupCacheRedis) GetGroupMembersInfo(ctx context.Context, groupID string, userIDs []string) ([]*relationtb.GroupMemberModel, error) { func (g *GroupCacheRedis) GetGroupMembersInfo(ctx context.Context, groupID string, userIDs []string) ([]*relationtb.GroupMemberModel, error) {
//var keys []string
//for _, userID := range userIDs {
// keys = append(keys, g.getGroupMemberInfoKey(groupID, userID))
//}
//return batchGetCache(ctx, g.rcClient, keys, g.expireTime, g.GetGroupMemberIndex, func(ctx context.Context) ([]*relationtb.GroupMemberModel, error) {
// return g.groupMemberDB.Find(ctx, []string{groupID}, userIDs, nil)
//})
return batchGetCache2(ctx, g.rcClient, g.expireTime, userIDs, func(userID string) string { return batchGetCache2(ctx, g.rcClient, g.expireTime, userIDs, func(userID string) string {
return g.getGroupMemberInfoKey(groupID, userID) return g.getGroupMemberInfoKey(groupID, userID)
}, func(ctx context.Context, userID string) (*relationtb.GroupMemberModel, error) { }, func(ctx context.Context, userID string) (*relationtb.GroupMemberModel, error) {
@ -446,13 +303,6 @@ func (g *GroupCacheRedis) GetAllGroupMemberInfo(ctx context.Context, groupID str
if err != nil { if err != nil {
return nil, err return nil, err
} }
//var keys []string
//for _, groupMemberID := range groupMemberIDs {
// keys = append(keys, g.getGroupMemberInfoKey(groupID, groupMemberID))
//}
//return batchGetCache(ctx, g.rcClient, keys, g.expireTime, g.GetGroupMemberIndex, func(ctx context.Context) ([]*relationtb.GroupMemberModel, error) {
// return g.groupMemberDB.Find(ctx, []string{groupID}, groupMemberIDs, nil)
//})
return g.GetGroupMembersInfo(ctx, groupID, groupMemberIDs) return g.GetGroupMembersInfo(ctx, groupID, groupMemberIDs)
} }

@ -17,22 +17,17 @@ package controller
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/dtm-labs/rockscache" "github.com/dtm-labs/rockscache"
"github.com/redis/go-redis/v9" "github.com/openimsdk/open-im-server/v3/pkg/common/pagination"
"go.mongodb.org/mongo-driver/mongo" "time"
"gorm.io/gorm"
"github.com/OpenIMSDK/protocol/constant" "github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/tools/tx" "github.com/OpenIMSDK/tools/tx"
"github.com/OpenIMSDK/tools/utils" "github.com/OpenIMSDK/tools/utils"
"github.com/redis/go-redis/v9"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/relation"
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
unrelationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation"
) )
type GroupDatabase interface { type GroupDatabase interface {
@ -40,11 +35,11 @@ type GroupDatabase interface {
CreateGroup(ctx context.Context, groups []*relationtb.GroupModel, groupMembers []*relationtb.GroupMemberModel) error CreateGroup(ctx context.Context, groups []*relationtb.GroupModel, groupMembers []*relationtb.GroupMemberModel) error
TakeGroup(ctx context.Context, groupID string) (group *relationtb.GroupModel, err error) TakeGroup(ctx context.Context, groupID string) (group *relationtb.GroupModel, err error)
FindGroup(ctx context.Context, groupIDs []string) (groups []*relationtb.GroupModel, err error) FindGroup(ctx context.Context, groupIDs []string) (groups []*relationtb.GroupModel, err error)
FindNotDismissedGroup(ctx context.Context, groupIDs []string) (groups []*relationtb.GroupModel, err error) //FindNotDismissedGroup(ctx context.Context, groupIDs []string) (groups []*relationtb.GroupModel, err error)
SearchGroup(ctx context.Context, keyword string, pageNumber, showNumber int32) (uint32, []*relationtb.GroupModel, error) SearchGroup(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*relationtb.GroupModel, error)
UpdateGroup(ctx context.Context, groupID string, data map[string]any) error UpdateGroup(ctx context.Context, groupID string, data map[string]any) error
DismissGroup(ctx context.Context, groupID string, deleteMember bool) error // 解散群,并删除群成员 DismissGroup(ctx context.Context, groupID string, deleteMember bool) error // 解散群,并删除群成员
GetGroupIDsByGroupType(ctx context.Context, groupType int) (groupIDs []string, err error) //GetGroupIDsByGroupType(ctx context.Context, groupType int) (groupIDs []string, err error)
// GroupMember // GroupMember
TakeGroupMember(ctx context.Context, groupID string, userID string) (groupMember *relationtb.GroupMemberModel, err error) TakeGroupMember(ctx context.Context, groupID string, userID string) (groupMember *relationtb.GroupMemberModel, err error)
TakeGroupOwner(ctx context.Context, groupID string) (*relationtb.GroupMemberModel, error) TakeGroupOwner(ctx context.Context, groupID string) (*relationtb.GroupMemberModel, error)
@ -52,11 +47,11 @@ type GroupDatabase interface {
FindGroupMemberUserID(ctx context.Context, groupID string) ([]string, error) FindGroupMemberUserID(ctx context.Context, groupID string) ([]string, error)
FindGroupMemberNum(ctx context.Context, groupID string) (uint32, error) FindGroupMemberNum(ctx context.Context, groupID string) (uint32, error)
FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error)
PageGroupRequest(ctx context.Context, groupIDs []string, pageNumber, showNumber int32) (uint32, []*relationtb.GroupRequestModel, error) PageGroupRequest(ctx context.Context, groupIDs []string, pagination pagination.Pagination) (int64, []*relationtb.GroupRequestModel, error)
PageGetJoinGroup(ctx context.Context, userID string, pageNumber, showNumber int32) (total uint32, totalGroupMembers []*relationtb.GroupMemberModel, err error) PageGetJoinGroup(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, totalGroupMembers []*relationtb.GroupMemberModel, err error)
PageGetGroupMember(ctx context.Context, groupID string, pageNumber, showNumber int32) (total uint32, totalGroupMembers []*relationtb.GroupMemberModel, err error) PageGetGroupMember(ctx context.Context, groupID string, pagination pagination.Pagination) (total int64, totalGroupMembers []*relationtb.GroupMemberModel, err error)
SearchGroupMember(ctx context.Context, keyword string, groupIDs []string, userIDs []string, roleLevels []int32, pageNumber, showNumber int32) (uint32, []*relationtb.GroupMemberModel, error) SearchGroupMember(ctx context.Context, keyword string, groupIDs []string, userIDs []string, roleLevels []int32, pagination pagination.Pagination) (int64, []*relationtb.GroupMemberModel, error)
HandlerGroupRequest(ctx context.Context, groupID string, userID string, handledMsg string, handleResult int32, member *relationtb.GroupMemberModel) error HandlerGroupRequest(ctx context.Context, groupID string, userID string, handledMsg string, handleResult int32, member *relationtb.GroupMemberModel) error
DeleteGroupMember(ctx context.Context, groupID string, userIDs []string) error DeleteGroupMember(ctx context.Context, groupID string, userIDs []string) error
MapGroupMemberUserID(ctx context.Context, groupIDs []string) (map[string]*relationtb.GroupSimpleUserID, error) MapGroupMemberUserID(ctx context.Context, groupIDs []string) (map[string]*relationtb.GroupSimpleUserID, error)
@ -68,14 +63,7 @@ type GroupDatabase interface {
CreateGroupRequest(ctx context.Context, requests []*relationtb.GroupRequestModel) error CreateGroupRequest(ctx context.Context, requests []*relationtb.GroupRequestModel) error
TakeGroupRequest(ctx context.Context, groupID string, userID string) (*relationtb.GroupRequestModel, error) TakeGroupRequest(ctx context.Context, groupID string, userID string) (*relationtb.GroupRequestModel, error)
FindGroupRequests(ctx context.Context, groupID string, userIDs []string) (int64, []*relationtb.GroupRequestModel, error) FindGroupRequests(ctx context.Context, groupID string, userIDs []string) (int64, []*relationtb.GroupRequestModel, error)
PageGroupRequestUser(ctx context.Context, userID string, pageNumber, showNumber int32) (uint32, []*relationtb.GroupRequestModel, error) PageGroupRequestUser(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*relationtb.GroupRequestModel, error)
// SuperGroupModelInterface
FindSuperGroup(ctx context.Context, groupIDs []string) ([]*unrelationtb.SuperGroupModel, error)
FindJoinSuperGroup(ctx context.Context, userID string) ([]string, error)
CreateSuperGroup(ctx context.Context, groupID string, initMemberIDList []string) error
DeleteSuperGroup(ctx context.Context, groupID string) error
DeleteSuperGroupMember(ctx context.Context, groupID string, userIDs []string) error
CreateSuperGroupMember(ctx context.Context, groupID string, userIDs []string) error
// 获取群总数 // 获取群总数
CountTotal(ctx context.Context, before *time.Time) (count int64, err error) CountTotal(ctx context.Context, before *time.Time) (count int64, err error)
@ -84,63 +72,30 @@ type GroupDatabase interface {
DeleteGroupMemberHash(ctx context.Context, groupIDs []string) error DeleteGroupMemberHash(ctx context.Context, groupIDs []string) error
} }
func NewGroupDatabase( func NewGroupDatabase(rdb redis.UniversalClient, groupDB relationtb.GroupModelInterface, groupMemberDB relationtb.GroupMemberModelInterface, groupRequestDB relationtb.GroupRequestModelInterface, ctxTx tx.CtxTx, hashCode func(ctx context.Context, groupID string) (uint64, error)) GroupDatabase {
group relationtb.GroupModelInterface,
member relationtb.GroupMemberModelInterface,
request relationtb.GroupRequestModelInterface,
tx tx.Tx,
ctxTx tx.CtxTx,
superGroup unrelationtb.SuperGroupModelInterface,
cache cache.GroupCache,
) GroupDatabase {
database := &groupDatabase{
groupDB: group,
groupMemberDB: member,
groupRequestDB: request,
tx: tx,
ctxTx: ctxTx,
cache: cache,
mongoDB: superGroup,
}
return database
}
func InitGroupDatabase(db *gorm.DB, rdb redis.UniversalClient, database *mongo.Database, hashCode func(ctx context.Context, groupID string) (uint64, error)) GroupDatabase {
rcOptions := rockscache.NewDefaultOptions() rcOptions := rockscache.NewDefaultOptions()
rcOptions.StrongConsistency = true rcOptions.StrongConsistency = true
rcOptions.RandomExpireAdjustment = 0.2 rcOptions.RandomExpireAdjustment = 0.2
return NewGroupDatabase( return &groupDatabase{
relation.NewGroupDB(db), groupDB: groupDB,
relation.NewGroupMemberDB(db), groupMemberDB: groupMemberDB,
relation.NewGroupRequest(db), groupRequestDB: groupRequestDB,
tx.NewGorm(db), ctxTx: ctxTx,
tx.NewMongo(database.Client()), cache: cache.NewGroupCacheRedis(rdb, groupDB, groupMemberDB, groupRequestDB, hashCode, rcOptions),
unrelation.NewSuperGroupMongoDriver(database), }
cache.NewGroupCacheRedis(
rdb,
relation.NewGroupDB(db),
relation.NewGroupMemberDB(db),
relation.NewGroupRequest(db),
unrelation.NewSuperGroupMongoDriver(database),
hashCode,
rcOptions,
),
)
} }
type groupDatabase struct { type groupDatabase struct {
groupDB relationtb.GroupModelInterface groupDB relationtb.GroupModelInterface
groupMemberDB relationtb.GroupMemberModelInterface groupMemberDB relationtb.GroupMemberModelInterface
groupRequestDB relationtb.GroupRequestModelInterface groupRequestDB relationtb.GroupRequestModelInterface
tx tx.Tx
ctxTx tx.CtxTx ctxTx tx.CtxTx
cache cache.GroupCache cache cache.GroupCache
mongoDB unrelationtb.SuperGroupModelInterface
} }
func (g *groupDatabase) GetGroupIDsByGroupType(ctx context.Context, groupType int) (groupIDs []string, err error) { //func (g *groupDatabase) GetGroupIDsByGroupType(ctx context.Context, groupType int) (groupIDs []string, err error) {
return g.groupDB.GetGroupIDsByGroupType(ctx, groupType) // return g.groupDB.GetGroupIDsByGroupType(ctx, groupType)
} //}
func (g *groupDatabase) FindGroupMemberUserID(ctx context.Context, groupID string) ([]string, error) { func (g *groupDatabase) FindGroupMemberUserID(ctx context.Context, groupID string) ([]string, error) {
return g.cache.GetGroupMemberIDs(ctx, groupID) return g.cache.GetGroupMemberIDs(ctx, groupID)
@ -154,41 +109,41 @@ func (g *groupDatabase) FindGroupMemberNum(ctx context.Context, groupID string)
return uint32(num), nil return uint32(num), nil
} }
func (g *groupDatabase) CreateGroup( func (g *groupDatabase) CreateGroup(ctx context.Context, groups []*relationtb.GroupModel, groupMembers []*relationtb.GroupMemberModel) error {
ctx context.Context, if len(groups)+len(groupMembers) == 0 {
groups []*relationtb.GroupModel, return nil
groupMembers []*relationtb.GroupMemberModel, }
) error { return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
cache := g.cache.NewCache() c := g.cache.NewCache()
if err := g.tx.Transaction(func(tx any) error {
if len(groups) > 0 { if len(groups) > 0 {
if err := g.groupDB.NewTx(tx).Create(ctx, groups); err != nil { if err := g.groupDB.Create(ctx, groups); err != nil {
return err return err
} }
for _, group := range groups {
c = c.DelGroupsInfo(group.GroupID)
c = c.DelGroupMembersHash(group.GroupID)
c = c.DelGroupsMemberNum(group.GroupID)
c = c.DelGroupMemberIDs(group.GroupID)
}
} }
if len(groupMembers) > 0 { if len(groupMembers) > 0 {
if err := g.groupMemberDB.NewTx(tx).Create(ctx, groupMembers); err != nil { if err := g.groupMemberDB.Create(ctx, groupMembers); err != nil {
return err return err
} }
} temp := make(map[string]struct{})
createGroupIDs := utils.DistinctAnyGetComparable(groups, func(group *relationtb.GroupModel) string {
return group.GroupID
})
m := make(map[string]struct{})
for _, groupMember := range groupMembers { for _, groupMember := range groupMembers {
if _, ok := m[groupMember.GroupID]; !ok { if _, ok := temp[groupMember.GroupID]; !ok {
m[groupMember.GroupID] = struct{}{} temp[groupMember.GroupID] = struct{}{}
cache = cache.DelGroupMemberIDs(groupMember.GroupID).DelGroupMembersHash(groupMember.GroupID).DelGroupsMemberNum(groupMember.GroupID) c = c.DelGroupMembersHash(groupMember.GroupID)
c = c.DelGroupsMemberNum(groupMember.GroupID)
c = c.DelGroupMemberIDs(groupMember.GroupID)
} }
cache = cache.DelJoinedGroupID(groupMember.UserID).DelGroupMembersInfo(groupMember.GroupID, groupMember.UserID) c = c.DelJoinedGroupID(groupMember.UserID)
c = c.DelGroupMembersInfo(groupMember.GroupID, groupMember.UserID)
} }
cache = cache.DelGroupsInfo(createGroupIDs...)
return nil
}); err != nil {
return err
} }
return cache.ExecDel(ctx) return c.ExecDel(ctx)
})
} }
func (g *groupDatabase) TakeGroup(ctx context.Context, groupID string) (group *relationtb.GroupModel, err error) { func (g *groupDatabase) TakeGroup(ctx context.Context, groupID string) (group *relationtb.GroupModel, err error) {
@ -199,12 +154,8 @@ func (g *groupDatabase) FindGroup(ctx context.Context, groupIDs []string) (group
return g.cache.GetGroupsInfo(ctx, groupIDs) return g.cache.GetGroupsInfo(ctx, groupIDs)
} }
func (g *groupDatabase) SearchGroup( func (g *groupDatabase) SearchGroup(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*relationtb.GroupModel, error) {
ctx context.Context, return g.groupDB.Search(ctx, keyword, pagination)
keyword string,
pageNumber, showNumber int32,
) (uint32, []*relationtb.GroupModel, error) {
return g.groupDB.Search(ctx, keyword, pageNumber, showNumber)
} }
func (g *groupDatabase) UpdateGroup(ctx context.Context, groupID string, data map[string]any) error { func (g *groupDatabase) UpdateGroup(ctx context.Context, groupID string, data map[string]any) error {
@ -254,12 +205,8 @@ func (g *groupDatabase) FindUserManagedGroupID(ctx context.Context, userID strin
return g.groupMemberDB.FindUserManagedGroupID(ctx, userID) return g.groupMemberDB.FindUserManagedGroupID(ctx, userID)
} }
func (g *groupDatabase) PageGroupRequest( func (g *groupDatabase) PageGroupRequest(ctx context.Context, groupIDs []string, pagination pagination.Pagination) (int64, []*relationtb.GroupRequestModel, error) {
ctx context.Context, return g.groupRequestDB.PageGroup(ctx, groupIDs, pagination)
groupIDs []string,
pageNumber, showNumber int32,
) (uint32, []*relationtb.GroupRequestModel, error) {
return g.groupRequestDB.PageGroup(ctx, groupIDs, pageNumber, showNumber)
} }
func (g *groupDatabase) FindGroupMember(ctx context.Context, groupIDs []string, userIDs []string, roleLevels []int32) (totalGroupMembers []*relationtb.GroupMemberModel, err error) { func (g *groupDatabase) FindGroupMember(ctx context.Context, groupIDs []string, userIDs []string, roleLevels []int32) (totalGroupMembers []*relationtb.GroupMemberModel, err error) {
@ -294,8 +241,8 @@ func (g *groupDatabase) FindGroupMember(ctx context.Context, groupIDs []string,
func (g *groupDatabase) PageGetJoinGroup( func (g *groupDatabase) PageGetJoinGroup(
ctx context.Context, ctx context.Context,
userID string, userID string,
pageNumber, showNumber int32, pagination pagination.Pagination,
) (total uint32, totalGroupMembers []*relationtb.GroupMemberModel, err error) { ) (total int64, totalGroupMembers []*relationtb.GroupMemberModel, err error) {
groupIDs, err := g.cache.GetJoinedGroupIDs(ctx, userID) groupIDs, err := g.cache.GetJoinedGroupIDs(ctx, userID)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
@ -313,8 +260,8 @@ func (g *groupDatabase) PageGetJoinGroup(
func (g *groupDatabase) PageGetGroupMember( func (g *groupDatabase) PageGetGroupMember(
ctx context.Context, ctx context.Context,
groupID string, groupID string,
pageNumber, showNumber int32, pagination pagination.Pagination,
) (total uint32, totalGroupMembers []*relationtb.GroupMemberModel, err error) { ) (total int64, totalGroupMembers []*relationtb.GroupMemberModel, err error) {
groupMemberIDs, err := g.cache.GetGroupMemberIDs(ctx, groupID) groupMemberIDs, err := g.cache.GetGroupMemberIDs(ctx, groupID)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
@ -330,15 +277,8 @@ func (g *groupDatabase) PageGetGroupMember(
return uint32(len(groupMemberIDs)), members, nil return uint32(len(groupMemberIDs)), members, nil
} }
func (g *groupDatabase) SearchGroupMember( func (g *groupDatabase) SearchGroupMember(ctx context.Context, keyword string, groupIDs []string, userIDs []string, roleLevels []int32, pagination pagination.Pagination) (int64, []*relationtb.GroupMemberModel, error) {
ctx context.Context, return g.groupMemberDB.SearchMember(ctx, keyword, groupIDs, userIDs, roleLevels, pagination)
keyword string,
groupIDs []string,
userIDs []string,
roleLevels []int32,
pageNumber, showNumber int32,
) (uint32, []*relationtb.GroupMemberModel, error) {
return g.groupMemberDB.SearchMember(ctx, keyword, groupIDs, userIDs, roleLevels, pageNumber, showNumber)
} }
func (g *groupDatabase) HandlerGroupRequest( func (g *groupDatabase) HandlerGroupRequest(
@ -484,64 +424,11 @@ func (g *groupDatabase) TakeGroupRequest(
func (g *groupDatabase) PageGroupRequestUser( func (g *groupDatabase) PageGroupRequestUser(
ctx context.Context, ctx context.Context,
userID string, userID string,
pageNumber, showNumber int32, pagination pagination.Pagination,
) (uint32, []*relationtb.GroupRequestModel, error) { ) (int64, []*relationtb.GroupRequestModel, error) {
return g.groupRequestDB.Page(ctx, userID, pageNumber, showNumber) return g.groupRequestDB.Page(ctx, userID, pageNumber, showNumber)
} }
func (g *groupDatabase) FindSuperGroup(
ctx context.Context,
groupIDs []string,
) (models []*unrelationtb.SuperGroupModel, err error) {
return g.cache.GetSuperGroupMemberIDs(ctx, groupIDs...)
}
func (g *groupDatabase) FindJoinSuperGroup(ctx context.Context, userID string) ([]string, error) {
return g.cache.GetJoinedSuperGroupIDs(ctx, userID)
}
func (g *groupDatabase) CreateSuperGroup(ctx context.Context, groupID string, initMemberIDs []string) error {
if err := g.mongoDB.CreateSuperGroup(ctx, groupID, initMemberIDs); err != nil {
return err
}
return g.cache.DelSuperGroupMemberIDs(groupID).DelJoinedSuperGroupIDs(initMemberIDs...).ExecDel(ctx)
}
func (g *groupDatabase) DeleteSuperGroup(ctx context.Context, groupID string) error {
cache := g.cache.NewCache()
if err := g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
if err := g.mongoDB.DeleteSuperGroup(ctx, groupID); err != nil {
return err
}
models, err := g.cache.GetSuperGroupMemberIDs(ctx, groupID)
if err != nil {
return err
}
cache = cache.DelSuperGroupMemberIDs(groupID)
if len(models) > 0 {
cache = cache.DelJoinedSuperGroupIDs(models[0].MemberIDs...)
}
return nil
}); err != nil {
return err
}
return cache.ExecDel(ctx)
}
func (g *groupDatabase) DeleteSuperGroupMember(ctx context.Context, groupID string, userIDs []string) error {
if err := g.mongoDB.RemoverUserFromSuperGroup(ctx, groupID, userIDs); err != nil {
return err
}
return g.cache.DelSuperGroupMemberIDs(groupID).DelJoinedSuperGroupIDs(userIDs...).ExecDel(ctx)
}
func (g *groupDatabase) CreateSuperGroupMember(ctx context.Context, groupID string, userIDs []string) error {
if err := g.mongoDB.AddUserToSuperGroup(ctx, groupID, userIDs); err != nil {
return err
}
return g.cache.DelSuperGroupMemberIDs(groupID).DelJoinedSuperGroupIDs(userIDs...).ExecDel(ctx)
}
func (g *groupDatabase) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) { func (g *groupDatabase) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
return g.groupDB.CountTotal(ctx, before) return g.groupDB.CountTotal(ctx, before)
} }
@ -554,10 +441,6 @@ func (g *groupDatabase) FindGroupRequests(ctx context.Context, groupID string, u
return g.groupRequestDB.FindGroupRequests(ctx, groupID, userIDs) return g.groupRequestDB.FindGroupRequests(ctx, groupID, userIDs)
} }
func (g *groupDatabase) FindNotDismissedGroup(ctx context.Context, groupIDs []string) (groups []*relationtb.GroupModel, err error) {
return g.groupDB.FindNotDismissedGroup(ctx, groupIDs)
}
func (g *groupDatabase) DeleteGroupMemberHash(ctx context.Context, groupIDs []string) error { func (g *groupDatabase) DeleteGroupMemberHash(ctx context.Context, groupIDs []string) error {
if len(groupIDs) == 0 { if len(groupIDs) == 0 {
return nil return nil

@ -0,0 +1,56 @@
package newmgo
import (
"context"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/newmgo/mgotool"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
"github.com/openimsdk/open-im-server/v3/pkg/common/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"time"
)
func NewGroupMongo(db *mongo.Database) (relation.GroupModelInterface, error) {
return &GroupMgo{
coll: db.Collection("user"),
}, nil
}
type GroupMgo struct {
coll *mongo.Collection
}
func (g *GroupMgo) Create(ctx context.Context, groups []*relation.GroupModel) (err error) {
return mgotool.InsertMany(ctx, g.coll, groups)
}
func (g *GroupMgo) UpdateMap(ctx context.Context, groupID string, args map[string]any) (err error) {
if len(args) == 0 {
return nil
}
return mgotool.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID}, bson.M{"$set": args}, true)
}
func (g *GroupMgo) Find(ctx context.Context, groupIDs []string) (groups []*relation.GroupModel, err error) {
return mgotool.Find[*relation.GroupModel](ctx, g.coll, bson.M{"group_id": bson.M{"$in": groupIDs}})
}
func (g *GroupMgo) Take(ctx context.Context, groupID string) (group *relation.GroupModel, err error) {
return mgotool.FindOne[*relation.GroupModel](ctx, g.coll, bson.M{"group_id": groupID})
}
func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*relation.GroupModel, err error) {
return mgotool.FindPage[*relation.GroupModel](ctx, g.coll, bson.M{"group_name": bson.M{"$regex": keyword}}, pagination)
}
func (g *GroupMgo) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
if before == nil {
return mgotool.Count(ctx, g.coll, bson.M{})
}
return mgotool.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) {
//TODO implement me
panic("implement me")
}

@ -0,0 +1,72 @@
package newmgo
import (
"context"
"github.com/OpenIMSDK/protocol/constant"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/newmgo/mgotool"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
"github.com/openimsdk/open-im-server/v3/pkg/common/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewGroupMember(db *mongo.Database) (relation.GroupMemberModelInterface, error) {
return &GroupMemberMgo{coll: db.Collection("group_member")}, nil
}
type GroupMemberMgo struct {
coll *mongo.Collection
}
func (g *GroupMemberMgo) Create(ctx context.Context, groupMembers []*relation.GroupMemberModel) (err error) {
return mgotool.InsertMany(ctx, g.coll, groupMembers)
}
func (g *GroupMemberMgo) Delete(ctx context.Context, groupID string, userIDs []string) (err error) {
return mgotool.DeleteMany(ctx, g.coll, bson.M{"group_id": groupID, "user_id": bson.M{"$in": userIDs}})
}
func (g *GroupMemberMgo) Update(ctx context.Context, groupID string, userID string, data map[string]any) (err error) {
return mgotool.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}, bson.M{"$set": data}, true)
}
func (g *GroupMemberMgo) Find(ctx context.Context, groupIDs []string, userIDs []string, roleLevels []int32) (groupMembers []*relation.GroupMemberModel, err error) {
//TODO implement me
panic("implement me")
}
func (g *GroupMemberMgo) FindMemberUserID(ctx context.Context, groupID string) (userIDs []string, err error) {
return mgotool.Find[string](ctx, g.coll, bson.M{"group_id": groupID}, options.Find().SetProjection(bson.M{"user_id": 1}))
}
func (g *GroupMemberMgo) Take(ctx context.Context, groupID string, userID string) (groupMember *relation.GroupMemberModel, err error) {
return mgotool.FindOne[*relation.GroupMemberModel](ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID})
}
func (g *GroupMemberMgo) TakeOwner(ctx context.Context, groupID string) (groupMember *relation.GroupMemberModel, err error) {
return mgotool.FindOne[*relation.GroupMemberModel](ctx, g.coll, bson.M{"group_id": groupID, "role_level": constant.GroupOwner})
}
func (g *GroupMemberMgo) SearchMember(ctx context.Context, keyword string, groupIDs []string, userIDs []string, roleLevels []int32, pagination pagination.Pagination) (total int64, groupList []*relation.GroupMemberModel, err error) {
//TODO implement me
panic("implement me")
}
func (g *GroupMemberMgo) FindUserJoinedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) {
return mgotool.Find[string](ctx, g.coll, bson.M{"user_id": userID}, options.Find().SetProjection(bson.M{"group_id": 1}))
}
func (g *GroupMemberMgo) TakeGroupMemberNum(ctx context.Context, groupID string) (count int64, err error) {
return mgotool.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 mgotool.Find[string](ctx, g.coll, filter, options.Find().SetProjection(bson.M{"group_id": 1}))
}

@ -0,0 +1,51 @@
package newmgo
import (
"context"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
"github.com/openimsdk/open-im-server/v3/pkg/common/pagination"
"go.mongodb.org/mongo-driver/mongo"
)
func NewGroupRequestMgo(db *mongo.Database) (relation.GroupRequestModelInterface, error) {
return &GroupRequestMgo{coll: db.Collection("group_request")}, nil
}
type GroupRequestMgo struct {
coll *mongo.Collection
}
func (g *GroupRequestMgo) Create(ctx context.Context, groupRequests []*relation.GroupRequestModel) (err error) {
//TODO implement me
panic("implement me")
}
func (g *GroupRequestMgo) Delete(ctx context.Context, groupID string, userID string) (err error) {
//TODO implement me
panic("implement me")
}
func (g *GroupRequestMgo) UpdateHandler(ctx context.Context, groupID string, userID string, handledMsg string, handleResult int32) (err error) {
//TODO implement me
panic("implement me")
}
func (g *GroupRequestMgo) Take(ctx context.Context, groupID string, userID string) (groupRequest *relation.GroupRequestModel, err error) {
//TODO implement me
panic("implement me")
}
func (g *GroupRequestMgo) FindGroupRequests(ctx context.Context, groupID string, userIDs []string) (int64, []*relation.GroupRequestModel, error) {
//TODO implement me
panic("implement me")
}
func (g *GroupRequestMgo) Page(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, groups []*relation.GroupRequestModel, err error) {
//TODO implement me
panic("implement me")
}
func (g *GroupRequestMgo) PageGroup(ctx context.Context, groupIDs []string, pagination pagination.Pagination) (total int64, groups []*relation.GroupRequestModel, err error) {
//TODO implement me
panic("implement me")
}

@ -14,93 +14,93 @@
package relation package relation
import ( //import (
"context" // "context"
"time" // "time"
//
"github.com/OpenIMSDK/protocol/constant" // "github.com/OpenIMSDK/protocol/constant"
//
"gorm.io/gorm" // "gorm.io/gorm"
//
"github.com/OpenIMSDK/tools/errs" // "github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/ormutil" // "github.com/OpenIMSDK/tools/ormutil"
"github.com/OpenIMSDK/tools/utils" // "github.com/OpenIMSDK/tools/utils"
//
"github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" // "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
) //)
//
var _ relation.GroupModelInterface = (*GroupGorm)(nil) //var _ relation.GroupModelInterface = (*GroupGorm)(nil)
//
type GroupGorm struct { //type GroupGorm struct {
*MetaDB // *MetaDB
} //}
//
func NewGroupDB(db *gorm.DB) relation.GroupModelInterface { //func NewGroupDB(db *gorm.DB) relation.GroupModelInterface {
return &GroupGorm{NewMetaDB(db, &relation.GroupModel{})} // return &GroupGorm{NewMetaDB(db, &relation.GroupModel{})}
} //}
//
func (g *GroupGorm) NewTx(tx any) relation.GroupModelInterface { //func (g *GroupGorm) NewTx(tx any) relation.GroupModelInterface {
return &GroupGorm{NewMetaDB(tx.(*gorm.DB), &relation.GroupModel{})} // return &GroupGorm{NewMetaDB(tx.(*gorm.DB), &relation.GroupModel{})}
} //}
//
func (g *GroupGorm) Create(ctx context.Context, groups []*relation.GroupModel) (err error) { //func (g *GroupGorm) Create(ctx context.Context, groups []*relation.GroupModel) (err error) {
return utils.Wrap(g.DB.Create(&groups).Error, "") // return utils.Wrap(g.DB.Create(&groups).Error, "")
} //}
//
func (g *GroupGorm) UpdateMap(ctx context.Context, groupID string, args map[string]any) (err error) { //func (g *GroupGorm) UpdateMap(ctx context.Context, groupID string, args map[string]any) (err error) {
return utils.Wrap(g.DB.Where("group_id = ?", groupID).Model(&relation.GroupModel{}).Updates(args).Error, "") // return utils.Wrap(g.DB.Where("group_id = ?", groupID).Model(&relation.GroupModel{}).Updates(args).Error, "")
} //}
//
func (g *GroupGorm) UpdateStatus(ctx context.Context, groupID string, status int32) (err error) { //func (g *GroupGorm) UpdateStatus(ctx context.Context, groupID string, status int32) (err error) {
return utils.Wrap(g.DB.Where("group_id = ?", groupID).Model(&relation.GroupModel{}).Updates(map[string]any{"status": status}).Error, "") // return utils.Wrap(g.DB.Where("group_id = ?", groupID).Model(&relation.GroupModel{}).Updates(map[string]any{"status": status}).Error, "")
} //}
//
func (g *GroupGorm) Find(ctx context.Context, groupIDs []string) (groups []*relation.GroupModel, err error) { //func (g *GroupGorm) Find(ctx context.Context, groupIDs []string) (groups []*relation.GroupModel, err error) {
return groups, utils.Wrap(g.DB.Where("group_id in (?)", groupIDs).Find(&groups).Error, "") // return groups, utils.Wrap(g.DB.Where("group_id in (?)", groupIDs).Find(&groups).Error, "")
} //}
//
func (g *GroupGorm) Take(ctx context.Context, groupID string) (group *relation.GroupModel, err error) { //func (g *GroupGorm) Take(ctx context.Context, groupID string) (group *relation.GroupModel, err error) {
group = &relation.GroupModel{} // group = &relation.GroupModel{}
return group, utils.Wrap(g.DB.Where("group_id = ?", groupID).Take(group).Error, "") // return group, utils.Wrap(g.DB.Where("group_id = ?", groupID).Take(group).Error, "")
} //}
//
func (g *GroupGorm) Search(ctx context.Context, keyword string, pageNumber, showNumber int32) (total uint32, groups []*relation.GroupModel, err error) { //func (g *GroupGorm) Search(ctx context.Context, keyword string, pageNumber, showNumber int32) (total uint32, groups []*relation.GroupModel, err error) {
db := g.DB // db := g.DB
db = db.WithContext(ctx).Where("status!=?", constant.GroupStatusDismissed) // db = db.WithContext(ctx).Where("status!=?", constant.GroupStatusDismissed)
return ormutil.GormSearch[relation.GroupModel](db, []string{"name"}, keyword, pageNumber, showNumber) // return ormutil.GormSearch[relation.GroupModel](db, []string{"name"}, keyword, pageNumber, showNumber)
} //}
//
func (g *GroupGorm) GetGroupIDsByGroupType(ctx context.Context, groupType int) (groupIDs []string, err error) { //func (g *GroupGorm) GetGroupIDsByGroupType(ctx context.Context, groupType int) (groupIDs []string, err error) {
return groupIDs, utils.Wrap(g.DB.Model(&relation.GroupModel{}).Where("group_type = ? ", groupType).Pluck("group_id", &groupIDs).Error, "") // return groupIDs, utils.Wrap(g.DB.Model(&relation.GroupModel{}).Where("group_type = ? ", groupType).Pluck("group_id", &groupIDs).Error, "")
} //}
//
func (g *GroupGorm) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) { //func (g *GroupGorm) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
db := g.db(ctx).Model(&relation.GroupModel{}) // db := g.db(ctx).Model(&relation.GroupModel{})
if before != nil { // if before != nil {
db = db.Where("create_time < ?", before) // db = db.Where("create_time < ?", before)
} // }
if err := db.Count(&count).Error; err != nil { // if err := db.Count(&count).Error; err != nil {
return 0, err // return 0, err
} // }
return count, nil // return count, nil
} //}
//
func (g *GroupGorm) CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) { //func (g *GroupGorm) CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) {
var res []struct { // var res []struct {
Date time.Time `gorm:"column:date"` // Date time.Time `gorm:"column:date"`
Count int64 `gorm:"column:count"` // Count int64 `gorm:"column:count"`
} // }
err := g.db(ctx).Model(&relation.GroupModel{}).Select("DATE(create_time) AS date, count(1) AS count").Where("create_time >= ? and create_time < ?", start, end).Group("date").Find(&res).Error // err := g.db(ctx).Model(&relation.GroupModel{}).Select("DATE(create_time) AS date, count(1) AS count").Where("create_time >= ? and create_time < ?", start, end).Group("date").Find(&res).Error
if err != nil { // if err != nil {
return nil, errs.Wrap(err) // return nil, errs.Wrap(err)
} // }
v := make(map[string]int64) // v := make(map[string]int64)
for _, r := range res { // for _, r := range res {
v[r.Date.Format("2006-01-02")] = r.Count // v[r.Date.Format("2006-01-02")] = r.Count
} // }
return v, nil // return v, nil
} //}
//
func (g *GroupGorm) FindNotDismissedGroup(ctx context.Context, groupIDs []string) (groups []*relation.GroupModel, err error) { //func (g *GroupGorm) FindNotDismissedGroup(ctx context.Context, groupIDs []string) (groups []*relation.GroupModel, err error) {
return groups, utils.Wrap(g.DB.Where("group_id in (?) and status != ?", groupIDs, constant.GroupStatusDismissed).Find(&groups).Error, "") // return groups, utils.Wrap(g.DB.Where("group_id in (?) and status != ?", groupIDs, constant.GroupStatusDismissed).Find(&groups).Error, "")
} //}

@ -16,6 +16,7 @@ package relation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/pagination"
"time" "time"
) )
@ -23,22 +24,40 @@ const (
GroupModelTableName = "groups" GroupModelTableName = "groups"
) )
//type GroupModel struct {
// GroupID string `gorm:"column:group_id;primary_key;size:64" json:"groupID" binding:"required"`
// GroupName string `gorm:"column:name;size:255" json:"groupName"`
// Notification string `gorm:"column:notification;size:255" json:"notification"`
// Introduction string `gorm:"column:introduction;size:255" json:"introduction"`
// FaceURL string `gorm:"column:face_url;size:255" json:"faceURL"`
// CreateTime time.Time `gorm:"column:create_time;index:create_time;autoCreateTime"`
// Ex string `gorm:"column:ex" json:"ex;size:1024"`
// Status int32 `gorm:"column:status"`
// CreatorUserID string `gorm:"column:creator_user_id;size:64"`
// GroupType int32 `gorm:"column:group_type"`
// NeedVerification int32 `gorm:"column:need_verification"`
// LookMemberInfo int32 `gorm:"column:look_member_info" json:"lookMemberInfo"`
// ApplyMemberFriend int32 `gorm:"column:apply_member_friend" json:"applyMemberFriend"`
// NotificationUpdateTime time.Time `gorm:"column:notification_update_time"`
// NotificationUserID string `gorm:"column:notification_user_id;size:64"`
//}
type GroupModel struct { type GroupModel struct {
GroupID string `gorm:"column:group_id;primary_key;size:64" json:"groupID" binding:"required"` GroupID string `bson:"group_id"`
GroupName string `gorm:"column:name;size:255" json:"groupName"` GroupName string `bson:"group_name"`
Notification string `gorm:"column:notification;size:255" json:"notification"` Notification string `bson:"notification"`
Introduction string `gorm:"column:introduction;size:255" json:"introduction"` Introduction string `bson:"introduction"`
FaceURL string `gorm:"column:face_url;size:255" json:"faceURL"` FaceURL string `bson:"face_url"`
CreateTime time.Time `gorm:"column:create_time;index:create_time;autoCreateTime"` CreateTime time.Time `bson:"create_time"`
Ex string `gorm:"column:ex" json:"ex;size:1024"` Ex string `bson:"ex"`
Status int32 `gorm:"column:status"` Status int32 `bson:"status"`
CreatorUserID string `gorm:"column:creator_user_id;size:64"` CreatorUserID string `bson:"creator_user_id"`
GroupType int32 `gorm:"column:group_type"` GroupType int32 `bson:"group_type"`
NeedVerification int32 `gorm:"column:need_verification"` NeedVerification int32 `bson:"need_verification"`
LookMemberInfo int32 `gorm:"column:look_member_info" json:"lookMemberInfo"` LookMemberInfo int32 `bson:"look_member_info"`
ApplyMemberFriend int32 `gorm:"column:apply_member_friend" json:"applyMemberFriend"` ApplyMemberFriend int32 `bson:"apply_member_friend"`
NotificationUpdateTime time.Time `gorm:"column:notification_update_time"` NotificationUpdateTime time.Time `bson:"notification_update_time"`
NotificationUserID string `gorm:"column:notification_user_id;size:64"` NotificationUserID string `bson:"notification_user_id"`
} }
func (GroupModel) TableName() string { func (GroupModel) TableName() string {
@ -46,19 +65,14 @@ func (GroupModel) TableName() string {
} }
type GroupModelInterface interface { type GroupModelInterface interface {
NewTx(tx any) GroupModelInterface
Create(ctx context.Context, groups []*GroupModel) (err error) Create(ctx context.Context, groups []*GroupModel) (err error)
UpdateMap(ctx context.Context, groupID string, args map[string]any) (err error) UpdateMap(ctx context.Context, groupID string, args map[string]any) (err error)
UpdateStatus(ctx context.Context, groupID string, status int32) (err error) //UpdateStatus(ctx context.Context, groupID string, status int32) (err error)
Find(ctx context.Context, groupIDs []string) (groups []*GroupModel, err error) Find(ctx context.Context, groupIDs []string) (groups []*GroupModel, err error)
FindNotDismissedGroup(ctx context.Context, groupIDs []string) (groups []*GroupModel, err error) //FindNotDismissedGroup(ctx context.Context, groupIDs []string) (groups []*GroupModel, err error)
Take(ctx context.Context, groupID string) (group *GroupModel, err error) Take(ctx context.Context, groupID string) (group *GroupModel, err error)
Search( Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*GroupModel, err error)
ctx context.Context, //GetGroupIDsByGroupType(ctx context.Context, groupType int) (groupIDs []string, err error)
keyword string,
pageNumber, showNumber int32,
) (total uint32, groups []*GroupModel, err error)
GetGroupIDsByGroupType(ctx context.Context, groupType int) (groupIDs []string, err error)
// 获取群总数 // 获取群总数
CountTotal(ctx context.Context, before *time.Time) (count int64, err error) CountTotal(ctx context.Context, before *time.Time) (count int64, err error)
// 获取范围内群增量 // 获取范围内群增量

@ -16,6 +16,7 @@ package relation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/pagination"
"time" "time"
) )
@ -23,18 +24,32 @@ const (
GroupMemberModelTableName = "group_members" GroupMemberModelTableName = "group_members"
) )
//type GroupMemberModel struct {
// GroupID string `gorm:"column:group_id;primary_key;size:64"`
// UserID string `gorm:"column:user_id;primary_key;size:64"`
// Nickname string `gorm:"column:nickname;size:255"`
// FaceURL string `gorm:"column:user_group_face_url;size:255"`
// RoleLevel int32 `gorm:"column:role_level"`
// JoinTime time.Time `gorm:"column:join_time"`
// JoinSource int32 `gorm:"column:join_source"`
// InviterUserID string `gorm:"column:inviter_user_id;size:64"`
// OperatorUserID string `gorm:"column:operator_user_id;size:64"`
// MuteEndTime time.Time `gorm:"column:mute_end_time"`
// Ex string `gorm:"column:ex;size:1024"`
//}
type GroupMemberModel struct { type GroupMemberModel struct {
GroupID string `gorm:"column:group_id;primary_key;size:64"` GroupID string `bson:"group_id"`
UserID string `gorm:"column:user_id;primary_key;size:64"` UserID string `bson:"user_id"`
Nickname string `gorm:"column:nickname;size:255"` Nickname string `bson:"nickname"`
FaceURL string `gorm:"column:user_group_face_url;size:255"` FaceURL string `bson:"face_url"`
RoleLevel int32 `gorm:"column:role_level"` RoleLevel int32 `bson:"role_level"`
JoinTime time.Time `gorm:"column:join_time"` JoinTime time.Time `bson:"join_time"`
JoinSource int32 `gorm:"column:join_source"` JoinSource int32 `bson:"join_source"`
InviterUserID string `gorm:"column:inviter_user_id;size:64"` InviterUserID string `bson:"inviter_user_id"`
OperatorUserID string `gorm:"column:operator_user_id;size:64"` OperatorUserID string `bson:"operator_user_id"`
MuteEndTime time.Time `gorm:"column:mute_end_time"` MuteEndTime time.Time `bson:"mute_end_time"`
Ex string `gorm:"column:ex;size:1024"` Ex string `bson:"ex"`
} }
func (GroupMemberModel) TableName() string { func (GroupMemberModel) TableName() string {
@ -42,33 +57,21 @@ func (GroupMemberModel) TableName() string {
} }
type GroupMemberModelInterface interface { type GroupMemberModelInterface interface {
NewTx(tx any) GroupMemberModelInterface //NewTx(tx any) GroupMemberModelInterface
Create(ctx context.Context, groupMembers []*GroupMemberModel) (err error) Create(ctx context.Context, groupMembers []*GroupMemberModel) (err error)
Delete(ctx context.Context, groupID string, userIDs []string) (err error) Delete(ctx context.Context, groupID string, userIDs []string) (err error)
DeleteGroup(ctx context.Context, groupIDs []string) (err error) //DeleteGroup(ctx context.Context, groupIDs []string) (err error)
Update(ctx context.Context, groupID string, userID string, data map[string]any) (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) (rowsAffected int64, err error) //UpdateRoleLevel(ctx context.Context, groupID string, userID string, roleLevel int32) (rowsAffected int64, err error)
Find( Find(ctx context.Context, groupIDs []string, userIDs []string, roleLevels []int32) (groupMembers []*GroupMemberModel, err error)
ctx context.Context,
groupIDs []string,
userIDs []string,
roleLevels []int32,
) (groupMembers []*GroupMemberModel, err error)
FindMemberUserID(ctx context.Context, groupID string) (userIDs []string, err error) FindMemberUserID(ctx context.Context, groupID string) (userIDs []string, err error)
Take(ctx context.Context, groupID string, userID string) (groupMember *GroupMemberModel, err error) Take(ctx context.Context, groupID string, userID string) (groupMember *GroupMemberModel, err error)
TakeOwner(ctx context.Context, groupID string) (groupMember *GroupMemberModel, err error) TakeOwner(ctx context.Context, groupID string) (groupMember *GroupMemberModel, err error)
SearchMember( SearchMember(ctx context.Context, keyword string, groupIDs []string, userIDs []string, roleLevels []int32, pagination pagination.Pagination) (total int64, groupList []*GroupMemberModel, err error)
ctx context.Context, //MapGroupMemberNum(ctx context.Context, groupIDs []string) (count map[string]uint32, err error)
keyword string, //FindJoinUserID(ctx context.Context, groupIDs []string) (groupUsers map[string][]string, err error)
groupIDs []string,
userIDs []string,
roleLevels []int32,
pageNumber, showNumber int32,
) (total uint32, groupList []*GroupMemberModel, err error)
MapGroupMemberNum(ctx context.Context, groupIDs []string) (count map[string]uint32, err error)
FindJoinUserID(ctx context.Context, groupIDs []string) (groupUsers map[string][]string, err error)
FindUserJoinedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) FindUserJoinedGroupID(ctx context.Context, userID string) (groupIDs []string, err error)
TakeGroupMemberNum(ctx context.Context, groupID string) (count int64, err error) TakeGroupMemberNum(ctx context.Context, groupID string) (count int64, err error)
FindUsersJoinedGroupID(ctx context.Context, userIDs []string) (map[string][]string, error) //FindUsersJoinedGroupID(ctx context.Context, userIDs []string) (map[string][]string, error)
FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error)
} }

@ -16,6 +16,7 @@ package relation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/pagination"
"time" "time"
) )
@ -23,18 +24,32 @@ const (
GroupRequestModelTableName = "group_requests" GroupRequestModelTableName = "group_requests"
) )
//type GroupRequestModel struct {
// UserID string `gorm:"column:user_id;primary_key;size:64"`
// GroupID string `gorm:"column:group_id;primary_key;size:64"`
// HandleResult int32 `gorm:"column:handle_result"`
// ReqMsg string `gorm:"column:req_msg;size:1024"`
// HandledMsg string `gorm:"column:handle_msg;size:1024"`
// ReqTime time.Time `gorm:"column:req_time"`
// HandleUserID string `gorm:"column:handle_user_id;size:64"`
// HandledTime time.Time `gorm:"column:handle_time"`
// JoinSource int32 `gorm:"column:join_source"`
// InviterUserID string `gorm:"column:inviter_user_id;size:64"`
// Ex string `gorm:"column:ex;size:1024"`
//}
type GroupRequestModel struct { type GroupRequestModel struct {
UserID string `gorm:"column:user_id;primary_key;size:64"` UserID string `bson:"user_id"`
GroupID string `gorm:"column:group_id;primary_key;size:64"` GroupID string `bson:"group_id"`
HandleResult int32 `gorm:"column:handle_result"` HandleResult int32 `bson:"handle_result"`
ReqMsg string `gorm:"column:req_msg;size:1024"` ReqMsg string `bson:"req_msg"`
HandledMsg string `gorm:"column:handle_msg;size:1024"` HandledMsg string `bson:"handled_msg"`
ReqTime time.Time `gorm:"column:req_time"` ReqTime time.Time `bson:"req_time"`
HandleUserID string `gorm:"column:handle_user_id;size:64"` HandleUserID string `bson:"handle_user_id"`
HandledTime time.Time `gorm:"column:handle_time"` HandledTime time.Time `bson:"handled_time"`
JoinSource int32 `gorm:"column:join_source"` JoinSource int32 `bson:"join_source"`
InviterUserID string `gorm:"column:inviter_user_id;size:64"` InviterUserID string `bson:"inviter_user_id"`
Ex string `gorm:"column:ex;size:1024"` Ex string `bson:"ex"`
} }
func (GroupRequestModel) TableName() string { func (GroupRequestModel) TableName() string {
@ -42,20 +57,12 @@ func (GroupRequestModel) TableName() string {
} }
type GroupRequestModelInterface interface { type GroupRequestModelInterface interface {
NewTx(tx any) GroupRequestModelInterface //NewTx(tx any) GroupRequestModelInterface
Create(ctx context.Context, groupRequests []*GroupRequestModel) (err error) Create(ctx context.Context, groupRequests []*GroupRequestModel) (err error)
Delete(ctx context.Context, groupID string, userID string) (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) UpdateHandler(ctx context.Context, groupID string, userID string, handledMsg string, handleResult int32) (err error)
Take(ctx context.Context, groupID string, userID string) (groupRequest *GroupRequestModel, err error) Take(ctx context.Context, groupID string, userID string) (groupRequest *GroupRequestModel, err error)
FindGroupRequests(ctx context.Context, groupID string, userIDs []string) (int64, []*GroupRequestModel, error) FindGroupRequests(ctx context.Context, groupID string, userIDs []string) (int64, []*GroupRequestModel, error)
Page( Page(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, groups []*GroupRequestModel, err error)
ctx context.Context, PageGroup(ctx context.Context, groupIDs []string, pagination pagination.Pagination) (total int64, groups []*GroupRequestModel, err error)
userID string,
pageNumber, showNumber int32,
) (total uint32, groups []*GroupRequestModel, err error)
PageGroup(
ctx context.Context,
groupIDs []string,
pageNumber, showNumber int32,
) (total uint32, groups []*GroupRequestModel, err error)
} }

@ -14,40 +14,40 @@
package unrelation package unrelation
import ( //import (
"context" // "context"
) //)
//
const ( //const (
CSuperGroup = "super_group" // CSuperGroup = "super_group"
CUserToSuperGroup = "user_to_super_group" // CUserToSuperGroup = "user_to_super_group"
) //)
//
type SuperGroupModel struct { //type SuperGroupModel struct {
GroupID string `bson:"group_id" json:"groupID"` // GroupID string `bson:"group_id" json:"groupID"`
MemberIDs []string `bson:"member_id_list" json:"memberIDList"` // MemberIDs []string `bson:"member_id_list" json:"memberIDList"`
} //}
//
func (SuperGroupModel) TableName() string { //func (SuperGroupModel) TableName() string {
return CSuperGroup // return CSuperGroup
} //}
//
type UserToSuperGroupModel struct { //type UserToSuperGroupModel struct {
UserID string `bson:"user_id" json:"userID"` // UserID string `bson:"user_id" json:"userID"`
GroupIDs []string `bson:"group_id_list" json:"groupIDList"` // GroupIDs []string `bson:"group_id_list" json:"groupIDList"`
} //}
//
func (UserToSuperGroupModel) TableName() string { //func (UserToSuperGroupModel) TableName() string {
return CUserToSuperGroup // return CUserToSuperGroup
} //}
//
type SuperGroupModelInterface interface { //type SuperGroupModelInterface interface {
CreateSuperGroup(ctx context.Context, groupID string, initMemberIDs []string) error // CreateSuperGroup(ctx context.Context, groupID string, initMemberIDs []string) error
TakeSuperGroup(ctx context.Context, groupID string) (group *SuperGroupModel, err error) // TakeSuperGroup(ctx context.Context, groupID string) (group *SuperGroupModel, err error)
FindSuperGroup(ctx context.Context, groupIDs []string) (groups []*SuperGroupModel, err error) // FindSuperGroup(ctx context.Context, groupIDs []string) (groups []*SuperGroupModel, err error)
AddUserToSuperGroup(ctx context.Context, groupID string, userIDs []string) error // AddUserToSuperGroup(ctx context.Context, groupID string, userIDs []string) error
RemoverUserFromSuperGroup(ctx context.Context, groupID string, userIDs []string) error // RemoverUserFromSuperGroup(ctx context.Context, groupID string, userIDs []string) error
GetSuperGroupByUserID(ctx context.Context, userID string) (*UserToSuperGroupModel, error) // GetSuperGroupByUserID(ctx context.Context, userID string) (*UserToSuperGroupModel, error)
DeleteSuperGroup(ctx context.Context, groupID string) error // DeleteSuperGroup(ctx context.Context, groupID string) error
RemoveGroupFromUser(ctx context.Context, groupID string, userIDs []string) error // RemoveGroupFromUser(ctx context.Context, groupID string, userIDs []string) error
} //}

Loading…
Cancel
Save