From ab6510f1a0f0b1a6cbccc00c035f12d00fd1f25a Mon Sep 17 00:00:00 2001 From: cepvor Date: Thu, 14 May 2026 16:38:45 +0800 Subject: [PATCH] =?UTF-8?q?fix(repo):=20=E4=B8=BA=E5=85=AC=E5=91=8A?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=B7=BB=E5=8A=A0=E5=88=86=E9=A1=B5=E4=B8=8A?= =?UTF-8?q?=E9=99=90=EF=BC=8C=E4=BC=98=E5=8C=96=E5=88=86=E7=BB=84=E6=8C=89?= =?UTF-8?q?=E8=B4=A6=E6=88=B7=E6=95=B0=E6=8E=92=E5=BA=8F=E7=9A=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - announcement ListActive: 添加 Limit(200) 防止无界查询 - group listWithAccountCountSort: 改为先只查 ID + sort_order, 再批量加载账户统计,排序分页后仅加载当前页的完整实体, 避免全量加载所有字段后做内存排序。 Co-Authored-By: deepseek-v4-pro[1m] --- .../internal/repository/announcement_repo.go | 3 +- backend/internal/repository/group_repo.go | 85 ++++++++++++++----- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/backend/internal/repository/announcement_repo.go b/backend/internal/repository/announcement_repo.go index afe1fb25..f19c24f1 100644 --- a/backend/internal/repository/announcement_repo.go +++ b/backend/internal/repository/announcement_repo.go @@ -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 { diff --git a/backend/internal/repository/group_repo.go b/backend/internal/repository/group_repo.go index 112575f4..9b6377bc 100644 --- a/backend/internal/repository/group_repo.go +++ b/backend/internal/repository/group_repo.go @@ -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) {