254 lines
8.5 KiB
TypeScript
254 lines
8.5 KiB
TypeScript
import { Controller, Get, Param, Logger, Inject, NotFoundException } from '@nestjs/common';
|
||
import { ApiTags, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
|
||
import {
|
||
REFERRAL_RELATIONSHIP_REPOSITORY,
|
||
IReferralRelationshipRepository,
|
||
TEAM_STATISTICS_REPOSITORY,
|
||
ITeamStatisticsRepository,
|
||
} from '../../domain';
|
||
|
||
/**
|
||
* 内部推荐链API - 供其他微服务调用
|
||
* 无需JWT认证(服务间内部通信)
|
||
*/
|
||
@ApiTags('Internal Referral Chain API')
|
||
@Controller('internal/referral-chain')
|
||
export class InternalReferralChainController {
|
||
private readonly logger = new Logger(InternalReferralChainController.name);
|
||
|
||
constructor(
|
||
@Inject(REFERRAL_RELATIONSHIP_REPOSITORY)
|
||
private readonly referralRepo: IReferralRelationshipRepository,
|
||
@Inject(TEAM_STATISTICS_REPOSITORY)
|
||
private readonly teamStatsRepo: ITeamStatisticsRepository,
|
||
) {}
|
||
|
||
/**
|
||
* 获取用户的祖先链(从直接推荐人到根节点)
|
||
*/
|
||
@Get(':accountSequence')
|
||
@ApiOperation({ summary: '获取用户推荐链(内部API)' })
|
||
@ApiParam({ name: 'accountSequence', description: '账户序列号' })
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '推荐链数据',
|
||
schema: {
|
||
type: 'object',
|
||
properties: {
|
||
accountSequence: { type: 'string', description: '格式: D + YYMMDD + 5位序号' },
|
||
userId: { type: 'string' },
|
||
ancestorPath: {
|
||
type: 'array',
|
||
items: { type: 'string' },
|
||
description: '祖先链(从直接推荐人到根节点的accountSequence列表)',
|
||
},
|
||
referrerId: { type: 'string', nullable: true },
|
||
},
|
||
},
|
||
})
|
||
async getReferralChain(@Param('accountSequence') accountSequence: string) {
|
||
this.logger.debug(`[INTERNAL] getReferralChain: accountSequence=${accountSequence}`);
|
||
|
||
const relationship = await this.referralRepo.findByAccountSequence(accountSequence);
|
||
|
||
if (!relationship) {
|
||
this.logger.debug(`[INTERNAL] No referral found for accountSequence: ${accountSequence}`);
|
||
// 返回空的祖先链而不是抛出错误
|
||
return {
|
||
accountSequence: accountSequence,
|
||
userId: null,
|
||
ancestorPath: [],
|
||
referrerId: null,
|
||
};
|
||
}
|
||
|
||
// referralChain 存储的是 userId (bigint),需要转换为 accountSequence
|
||
// 因为 authorization-service 使用 accountSequence 来查询授权记录
|
||
const ancestorUserIds = relationship.referralChain;
|
||
const ancestorPath: string[] = [];
|
||
|
||
for (const ancestorUserId of ancestorUserIds) {
|
||
const ancestorRelationship = await this.referralRepo.findByUserId(ancestorUserId);
|
||
if (ancestorRelationship) {
|
||
ancestorPath.push(ancestorRelationship.accountSequence);
|
||
} else {
|
||
this.logger.warn(`[INTERNAL] Ancestor userId ${ancestorUserId} not found`);
|
||
}
|
||
}
|
||
|
||
this.logger.debug(`[INTERNAL] getReferralChain result: ${ancestorPath.length} ancestors`);
|
||
|
||
return {
|
||
accountSequence: relationship.accountSequence,
|
||
userId: relationship.userId.toString(),
|
||
ancestorPath,
|
||
referrerId: relationship.referrerId?.toString() ?? null,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 批量获取用户的祖先链
|
||
*/
|
||
@Get('batch/:accountSequences')
|
||
@ApiOperation({ summary: '批量获取用户推荐链(内部API)' })
|
||
@ApiParam({ name: 'accountSequences', description: '账户序列号列表,逗号分隔' })
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '批量推荐链数据',
|
||
})
|
||
async getBatchReferralChains(@Param('accountSequences') accountSequences: string) {
|
||
const sequences = accountSequences.split(',').map((s) => s.trim());
|
||
this.logger.debug(`[INTERNAL] getBatchReferralChains: ${sequences.length} accounts`);
|
||
|
||
const results: Record<string, { userId: string | null; ancestorPath: string[]; referrerId: string | null }> = {};
|
||
|
||
for (const seq of sequences) {
|
||
const relationship = await this.referralRepo.findByAccountSequence(seq);
|
||
if (relationship) {
|
||
// 转换 userId 为 accountSequence
|
||
const ancestorPath: string[] = [];
|
||
for (const ancestorUserId of relationship.referralChain) {
|
||
const ancestorRelationship = await this.referralRepo.findByUserId(ancestorUserId);
|
||
if (ancestorRelationship) {
|
||
ancestorPath.push(ancestorRelationship.accountSequence);
|
||
}
|
||
}
|
||
|
||
results[seq] = {
|
||
userId: relationship.userId.toString(),
|
||
ancestorPath,
|
||
referrerId: relationship.referrerId?.toString() ?? null,
|
||
};
|
||
} else {
|
||
results[seq] = {
|
||
userId: null,
|
||
ancestorPath: [],
|
||
referrerId: null,
|
||
};
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
/**
|
||
* 获取用户的团队成员(下级用户)的accountSequence列表
|
||
* 用于查找下级社区
|
||
*/
|
||
@Get(':accountSequence/team-members')
|
||
@ApiOperation({ summary: '获取团队成员accountSequence列表(内部API)' })
|
||
@ApiParam({ name: 'accountSequence', description: '账户序列号' })
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '团队成员列表',
|
||
schema: {
|
||
type: 'object',
|
||
properties: {
|
||
accountSequence: { type: 'string', description: '格式: D + YYMMDD + 5位序号' },
|
||
teamMembers: {
|
||
type: 'array',
|
||
items: { type: 'string' },
|
||
description: '团队成员accountSequence列表(直接和间接下级)',
|
||
},
|
||
},
|
||
},
|
||
})
|
||
async getTeamMembers(@Param('accountSequence') accountSequence: string) {
|
||
this.logger.debug(`[INTERNAL] getTeamMembers: accountSequence=${accountSequence}`);
|
||
|
||
const relationship = await this.referralRepo.findByAccountSequence(accountSequence);
|
||
|
||
if (!relationship) {
|
||
return {
|
||
accountSequence: accountSequence,
|
||
teamMembers: [],
|
||
};
|
||
}
|
||
|
||
// 获取所有直推用户
|
||
const directReferrals = await this.referralRepo.findDirectReferrals(relationship.userId);
|
||
|
||
// 递归获取所有下级成员的accountSequence
|
||
const teamMembers: string[] = [];
|
||
const queue = [...directReferrals];
|
||
|
||
while (queue.length > 0) {
|
||
const current = queue.shift()!;
|
||
teamMembers.push(current.accountSequence);
|
||
|
||
// 获取当前用户的直推
|
||
const subReferrals = await this.referralRepo.findDirectReferrals(current.userId);
|
||
queue.push(...subReferrals);
|
||
}
|
||
|
||
return {
|
||
accountSequence: accountSequence,
|
||
teamMembers,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取用户的完整团队统计(内部API)
|
||
* 用于用户详情页展示
|
||
*/
|
||
@Get(':accountSequence/profile-stats')
|
||
@ApiOperation({ summary: '获取用户团队统计(内部API)' })
|
||
@ApiParam({ name: 'accountSequence', description: '账户序列号' })
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '用户团队统计',
|
||
schema: {
|
||
type: 'object',
|
||
properties: {
|
||
accountSequence: { type: 'string' },
|
||
directReferralCount: { type: 'number', description: '直推人数' },
|
||
umbrellaUserCount: { type: 'number', description: '伞下用户数(团队总人数)' },
|
||
personalPlantingCount: { type: 'number', description: '个人认种数量' },
|
||
teamPlantingCount: { type: 'number', description: '团队认种数量' },
|
||
},
|
||
},
|
||
})
|
||
async getProfileStats(@Param('accountSequence') accountSequence: string) {
|
||
this.logger.debug(`[INTERNAL] getProfileStats: accountSequence=${accountSequence}`);
|
||
|
||
const relationship = await this.referralRepo.findByAccountSequence(accountSequence);
|
||
|
||
if (!relationship) {
|
||
return {
|
||
accountSequence: accountSequence,
|
||
directReferralCount: 0,
|
||
umbrellaUserCount: 0,
|
||
personalPlantingCount: 0,
|
||
teamPlantingCount: 0,
|
||
};
|
||
}
|
||
|
||
// 获取直推人数
|
||
const directReferrals = await this.referralRepo.findDirectReferrals(relationship.userId);
|
||
const directReferralCount = directReferrals.length;
|
||
|
||
// 获取伞下用户数(递归获取所有下级)
|
||
let umbrellaUserCount = 0;
|
||
const queue = [...directReferrals];
|
||
while (queue.length > 0) {
|
||
umbrellaUserCount++;
|
||
const current = queue.shift()!;
|
||
const subReferrals = await this.referralRepo.findDirectReferrals(current.userId);
|
||
queue.push(...subReferrals);
|
||
}
|
||
|
||
// 获取团队统计
|
||
const teamStats = await this.teamStatsRepo.findByUserId(relationship.userId);
|
||
const personalPlantingCount = teamStats?.personalPlantingCount ?? 0;
|
||
const teamPlantingCount = teamStats?.teamPlantingCount ?? 0;
|
||
|
||
return {
|
||
accountSequence: accountSequence,
|
||
directReferralCount,
|
||
umbrellaUserCount,
|
||
personalPlantingCount,
|
||
teamPlantingCount,
|
||
};
|
||
}
|
||
}
|