package repository import ( "with_you/internal/model" "with_you/internal/pkg/cursor" "context" "errors" "gorm.io/gorm" ) // GroupRepository 群组仓库接口 type GroupRepository interface { // 群组操作 Create(group *model.Group) error GetByID(id string) (*model.Group, error) Update(group *model.Group) error Delete(id string) error GetByOwnerID(ownerID string, page, pageSize int) ([]model.Group, int64, error) TransferOwner(groupID, oldOwnerID, newOwnerID string) error // 群成员操作 AddMember(member *model.GroupMember) error BatchAddMembers(groupID string, members []*model.GroupMember) error GetMember(groupID string, userID string) (*model.GroupMember, error) GetMembers(groupID string, page, pageSize int) ([]model.GroupMember, int64, error) UpdateMember(member *model.GroupMember) error RemoveMember(groupID string, userID string) error GetMemberCount(groupID string) (int64, error) IsMember(groupID string, userID string) (bool, error) GetUserGroups(userID string, page, pageSize int) ([]model.Group, int64, error) // 角色相关 GetMemberRole(groupID string, userID string) (string, error) SetMemberRole(groupID string, userID string, role string) error GetAdmins(groupID string) ([]model.GroupMember, error) // 群公告操作 CreateAnnouncement(announcement *model.GroupAnnouncement) error GetAnnouncements(groupID string, page, pageSize int) ([]model.GroupAnnouncement, int64, error) GetAnnouncementByID(id string) (*model.GroupAnnouncement, error) DeleteAnnouncement(id string) error // 管理端群组操作 GetGroupList(page, pageSize int, keyword, status, ownerID, startDate, endDate string) ([]model.Group, int64, error) GetGroupWithOwner(groupID string) (*model.Group, error) GetLatestAnnouncement(groupID string) (*model.GroupAnnouncement, error) UpdateGroupStatus(groupID string, status string) error GetGroupPostCount(groupID string) (int64, error) GetMembersWithUserInfo(groupID string, page, pageSize int) ([]GroupMemberWithUser, int64, error) // 游标分页方法 GetGroupsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Group], error) GetUserGroupsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Group], error) GetMembersByCursor(ctx context.Context, groupID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.GroupMember], error) GetAnnouncementsByCursor(ctx context.Context, groupID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.GroupAnnouncement], error) // 批量操作 GetExistingMembers(groupID string, userIDs []string) (map[string]bool, error) } // GroupMemberWithUser 群成员带用户信息的结构 type GroupMemberWithUser struct { model.GroupMember User *model.User } // groupRepository 群组仓库实现 type groupRepository struct { db *gorm.DB } // NewGroupRepository 创建群组仓库 func NewGroupRepository(db *gorm.DB) GroupRepository { return &groupRepository{db: db} } // Create 创建群组 func (r *groupRepository) Create(group *model.Group) error { return r.db.Create(group).Error } // GetByID 根据ID获取群组 func (r *groupRepository) GetByID(id string) (*model.Group, error) { var group model.Group err := r.db.First(&group, "id = ?", id).Error if err != nil { return nil, err } return &group, nil } // Update 更新群组 func (r *groupRepository) Update(group *model.Group) error { return r.db.Save(group).Error } // TransferOwner 转让群主(在事务中更新群主和角色) func (r *groupRepository) TransferOwner(groupID, oldOwnerID, newOwnerID string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 更新群组的群主 if err := tx.Model(&model.Group{}).Where("id = ?", groupID).Update("owner_id", newOwnerID).Error; err != nil { return err } // 更新原群主为管理员 if err := tx.Model(&model.GroupMember{}). Where("group_id = ? AND user_id = ?", groupID, oldOwnerID). Update("role", model.GroupRoleAdmin).Error; err != nil { return err } // 更新新群主为群主 if err := tx.Model(&model.GroupMember{}). Where("group_id = ? AND user_id = ?", groupID, newOwnerID). Update("role", model.GroupRoleOwner).Error; err != nil { return err } return nil }) } // Delete 删除群组 func (r *groupRepository) Delete(id string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 删除群成员 if err := tx.Where("group_id = ?", id).Delete(&model.GroupMember{}).Error; err != nil { return err } // 删除群公告 if err := tx.Where("group_id = ?", id).Delete(&model.GroupAnnouncement{}).Error; err != nil { return err } // 删除群组 if err := tx.Delete(&model.Group{}, "id = ?", id).Error; err != nil { return err } return nil }) } // GetByOwnerID 根据群主ID获取群组列表 func (r *groupRepository) GetByOwnerID(ownerID string, page, pageSize int) ([]model.Group, int64, error) { var groups []model.Group var total int64 query := r.db.Model(&model.Group{}).Where("owner_id = ?", ownerID) query.Count(&total) offset := (page - 1) * pageSize err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&groups).Error return groups, total, err } // AddMember 添加群成员 func (r *groupRepository) AddMember(member *model.GroupMember) error { return r.db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(member).Error; err != nil { return err } return tx.Model(&model.Group{}).Where("id = ?", member.GroupID). Update("member_count", gorm.Expr("member_count + ?", 1)).Error }) } func (r *groupRepository) BatchAddMembers(groupID string, members []*model.GroupMember) error { if len(members) == 0 { return nil } return r.db.Transaction(func(tx *gorm.DB) error { if err := tx.CreateInBatches(members, 100).Error; err != nil { return err } return tx.Model(&model.Group{}).Where("id = ?", groupID). Update("member_count", gorm.Expr("member_count + ?", len(members))).Error }) } // GetMember 获取群成员 func (r *groupRepository) GetMember(groupID string, userID string) (*model.GroupMember, error) { var member model.GroupMember err := r.db.First(&member, "group_id = ? AND user_id = ?", groupID, userID).Error if err != nil { return nil, err } return &member, nil } // GetMembers 获取群成员列表 func (r *groupRepository) GetMembers(groupID string, page, pageSize int) ([]model.GroupMember, int64, error) { var members []model.GroupMember var total int64 query := r.db.Model(&model.GroupMember{}).Where("group_id = ?", groupID) query.Count(&total) offset := (page - 1) * pageSize err := query.Offset(offset).Limit(pageSize).Order("created_at ASC").Find(&members).Error return members, total, err } // UpdateMember 更新群成员 func (r *groupRepository) UpdateMember(member *model.GroupMember) error { return r.db.Save(member).Error } // RemoveMember 移除群成员 func (r *groupRepository) RemoveMember(groupID string, userID string) error { return r.db.Transaction(func(tx *gorm.DB) error { // 删除成员 if err := tx.Where("group_id = ? AND user_id = ?", groupID, userID).Delete(&model.GroupMember{}).Error; err != nil { return err } // 更新群组成员数量 return tx.Model(&model.Group{}).Where("id = ?", groupID). Update("member_count", gorm.Expr("member_count - ?", 1)).Error }) } // GetMemberCount 获取群成员数量 func (r *groupRepository) GetMemberCount(groupID string) (int64, error) { var count int64 err := r.db.Model(&model.GroupMember{}).Where("group_id = ?", groupID).Count(&count).Error return count, err } // IsMember 检查是否是群成员 func (r *groupRepository) IsMember(groupID string, userID string) (bool, error) { var count int64 err := r.db.Model(&model.GroupMember{}).Where("group_id = ? AND user_id = ?", groupID, userID).Count(&count).Error return count > 0, err } // GetExistingMembers 批量检查用户是否是群成员(解决 N+1 问题) // 返回 map[userID]bool func (r *groupRepository) GetExistingMembers(groupID string, userIDs []string) (map[string]bool, error) { result := make(map[string]bool) if len(userIDs) == 0 || groupID == "" { return result, nil } // 初始化所有用户为 false for _, id := range userIDs { result[id] = false } // 一次性查询所有群成员记录 var existingIDs []string err := r.db.Model(&model.GroupMember{}). Where("group_id = ? AND user_id IN ?", groupID, userIDs). Pluck("user_id", &existingIDs).Error if err != nil { return nil, err } // 更新已存在的成员 for _, id := range existingIDs { result[id] = true } return result, nil } // GetUserGroups 获取用户加入的群组列表 func (r *groupRepository) GetUserGroups(userID string, page, pageSize int) ([]model.Group, int64, error) { var groups []model.Group var total int64 // 通过群成员表查询用户加入的群组 subQuery := r.db.Model(&model.GroupMember{}). Select("group_id"). Where("user_id = ?", userID) query := r.db.Model(&model.Group{}).Where("id IN (?)", subQuery) query.Count(&total) offset := (page - 1) * pageSize err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&groups).Error return groups, total, err } // GetMemberRole 获取成员角色 func (r *groupRepository) GetMemberRole(groupID string, userID string) (string, error) { member, err := r.GetMember(groupID, userID) if err != nil { return "", err } return member.Role, nil } // SetMemberRole 设置成员角色 func (r *groupRepository) SetMemberRole(groupID string, userID string, role string) error { return r.db.Model(&model.GroupMember{}). Where("group_id = ? AND user_id = ?", groupID, userID). Update("role", role).Error } // GetAdmins 获取群管理员列表 func (r *groupRepository) GetAdmins(groupID string) ([]model.GroupMember, error) { var admins []model.GroupMember err := r.db.Where("group_id = ? AND role = ?", groupID, model.GroupRoleAdmin).Find(&admins).Error return admins, err } // CreateAnnouncement 创建群公告 func (r *groupRepository) CreateAnnouncement(announcement *model.GroupAnnouncement) error { return r.db.Create(announcement).Error } // GetAnnouncements 获取群公告列表 func (r *groupRepository) GetAnnouncements(groupID string, page, pageSize int) ([]model.GroupAnnouncement, int64, error) { var announcements []model.GroupAnnouncement var total int64 query := r.db.Model(&model.GroupAnnouncement{}).Where("group_id = ?", groupID) query.Count(&total) offset := (page - 1) * pageSize // 置顶的排在前面,然后按时间倒序 err := query.Offset(offset).Limit(pageSize).Order("is_pinned DESC, created_at DESC").Find(&announcements).Error return announcements, total, err } // GetAnnouncementByID 根据ID获取群公告 func (r *groupRepository) GetAnnouncementByID(id string) (*model.GroupAnnouncement, error) { var announcement model.GroupAnnouncement err := r.db.First(&announcement, "id = ?", id).Error if err != nil { return nil, err } return &announcement, nil } // DeleteAnnouncement 删除群公告 func (r *groupRepository) DeleteAnnouncement(id string) error { return r.db.Delete(&model.GroupAnnouncement{}, "id = ?", id).Error } // ==================== 管理端群组操作 ==================== // GetGroupList 获取群组列表(管理端) func (r *groupRepository) GetGroupList(page, pageSize int, keyword, status, ownerID, startDate, endDate string) ([]model.Group, int64, error) { var groups []model.Group var total int64 query := r.db.Model(&model.Group{}) // 关键词搜索 if keyword != "" { query = query.Where("name LIKE ? OR description LIKE ?", "%"+keyword+"%", "%"+keyword+"%") } // 状态筛选(这里假设群组有status字段,如果没有则忽略) // 注意:当前Group模型没有status字段,状态通过是否解散来判断 // 如果需要status字段,需要在模型中添加 // 群主筛选 if ownerID != "" { query = query.Where("owner_id = ?", ownerID) } // 日期筛选 if startDate != "" { query = query.Where("created_at >= ?", startDate+" 00:00:00") } if endDate != "" { query = query.Where("created_at <= ?", endDate+" 23:59:59") } query.Count(&total) offset := (page - 1) * pageSize err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&groups).Error return groups, total, err } // GetGroupWithOwner 获取群组详情(包含群主信息) func (r *groupRepository) GetGroupWithOwner(groupID string) (*model.Group, error) { var group model.Group err := r.db.First(&group, "id = ?", groupID).Error if err != nil { return nil, err } return &group, nil } // GetLatestAnnouncement 获取群组最新公告 func (r *groupRepository) GetLatestAnnouncement(groupID string) (*model.GroupAnnouncement, error) { var announcement model.GroupAnnouncement err := r.db.Where("group_id = ?", groupID). Order("is_pinned DESC, created_at DESC"). First(&announcement).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } return nil, err } return &announcement, nil } // UpdateGroupStatus 更新群组状态 func (r *groupRepository) UpdateGroupStatus(groupID string, status string) error { // 注意:当前Group模型没有status字段 // 这里我们通过更新一个虚拟字段或者使用其他方式来实现 // 暂时通过更新updated_at来模拟状态更新 // 如果需要真正的状态字段,需要在模型中添加 return r.db.Model(&model.Group{}).Where("id = ?", groupID). Update("updated_at", gorm.Expr("NOW()")).Error } // GetGroupPostCount 获取群组帖子数量 func (r *groupRepository) GetGroupPostCount(groupID string) (int64, error) { var count int64 // 假设帖子表有group_id字段 err := r.db.Model(&model.Post{}).Where("group_id = ?", groupID).Count(&count).Error return count, err } // GetMembersWithUserInfo 获取群成员列表(包含用户信息) func (r *groupRepository) GetMembersWithUserInfo(groupID string, page, pageSize int) ([]GroupMemberWithUser, int64, error) { var members []GroupMemberWithUser var total int64 // 先获取总数 err := r.db.Model(&model.GroupMember{}).Where("group_id = ?", groupID).Count(&total).Error if err != nil { return nil, 0, err } // 联表查询获取成员和用户信息 offset := (page - 1) * pageSize query := ` SELECT gm.*, u.id as user_id, u.username, u.nickname, u.avatar FROM group_members gm LEFT JOIN users u ON gm.user_id = u.id WHERE gm.group_id = ? ORDER BY CASE gm.role WHEN 'owner' THEN 1 WHEN 'admin' THEN 2 WHEN 'member' THEN 3 END, gm.join_time ASC LIMIT ? OFFSET ? ` type memberRow struct { ID string `gorm:"column:id"` GroupID string `gorm:"column:group_id"` UserID string `gorm:"column:user_id"` Role string `gorm:"column:role"` Nickname string `gorm:"column:nickname"` Muted bool `gorm:"column:muted"` JoinTime string `gorm:"column:join_time"` CreatedAt string `gorm:"column:created_at"` UpdatedAt string `gorm:"column:updated_at"` // 用户信息 Username string `gorm:"column:username"` UserNickname string `gorm:"column:nickname"` UserAvatar string `gorm:"column:avatar"` } var rows []memberRow err = r.db.Raw(query, groupID, pageSize, offset).Scan(&rows).Error if err != nil { return nil, 0, err } members = make([]GroupMemberWithUser, 0, len(rows)) for _, row := range rows { member := model.GroupMember{ ID: row.ID, GroupID: row.GroupID, UserID: row.UserID, Role: row.Role, Nickname: row.Nickname, Muted: row.Muted, } user := &model.User{ ID: row.UserID, Username: row.Username, Nickname: row.UserNickname, Avatar: row.UserAvatar, } members = append(members, GroupMemberWithUser{ GroupMember: member, User: user, }) } return members, total, nil } // ========== Cursor Pagination Methods ========== // GetGroupsByCursor 游标分页获取群组列表 // 排序方式:created_at DESC func (r *groupRepository) GetGroupsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Group], error) { // 构建基础查询 query := r.db.WithContext(ctx).Model(&model.Group{}) // 使用游标构建器 builder := cursor.NewBuilder(query, cursor.SortByCreatedAtDesc). WithCursor(req.Cursor, req.Direction). WithPageSize(req.PageSize) if builder.Error() != nil { // 无效游标,返回空列表 return cursor.NewCursorPageResult([]*model.Group{}, "", "", false), nil } // 执行查询 var groups []*model.Group query = builder.Build() if err := query.Find(&groups).Error; err != nil { return nil, err } // 构建响应 pageSize := builder.GetPageSize() hasMore := cursor.HasMore(len(groups), pageSize) if hasMore { groups = groups[:pageSize] } // 生成游标 var nextCursor, prevCursor string if len(groups) > 0 { // 下一页游标 if hasMore { lastGroup := groups[len(groups)-1] nextCursor = cursor.NewCursor( cursor.FormatTime(lastGroup.CreatedAt), lastGroup.ID, cursor.SortByCreatedAtDesc, ).Encode() } // 上一页游标 firstGroup := groups[0] prevCursor = cursor.NewCursor( cursor.FormatTime(firstGroup.CreatedAt), firstGroup.ID, cursor.SortByCreatedAtDesc, ).Encode() } return cursor.NewCursorPageResult(groups, nextCursor, prevCursor, hasMore), nil } // GetUserGroupsByCursor 游标分页获取用户加入的群组列表 // 排序方式:created_at DESC func (r *groupRepository) GetUserGroupsByCursor(ctx context.Context, userID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Group], error) { // 通过群成员表查询用户加入的群组 subQuery := r.db.Model(&model.GroupMember{}). Select("group_id"). Where("user_id = ?", userID) // 构建基础查询 query := r.db.WithContext(ctx).Model(&model.Group{}).Where("id IN (?)", subQuery) // 使用游标构建器 builder := cursor.NewBuilder(query, cursor.SortByCreatedAtDesc). WithCursor(req.Cursor, req.Direction). WithPageSize(req.PageSize) if builder.Error() != nil { // 无效游标,返回空列表 return cursor.NewCursorPageResult([]*model.Group{}, "", "", false), nil } // 执行查询 var groups []*model.Group query = builder.Build() if err := query.Find(&groups).Error; err != nil { return nil, err } // 构建响应 pageSize := builder.GetPageSize() hasMore := cursor.HasMore(len(groups), pageSize) if hasMore { groups = groups[:pageSize] } // 生成游标 var nextCursor, prevCursor string if len(groups) > 0 { // 下一页游标 if hasMore { lastGroup := groups[len(groups)-1] nextCursor = cursor.NewCursor( cursor.FormatTime(lastGroup.CreatedAt), lastGroup.ID, cursor.SortByCreatedAtDesc, ).Encode() } // 上一页游标 firstGroup := groups[0] prevCursor = cursor.NewCursor( cursor.FormatTime(firstGroup.CreatedAt), firstGroup.ID, cursor.SortByCreatedAtDesc, ).Encode() } return cursor.NewCursorPageResult(groups, nextCursor, prevCursor, hasMore), nil } // GetMembersByCursor 游标分页获取群成员列表 // 排序方式:join_time DESC(新成员优先) func (r *groupRepository) GetMembersByCursor(ctx context.Context, groupID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.GroupMember], error) { // 构建基础查询 query := r.db.WithContext(ctx).Model(&model.GroupMember{}).Where("group_id = ?", groupID) // 使用游标构建器 builder := cursor.NewBuilder(query, cursor.SortByJoinTimeDesc). WithCursor(req.Cursor, req.Direction). WithPageSize(req.PageSize) if builder.Error() != nil { // 无效游标,返回空列表 return cursor.NewCursorPageResult([]*model.GroupMember{}, "", "", false), nil } // 执行查询 var members []*model.GroupMember query = builder.Build() if err := query.Find(&members).Error; err != nil { return nil, err } // 构建响应 pageSize := builder.GetPageSize() hasMore := cursor.HasMore(len(members), pageSize) if hasMore { members = members[:pageSize] } // 生成游标 var nextCursor, prevCursor string if len(members) > 0 { // 下一页游标 if hasMore { lastMember := members[len(members)-1] nextCursor = cursor.NewCursor( cursor.FormatTime(lastMember.JoinTime), lastMember.ID, cursor.SortByJoinTimeDesc, ).Encode() } // 上一页游标 firstMember := members[0] prevCursor = cursor.NewCursor( cursor.FormatTime(firstMember.JoinTime), firstMember.ID, cursor.SortByJoinTimeDesc, ).Encode() } return cursor.NewCursorPageResult(members, nextCursor, prevCursor, hasMore), nil } // GetAnnouncementsByCursor 游标分页获取群公告列表 // 排序方式:is_pinned DESC, created_at DESC(置顶优先,然后按时间倒序) // 注意:由于有置顶逻辑,这里使用 created_at DESC 作为游标排序 func (r *groupRepository) GetAnnouncementsByCursor(ctx context.Context, groupID string, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.GroupAnnouncement], error) { // 构建基础查询 query := r.db.WithContext(ctx).Model(&model.GroupAnnouncement{}).Where("group_id = ?", groupID) // 使用游标构建器 builder := cursor.NewBuilder(query, cursor.SortByCreatedAtDesc). WithCursor(req.Cursor, req.Direction). WithPageSize(req.PageSize) if builder.Error() != nil { // 无效游标,返回空列表 return cursor.NewCursorPageResult([]*model.GroupAnnouncement{}, "", "", false), nil } // 执行查询(置顶的排在前面) var announcements []*model.GroupAnnouncement query = builder.Build().Order("is_pinned DESC, created_at DESC") if err := query.Find(&announcements).Error; err != nil { return nil, err } // 构建响应 pageSize := builder.GetPageSize() hasMore := cursor.HasMore(len(announcements), pageSize) if hasMore { announcements = announcements[:pageSize] } // 生成游标 var nextCursor, prevCursor string if len(announcements) > 0 { // 下一页游标 if hasMore { lastAnnouncement := announcements[len(announcements)-1] nextCursor = cursor.NewCursor( cursor.FormatTime(lastAnnouncement.CreatedAt), lastAnnouncement.ID, cursor.SortByCreatedAtDesc, ).Encode() } // 上一页游标 firstAnnouncement := announcements[0] prevCursor = cursor.NewCursor( cursor.FormatTime(firstAnnouncement.CreatedAt), firstAnnouncement.ID, cursor.SortByCreatedAtDesc, ).Encode() } return cursor.NewCursorPageResult(announcements, nextCursor, prevCursor, hasMore), nil }