From 9d65eef1b1106ffd9a7885e182f311227c1ee8e8 Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 16 Jan 2026 08:25:07 -0800 Subject: [PATCH] fix(mining-admin): fetch dashboard data from remote services Dashboard now fetches totalDistributed and totalBurned directly from mining-service and trading-service APIs instead of relying solely on CDC sync which may not have data. - Add fetchRemoteServiceData() to get real-time data - Use mining-service /admin/status for totalDistributed - Use trading-service /asset/market for totalBurned and circulationPool - Add 30-second cache to reduce API calls Co-Authored-By: Claude Opus 4.5 --- .../api/controllers/dashboard.controller.ts | 12 +- .../application/services/dashboard.service.ts | 122 +++++++++++++++++- 2 files changed, 129 insertions(+), 5 deletions(-) diff --git a/backend/services/mining-admin-service/src/api/controllers/dashboard.controller.ts b/backend/services/mining-admin-service/src/api/controllers/dashboard.controller.ts index 1baafe77..7c665f6a 100644 --- a/backend/services/mining-admin-service/src/api/controllers/dashboard.controller.ts +++ b/backend/services/mining-admin-service/src/api/controllers/dashboard.controller.ts @@ -30,6 +30,9 @@ export class DashboardController { const dc = raw.detailedContribution || {}; // 转换为前端期望的格式 + // 优先使用远程服务数据,因为 CDC 同步可能不完整 + const remoteData = raw.remoteData || {}; + return { // 基础统计 totalUsers: raw.users?.total || 0, @@ -39,9 +42,12 @@ export class DashboardController { networkTotalContribution: raw.contribution?.totalContribution || '0', networkLevelPending: dc.levelContribution?.pending || '0', networkBonusPending: dc.bonusContribution?.pending || '0', - totalDistributed: raw.mining?.totalMined || '0', - totalBurned: raw.mining?.latestDailyStat?.totalBurned || '0', - circulationPool: raw.trading?.circulationPool?.totalShares || '0', + // 已分配积分股:优先使用远程数据 + totalDistributed: remoteData.totalDistributed || raw.mining?.totalMined || '0', + // 已销毁积分股:优先使用远程数据 + totalBurned: remoteData.totalBurned || raw.mining?.latestDailyStat?.totalBurned || '0', + // 流通池:优先使用远程数据 + circulationPool: remoteData.circulationPool || raw.trading?.circulationPool?.totalShares || '0', currentPrice: raw.latestPrice?.close || '1', priceChange24h, totalOrders: raw.trading?.totalAccounts || 0, diff --git a/backend/services/mining-admin-service/src/application/services/dashboard.service.ts b/backend/services/mining-admin-service/src/application/services/dashboard.service.ts index 3aaaccd8..246ea136 100644 --- a/backend/services/mining-admin-service/src/application/services/dashboard.service.ts +++ b/backend/services/mining-admin-service/src/application/services/dashboard.service.ts @@ -12,9 +12,19 @@ const RATE_CITY = new Decimal('0.02'); const RATE_LEVEL_TOTAL = new Decimal('0.075'); const RATE_BONUS_TOTAL = new Decimal('0.075'); +// 远程服务数据缓存 +interface RemoteServiceData { + totalDistributed: string; + totalBurned: string; + circulationPool: string; + fetchedAt: Date; +} + @Injectable() export class DashboardService { private readonly logger = new Logger(DashboardService.name); + private remoteDataCache: RemoteServiceData | null = null; + private readonly CACHE_TTL_MS = 30000; // 30秒缓存 constructor( private readonly prisma: PrismaService, @@ -34,6 +44,7 @@ export class DashboardService { latestReport, latestKLine, detailedContributionStats, + remoteData, ] = await Promise.all([ this.getUserStats(), this.getContributionStats(), @@ -42,13 +53,40 @@ export class DashboardService { this.prisma.dailyReport.findFirst({ orderBy: { reportDate: 'desc' } }), this.prisma.syncedDayKLine.findFirst({ orderBy: { klineDate: 'desc' } }), this.getDetailedContributionStats(), + this.fetchRemoteServiceData(), ]); + // 合并远程服务数据(如果本地数据为空或为0则使用远程数据) + const totalMined = miningStats.totalMined !== '0' + ? miningStats.totalMined + : remoteData.totalDistributed; + + const totalBurned = miningStats.latestDailyStat?.totalBurned || remoteData.totalBurned; + + const circulationPoolShares = tradingStats.circulationPool?.totalShares !== '0' + ? tradingStats.circulationPool?.totalShares + : remoteData.circulationPool; + return { users: userStats, contribution: contributionStats, - mining: miningStats, - trading: tradingStats, + mining: { + ...miningStats, + totalMined, // 使用合并后的已分配数据 + }, + trading: { + ...tradingStats, + circulationPool: { + totalShares: circulationPoolShares || '0', + totalCash: tradingStats.circulationPool?.totalCash || '0', + }, + }, + // 直接提供远程数据用于仪表盘显示 + remoteData: { + totalDistributed: remoteData.totalDistributed, + totalBurned: remoteData.totalBurned, + circulationPool: remoteData.circulationPool, + }, detailedContribution: detailedContributionStats, latestReport: latestReport ? this.formatDailyReport(latestReport) @@ -460,6 +498,86 @@ export class DashboardService { }; } + // =========================================================================== + // 远程服务数据获取(实时数据备选方案) + // =========================================================================== + + /** + * 从 mining-service 和 trading-service 获取实时数据 + * 用于补充 CDC 同步数据的不足 + */ + private async fetchRemoteServiceData(): Promise { + // 检查缓存 + if ( + this.remoteDataCache && + Date.now() - this.remoteDataCache.fetchedAt.getTime() < this.CACHE_TTL_MS + ) { + return this.remoteDataCache; + } + + const miningServiceUrl = this.configService.get( + 'MINING_SERVICE_URL', + 'http://localhost:3021', + ); + const tradingServiceUrl = this.configService.get( + 'TRADING_SERVICE_URL', + 'http://localhost:3022', + ); + + let totalDistributed = '0'; + let totalBurned = '0'; + let circulationPool = '0'; + + try { + // 从 mining-service 获取已分配积分股 + const miningResponse = await fetch( + `${miningServiceUrl}/api/v2/admin/status`, + ); + if (miningResponse.ok) { + const miningResult = await miningResponse.json(); + const miningData = miningResult.data || miningResult; + // 使用 remainingDistribution 计算已分配 + // 总量 50亿 - 剩余 = 已分配 + const distributionPool = new Decimal( + miningData.distributionPool || '5000000000', + ); + const remaining = new Decimal( + miningData.remainingDistribution || '5000000000', + ); + totalDistributed = distributionPool.minus(remaining).toString(); + } + } catch (error) { + this.logger.warn(`Failed to fetch mining service data: ${error.message}`); + } + + try { + // 从 trading-service 获取市场概览(包含销毁和流通池数据) + const marketResponse = await fetch( + `${tradingServiceUrl}/api/v2/asset/market`, + ); + if (marketResponse.ok) { + const marketResult = await marketResponse.json(); + const marketData = marketResult.data || marketResult; + // blackHoleAmount 是已销毁总量 + totalBurned = marketData.blackHoleAmount || '0'; + // circulationPool 是流通池余额 + circulationPool = marketData.circulationPool || '0'; + } + } catch (error) { + this.logger.warn(`Failed to fetch market overview: ${error.message}`); + } + + // 更新缓存 + this.remoteDataCache = { + totalDistributed, + totalBurned, + circulationPool, + fetchedAt: new Date(), + }; + + return this.remoteDataCache; + } + // =========================================================================== // 辅助方法 // ===========================================================================