feat(repository): enhance repository methods with batch processing and transaction support
- Updated Like and Unlike methods in CommentRepository to use transactions for data consistency. - Introduced IsLikedBatch method for batch checking if comments are liked, addressing N+1 query issues. - Enhanced BatchDelete method in PostRepository to perform batch deletions within a single transaction, improving efficiency. - Added BatchUpdateStatus method for posts to utilize a single SQL update for status changes, reducing database load. - Implemented BatchUpdateSortOrder in StickerRepository using CASE WHEN for efficient sorting updates. - Added IsFollowingBatch and IsBlockedBatch methods in UserRepository for batch checking follow and block relationships, optimizing performance.
This commit is contained in:
@@ -255,46 +255,49 @@ func (r *CommentRepository) GetReplies(parentID string) ([]*model.Comment, error
|
||||
return comments, err
|
||||
}
|
||||
|
||||
// Like 点赞评论
|
||||
// Like 点赞评论(使用事务保证数据一致性)
|
||||
func (r *CommentRepository) Like(commentID, userID string) error {
|
||||
// 检查是否已经点赞
|
||||
var existing model.CommentLike
|
||||
err := r.db.Where("comment_id = ? AND user_id = ?", commentID, userID).First(&existing).Error
|
||||
if err == nil {
|
||||
// 已经点赞
|
||||
return nil
|
||||
}
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 检查是否已经点赞
|
||||
var existing model.CommentLike
|
||||
err := tx.Where("comment_id = ? AND user_id = ?", commentID, userID).First(&existing).Error
|
||||
if err == nil {
|
||||
// 已经点赞
|
||||
return nil
|
||||
}
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建点赞记录
|
||||
like := &model.CommentLike{
|
||||
CommentID: commentID,
|
||||
UserID: userID,
|
||||
}
|
||||
err = r.db.Create(like).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 创建点赞记录
|
||||
like := &model.CommentLike{
|
||||
CommentID: commentID,
|
||||
UserID: userID,
|
||||
}
|
||||
if err := tx.Create(like).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 增加评论点赞数
|
||||
return r.db.Model(&model.Comment{}).Where("id = ?", commentID).
|
||||
UpdateColumn("likes_count", gorm.Expr("likes_count + 1")).Error
|
||||
// 增加评论点赞数
|
||||
return tx.Model(&model.Comment{}).Where("id = ?", commentID).
|
||||
UpdateColumn("likes_count", gorm.Expr("likes_count + 1")).Error
|
||||
})
|
||||
}
|
||||
|
||||
// Unlike 取消点赞评论
|
||||
// Unlike 取消点赞评论(使用事务保证数据一致性)
|
||||
func (r *CommentRepository) Unlike(commentID, userID string) error {
|
||||
result := r.db.Where("comment_id = ? AND user_id = ?", commentID, userID).Delete(&model.CommentLike{})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected > 0 {
|
||||
// 减少评论点赞数
|
||||
return r.db.Model(&model.Comment{}).Where("id = ?", commentID).
|
||||
UpdateColumn("likes_count", gorm.Expr("likes_count - 1")).Error
|
||||
}
|
||||
return nil
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Where("comment_id = ? AND user_id = ?", commentID, userID).Delete(&model.CommentLike{})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected > 0 {
|
||||
// 减少评论点赞数
|
||||
return tx.Model(&model.Comment{}).Where("id = ?", commentID).
|
||||
UpdateColumn("likes_count", gorm.Expr("likes_count - 1")).Error
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// IsLiked 检查是否已点赞
|
||||
@@ -304,6 +307,33 @@ func (r *CommentRepository) IsLiked(commentID, userID string) bool {
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// IsLikedBatch 批量检查是否点赞(解决 N+1 问题)
|
||||
// 返回 map[commentID]bool
|
||||
func (r *CommentRepository) IsLikedBatch(commentIDs []string, userID string) map[string]bool {
|
||||
result := make(map[string]bool)
|
||||
if len(commentIDs) == 0 || userID == "" {
|
||||
return result
|
||||
}
|
||||
|
||||
// 初始化所有 commentID 为 false
|
||||
for _, commentID := range commentIDs {
|
||||
result[commentID] = false
|
||||
}
|
||||
|
||||
// 一次性查询所有点赞记录
|
||||
var likedCommentIDs []string
|
||||
r.db.Model(&model.CommentLike{}).
|
||||
Where("comment_id IN ? AND user_id = ?", commentIDs, userID).
|
||||
Pluck("comment_id", &likedCommentIDs)
|
||||
|
||||
// 更新已点赞的评论
|
||||
for _, commentID := range likedCommentIDs {
|
||||
result[commentID] = true
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// AdminCommentListFilter 管理端评论列表筛选条件
|
||||
type AdminCommentListFilter struct {
|
||||
Keyword string
|
||||
@@ -367,17 +397,58 @@ func (r *CommentRepository) GetAdminCommentByID(id string) (*model.Comment, erro
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除评论
|
||||
// BatchDelete 批量删除评论(使用单次事务批量删除,避免 N+1 问题)
|
||||
func (r *CommentRepository) BatchDelete(ids []string) ([]string, error) {
|
||||
var failedIDs []string
|
||||
|
||||
for _, id := range ids {
|
||||
if err := r.Delete(id); err != nil {
|
||||
failedIDs = append(failedIDs, id)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return failedIDs, nil
|
||||
// 先查询所有评论获取 post_id 和状态信息
|
||||
type commentInfo struct {
|
||||
ID string
|
||||
PostID string
|
||||
Status model.CommentStatus
|
||||
}
|
||||
var comments []commentInfo
|
||||
if err := r.db.Model(&model.Comment{}).Where("id IN ?", ids).Scan(&comments).Error; err != nil {
|
||||
return ids, err
|
||||
}
|
||||
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 批量删除评论点赞记录
|
||||
if err := tx.Where("comment_id IN ?", ids).Delete(&model.CommentLike{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 批量删除评论(软删除)
|
||||
if err := tx.Where("id IN ?", ids).Delete(&model.Comment{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新帖子的评论数(仅统计已发布评论)
|
||||
postCountMap := make(map[string]int)
|
||||
for _, c := range comments {
|
||||
if c.Status == model.CommentStatusPublished {
|
||||
postCountMap[c.PostID]++
|
||||
}
|
||||
}
|
||||
for postID, count := range postCountMap {
|
||||
if err := tx.Model(&model.Post{}).Where("id = ?", postID).
|
||||
UpdateColumns(map[string]any{
|
||||
"comments_count": gorm.Expr("comments_count - ?", count),
|
||||
"updated_at": gorm.Expr("updated_at"),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return ids, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetCommentsByPostIDForAdmin 管理端获取帖子的评论列表(包含所有状态)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -188,6 +189,36 @@ func (r *groupRepository) IsMember(groupID string, userID string) (bool, 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
|
||||
@@ -318,7 +349,7 @@ func (r *groupRepository) GetLatestAnnouncement(groupID string) (*model.GroupAnn
|
||||
Order("is_pinned DESC, created_at DESC").
|
||||
First(&announcement).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
|
||||
@@ -107,7 +107,7 @@ func (r *LoginLogRepository) DeleteOldLogs(ctx context.Context, beforeDate strin
|
||||
return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.LoginLog{}).Error
|
||||
}
|
||||
|
||||
// GetStatistics 获取登录日志统计
|
||||
// GetStatistics 获取登录日志统计(使用单次 GROUP BY 查询,避免多次 COUNT)
|
||||
func (r *LoginLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*LoginLogStatistics, error) {
|
||||
stats := &LoginLogStatistics{}
|
||||
|
||||
@@ -119,20 +119,37 @@ func (r *LoginLogRepository) GetStatistics(ctx context.Context, startTime, endTi
|
||||
query = query.Where("created_at <= ?", endTime)
|
||||
}
|
||||
|
||||
if err := query.Count(&stats.TotalCount).Error; err != nil {
|
||||
// 使用单次 GROUP BY 查询获取 result 计数
|
||||
type resultCount struct {
|
||||
Result string
|
||||
Count int64
|
||||
}
|
||||
var results []resultCount
|
||||
err := query.Select("result, count(*) as count").Group("result").Scan(&results).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := query.Where("result = ?", string(model.LoginResultSuccess)).Count(&stats.SuccessCount).Error; err != nil {
|
||||
return nil, err
|
||||
// 汇总结果
|
||||
for _, r := range results {
|
||||
stats.TotalCount += r.Count
|
||||
if r.Result == string(model.LoginResultSuccess) {
|
||||
stats.SuccessCount = r.Count
|
||||
} else if r.Result == string(model.LoginResultFail) {
|
||||
stats.FailCount = r.Count
|
||||
}
|
||||
}
|
||||
|
||||
if err := query.Where("result = ?", string(model.LoginResultFail)).Count(&stats.FailCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := query.Where("event = ?", string(model.LoginEventLogin)).Count(&stats.LoginCount).Error; err != nil {
|
||||
return nil, err
|
||||
// 单独查询登录次数
|
||||
if startTime != "" || endTime != "" {
|
||||
loginQuery := r.db.WithContext(ctx).Model(&model.LoginLog{})
|
||||
if startTime != "" {
|
||||
loginQuery = loginQuery.Where("created_at >= ?", startTime)
|
||||
}
|
||||
if endTime != "" {
|
||||
loginQuery = loginQuery.Where("created_at <= ?", endTime)
|
||||
}
|
||||
loginQuery.Where("event = ?", string(model.LoginEventLogin)).Count(&stats.LoginCount)
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -188,7 +189,7 @@ func (r *MessageRepository) GetParticipant(conversationID string, userID string)
|
||||
err := r.db.Where("conversation_id = ? AND user_id = ?", conversationID, userID).First(&participant).Error
|
||||
if err != nil {
|
||||
// 如果找不到参与者,尝试添加(修复没有参与者记录的问题)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// 检查会话是否存在
|
||||
var conv model.Conversation
|
||||
if err := r.db.Where("id = ?", conversationID).First(&conv).Error; err == nil {
|
||||
@@ -513,7 +514,7 @@ func (r *MessageRepository) DeleteConversationByGroupID(groupID string) error {
|
||||
conv, err := r.GetConversationByGroupID(groupID)
|
||||
if err != nil {
|
||||
// 如果会话不存在,直接返回
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -89,7 +89,7 @@ func (r *OperationLogRepository) DeleteOldLogs(ctx context.Context, beforeDate s
|
||||
return r.db.WithContext(ctx).Where("created_at < ?", beforeDate).Delete(&model.OperationLog{}).Error
|
||||
}
|
||||
|
||||
// GetStatistics 获取操作日志统计
|
||||
// GetStatistics 获取操作日志统计(使用单次 GROUP BY 查询,避免多次 COUNT)
|
||||
func (r *OperationLogRepository) GetStatistics(ctx context.Context, startTime, endTime string) (*OperationLogStatistics, error) {
|
||||
stats := &OperationLogStatistics{}
|
||||
|
||||
@@ -101,16 +101,25 @@ func (r *OperationLogRepository) GetStatistics(ctx context.Context, startTime, e
|
||||
query = query.Where("created_at <= ?", endTime)
|
||||
}
|
||||
|
||||
if err := query.Count(&stats.TotalCount).Error; err != nil {
|
||||
// 使用单次 GROUP BY 查询获取各状态计数
|
||||
type statusCount struct {
|
||||
Status string
|
||||
Count int64
|
||||
}
|
||||
var results []statusCount
|
||||
err := query.Select("status, count(*) as count").Group("status").Scan(&results).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := query.Where("status = ?", "success").Count(&stats.SuccessCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := query.Where("status = ?", "fail").Count(&stats.FailCount).Error; err != nil {
|
||||
return nil, err
|
||||
// 汇总结果
|
||||
for _, r := range results {
|
||||
stats.TotalCount += r.Count
|
||||
if r.Status == "success" {
|
||||
stats.SuccessCount = r.Count
|
||||
} else if r.Status == "fail" {
|
||||
stats.FailCount = r.Count
|
||||
}
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
|
||||
@@ -802,30 +802,101 @@ func (r *PostRepository) GetByIDForAdmin(id string) (*model.Post, error) {
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除帖子
|
||||
// BatchDelete 批量删除帖子(使用单次事务批量删除,避免 N+1 问题)
|
||||
func (r *PostRepository) BatchDelete(ids []string) ([]string, error) {
|
||||
var failedIDs []string
|
||||
|
||||
for _, id := range ids {
|
||||
if err := r.Delete(id); err != nil {
|
||||
failedIDs = append(failedIDs, id)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return failedIDs, nil
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 批量删除帖子图片
|
||||
if err := tx.Where("post_id IN ?", ids).Delete(&model.PostImage{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 批量删除帖子点赞记录
|
||||
if err := tx.Where("post_id IN ?", ids).Delete(&model.PostLike{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 批量删除帖子收藏记录
|
||||
if err := tx.Where("post_id IN ?", ids).Delete(&model.Favorite{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 批量删除评论点赞记录(子查询获取所有评论ID)
|
||||
if err := tx.Where("comment_id IN (SELECT id FROM comments WHERE post_id IN ?)", ids).Delete(&model.CommentLike{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 批量删除帖子评论
|
||||
if err := tx.Where("post_id IN ?", ids).Delete(&model.Comment{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 批量删除投票选项
|
||||
if err := tx.Where("post_id IN ?", ids).Delete(&model.VoteOption{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 批量删除用户投票记录
|
||||
if err := tx.Where("post_id IN ?", ids).Delete(&model.UserVote{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 最后批量删除帖子本身(软删除)
|
||||
return tx.Where("id IN ?", ids).Delete(&model.Post{}).Error
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return ids, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// BatchUpdateStatus 批量更新帖子审核状态
|
||||
// BatchUpdateStatus 批量更新帖子审核状态(使用单条 SQL 批量更新,避免 N+1 问题)
|
||||
func (r *PostRepository) BatchUpdateStatus(ids []string, status model.PostStatus, reviewedBy string) ([]string, error) {
|
||||
var failedIDs []string
|
||||
|
||||
for _, id := range ids {
|
||||
if err := r.UpdateModerationStatus(id, status, "", reviewedBy); err != nil {
|
||||
failedIDs = append(failedIDs, id)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return failedIDs, nil
|
||||
// 使用单条 SQL 批量更新
|
||||
result := r.db.Model(&model.Post{}).
|
||||
Where("id IN ?", ids).
|
||||
Updates(map[string]any{
|
||||
"status": status,
|
||||
"reviewed_at": gorm.Expr("CURRENT_TIMESTAMP"),
|
||||
"reviewed_by": reviewedBy,
|
||||
"reject_reason": "",
|
||||
"updated_at": gorm.Expr("updated_at"),
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
return ids, result.Error
|
||||
}
|
||||
|
||||
// 返回实际更新的帖子数量
|
||||
if result.RowsAffected < int64(len(ids)) {
|
||||
// 部分帖子可能不存在,查询实际更新的 ID
|
||||
var updatedIDs []string
|
||||
r.db.Model(&model.Post{}).Where("id IN ?", ids).Pluck("id", &updatedIDs)
|
||||
|
||||
// 找出未更新的 ID
|
||||
updatedMap := make(map[string]bool, len(updatedIDs))
|
||||
for _, id := range updatedIDs {
|
||||
updatedMap[id] = true
|
||||
}
|
||||
|
||||
var failedIDs []string
|
||||
for _, id := range ids {
|
||||
if !updatedMap[id] {
|
||||
failedIDs = append(failedIDs, id)
|
||||
}
|
||||
}
|
||||
return failedIDs, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// UpdatePinStatus 更新帖子置顶状态
|
||||
|
||||
@@ -2,6 +2,7 @@ package repository
|
||||
|
||||
import (
|
||||
"carrot_bbs/internal/model"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -88,18 +89,31 @@ func (r *stickerRepository) UpdateSortOrder(id string, sortOrder int) error {
|
||||
Update("sort_order", sortOrder).Error
|
||||
}
|
||||
|
||||
// BatchUpdateSortOrder 批量更新排序
|
||||
// BatchUpdateSortOrder 批量更新排序(使用 CASE WHEN 批量更新,避免 N+1 问题)
|
||||
func (r *stickerRepository) BatchUpdateSortOrder(userID string, orders map[string]int) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
for id, sortOrder := range orders {
|
||||
if err := tx.Model(&model.UserSticker{}).
|
||||
Where("id = ? AND user_id = ?", id, userID).
|
||||
Update("sort_order", sortOrder).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(orders) == 0 {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// 构建 CASE WHEN 批量更新 SQL
|
||||
var cases []string
|
||||
var args []any
|
||||
|
||||
for id, sortOrder := range orders {
|
||||
cases = append(cases, "WHEN id = ? THEN ?")
|
||||
args = append(args, id, sortOrder)
|
||||
}
|
||||
|
||||
ids := make([]string, 0, len(orders))
|
||||
for id := range orders {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
sql := `UPDATE user_stickers SET sort_order = CASE ` +
|
||||
strings.Join(cases, " ") + ` END WHERE id IN ? AND user_id = ?`
|
||||
args = append(args, ids, userID)
|
||||
|
||||
return r.db.Exec(sql, args...).Error
|
||||
}
|
||||
|
||||
// CountByUserID 获取用户表情数量
|
||||
|
||||
@@ -140,6 +140,36 @@ func (r *UserRepository) IsFollowing(followerID, followingID string) (bool, erro
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// IsFollowingBatch 批量检查当前用户是否关注了目标用户(解决 N+1 问题)
|
||||
// 返回 map[targetUserID]bool
|
||||
func (r *UserRepository) IsFollowingBatch(followerID string, targetUserIDs []string) (map[string]bool, error) {
|
||||
result := make(map[string]bool)
|
||||
if len(targetUserIDs) == 0 || followerID == "" {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 初始化所有目标用户为 false
|
||||
for _, id := range targetUserIDs {
|
||||
result[id] = false
|
||||
}
|
||||
|
||||
// 一次性查询所有关注记录
|
||||
var followingIDs []string
|
||||
err := r.db.Model(&model.Follow{}).
|
||||
Where("follower_id = ? AND following_id IN ?", followerID, targetUserIDs).
|
||||
Pluck("following_id", &followingIDs).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新已关注的用户
|
||||
for _, id := range followingIDs {
|
||||
result[id] = true
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// IncrementFollowersCount 增加用户粉丝数
|
||||
func (r *UserRepository) IncrementFollowersCount(userID string) error {
|
||||
return r.db.Model(&model.User{}).Where("id = ?", userID).
|
||||
@@ -243,6 +273,36 @@ func (r *UserRepository) IsBlocked(blockerID, blockedID string) (bool, error) {
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// IsBlockedBatch 批量检查拉黑关系(解决 N+1 问题)
|
||||
// 返回 map[blockedUserID]bool
|
||||
func (r *UserRepository) IsBlockedBatch(blockerID string, blockedIDs []string) (map[string]bool, error) {
|
||||
result := make(map[string]bool)
|
||||
if len(blockedIDs) == 0 || blockerID == "" {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 初始化所有目标用户为 false
|
||||
for _, id := range blockedIDs {
|
||||
result[id] = false
|
||||
}
|
||||
|
||||
// 一次性查询所有拉黑记录
|
||||
var blockedUserIDs []string
|
||||
err := r.db.Model(&model.UserBlock{}).
|
||||
Where("blocker_id = ? AND blocked_id IN ?", blockerID, blockedIDs).
|
||||
Pluck("blocked_id", &blockedUserIDs).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新已拉黑的用户
|
||||
for _, id := range blockedUserIDs {
|
||||
result[id] = true
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// IsBlockedEitherDirection 检查是否任一方向存在拉黑
|
||||
func (r *UserRepository) IsBlockedEitherDirection(userA, userB string) (bool, error) {
|
||||
var count int64
|
||||
|
||||
@@ -17,21 +17,24 @@ func NewVoteRepository(db *gorm.DB) *VoteRepository {
|
||||
return &VoteRepository{db: db}
|
||||
}
|
||||
|
||||
// CreateOptions 批量创建投票选项
|
||||
// CreateOptions 批量创建投票选项(使用批量插入,避免 N+1 问题)
|
||||
func (r *VoteRepository) CreateOptions(postID string, options []string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
for i, content := range options {
|
||||
option := &model.VoteOption{
|
||||
PostID: postID,
|
||||
Content: content,
|
||||
SortOrder: i,
|
||||
}
|
||||
if err := tx.Create(option).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(options) == 0 {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// 构建批量插入的选项切片
|
||||
voteOptions := make([]model.VoteOption, len(options))
|
||||
for i, content := range options {
|
||||
voteOptions[i] = model.VoteOption{
|
||||
PostID: postID,
|
||||
Content: content,
|
||||
SortOrder: i,
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 GORM 的 Create 方法批量插入
|
||||
return r.db.CreateInBatches(voteOptions, 100).Error
|
||||
}
|
||||
|
||||
// GetOptionsByPostID 获取帖子的所有投票选项
|
||||
@@ -58,9 +61,9 @@ func (r *VoteRepository) Vote(postID, userID, optionID string) error {
|
||||
// 验证选项是否属于该帖子
|
||||
var option model.VoteOption
|
||||
if err := tx.Where("id = ? AND post_id = ?", optionID, postID).First(&option).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New("invalid option")
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("invalid option")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -86,9 +89,9 @@ func (r *VoteRepository) Unvote(postID, userID string) error {
|
||||
var userVote model.UserVote
|
||||
err := tx.Where("post_id = ? AND user_id = ?", postID, userID).First(&userVote).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil // 没有投票记录,直接返回
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil // 没有投票记录,直接返回
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -113,7 +116,7 @@ func (r *VoteRepository) GetUserVote(postID, userID string) (*model.UserVote, er
|
||||
var userVote model.UserVote
|
||||
err := r.db.Where("post_id = ? AND user_id = ?", postID, userID).First(&userVote).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user