Implement sensitive word filtering feature with configurable database and Redis support. Add security enhancements including WebSocket origin validation, improved password strength requirements, timing attack prevention, and production JWT secret validation. Add batch operation validation limits and optimize user blocking queries with subqueries. BREAKING CHANGE: Password validation now requires minimum 8 characters with uppercase, lowercase, and digit (was 6-50 characters without complexity requirements)
703 lines
22 KiB
Go
703 lines
22 KiB
Go
package repository
|
||
|
||
import (
|
||
"carrot_bbs/internal/model"
|
||
"carrot_bbs/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
|
||
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)
|
||
}
|
||
|
||
// 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
|
||
})
|
||
}
|
||
|
||
// 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
|
||
}
|