From 23994a23bea47f494527dc88342b0064c45c98fc Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 7 Jan 2026 22:56:07 -0800 Subject: [PATCH] =?UTF-8?q?feat(admin-service):=20=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E9=A1=B5=E4=BD=BF=E7=94=A8=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 getBatchUserStats 批量查询方法 - user.controller 注入 userDetailRepository - listUsers 接口使用实时统计替代预计算字段 实时查询的字段: - personalAdoptionCount: 个人认种量 - teamAddressCount: 团队地址数 - teamAdoptionCount: 团队认种量 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../src/api/controllers/user.controller.ts | 36 ++++++++-- .../user-detail-query.repository.ts | 10 +++ .../user-detail-query.repository.impl.ts | 72 +++++++++++++++++++ 3 files changed, 111 insertions(+), 7 deletions(-) diff --git a/backend/services/admin-service/src/api/controllers/user.controller.ts b/backend/services/admin-service/src/api/controllers/user.controller.ts index 42ec4fd9..b93f3de2 100644 --- a/backend/services/admin-service/src/api/controllers/user.controller.ts +++ b/backend/services/admin-service/src/api/controllers/user.controller.ts @@ -17,6 +17,10 @@ import { UserQueryFilters, UserQuerySort, } from '../../domain/repositories/user-query.repository'; +import { + IUserDetailQueryRepository, + USER_DETAIL_QUERY_REPOSITORY, +} from '../../domain/repositories/user-detail-query.repository'; /** * 用户管理控制器 @@ -28,6 +32,8 @@ export class UserController { constructor( @Inject(USER_QUERY_REPOSITORY) private readonly userQueryRepository: IUserQueryRepository, + @Inject(USER_DETAIL_QUERY_REPOSITORY) + private readonly userDetailRepository: IUserDetailQueryRepository, ) {} /** @@ -62,11 +68,18 @@ export class UserController { sort, ); - // 获取所有用户的团队总认种数用于计算百分比 - const totalTeamAdoptions = result.items.reduce((sum, item) => sum + item.teamAdoptionCount, 0); + // 批量获取实时统计数据 + const accountSequences = result.items.map(item => item.accountSequence); + const statsMap = await this.userDetailRepository.getBatchUserStats(accountSequences); + + // 获取所有用户的团队总认种数用于计算百分比(使用实时数据) + let totalTeamAdoptions = 0; + for (const stats of statsMap.values()) { + totalTeamAdoptions += stats.teamAdoptionCount; + } return { - items: result.items.map((item) => this.mapToListItem(item, totalTeamAdoptions)), + items: result.items.map((item) => this.mapToListItem(item, totalTeamAdoptions, statsMap.get(item.accountSequence))), total: result.total, page: result.page, pageSize: result.pageSize, @@ -134,7 +147,16 @@ export class UserController { // ==================== Private Methods ==================== - private mapToListItem(item: UserQueryItem, totalTeamAdoptions: number): UserListItemDto { + private mapToListItem( + item: UserQueryItem, + totalTeamAdoptions: number, + realTimeStats?: { personalAdoptionCount: number; teamAddressCount: number; teamAdoptionCount: number }, + ): UserListItemDto { + // 使用实时统计数据(如果有),否则使用预计算数据 + const personalAdoptions = realTimeStats?.personalAdoptionCount ?? item.personalAdoptionCount; + const teamAddresses = realTimeStats?.teamAddressCount ?? item.teamAddressCount; + const teamAdoptions = realTimeStats?.teamAdoptionCount ?? item.teamAdoptionCount; + // 计算省市认种百分比 const provincePercentage = totalTeamAdoptions > 0 ? Math.round((item.provinceAdoptionCount / totalTeamAdoptions) * 100) @@ -149,9 +171,9 @@ export class UserController { avatar: item.avatarUrl, nickname: item.nickname, phoneNumberMasked: item.phoneNumberMasked, - personalAdoptions: item.personalAdoptionCount, - teamAddresses: item.teamAddressCount, - teamAdoptions: item.teamAdoptionCount, + personalAdoptions, + teamAddresses, + teamAdoptions, provincialAdoptions: { count: item.provinceAdoptionCount, percentage: provincePercentage, diff --git a/backend/services/admin-service/src/domain/repositories/user-detail-query.repository.ts b/backend/services/admin-service/src/domain/repositories/user-detail-query.repository.ts index a17a74d3..f5de5b00 100644 --- a/backend/services/admin-service/src/domain/repositories/user-detail-query.repository.ts +++ b/backend/services/admin-service/src/domain/repositories/user-detail-query.repository.ts @@ -261,4 +261,14 @@ export interface IUserDetailQueryRepository { * 团队包括所有直推和间推用户 */ getTeamStats(accountSequence: string): Promise<{ teamAddressCount: number; teamAdoptionCount: number }>; + + /** + * 批量获取用户实时统计(用于用户列表页面) + * 返回 Map + */ + getBatchUserStats(accountSequences: string[]): Promise>; } diff --git a/backend/services/admin-service/src/infrastructure/persistence/repositories/user-detail-query.repository.impl.ts b/backend/services/admin-service/src/infrastructure/persistence/repositories/user-detail-query.repository.impl.ts index 1fc47857..1accf6ee 100644 --- a/backend/services/admin-service/src/infrastructure/persistence/repositories/user-detail-query.repository.impl.ts +++ b/backend/services/admin-service/src/infrastructure/persistence/repositories/user-detail-query.repository.impl.ts @@ -545,6 +545,78 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository return { teamAddressCount, teamAdoptionCount }; } + async getBatchUserStats(accountSequences: string[]): Promise> { + const result = new Map(); + + if (accountSequences.length === 0) return result; + + // 1. 批量获取个人认种量 + const personalAdoptionCounts = await this.prisma.plantingOrderQueryView.groupBy({ + by: ['accountSequence'], + where: { + accountSequence: { in: accountSequences }, + status: 'MINING_ENABLED', + }, + _count: { id: true }, + }); + const personalAdoptionMap = new Map( + personalAdoptionCounts.map(p => [p.accountSequence, p._count.id]) + ); + + // 2. 批量获取 userId 用于团队统计 + const referrals = await this.prisma.referralQueryView.findMany({ + where: { accountSequence: { in: accountSequences } }, + select: { accountSequence: true, userId: true }, + }); + const userIdMap = new Map(referrals.map(r => [r.accountSequence, r.userId])); + + // 3. 对每个用户计算团队统计(这里需要单独查询,因为 PostgreSQL 数组查询不支持批量) + for (const accountSequence of accountSequences) { + const userId = userIdMap.get(accountSequence); + let teamAddressCount = 0; + let teamAdoptionCount = 0; + + if (userId) { + // 获取团队成员 + const teamMembers = await this.prisma.referralQueryView.findMany({ + where: { + ancestorPath: { has: userId }, + }, + select: { accountSequence: true }, + }); + + teamAddressCount = teamMembers.length; + + // 获取团队认种量 + if (teamMembers.length > 0) { + const count = await this.prisma.plantingOrderQueryView.count({ + where: { + accountSequence: { in: teamMembers.map(m => m.accountSequence) }, + status: 'MINING_ENABLED', + }, + }); + teamAdoptionCount = count; + } + } + + result.set(accountSequence, { + personalAdoptionCount: personalAdoptionMap.get(accountSequence) || 0, + teamAddressCount, + teamAdoptionCount, + }); + } + + return result; + } + // ============================================================================ // 辅助方法 // ============================================================================