feat(admin-service): 用户列表页使用实时统计数据
- 添加 getBatchUserStats 批量查询方法 - user.controller 注入 userDetailRepository - listUsers 接口使用实时统计替代预计算字段 实时查询的字段: - personalAdoptionCount: 个人认种量 - teamAddressCount: 团队地址数 - teamAdoptionCount: 团队认种量 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6a05150017
commit
23994a23be
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -261,4 +261,14 @@ export interface IUserDetailQueryRepository {
|
|||
* 团队包括所有直推和间推用户
|
||||
*/
|
||||
getTeamStats(accountSequence: string): Promise<{ teamAddressCount: number; teamAdoptionCount: number }>;
|
||||
|
||||
/**
|
||||
* 批量获取用户实时统计(用于用户列表页面)
|
||||
* 返回 Map<accountSequence, stats>
|
||||
*/
|
||||
getBatchUserStats(accountSequences: string[]): Promise<Map<string, {
|
||||
personalAdoptionCount: number;
|
||||
teamAddressCount: number;
|
||||
teamAdoptionCount: number;
|
||||
}>>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -545,6 +545,78 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
|
|||
return { teamAddressCount, teamAdoptionCount };
|
||||
}
|
||||
|
||||
async getBatchUserStats(accountSequences: string[]): Promise<Map<string, {
|
||||
personalAdoptionCount: number;
|
||||
teamAddressCount: number;
|
||||
teamAdoptionCount: number;
|
||||
}>> {
|
||||
const result = new Map<string, {
|
||||
personalAdoptionCount: number;
|
||||
teamAddressCount: number;
|
||||
teamAdoptionCount: number;
|
||||
}>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 辅助方法
|
||||
// ============================================================================
|
||||
|
|
|
|||
Loading…
Reference in New Issue