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:
parent
e80e672ffe
commit
22702e898b
|
|
@ -220,8 +220,119 @@ export class DashboardService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取详细算力分解统计(按用户需求)
|
* 获取详细算力分解统计(按用户需求)
|
||||||
|
* 优先从 contribution-service API 获取完整数据(包含 pending),
|
||||||
|
* 如果 API 调用失败则回退到本地数据
|
||||||
*/
|
*/
|
||||||
private async getDetailedContributionStats() {
|
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({
|
const adoptionStats = await this.prisma.syncedAdoption.aggregate({
|
||||||
where: { status: 'MINING_ENABLED' },
|
where: { status: 'MINING_ENABLED' },
|
||||||
|
|
@ -288,7 +399,7 @@ export class DashboardService {
|
||||||
const levelTheory = networkTotal.mul(RATE_LEVEL_TOTAL);
|
const levelTheory = networkTotal.mul(RATE_LEVEL_TOTAL);
|
||||||
const bonusTheory = networkTotal.mul(RATE_BONUS_TOTAL);
|
const bonusTheory = networkTotal.mul(RATE_BONUS_TOTAL);
|
||||||
|
|
||||||
// 计算未解锁(理论 - 已解锁)
|
// 计算未解锁(理论 - 已解锁)- 仅用于总数,各档位无法获取
|
||||||
const levelPending = levelTheory.minus(levelUnlocked).greaterThan(0)
|
const levelPending = levelTheory.minus(levelUnlocked).greaterThan(0)
|
||||||
? levelTheory.minus(levelUnlocked)
|
? levelTheory.minus(levelUnlocked)
|
||||||
: new Decimal(0);
|
: new Decimal(0);
|
||||||
|
|
@ -323,27 +434,27 @@ export class DashboardService {
|
||||||
provinceActual: provinceActual.toString(),
|
provinceActual: provinceActual.toString(),
|
||||||
cityActual: cityActual.toString(),
|
cityActual: cityActual.toString(),
|
||||||
|
|
||||||
// 层级算力详情
|
// 层级算力详情(本地无法获取各档位 pending,显示为 N/A)
|
||||||
levelContribution: {
|
levelContribution: {
|
||||||
total: levelTheory.toString(),
|
total: levelTheory.toString(),
|
||||||
unlocked: levelUnlocked.toString(),
|
unlocked: levelUnlocked.toString(),
|
||||||
pending: levelPending.toString(),
|
pending: levelPending.toString(),
|
||||||
byTier: {
|
byTier: {
|
||||||
tier1: { unlocked: levelTier1.toString(), pending: '0' },
|
tier1: { unlocked: levelTier1.toString(), pending: 'N/A' },
|
||||||
tier2: { unlocked: levelTier2.toString(), pending: '0' },
|
tier2: { unlocked: levelTier2.toString(), pending: 'N/A' },
|
||||||
tier3: { unlocked: levelTier3.toString(), pending: '0' },
|
tier3: { unlocked: levelTier3.toString(), pending: 'N/A' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// 团队奖励算力详情
|
// 团队奖励算力详情(本地无法获取各档位 pending,显示为 N/A)
|
||||||
bonusContribution: {
|
bonusContribution: {
|
||||||
total: bonusTheory.toString(),
|
total: bonusTheory.toString(),
|
||||||
unlocked: bonusUnlocked.toString(),
|
unlocked: bonusUnlocked.toString(),
|
||||||
pending: bonusPending.toString(),
|
pending: bonusPending.toString(),
|
||||||
byTier: {
|
byTier: {
|
||||||
tier1: { unlocked: bonusTier1.toString(), pending: '0' },
|
tier1: { unlocked: bonusTier1.toString(), pending: 'N/A' },
|
||||||
tier2: { unlocked: bonusTier2.toString(), pending: '0' },
|
tier2: { unlocked: bonusTier2.toString(), pending: 'N/A' },
|
||||||
tier3: { unlocked: bonusTier3.toString(), pending: '0' },
|
tier3: { unlocked: bonusTier3.toString(), pending: 'N/A' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -726,7 +726,7 @@ export class MiningDistributionService {
|
||||||
remainingDistribution: newRemaining.toString(),
|
remainingDistribution: newRemaining.toString(),
|
||||||
halvingPeriodYears: config.halvingPeriodYears,
|
halvingPeriodYears: config.halvingPeriodYears,
|
||||||
currentEra: config.currentEra,
|
currentEra: config.currentEra,
|
||||||
minuteDistribution: config.minuteDistribution.toString(),
|
secondDistribution: config.secondDistribution.toString(),
|
||||||
isActive: config.isActive,
|
isActive: config.isActive,
|
||||||
activatedAt: config.activatedAt?.toISOString(),
|
activatedAt: config.activatedAt?.toISOString(),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue