fix(mining-admin): 修复仪表板待解锁算力显示为0的问题

- mining-admin-service: 新增 fetchContributionServiceStats() 方法,
  从 contribution-service API 获取完整的 pending 数据
- mining-admin-service: 重构 getDetailedContributionStats(),优先
  使用 API 数据,失败时回退到本地数据
- mining-service: 修复 publishMiningConfigUpdated 中使用已废弃的
  minuteDistribution 字段导致的错误

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-17 00:09:40 -08:00
parent e80e672ffe
commit 22702e898b
2 changed files with 121 additions and 10 deletions

View File

@ -220,8 +220,119 @@ export class DashboardService {
/**
*
* contribution-service API pending
* API 退
*/
private async getDetailedContributionStats() {
// 尝试从 contribution-service 获取完整数据
const contributionServiceData = await this.fetchContributionServiceStats();
if (contributionServiceData) {
return contributionServiceData;
}
// 回退:从本地同步数据计算
return this.getDetailedContributionStatsFromLocal();
}
/**
* contribution-service API
*/
private async fetchContributionServiceStats(): Promise<any | null> {
const contributionServiceUrl = this.configService.get<string>(
'CONTRIBUTION_SERVICE_URL',
'http://localhost:3020',
);
try {
const response = await fetch(`${contributionServiceUrl}/api/v2/contribution/stats`);
if (!response.ok) {
this.logger.warn(`Contribution service returned ${response.status}`);
return null;
}
const result = await response.json();
const data = result.data || result;
// 获取系统账户实际值(本地数据)
const systemAccounts = await this.prisma.syncedSystemContribution.findMany();
let operationActual = new Decimal(0);
let provinceActual = new Decimal(0);
let cityActual = new Decimal(0);
for (const account of systemAccounts) {
const balance = new Decimal(account.contributionBalance || 0);
if (account.accountType === 'OPERATION') operationActual = operationActual.plus(balance);
else if (account.accountType === 'PROVINCE') provinceActual = provinceActual.plus(balance);
else if (account.accountType === 'CITY') cityActual = cityActual.plus(balance);
}
return {
totalTrees: data.totalTrees || 0,
// 理论值
networkTotalTheory: data.networkTotalContribution || '0',
personalTheory: data.personalTotalContribution || '0',
operationTheory: data.operationTotalContribution || '0',
provinceTheory: data.provinceTotalContribution || '0',
cityTheory: data.cityTotalContribution || '0',
levelTheory: data.levelContribution?.total || '0',
bonusTheory: data.bonusContribution?.total || '0',
// 实际值
operationActual: operationActual.toString(),
provinceActual: provinceActual.toString(),
cityActual: cityActual.toString(),
// 层级算力详情(包含正确的 pending 数据)
levelContribution: {
total: data.levelContribution?.total || '0',
unlocked: data.levelContribution?.unlocked || '0',
pending: data.levelContribution?.pending || '0',
byTier: {
tier1: {
unlocked: data.levelContribution?.byTier?.tier1?.unlocked || '0',
pending: data.levelContribution?.byTier?.tier1?.pending || '0',
},
tier2: {
unlocked: data.levelContribution?.byTier?.tier2?.unlocked || '0',
pending: data.levelContribution?.byTier?.tier2?.pending || '0',
},
tier3: {
unlocked: data.levelContribution?.byTier?.tier3?.unlocked || '0',
pending: data.levelContribution?.byTier?.tier3?.pending || '0',
},
},
},
// 团队奖励算力详情(包含正确的 pending 数据)
bonusContribution: {
total: data.bonusContribution?.total || '0',
unlocked: data.bonusContribution?.unlocked || '0',
pending: data.bonusContribution?.pending || '0',
byTier: {
tier1: {
unlocked: data.bonusContribution?.byTier?.tier1?.unlocked || '0',
pending: data.bonusContribution?.byTier?.tier1?.pending || '0',
},
tier2: {
unlocked: data.bonusContribution?.byTier?.tier2?.unlocked || '0',
pending: data.bonusContribution?.byTier?.tier2?.pending || '0',
},
tier3: {
unlocked: data.bonusContribution?.byTier?.tier3?.unlocked || '0',
pending: data.bonusContribution?.byTier?.tier3?.pending || '0',
},
},
},
};
} catch (error) {
this.logger.warn(`Failed to fetch contribution service stats: ${error.message}`);
return null;
}
}
/**
* 退
*/
private async getDetailedContributionStatsFromLocal() {
// 获取总树数
const adoptionStats = await this.prisma.syncedAdoption.aggregate({
where: { status: 'MINING_ENABLED' },
@ -288,7 +399,7 @@ export class DashboardService {
const levelTheory = networkTotal.mul(RATE_LEVEL_TOTAL);
const bonusTheory = networkTotal.mul(RATE_BONUS_TOTAL);
// 计算未解锁(理论 - 已解锁)
// 计算未解锁(理论 - 已解锁)- 仅用于总数,各档位无法获取
const levelPending = levelTheory.minus(levelUnlocked).greaterThan(0)
? levelTheory.minus(levelUnlocked)
: new Decimal(0);
@ -323,27 +434,27 @@ export class DashboardService {
provinceActual: provinceActual.toString(),
cityActual: cityActual.toString(),
// 层级算力详情
// 层级算力详情(本地无法获取各档位 pending显示为 N/A
levelContribution: {
total: levelTheory.toString(),
unlocked: levelUnlocked.toString(),
pending: levelPending.toString(),
byTier: {
tier1: { unlocked: levelTier1.toString(), pending: '0' },
tier2: { unlocked: levelTier2.toString(), pending: '0' },
tier3: { unlocked: levelTier3.toString(), pending: '0' },
tier1: { unlocked: levelTier1.toString(), pending: 'N/A' },
tier2: { unlocked: levelTier2.toString(), pending: 'N/A' },
tier3: { unlocked: levelTier3.toString(), pending: 'N/A' },
},
},
// 团队奖励算力详情
// 团队奖励算力详情(本地无法获取各档位 pending显示为 N/A
bonusContribution: {
total: bonusTheory.toString(),
unlocked: bonusUnlocked.toString(),
pending: bonusPending.toString(),
byTier: {
tier1: { unlocked: bonusTier1.toString(), pending: '0' },
tier2: { unlocked: bonusTier2.toString(), pending: '0' },
tier3: { unlocked: bonusTier3.toString(), pending: '0' },
tier1: { unlocked: bonusTier1.toString(), pending: 'N/A' },
tier2: { unlocked: bonusTier2.toString(), pending: 'N/A' },
tier3: { unlocked: bonusTier3.toString(), pending: 'N/A' },
},
},
};

View File

@ -726,7 +726,7 @@ export class MiningDistributionService {
remainingDistribution: newRemaining.toString(),
halvingPeriodYears: config.halvingPeriodYears,
currentEra: config.currentEra,
minuteDistribution: config.minuteDistribution.toString(),
secondDistribution: config.secondDistribution.toString(),
isActive: config.isActive,
activatedAt: config.activatedAt?.toISOString(),
},