From e9dea69ee9c0aaa5aa06bc688ce8793571145310 Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 23 Jan 2026 02:01:40 -0800 Subject: [PATCH] =?UTF-8?q?feat(batch-mining):=20=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=89=B9=E9=87=8F=E8=A1=A5=E5=8F=91=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E8=B5=B7=E5=A7=8B=E6=97=A5=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构批量补发功能,将硬编码的起始日期(2025-11-08)改为从 Excel 数据中 动态获取,提高计算的准确性和灵活性。 后端改动 (mining-service): - 新增 DEFAULT_MINING_START_DATE 常量作为找不到有效数据时的默认值 - 新增 getCalculatedStartDate() 方法:从批次1用户的 miningStartDate 中 获取最早日期 - 新增 parseDate() 方法:支持解析 2025.11.8、2025-11-08、2025/11/8 格式 - 修改 buildMiningPhases() 方法:新增 startDateStr 参数,不再硬编码日期 - 修改 preview/execute 方法:在返回结果中包含 calculatedStartDate 字段 前端改动 (mining-admin-web): - 更新 BatchPreviewResult 接口,新增 calculatedStartDate 字段 - 预览结果描述中显示计算起始日期(蓝色高亮) - 确认对话框中新增"计算起始日期"行 降级策略: - 若批次1用户不存在或日期均无效,自动使用默认日期 2025-11-08 Co-Authored-By: Claude Opus 4.5 --- .../services/batch-mining.service.ts | 99 +++++++++++++++++-- .../src/app/(dashboard)/batch-mining/page.tsx | 10 ++ 2 files changed, 102 insertions(+), 7 deletions(-) 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 148af2e7..9b51daa6 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 @@ -84,9 +84,11 @@ export interface BatchMiningPreviewResult { }[]; grandTotalAmount: string; message: string; + calculatedStartDate: string; // 计算使用的起始日期 (YYYY-MM-DD) } // 常量 +const DEFAULT_MINING_START_DATE = '2025-11-08'; // 默认起始日期(找不到有效数据时使用) const BASE_CONTRIBUTION_PER_TREE = new Decimal('22617'); // 每棵树的基础算力 const SECONDS_PER_DAY = 86400; // 每天产出的70%分给补发用户 @@ -158,6 +160,7 @@ export class BatchMiningService { batches: [], grandTotalAmount: '0', message: `批量补发已于 ${existing?.executedAt?.toISOString()} 执行过,操作人: ${existing?.operatorName}`, + calculatedStartDate: DEFAULT_MINING_START_DATE, }; } @@ -215,12 +218,16 @@ export class BatchMiningService { } } + // 获取计算起始日期(从第一批次用户的挖矿开始时间获取,找不到则使用默认值) + const calculatedStartDate = this.getCalculatedStartDate(items); + this.logger.log(`[preview] 计算起始日期: ${calculatedStartDate}`); + // 定义挖矿阶段 // 阶段1: 批次1独挖 preMineDays 天 // 阶段2: 批次1+2共挖 preMineDays 天 // ...依次类推 // 最后阶段: 所有批次共挖(剩余天数) - const phases = this.buildMiningPhases(items, sortedBatches, batchContributions); + const phases = this.buildMiningPhases(items, sortedBatches, batchContributions, calculatedStartDate); this.logger.log(`[preview] 挖矿阶段: ${JSON.stringify(phases.map(p => ({ phase: p.phaseNumber, days: p.daysInPhase, @@ -350,8 +357,9 @@ export class BatchMiningService { batches: batchResults, grandTotalAmount: grandTotalAmount.toFixed(8), message: `预览成功: ${sortedBatches.length} 个批次, ${items.length} 个用户, 总补发金额 ${grandTotalAmount.toFixed(8)}`, + calculatedStartDate, }; - this.logger.log(`[preview] 预览完成: ${result.message}`); + this.logger.log(`[preview] 预览完成: ${result.message}, 起始日期: ${calculatedStartDate}`); return result; } @@ -359,7 +367,7 @@ export class BatchMiningService { * 构建挖矿阶段 * * 业务规则: - * - 总挖矿天数 = 从第一批用户开始挖矿(11月8日)到今天的自然天数 + * - 总挖矿天数 = 从第一批用户开始挖矿到今天的自然天数 * - preMineDays 表示该批次比下一批次提前开始的天数 * - 阶段1: 只有批次1,持续批次1的 preMineDays 天 * - 阶段2: 批次1+2,持续批次2的 preMineDays 天 @@ -370,6 +378,7 @@ export class BatchMiningService { items: BatchMiningItem[], sortedBatches: number[], batchContributions: Map, + startDateStr: string, // 计算起始日期 (YYYY-MM-DD) ): MiningPhase[] { const phases: MiningPhase[] = []; @@ -377,13 +386,13 @@ export class BatchMiningService { return phases; } - // 计算总挖矿天数:从2025年11月8日到今天 - const miningStartDate = new Date('2025-11-08'); + // 计算总挖矿天数:从起始日期到今天 + const miningStartDate = new Date(startDateStr); miningStartDate.setHours(0, 0, 0, 0); const today = new Date(); today.setHours(0, 0, 0, 0); const totalMiningDays = Math.floor((today.getTime() - miningStartDate.getTime()) / (1000 * 60 * 60 * 24)); - this.logger.log(`[buildMiningPhases] 总挖矿天数: ${totalMiningDays} (从2025-11-08到今天)`); + this.logger.log(`[buildMiningPhases] 总挖矿天数: ${totalMiningDays} (从${startDateStr}到今天)`); // 获取每个批次的提前天数 const batchPreMineDays = new Map(); @@ -516,8 +525,12 @@ export class BatchMiningService { } } + // 获取计算起始日期 + const calculatedStartDate = this.getCalculatedStartDate(items); + this.logger.log(`[execute] 计算起始日期: ${calculatedStartDate}`); + // 构建挖矿阶段 - const phases = this.buildMiningPhases(items, sortedBatches, batchContributions); + const phases = this.buildMiningPhases(items, sortedBatches, batchContributions, calculatedStartDate); // 每天补发额度 = 日产出 × 70% const dailyDistribution = secondDistribution.times(SECONDS_PER_DAY); @@ -881,4 +894,76 @@ export class BatchMiningService { private calculateUserContribution(treeCount: number): Decimal { return BASE_CONTRIBUTION_PER_TREE.times(treeCount); } + + /** + * 获取计算起始日期 + * 从第一批次用户的挖矿开始时间中获取最早的日期 + * 如果找不到有效数据,返回默认日期 2025-11-08 + */ + private getCalculatedStartDate(items: BatchMiningItem[]): string { + // 找出批次1的用户 + const batch1Items = items.filter(item => item.batch === 1); + + if (batch1Items.length === 0) { + this.logger.warn(`[getCalculatedStartDate] 未找到批次1的用户,使用默认日期: ${DEFAULT_MINING_START_DATE}`); + return DEFAULT_MINING_START_DATE; + } + + // 解析并找出最早的日期 + let earliestDate: Date | null = null; + let earliestDateStr = ''; + + for (const item of batch1Items) { + const dateStr = item.miningStartDate; + if (!dateStr) continue; + + const parsed = this.parseDate(dateStr); + if (parsed && (!earliestDate || parsed < earliestDate)) { + earliestDate = parsed; + earliestDateStr = dateStr; + } + } + + if (!earliestDate) { + this.logger.warn(`[getCalculatedStartDate] 批次1用户的挖矿开始日期均无效,使用默认日期: ${DEFAULT_MINING_START_DATE}`); + return DEFAULT_MINING_START_DATE; + } + + // 格式化为 YYYY-MM-DD + const year = earliestDate.getFullYear(); + const month = String(earliestDate.getMonth() + 1).padStart(2, '0'); + const day = String(earliestDate.getDate()).padStart(2, '0'); + const formattedDate = `${year}-${month}-${day}`; + + this.logger.log(`[getCalculatedStartDate] 从批次1用户中找到最早日期: ${formattedDate} (原始值: ${earliestDateStr})`); + return formattedDate; + } + + /** + * 解析日期字符串 + * 支持格式: 2025.11.8, 2025-11-08, 2025/11/8 + */ + private parseDate(dateStr: string): Date | null { + if (!dateStr) return null; + + const formats = [ + /^(\d{4})\.(\d{1,2})\.(\d{1,2})$/, // 2025.11.8 + /^(\d{4})-(\d{1,2})-(\d{1,2})$/, // 2025-11-08 + /^(\d{4})\/(\d{1,2})\/(\d{1,2})$/, // 2025/11/8 + ]; + + for (const format of formats) { + const match = dateStr.match(format); + if (match) { + const year = parseInt(match[1], 10); + const month = parseInt(match[2], 10) - 1; + const day = parseInt(match[3], 10); + const date = new Date(year, month, day); + date.setHours(0, 0, 0, 0); + return date; + } + } + + return null; + } } diff --git a/frontend/mining-admin-web/src/app/(dashboard)/batch-mining/page.tsx b/frontend/mining-admin-web/src/app/(dashboard)/batch-mining/page.tsx index abca83e3..03c359b1 100644 --- a/frontend/mining-admin-web/src/app/(dashboard)/batch-mining/page.tsx +++ b/frontend/mining-admin-web/src/app/(dashboard)/batch-mining/page.tsx @@ -63,6 +63,7 @@ interface BatchPreviewResult { message: string; parsedItems?: BatchItem[]; originalFileName?: string; + calculatedStartDate?: string; // 计算使用的起始日期 (YYYY-MM-DD) } interface BatchExecutionRecord { @@ -427,6 +428,11 @@ export default function BatchMiningPage() { 共 {previewResult.totalBatches} 个批次,{previewResult.totalUsers} 个用户, 总补发金额: {formatNumber(previewResult.grandTotalAmount)} 积分股 + {previewResult.calculatedStartDate && ( + + (从 {previewResult.calculatedStartDate} 开始计算) + + )} @@ -549,6 +555,10 @@ export default function BatchMiningPage() { 批次数 {previewResult?.totalBatches} +
+ 计算起始日期 + {previewResult?.calculatedStartDate} +
总补发金额