refactor: remove Gorse integration and implement HotRank feature
Some checks failed
Build Backend / build-docker (push) Has been cancelled
Build Backend / build (push) Has been cancelled

- Removed Gorse-related configurations, handlers, and dependencies from the codebase.
- Introduced HotRank feature with configuration options for ranking posts based on recent activity.
- Updated application structure to support HotRank processing, including new caching mechanisms and database interactions.
- Cleaned up related DTOs and repository methods to reflect the removal of Gorse and the addition of HotRank functionality.
This commit is contained in:
lafay
2026-03-24 05:18:30 +08:00
parent b41567a39a
commit 176cd20847
32 changed files with 735 additions and 1128 deletions

View File

@@ -72,7 +72,6 @@ func (r *CommentRepository) Delete(id string) error {
if err := tx.Model(&model.Post{}).Where("id = ?", comment.PostID).
UpdateColumns(map[string]interface{}{
"comments_count": gorm.Expr("comments_count - 1"),
"hot_score": gorm.Expr("likes_count * 2 + (comments_count - 1) * 3 + views_count * 0.1"),
"updated_at": gorm.Expr("updated_at"),
}).Error; err != nil {
return err
@@ -102,7 +101,6 @@ func (r *CommentRepository) ApplyPublishedStats(comment *model.Comment) error {
if err := tx.Model(&model.Post{}).Where("id = ?", comment.PostID).
UpdateColumns(map[string]any{
"comments_count": gorm.Expr("comments_count + 1"),
"hot_score": gorm.Expr("likes_count * 2 + (comments_count + 1) * 3 + views_count * 0.1"),
"updated_at": gorm.Expr("updated_at"),
}).Error; err != nil {
return err

View File

@@ -1,12 +1,13 @@
package repository
import (
"carrot_bbs/internal/model"
"carrot_bbs/internal/pkg/cursor"
"context"
"strings"
"time"
"carrot_bbs/internal/model"
"carrot_bbs/internal/pkg/cursor"
"gorm.io/gorm"
)
@@ -258,12 +259,10 @@ func (r *PostRepository) Like(postID, userID string) error {
return err
}
// 增加帖子点赞数并同步热度分
// 点赞属于统计字段更新不应影响帖子内容更新时间updated_at
// 增加帖子点赞数(热门排序由定时任务写 Redis ZSET / top_ids不写库
return tx.Model(&model.Post{}).Where("id = ?", postID).
UpdateColumns(map[string]any{
"likes_count": gorm.Expr("likes_count + 1"),
"hot_score": gorm.Expr("(likes_count + 1) * 2 + comments_count * 3 + views_count * 0.1"),
"updated_at": gorm.Expr("updated_at"),
}).Error
})
@@ -277,12 +276,9 @@ func (r *PostRepository) Unlike(postID, userID string) error {
return result.Error
}
if result.RowsAffected > 0 {
// 减少帖子点赞数并同步热度分
// 取消点赞属于统计字段更新不应影响帖子内容更新时间updated_at
return tx.Model(&model.Post{}).Where("id = ?", postID).
UpdateColumns(map[string]any{
"likes_count": gorm.Expr("likes_count - 1"),
"hot_score": gorm.Expr("(likes_count - 1) * 2 + comments_count * 3 + views_count * 0.1"),
"updated_at": gorm.Expr("updated_at"),
}).Error
}
@@ -350,7 +346,7 @@ func (r *PostRepository) Favorite(postID, userID string) error {
return tx.Model(&model.Post{}).Where("id = ?", postID).
UpdateColumns(map[string]any{
"favorites_count": gorm.Expr("favorites_count + 1"),
"updated_at": gorm.Expr("updated_at"),
"updated_at": gorm.Expr("updated_at"),
}).Error
})
}
@@ -367,7 +363,7 @@ func (r *PostRepository) Unfavorite(postID, userID string) error {
return tx.Model(&model.Post{}).Where("id = ?", postID).
UpdateColumns(map[string]any{
"favorites_count": gorm.Expr("favorites_count - 1"),
"updated_at": gorm.Expr("updated_at"),
"updated_at": gorm.Expr("updated_at"),
}).Error
}
return nil
@@ -411,10 +407,8 @@ func (r *PostRepository) IsFavoritedBatch(postIDs []string, userID string) map[s
// IncrementViews 增加帖子观看量
func (r *PostRepository) IncrementViews(postID string) error {
return r.db.Model(&model.Post{}).Where("id = ?", postID).
// 浏览量属于统计字段不应影响帖子内容更新时间updated_at
UpdateColumns(map[string]interface{}{
"views_count": gorm.Expr("views_count + 1"),
"hot_score": gorm.Expr("likes_count * 2 + comments_count * 3 + (views_count + 1) * 0.1"),
"updated_at": gorm.Expr("updated_at"),
}).Error
}
@@ -469,7 +463,7 @@ func (r *PostRepository) GetFollowingPosts(userID string, page, pageSize int) ([
return posts, total, err
}
// GetHotPosts 获取热门帖子(按点赞数和评论数排序
// GetHotPosts 热门榜降级Redis 未就绪时按最新发布排序
func (r *PostRepository) GetHotPosts(page, pageSize int) ([]*model.Post, int64, error) {
var posts []*model.Post
var total int64
@@ -477,15 +471,101 @@ func (r *PostRepository) GetHotPosts(page, pageSize int) ([]*model.Post, int64,
r.db.Model(&model.Post{}).Where("status = ?", model.PostStatusPublished).Count(&total)
offset := (page - 1) * pageSize
// 热门排序使用预计算热度分,避免每次请求进行表达式排序计算
err := r.db.Where("status = ?", model.PostStatusPublished).Preload("User").Preload("Images").
Offset(offset).Limit(pageSize).
Order("hot_score DESC, created_at DESC").
Order("created_at DESC").
Find(&posts).Error
return posts, total, err
}
// PostHotSnapshot 热门重算用字段
type PostHotSnapshot struct {
ID string `gorm:"column:id"`
LikesCount int `gorm:"column:likes_count"`
CommentsCount int `gorm:"column:comments_count"`
FavoritesCount int `gorm:"column:favorites_count"`
SharesCount int `gorm:"column:shares_count"`
ViewsCount int `gorm:"column:views_count"`
CreatedAt time.Time `gorm:"column:created_at"`
}
// ListPublishedPostIDsSince 按发布时间倒序取 ID热门候选池避免全表扫快照
func (r *PostRepository) ListPublishedPostIDsSince(since time.Time, limit int) ([]string, error) {
if limit <= 0 {
return nil, nil
}
type row struct {
ID string `gorm:"column:id"`
}
var rows []row
err := r.db.Model(&model.Post{}).
Select("id").
Where("status = ? AND created_at >= ?", model.PostStatusPublished, since).
Order("created_at DESC").
Limit(limit).
Find(&rows).Error
if err != nil {
return nil, err
}
out := make([]string, 0, len(rows))
for _, x := range rows {
if x.ID != "" {
out = append(out, x.ID)
}
}
return out, nil
}
// ListPinnedPublishedPostIDs 已发布且置顶的帖子 ID越新发布的置顶越靠前
func (r *PostRepository) ListPinnedPublishedPostIDs() ([]string, error) {
type row struct {
ID string `gorm:"column:id"`
}
var rows []row
err := r.db.Model(&model.Post{}).
Select("id").
Where("status = ? AND is_pinned = ?", model.PostStatusPublished, true).
Order("created_at DESC").
Find(&rows).Error
if err != nil {
return nil, err
}
out := make([]string, 0, len(rows))
for _, x := range rows {
if x.ID != "" {
out = append(out, x.ID)
}
}
return out, nil
}
// GetPostHotSnapshotsByIDs 仅拉取候选 ID 对应的热度字段(分批 IN 查询)
func (r *PostRepository) GetPostHotSnapshotsByIDs(ids []string) ([]PostHotSnapshot, error) {
if len(ids) == 0 {
return nil, nil
}
const chunkSize = 400
var all []PostHotSnapshot
for i := 0; i < len(ids); i += chunkSize {
end := i + chunkSize
if end > len(ids) {
end = len(ids)
}
batch := ids[i:end]
var rows []PostHotSnapshot
err := r.db.Model(&model.Post{}).
Select("id", "likes_count", "comments_count", "favorites_count", "shares_count", "views_count", "created_at").
Where("status = ? AND id IN ?", model.PostStatusPublished, batch).
Find(&rows).Error
if err != nil {
return nil, err
}
all = append(all, rows...)
}
return all, nil
}
// GetByIDs 根据ID列表获取帖子保持传入顺序
func (r *PostRepository) GetByIDs(ids []string) ([]*model.Post, error) {
if len(ids) == 0 {
@@ -995,9 +1075,9 @@ func (r *PostRepository) GetFollowingPostsByCursor(ctx context.Context, userID s
func (r *PostRepository) GetHotPostsByCursor(ctx context.Context, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Post], error) {
db := r.getDB(ctx).WithContext(ctx)
// 构建基础查询 - 热门帖子按热度分数排序
// 热门游标降级:与 GetHotPosts 一致,按最新发布排序
query := db.Model(&model.Post{}).Where("status = ?", model.PostStatusPublished).
Order("hot_score DESC, created_at DESC")
Order("created_at DESC")
// 使用游标构建器
builder := cursor.NewBuilder(query, cursor.SortByCreatedAtDesc).