perf: reduce topic list queries

This commit is contained in:
2026-01-13 15:20:56 +08:00
parent 5ac2ea028c
commit 95c14fdd86
2 changed files with 74 additions and 20 deletions

View File

@@ -539,32 +539,85 @@ func (s *content) ListTopics(ctx context.Context, tenantID int64) ([]content_dto
return nil, errorx.ErrDatabaseError.WithCause(err)
}
var topics []content_dto.Topic
if len(results) == 0 {
return []content_dto.Topic{}, nil
}
genres := make([]string, 0, len(results))
for _, r := range results {
if r.Genre == "" {
continue
}
genres = append(genres, r.Genre)
}
contentIDByGenre := make(map[string]int64, len(genres))
contentMap := make(map[int64]*models.Content, len(genres))
if len(genres) > 0 {
// 批量获取每个分类最新内容,避免逐条查询导致 N+1。
var rows []struct {
Genre string `gorm:"column:genre"`
ContentID int64 `gorm:"column:content_id"`
}
contentQuery := models.ContentQuery.WithContext(ctx).UnderlyingDB().
Model(&models.Content{}).
Select("distinct on (genre) genre, id as content_id").
Where("status = ?", consts.ContentStatusPublished).
Where("genre IN ?", genres).
Order("genre, published_at desc, id desc")
if tenantID > 0 {
contentQuery = contentQuery.Where("tenant_id = ?", tenantID)
}
if err := contentQuery.Scan(&rows).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
contentIDs := make([]int64, 0, len(rows))
seen := make(map[int64]struct{}, len(rows))
for _, row := range rows {
if row.ContentID == 0 {
continue
}
contentIDByGenre[row.Genre] = row.ContentID
if _, ok := seen[row.ContentID]; ok {
continue
}
seen[row.ContentID] = struct{}{}
contentIDs = append(contentIDs, row.ContentID)
}
if len(contentIDs) > 0 {
var contents []*models.Content
if err := models.ContentQuery.WithContext(ctx).
UnderlyingDB().
Preload("ContentAssets").
Preload("ContentAssets.Asset").
Where("id IN ?", contentIDs).
Find(&contents).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, c := range contents {
contentMap[c.ID] = c
}
}
}
topics := make([]content_dto.Topic, 0, len(results))
for i, r := range results {
if r.Genre == "" {
continue
}
// Fetch latest content in this genre to get a cover
var c models.Content
query := models.ContentQuery.WithContext(ctx).
Where(models.ContentQuery.Genre.Eq(r.Genre), models.ContentQuery.Status.Eq(consts.ContentStatusPublished))
if tenantID > 0 {
query = query.Where(models.ContentQuery.TenantID.Eq(tenantID))
}
query.
Order(models.ContentQuery.PublishedAt.Desc()).
UnderlyingDB().
Preload("ContentAssets").
Preload("ContentAssets.Asset").
First(&c)
cover := ""
for _, ca := range c.ContentAssets {
if (ca.Role == consts.ContentAssetRoleCover || ca.Role == consts.ContentAssetRoleMain) && ca.Asset != nil {
cover = Common.GetAssetURL(ca.Asset.ObjectKey)
if ca.Role == consts.ContentAssetRoleCover {
break // Prefer cover
if contentID, ok := contentIDByGenre[r.Genre]; ok {
if c := contentMap[contentID]; c != nil {
for _, ca := range c.ContentAssets {
if (ca.Role == consts.ContentAssetRoleCover || ca.Role == consts.ContentAssetRoleMain) && ca.Asset != nil {
cover = Common.GetAssetURL(ca.Asset.ObjectKey)
if ca.Role == consts.ContentAssetRoleCover {
break // Prefer cover
}
}
}
}
}