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> | 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> | 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('REFERRAL_SERVICE_URL') || 'http://referral-service:3004'; this.enabled = this.configService.get('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 { 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( `/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 { 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( `/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 { 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( `/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 { 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( `/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 []; } } }