238 lines
7.6 KiB
TypeScript
238 lines
7.6 KiB
TypeScript
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||
import { ConfigService } from '@nestjs/config';
|
||
import axios, { AxiosInstance } from 'axios';
|
||
import {
|
||
ITeamStatisticsRepository,
|
||
TeamStatistics,
|
||
} from '../../domain/services/assessment-calculator.service';
|
||
|
||
/**
|
||
* 团队统计数据接口(来自 referral-service)
|
||
*/
|
||
interface ReferralTeamStatsResponse {
|
||
userId: string;
|
||
accountSequence: string;
|
||
totalTeamPlantingCount: number;
|
||
provinceCityDistribution: Record<string, Record<string, number>> | null;
|
||
}
|
||
|
||
/**
|
||
* 推荐链数据接口
|
||
*/
|
||
interface ReferralChainResponse {
|
||
accountSequence: number;
|
||
userId: string | null;
|
||
ancestorPath: string[];
|
||
referrerId: string | null;
|
||
}
|
||
|
||
/**
|
||
* 团队成员数据接口
|
||
*/
|
||
interface TeamMembersResponse {
|
||
accountSequence: number;
|
||
teamMembers: number[];
|
||
}
|
||
|
||
/**
|
||
* 适配器类:将 referral-service 返回的数据转换为 authorization-service 需要的格式
|
||
*/
|
||
class TeamStatisticsAdapter implements TeamStatistics {
|
||
constructor(
|
||
public readonly userId: string,
|
||
public readonly accountSequence: bigint,
|
||
public readonly totalTeamPlantingCount: number,
|
||
private readonly provinceCityDistribution: Record<string, Record<string, number>> | null,
|
||
) {}
|
||
|
||
getProvinceTeamCount(provinceCode: string): number {
|
||
if (!this.provinceCityDistribution || !this.provinceCityDistribution[provinceCode]) {
|
||
return 0;
|
||
}
|
||
// 计算该省所有城市的总和
|
||
const provinceCities = this.provinceCityDistribution[provinceCode];
|
||
return Object.values(provinceCities).reduce((sum, count) => sum + count, 0);
|
||
}
|
||
|
||
getCityTeamCount(cityCode: string): number {
|
||
if (!this.provinceCityDistribution) {
|
||
return 0;
|
||
}
|
||
// 遍历所有省份找到该城市
|
||
for (const provinceCode of Object.keys(this.provinceCityDistribution)) {
|
||
const provinceCities = this.provinceCityDistribution[provinceCode];
|
||
if (provinceCities && cityCode in provinceCities) {
|
||
return provinceCities[cityCode];
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Referral Service HTTP 客户端
|
||
* 用于从 referral-service 获取团队统计数据
|
||
*/
|
||
@Injectable()
|
||
export class ReferralServiceClient implements ITeamStatisticsRepository, OnModuleInit {
|
||
private readonly logger = new Logger(ReferralServiceClient.name);
|
||
private httpClient: AxiosInstance;
|
||
private readonly baseUrl: string;
|
||
private readonly enabled: boolean;
|
||
|
||
constructor(private readonly configService: ConfigService) {
|
||
this.baseUrl = this.configService.get<string>('REFERRAL_SERVICE_URL') || 'http://referral-service:3004';
|
||
this.enabled = this.configService.get<boolean>('REFERRAL_SERVICE_ENABLED') !== false;
|
||
}
|
||
|
||
onModuleInit() {
|
||
this.httpClient = axios.create({
|
||
baseURL: this.baseUrl,
|
||
timeout: 10000,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
});
|
||
|
||
this.logger.log(`[INIT] ReferralServiceClient initialized: ${this.baseUrl}, enabled: ${this.enabled}`);
|
||
}
|
||
|
||
/**
|
||
* 根据 userId 查询团队统计
|
||
*/
|
||
async findByUserId(userId: string): Promise<TeamStatistics | null> {
|
||
if (!this.enabled) {
|
||
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
||
return this.createEmptyStats(userId, BigInt(0));
|
||
}
|
||
|
||
try {
|
||
this.logger.debug(`[HTTP] GET /internal/team-statistics/user/${userId}`);
|
||
|
||
const response = await this.httpClient.get<ReferralTeamStatsResponse | null>(
|
||
`/api/v1/internal/team-statistics/user/${userId}`,
|
||
);
|
||
|
||
if (!response.data) {
|
||
this.logger.debug(`[HTTP] No stats found for userId: ${userId}`);
|
||
return this.createEmptyStats(userId, BigInt(0));
|
||
}
|
||
|
||
const data = response.data;
|
||
this.logger.debug(`[HTTP] Got stats for userId ${userId}: totalTeamPlantingCount=${data.totalTeamPlantingCount}`);
|
||
|
||
return new TeamStatisticsAdapter(
|
||
data.userId,
|
||
BigInt(data.accountSequence || 0),
|
||
data.totalTeamPlantingCount,
|
||
data.provinceCityDistribution,
|
||
);
|
||
} catch (error) {
|
||
this.logger.error(`[HTTP] Failed to get stats for userId ${userId}:`, error);
|
||
// 返回空数据而不是抛出错误,避免影响主流程
|
||
return this.createEmptyStats(userId, BigInt(0));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据 accountSequence 查询团队统计
|
||
*/
|
||
async findByAccountSequence(accountSequence: bigint): Promise<TeamStatistics | null> {
|
||
if (!this.enabled) {
|
||
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
||
return this.createEmptyStats('', accountSequence);
|
||
}
|
||
|
||
try {
|
||
this.logger.debug(`[HTTP] GET /internal/team-statistics/account/${accountSequence}`);
|
||
|
||
const response = await this.httpClient.get<ReferralTeamStatsResponse | null>(
|
||
`/api/v1/internal/team-statistics/account/${accountSequence}`,
|
||
);
|
||
|
||
if (!response.data) {
|
||
this.logger.debug(`[HTTP] No stats found for accountSequence: ${accountSequence}`);
|
||
return this.createEmptyStats('', accountSequence);
|
||
}
|
||
|
||
const data = response.data;
|
||
this.logger.debug(`[HTTP] Got stats for accountSequence ${accountSequence}: totalTeamPlantingCount=${data.totalTeamPlantingCount}`);
|
||
|
||
return new TeamStatisticsAdapter(
|
||
data.userId,
|
||
BigInt(data.accountSequence || accountSequence.toString()),
|
||
data.totalTeamPlantingCount,
|
||
data.provinceCityDistribution,
|
||
);
|
||
} catch (error) {
|
||
this.logger.error(`[HTTP] Failed to get stats for accountSequence ${accountSequence}:`, error);
|
||
// 返回空数据而不是抛出错误,避免影响主流程
|
||
return this.createEmptyStats('', accountSequence);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建空的统计数据
|
||
*/
|
||
private createEmptyStats(userId: string, accountSequence: bigint): TeamStatistics {
|
||
return new TeamStatisticsAdapter(userId, accountSequence, 0, null);
|
||
}
|
||
|
||
/**
|
||
* 获取用户的祖先链(推荐链)
|
||
* 返回从直接推荐人到根节点的 accountSequence 列表
|
||
*/
|
||
async getReferralChain(accountSequence: bigint): Promise<number[]> {
|
||
if (!this.enabled) {
|
||
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
||
return [];
|
||
}
|
||
|
||
try {
|
||
this.logger.debug(`[HTTP] GET /internal/referral-chain/${accountSequence}`);
|
||
|
||
const response = await this.httpClient.get<ReferralChainResponse>(
|
||
`/api/v1/internal/referral-chain/${accountSequence}`,
|
||
);
|
||
|
||
if (!response.data || !response.data.ancestorPath) {
|
||
return [];
|
||
}
|
||
|
||
// ancestorPath 存储的是 userId (bigint string),我们需要映射到 accountSequence
|
||
// 由于 referral-service 中 userId = BigInt(accountSequence),可以直接转换
|
||
return response.data.ancestorPath.map((id) => Number(id));
|
||
} catch (error) {
|
||
this.logger.error(`[HTTP] Failed to get referral chain for accountSequence ${accountSequence}:`, error);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取用户的团队成员 accountSequence 列表
|
||
*/
|
||
async getTeamMembers(accountSequence: bigint): Promise<number[]> {
|
||
if (!this.enabled) {
|
||
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
||
return [];
|
||
}
|
||
|
||
try {
|
||
this.logger.debug(`[HTTP] GET /internal/referral-chain/${accountSequence}/team-members`);
|
||
|
||
const response = await this.httpClient.get<TeamMembersResponse>(
|
||
`/api/v1/internal/referral-chain/${accountSequence}/team-members`,
|
||
);
|
||
|
||
if (!response.data || !response.data.teamMembers) {
|
||
return [];
|
||
}
|
||
|
||
return response.data.teamMembers;
|
||
} catch (error) {
|
||
this.logger.error(`[HTTP] Failed to get team members for accountSequence ${accountSequence}:`, error);
|
||
return [];
|
||
}
|
||
}
|
||
}
|