fix(repo): 为公告查询添加分页上限,优化分组按账户数排序的数据加载

- announcement ListActive: 添加 Limit(200) 防止无界查询
- group listWithAccountCountSort: 改为先只查 ID + sort_order,
  再批量加载账户统计,排序分页后仅加载当前页的完整实体,
  避免全量加载所有字段后做内存排序。

Co-Authored-By: deepseek-v4-pro[1m] <deepseek-ai@claude-code-best.win>
This commit is contained in:
cepvor 2026-05-14 16:38:45 +08:00
parent 18790386a7
commit ab6510f1a0
2 changed files with 66 additions and 22 deletions

View File

@ -204,7 +204,8 @@ func (r *announcementRepository) ListActive(ctx context.Context, now time.Time)
announcement.Or(announcement.StartsAtIsNil(), announcement.StartsAtLTE(now)),
announcement.Or(announcement.EndsAtIsNil(), announcement.EndsAtGT(now)),
).
Order(dbent.Desc(announcement.FieldID))
Order(dbent.Desc(announcement.FieldID)).
Limit(200)
items, err := q.All(ctx)
if err != nil {

View File

@ -283,47 +283,90 @@ func (r *groupRepository) ListWithFilters(ctx context.Context, params pagination
}
func (r *groupRepository) listWithAccountCountSort(ctx context.Context, q *dbent.GroupQuery, params pagination.PaginationParams, total int) ([]service.Group, *pagination.PaginationResult, error) {
groups, err := q.
// 第一步:只查 ID + sort_order轻量不做分页 — 需要全量排序 account_count
rows, err := q.Clone().
Select(group.FieldID, group.FieldSortOrder).
Order(dbent.Asc(group.FieldSortOrder), dbent.Asc(group.FieldID)).
All(ctx)
if err != nil {
return nil, nil, err
}
groupIDs := make([]int64, 0, len(groups))
outGroups := make([]service.Group, 0, len(groups))
for i := range groups {
g := groupEntityToService(groups[i])
outGroups = append(outGroups, *g)
groupIDs = append(groupIDs, g.ID)
type sortEntry struct {
id int64
sortOrder int
accountCount int64
}
entries := make([]sortEntry, 0, len(rows))
groupIDs := make([]int64, len(rows))
for i, r := range rows {
groupIDs[i] = r.ID
entries = append(entries, sortEntry{id: r.ID, sortOrder: r.SortOrder})
}
// 第二步:批量加载 account counts一次 SQL
counts, err := r.loadAccountCounts(ctx, groupIDs)
if err != nil {
return nil, nil, err
}
for i := range outGroups {
c := counts[outGroups[i].ID]
outGroups[i].AccountCount = c.Total
outGroups[i].ActiveAccountCount = c.Active
outGroups[i].RateLimitedAccountCount = c.RateLimited
for i := range entries {
c := counts[entries[i].id]
if c.Total > 0 {
entries[i].accountCount = c.Total
}
}
// 第三步Go 侧排序(数据量 = Group 总数,通常 < 200安全
sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc)
sort.SliceStable(outGroups, func(i, j int) bool {
if outGroups[i].AccountCount == outGroups[j].AccountCount {
if outGroups[i].SortOrder == outGroups[j].SortOrder {
return outGroups[i].ID < outGroups[j].ID
}
return outGroups[i].SortOrder < outGroups[j].SortOrder
tieCmp := func(a, b sortEntry) bool {
if a.sortOrder == b.sortOrder {
return a.id < b.id
}
return a.sortOrder < b.sortOrder
}
sort.SliceStable(entries, func(i, j int) bool {
if entries[i].accountCount == entries[j].accountCount {
return tieCmp(entries[i], entries[j])
}
if sortOrder == pagination.SortOrderAsc {
return outGroups[i].AccountCount < outGroups[j].AccountCount
return entries[i].accountCount < entries[j].accountCount
}
return outGroups[i].AccountCount > outGroups[j].AccountCount
return entries[i].accountCount > entries[j].accountCount
})
return paginateSlice(outGroups, params), paginationResultFromTotal(int64(total), params), nil
// 第四步:分页,只加载当前页需要的完整 Group。
page := paginateSlice(entries, params)
if len(page) == 0 {
return nil, paginationResultFromTotal(int64(total), params), nil
}
pageIDs := make([]int64, len(page))
pageIdx := make(map[int64]int, len(page))
for i, e := range page {
pageIDs[i] = e.id
pageIdx[e.id] = i
}
groups, err := r.client.Group.Query().
Where(group.IDIn(pageIDs...)).
All(ctx)
if err != nil {
return nil, nil, err
}
outGroups := make([]service.Group, len(page))
for i := range groups {
g := groupEntityToService(groups[i])
c := counts[g.ID]
g.AccountCount = c.Total
g.ActiveAccountCount = c.Active
g.RateLimitedAccountCount = c.RateLimited
if idx, ok := pageIdx[g.ID]; ok {
outGroups[idx] = *g
}
}
return outGroups, paginationResultFromTotal(int64(total), params), nil
}
func groupListOrder(params pagination.PaginationParams) []func(*entsql.Selector) {