fix(batch-mining): 修复重复用户计算问题

- 添加 userBatchContributions 按用户-批次跟踪算力
- 修复阶段计算时同一用户被重复计算的问题
- 修复输出结果时同一用户金额被重复累加的问题
- 使用 processedInPhase Set 避免同一阶段重复处理同一用户

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-21 23:24:03 -08:00
parent f14ad0b7ad
commit 2358b3ea17
1 changed files with 104 additions and 32 deletions

View File

@ -189,10 +189,30 @@ export class BatchMiningService {
this.logger.log(`[preview] 批次${batchNum}算力: ${batchTotal.toFixed(2)}`);
}
// 计算每个用户的算力
// 计算每个用户在每个批次中的算力
// key: `${accountSequence}-${batch}`, value: 该用户在该批次的算力
const userBatchContributions = new Map<string, Decimal>();
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<string, Decimal>();
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<string, Decimal>();
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<string>();
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<string>();
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 123
* - 1 preMineDays=3 13
* - 2 preMineDays=2 1+22
* - 3 preMineDays=1 1+2+31
* - 1 preMineDays=3 12313
* - 2 preMineDays=2 2321+22
* - 3 preMineDays=1 3411+2+31
* - 4 preMineDays=0 4
*
*
* - 阶段1: 只有批次1 preMineDays
* - 阶段2: 批次1+2 preMineDays
* - 阶段3: 批次1+2+3 preMineDays
* - ...
*
* - 阶段1: 只有批次11 preMineDays 2
* - 阶段2: 批次1+22 preMineDays 3
* - 阶段3: 批次1+2+33 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<string, Decimal>();
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<string, Decimal>();
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<string>();
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)!;