diff --git a/backend/services/mining-service/src/application/services/batch-mining.service.ts b/backend/services/mining-service/src/application/services/batch-mining.service.ts index f51f0f88..bf23d481 100644 --- a/backend/services/mining-service/src/application/services/batch-mining.service.ts +++ b/backend/services/mining-service/src/application/services/batch-mining.service.ts @@ -189,10 +189,30 @@ export class BatchMiningService { this.logger.log(`[preview] 批次${batchNum}算力: ${batchTotal.toFixed(2)}`); } - // 计算每个用户的算力 + // 计算每个用户在每个批次中的算力 + // key: `${accountSequence}-${batch}`, value: 该用户在该批次的算力 + const userBatchContributions = new Map(); + for (const item of items) { + const key = `${item.accountSequence}-${item.batch}`; + const contribution = this.calculateUserContribution(item.treeCount); + const existing = userBatchContributions.get(key); + if (existing) { + userBatchContributions.set(key, existing.plus(contribution)); + } else { + userBatchContributions.set(key, contribution); + } + } + + // 计算每个用户的总算力(用于显示) const userContributions = new Map(); for (const item of items) { - userContributions.set(item.accountSequence, this.calculateUserContribution(item.treeCount)); + const contribution = this.calculateUserContribution(item.treeCount); + const existing = userContributions.get(item.accountSequence); + if (existing) { + userContributions.set(item.accountSequence, existing.plus(contribution)); + } else { + userContributions.set(item.accountSequence, contribution); + } } // 定义挖矿阶段 @@ -214,6 +234,7 @@ export class BatchMiningService { this.logger.log(`[preview] 每日产出: ${dailyDistribution.toFixed(8)}, 每日补发额度(70%): ${dailyAllocation.toFixed(8)}`); // 计算每个用户在各阶段的收益 + // 使用 Set 记录已处理的用户,避免重复计算 const userAmounts = new Map(); for (const item of items) { userAmounts.set(item.accountSequence, new Decimal(0)); @@ -223,12 +244,26 @@ export class BatchMiningService { // 该阶段的总补发额 = 每日补发额度 × 阶段天数 const phaseAllocation = dailyAllocation.times(phase.daysInPhase); + // 用 Set 记录本阶段已处理的用户,避免重复 + const processedInPhase = new Set(); + for (const item of items) { - // 检查该用户的批次是否参与此阶段 - if (phase.participatingBatches.includes(item.batch)) { - const userContribution = userContributions.get(item.accountSequence)!; - // 用户收益 = 阶段补发额 × (用户算力 / 当前参与总算力) - const ratio = userContribution.dividedBy(phase.participatingContribution); + // 检查该用户的批次是否参与此阶段,且该用户在本阶段尚未处理 + if (phase.participatingBatches.includes(item.batch) && !processedInPhase.has(item.accountSequence)) { + processedInPhase.add(item.accountSequence); + + // 计算用户在该阶段参与的批次中的总算力 + let userPhaseContribution = new Decimal(0); + for (const batch of phase.participatingBatches) { + const key = `${item.accountSequence}-${batch}`; + const batchContrib = userBatchContributions.get(key); + if (batchContrib) { + userPhaseContribution = userPhaseContribution.plus(batchContrib); + } + } + + // 用户收益 = 阶段补发额 × (用户在该阶段的算力 / 当前参与总算力) + const ratio = userPhaseContribution.dividedBy(phase.participatingContribution); const phaseAmount = phaseAllocation.times(ratio); const currentAmount = userAmounts.get(item.accountSequence)!; @@ -254,6 +289,8 @@ export class BatchMiningService { const users: BatchMiningPreviewResult['batches'][0]['users'] = []; let batchTotalAmount = new Decimal(0); + // 跟踪本批次已处理的用户,避免重复累加金额 + const processedUsersInBatch = new Set(); for (const item of batchItems) { const userContribution = userContributions.get(item.accountSequence)!; @@ -272,7 +309,11 @@ export class BatchMiningService { estimatedAmount: amount.toFixed(8), }); - batchTotalAmount = batchTotalAmount.plus(amount); + // 只在第一次遇到该用户时累加金额 + if (!processedUsersInBatch.has(item.accountSequence)) { + processedUsersInBatch.add(item.accountSequence); + batchTotalAmount = batchTotalAmount.plus(amount); + } } // 计算到当前批次的累计算力 @@ -311,16 +352,15 @@ export class BatchMiningService { * * 根据批次的"提前天数"构建各挖矿阶段: * - preMineDays 表示该批次比下一批次提前开始的天数 - * - 批次1的 preMineDays=3 意味着批次1比批次2提前3天 - * - 批次1的 preMineDays=3 意味着批次1先独挖3天 - * - 批次2的 preMineDays=2 意味着批次1+2一起挖2天 - * - 批次3的 preMineDays=1 意味着批次1+2+3一起挖1天 + * - 批次1的 preMineDays=3 意味着批次1比批次2提前3天,所以批次1先独挖3天 + * - 批次2的 preMineDays=2 意味着批次2比批次3提前2天,所以批次1+2一起挖2天 + * - 批次3的 preMineDays=1 意味着批次3比批次4提前1天,所以批次1+2+3一起挖1天 + * - 批次4的 preMineDays=0 意味着批次4是最后一批,所有批次一起挖剩余天数 * - * 阶段划分: - * - 阶段1: 只有批次1,持续 preMineDays 天 - * - 阶段2: 批次1+2,持续 preMineDays 天 - * - 阶段3: 批次1+2+3,持续 preMineDays 天 - * - ... + * 正确的阶段划分: + * - 阶段1: 只有批次1,持续批次1的 preMineDays 天(在批次2加入之前) + * - 阶段2: 批次1+2,持续批次2的 preMineDays 天(在批次3加入之前) + * - 阶段3: 批次1+2+3,持续批次3的 preMineDays 天(在批次4加入之前) * - 最后阶段: 所有批次一起挖 (totalMiningDays - 所有提前天数之和) */ private buildMiningPhases( @@ -359,19 +399,18 @@ export class BatchMiningService { let participatingBatches: number[] = []; let usedDays = 0; // 已分配的天数 - // 按批次顺序添加阶段(提前挖矿阶段) - // 每个批次加入后,挖该批次的 preMineDays 天 + // 按批次顺序添加阶段 + // 关键:先用当前批次的 preMineDays 创建阶段,然后下一批次加入 for (let i = 0; i < sortedBatches.length; i++) { const currentBatch = sortedBatches[i]; const currentPreMineDays = batchPreMineDays.get(currentBatch) || 0; - // 当前批次加入挖矿 + // 先把当前批次加入参与列表 participatingBatches.push(currentBatch); - // 该阶段持续天数 = 当前批次的提前天数 - const phaseDays = currentPreMineDays; - - if (phaseDays > 0) { + // 该阶段持续天数 = 当前批次的 preMineDays(当前批次比下一批次提前的天数) + // 如果 preMineDays > 0,说明当前参与批次要在下一批次加入前独挖这些天 + if (currentPreMineDays > 0) { // 计算该阶段参与用户的总算力 let participatingContribution = new Decimal(0); for (const batch of participatingBatches) { @@ -382,14 +421,14 @@ export class BatchMiningService { phaseNumber: currentPhase, startDate: new Date(), endDate: new Date(), - daysInPhase: phaseDays, + daysInPhase: currentPreMineDays, participatingContribution, participatingBatches: [...participatingBatches], }); - this.logger.log(`[buildMiningPhases] 阶段${currentPhase}: ${phaseDays}天, 批次[${participatingBatches.join(',')}], 参与算力=${participatingContribution.toFixed(2)}`); + this.logger.log(`[buildMiningPhases] 阶段${currentPhase}: ${currentPreMineDays}天, 批次[${participatingBatches.join(',')}], 参与算力=${participatingContribution.toFixed(2)}`); currentPhase++; - usedDays += phaseDays; + usedDays += currentPreMineDays; } } @@ -458,10 +497,29 @@ export class BatchMiningService { batchContributions.set(batchNum, batchTotal); } - // 计算每个用户的算力 + // 计算每个用户在每个批次中的算力 + const userBatchContributions = new Map(); + for (const item of items) { + const key = `${item.accountSequence}-${item.batch}`; + const contribution = this.calculateUserContribution(item.treeCount); + const existing = userBatchContributions.get(key); + if (existing) { + userBatchContributions.set(key, existing.plus(contribution)); + } else { + userBatchContributions.set(key, contribution); + } + } + + // 计算每个用户的总算力(用于记录) const userContributions = new Map(); for (const item of items) { - userContributions.set(item.accountSequence, this.calculateUserContribution(item.treeCount)); + const contribution = this.calculateUserContribution(item.treeCount); + const existing = userContributions.get(item.accountSequence); + if (existing) { + userContributions.set(item.accountSequence, existing.plus(contribution)); + } else { + userContributions.set(item.accountSequence, contribution); + } } // 构建挖矿阶段 @@ -480,10 +538,24 @@ export class BatchMiningService { for (const phase of phases) { const phaseAllocation = dailyAllocation.times(phase.daysInPhase); + // 用 Set 记录本阶段已处理的用户,避免重复 + const processedInPhase = new Set(); + for (const item of items) { - if (phase.participatingBatches.includes(item.batch)) { - const userContribution = userContributions.get(item.accountSequence)!; - const ratio = userContribution.dividedBy(phase.participatingContribution); + if (phase.participatingBatches.includes(item.batch) && !processedInPhase.has(item.accountSequence)) { + processedInPhase.add(item.accountSequence); + + // 计算用户在该阶段参与的批次中的总算力 + let userPhaseContribution = new Decimal(0); + for (const batch of phase.participatingBatches) { + const key = `${item.accountSequence}-${batch}`; + const batchContrib = userBatchContributions.get(key); + if (batchContrib) { + userPhaseContribution = userPhaseContribution.plus(batchContrib); + } + } + + const ratio = userPhaseContribution.dividedBy(phase.participatingContribution); const phaseAmount = phaseAllocation.times(ratio); const currentAmount = userAmounts.get(item.accountSequence)!;