From 7a4f5591b7007e2fb6d9d50bf83fb2f36c61629d Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 21 Jan 2026 05:49:04 -0800 Subject: [PATCH] =?UTF-8?q?feat(batch-mining):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=AF=A6=E7=BB=86=E7=9A=84=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mining-service batch-mining.service.ts: 添加所有方法的详细日志 - mining-admin-service batch-mining.service.ts: 添加 HTTP 请求和响应日志 - mining-admin-service batch-mining.controller.ts: 添加控制器层日志 - frontend batch-mining page.tsx: 添加前端 console.log 日志 便于调试部署后的 404 等问题 Co-Authored-By: Claude Opus 4.5 --- .../controllers/batch-mining.controller.ts | 121 +++++++++++++++--- .../services/batch-mining.service.ts | 114 +++++++++++------ .../services/batch-mining.service.ts | 18 ++- .../src/app/(dashboard)/batch-mining/page.tsx | 54 ++++++-- 4 files changed, 237 insertions(+), 70 deletions(-) diff --git a/backend/services/mining-admin-service/src/api/controllers/batch-mining.controller.ts b/backend/services/mining-admin-service/src/api/controllers/batch-mining.controller.ts index 49e5cbd1..68b8008e 100644 --- a/backend/services/mining-admin-service/src/api/controllers/batch-mining.controller.ts +++ b/backend/services/mining-admin-service/src/api/controllers/batch-mining.controller.ts @@ -8,6 +8,7 @@ import { HttpStatus, UseInterceptors, UploadedFile, + Logger, } from '@nestjs/common'; import { ApiTags, @@ -24,12 +25,22 @@ import { BatchMiningService, BatchMiningItem } from '../../application/services/ @ApiBearerAuth() @Controller('batch-mining') export class BatchMiningController { + private readonly logger = new Logger(BatchMiningController.name); + constructor(private readonly batchMiningService: BatchMiningService) {} @Get('status') @ApiOperation({ summary: '获取批量补发状态(是否已执行)' }) async getStatus() { - return this.batchMiningService.getStatus(); + this.logger.log(`[GET /batch-mining/status] 请求获取批量补发状态`); + try { + const result = await this.batchMiningService.getStatus(); + this.logger.log(`[GET /batch-mining/status] 返回: ${JSON.stringify(result)}`); + return result; + } catch (error) { + this.logger.error(`[GET /batch-mining/status] 错误:`, error); + throw error; + } } @Post('upload-preview') @@ -49,42 +60,56 @@ export class BatchMiningController { }) @UseInterceptors(FileInterceptor('file')) async uploadAndPreview(@UploadedFile() file: Express.Multer.File) { + this.logger.log(`[POST /batch-mining/upload-preview] 开始处理上传预览请求`); + if (!file) { + this.logger.error(`[POST /batch-mining/upload-preview] 未收到文件`); throw new HttpException('请上传文件', HttpStatus.BAD_REQUEST); } + this.logger.log(`[POST /batch-mining/upload-preview] 收到文件: ${file.originalname}, 大小: ${file.size}, 类型: ${file.mimetype}`); + // 检查文件类型 const validTypes = [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel', ]; if (!validTypes.includes(file.mimetype) && !file.originalname.endsWith('.xlsx')) { + this.logger.error(`[POST /batch-mining/upload-preview] 文件类型不正确: ${file.mimetype}`); throw new HttpException('请上传 Excel 文件 (.xlsx)', HttpStatus.BAD_REQUEST); } try { // 解析 Excel + this.logger.log(`[POST /batch-mining/upload-preview] 开始解析 Excel...`); const workbook = XLSX.read(file.buffer, { type: 'buffer' }); + this.logger.log(`[POST /batch-mining/upload-preview] Excel Sheet 列表: ${workbook.SheetNames.join(', ')}`); + const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; // 尝试读取 Sheet2(如果存在) - const actualSheet = workbook.SheetNames.includes('Sheet2') - ? workbook.Sheets['Sheet2'] - : worksheet; + const actualSheetName = workbook.SheetNames.includes('Sheet2') ? 'Sheet2' : sheetName; + const actualSheet = workbook.Sheets[actualSheetName]; + this.logger.log(`[POST /batch-mining/upload-preview] 使用 Sheet: ${actualSheetName}`); // 转换为数组 const rows: any[][] = XLSX.utils.sheet_to_json(actualSheet, { header: 1 }); + this.logger.log(`[POST /batch-mining/upload-preview] Excel 总行数: ${rows.length}`); // 解析数据 const items = this.batchMiningService.parseExcelData(rows); + this.logger.log(`[POST /batch-mining/upload-preview] 解析后有效数据: ${items.length} 条`); if (items.length === 0) { + this.logger.error(`[POST /batch-mining/upload-preview] Excel 文件中没有有效数据`); throw new HttpException('Excel 文件中没有有效数据', HttpStatus.BAD_REQUEST); } // 调用预览 API + this.logger.log(`[POST /batch-mining/upload-preview] 调用 mining-service 预览 API...`); const preview = await this.batchMiningService.preview(items); + this.logger.log(`[POST /batch-mining/upload-preview] 预览成功, 总金额: ${preview.grandTotalAmount}`); return { ...preview, @@ -95,6 +120,7 @@ export class BatchMiningController { if (error instanceof HttpException) { throw error; } + this.logger.error(`[POST /batch-mining/upload-preview] 解析 Excel 文件失败:`, error); throw new HttpException( `解析 Excel 文件失败: ${error instanceof Error ? error.message : error}`, HttpStatus.BAD_REQUEST, @@ -127,10 +153,21 @@ export class BatchMiningController { }, }) async preview(@Body() body: { items: BatchMiningItem[] }) { + this.logger.log(`[POST /batch-mining/preview] 请求预览, 数据条数: ${body.items?.length || 0}`); + if (!body.items || body.items.length === 0) { + this.logger.error(`[POST /batch-mining/preview] 数据为空`); throw new HttpException('数据不能为空', HttpStatus.BAD_REQUEST); } - return this.batchMiningService.preview(body.items); + + try { + const result = await this.batchMiningService.preview(body.items); + this.logger.log(`[POST /batch-mining/preview] 预览成功`); + return result; + } catch (error) { + this.logger.error(`[POST /batch-mining/preview] 错误:`, error); + throw error; + } } @Post('upload-execute') @@ -159,11 +196,17 @@ export class BatchMiningController { @Body() body: { reason: string }, @Req() req: any, ) { + this.logger.log(`[POST /batch-mining/upload-execute] 开始处理上传执行请求`); + if (!file) { + this.logger.error(`[POST /batch-mining/upload-execute] 未收到文件`); throw new HttpException('请上传文件', HttpStatus.BAD_REQUEST); } + this.logger.log(`[POST /batch-mining/upload-execute] 收到文件: ${file.originalname}, 原因: ${body.reason}`); + if (!body.reason || body.reason.trim().length === 0) { + this.logger.error(`[POST /batch-mining/upload-execute] 补发原因为空`); throw new HttpException('补发原因不能为空', HttpStatus.BAD_REQUEST); } @@ -173,31 +216,39 @@ export class BatchMiningController { 'application/vnd.ms-excel', ]; if (!validTypes.includes(file.mimetype) && !file.originalname.endsWith('.xlsx')) { + this.logger.error(`[POST /batch-mining/upload-execute] 文件类型不正确: ${file.mimetype}`); throw new HttpException('请上传 Excel 文件 (.xlsx)', HttpStatus.BAD_REQUEST); } try { // 解析 Excel + this.logger.log(`[POST /batch-mining/upload-execute] 开始解析 Excel...`); const workbook = XLSX.read(file.buffer, { type: 'buffer' }); + this.logger.log(`[POST /batch-mining/upload-execute] Excel Sheet 列表: ${workbook.SheetNames.join(', ')}`); // 尝试读取 Sheet2(如果存在) - const actualSheet = workbook.SheetNames.includes('Sheet2') - ? workbook.Sheets['Sheet2'] - : workbook.Sheets[workbook.SheetNames[0]]; + const actualSheetName = workbook.SheetNames.includes('Sheet2') ? 'Sheet2' : workbook.SheetNames[0]; + const actualSheet = workbook.Sheets[actualSheetName]; + this.logger.log(`[POST /batch-mining/upload-execute] 使用 Sheet: ${actualSheetName}`); // 转换为数组 const rows: any[][] = XLSX.utils.sheet_to_json(actualSheet, { header: 1 }); + this.logger.log(`[POST /batch-mining/upload-execute] Excel 总行数: ${rows.length}`); // 解析数据 const items = this.batchMiningService.parseExcelData(rows); + this.logger.log(`[POST /batch-mining/upload-execute] 解析后有效数据: ${items.length} 条`); if (items.length === 0) { + this.logger.error(`[POST /batch-mining/upload-execute] Excel 文件中没有有效数据`); throw new HttpException('Excel 文件中没有有效数据', HttpStatus.BAD_REQUEST); } const admin = req.admin; + this.logger.log(`[POST /batch-mining/upload-execute] 操作管理员: ${admin?.username} (${admin?.id})`); // 调用执行 API + this.logger.log(`[POST /batch-mining/upload-execute] 调用 mining-service 执行 API...`); const result = await this.batchMiningService.execute( { items, @@ -208,6 +259,8 @@ export class BatchMiningController { admin.id, ); + this.logger.log(`[POST /batch-mining/upload-execute] 执行成功: successCount=${result.successCount}, totalAmount=${result.totalAmount}`); + return { ...result, originalFileName: file.originalname, @@ -216,6 +269,7 @@ export class BatchMiningController { if (error instanceof HttpException) { throw error; } + this.logger.error(`[POST /batch-mining/upload-execute] 执行失败:`, error); throw new HttpException( `执行失败: ${error instanceof Error ? error.message : error}`, HttpStatus.BAD_REQUEST, @@ -252,34 +306,59 @@ export class BatchMiningController { @Body() body: { items: BatchMiningItem[]; reason: string }, @Req() req: any, ) { + this.logger.log(`[POST /batch-mining/execute] 请求执行批量补发`); + this.logger.log(`[POST /batch-mining/execute] 数据条数: ${body.items?.length || 0}, 原因: ${body.reason}`); + if (!body.items || body.items.length === 0) { + this.logger.error(`[POST /batch-mining/execute] 数据为空`); throw new HttpException('数据不能为空', HttpStatus.BAD_REQUEST); } if (!body.reason || body.reason.trim().length === 0) { + this.logger.error(`[POST /batch-mining/execute] 补发原因为空`); throw new HttpException('补发原因不能为空', HttpStatus.BAD_REQUEST); } const admin = req.admin; + this.logger.log(`[POST /batch-mining/execute] 操作管理员: ${admin?.username} (${admin?.id})`); - return this.batchMiningService.execute( - { - items: body.items, - operatorId: admin.id, - operatorName: admin.username, - reason: body.reason, - }, - admin.id, - ); + try { + const result = await this.batchMiningService.execute( + { + items: body.items, + operatorId: admin.id, + operatorName: admin.username, + reason: body.reason, + }, + admin.id, + ); + this.logger.log(`[POST /batch-mining/execute] 执行成功`); + return result; + } catch (error) { + this.logger.error(`[POST /batch-mining/execute] 错误:`, error); + throw error; + } } @Get('execution') @ApiOperation({ summary: '获取批量补发执行记录(含明细)' }) async getExecution() { - const execution = await this.batchMiningService.getExecution(); - if (!execution) { - throw new HttpException('尚未执行过批量补发', HttpStatus.NOT_FOUND); + this.logger.log(`[GET /batch-mining/execution] 请求获取执行记录`); + + try { + const execution = await this.batchMiningService.getExecution(); + if (!execution) { + this.logger.log(`[GET /batch-mining/execution] 尚未执行过批量补发`); + throw new HttpException('尚未执行过批量补发', HttpStatus.NOT_FOUND); + } + this.logger.log(`[GET /batch-mining/execution] 返回执行记录: id=${execution.id}`); + return execution; + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + this.logger.error(`[GET /batch-mining/execution] 错误:`, error); + throw error; } - return execution; } } diff --git a/backend/services/mining-admin-service/src/application/services/batch-mining.service.ts b/backend/services/mining-admin-service/src/application/services/batch-mining.service.ts index bdcc1889..75b4ba87 100644 --- a/backend/services/mining-admin-service/src/application/services/batch-mining.service.ts +++ b/backend/services/mining-admin-service/src/application/services/batch-mining.service.ts @@ -47,30 +47,35 @@ export class BatchMiningService { * 获取批量补发状态 */ async getStatus(): Promise { - try { - const response = await fetch( - `${this.miningServiceUrl}/admin/batch-mining/status`, - { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }, - ); + const url = `${this.miningServiceUrl}/admin/batch-mining/status`; + this.logger.log(`[getStatus] 开始获取批量补发状态, URL: ${url}`); + try { + this.logger.log(`[getStatus] 发送 GET 请求...`); + const response = await fetch(url, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }); + + this.logger.log(`[getStatus] 响应状态码: ${response.status}`); const result = await response.json(); + this.logger.log(`[getStatus] 响应数据: ${JSON.stringify(result)}`); if (!response.ok) { + this.logger.error(`[getStatus] 请求失败: ${result.message || '未知错误'}`); throw new HttpException( result.message || '获取状态失败', response.status, ); } + this.logger.log(`[getStatus] 成功获取状态: hasExecuted=${result.hasExecuted}`); return result; } catch (error) { if (error instanceof HttpException) { throw error; } - this.logger.error('Failed to get batch mining status', error); + this.logger.error(`[getStatus] 调用 mining-service 失败:`, error); throw new HttpException( `调用 mining-service 失败: ${error instanceof Error ? error.message : error}`, HttpStatus.INTERNAL_SERVER_ERROR, @@ -82,31 +87,38 @@ export class BatchMiningService { * 预览批量补发(计算但不执行) */ async preview(items: BatchMiningItem[]): Promise { - try { - const response = await fetch( - `${this.miningServiceUrl}/admin/batch-mining/preview`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ items }), - }, - ); + const url = `${this.miningServiceUrl}/admin/batch-mining/preview`; + this.logger.log(`[preview] 开始预览批量补发, URL: ${url}`); + this.logger.log(`[preview] 数据条数: ${items.length}`); + this.logger.log(`[preview] 前3条数据: ${JSON.stringify(items.slice(0, 3))}`); + try { + this.logger.log(`[preview] 发送 POST 请求...`); + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ items }), + }); + + this.logger.log(`[preview] 响应状态码: ${response.status}`); const result = await response.json(); + this.logger.log(`[preview] 响应数据概要: totalBatches=${result.totalBatches}, totalUsers=${result.totalUsers}, grandTotalAmount=${result.grandTotalAmount}`); if (!response.ok) { + this.logger.error(`[preview] 请求失败: ${result.message || '未知错误'}`); throw new HttpException( result.message || '预览失败', response.status, ); } + this.logger.log(`[preview] 预览成功`); return result; } catch (error) { if (error instanceof HttpException) { throw error; } - this.logger.error('Failed to preview batch mining', error); + this.logger.error(`[preview] 调用 mining-service 失败:`, error); throw new HttpException( `调用 mining-service 失败: ${error instanceof Error ? error.message : error}`, HttpStatus.INTERNAL_SERVER_ERROR, @@ -121,19 +133,26 @@ export class BatchMiningService { request: BatchMiningRequest, adminId: string, ): Promise { - try { - const response = await fetch( - `${this.miningServiceUrl}/admin/batch-mining/execute`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(request), - }, - ); + const url = `${this.miningServiceUrl}/admin/batch-mining/execute`; + this.logger.log(`[execute] 开始执行批量补发, URL: ${url}`); + this.logger.log(`[execute] 操作人: ${request.operatorName} (${request.operatorId})`); + this.logger.log(`[execute] 原因: ${request.reason}`); + this.logger.log(`[execute] 数据条数: ${request.items.length}`); + try { + this.logger.log(`[execute] 发送 POST 请求...`); + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request), + }); + + this.logger.log(`[execute] 响应状态码: ${response.status}`); const result = await response.json(); + this.logger.log(`[execute] 响应数据: ${JSON.stringify(result)}`); if (!response.ok) { + this.logger.error(`[execute] 请求失败: ${result.message || '未知错误'}`); throw new HttpException( result.message || '执行失败', response.status, @@ -141,6 +160,7 @@ export class BatchMiningService { } // 记录审计日志 + this.logger.log(`[execute] 记录审计日志...`); await this.prisma.auditLog.create({ data: { adminId, @@ -158,7 +178,7 @@ export class BatchMiningService { }); this.logger.log( - `Batch mining executed by admin ${adminId}: total=${result.totalUsers}, success=${result.successCount}, amount=${result.totalAmount}`, + `[execute] 批量补发执行成功: admin=${adminId}, total=${result.totalUsers}, success=${result.successCount}, amount=${result.totalAmount}`, ); return result; @@ -166,7 +186,7 @@ export class BatchMiningService { if (error instanceof HttpException) { throw error; } - this.logger.error('Failed to execute batch mining', error); + this.logger.error(`[execute] 调用 mining-service 失败:`, error); throw new HttpException( `调用 mining-service 失败: ${error instanceof Error ? error.message : error}`, HttpStatus.INTERNAL_SERVER_ERROR, @@ -178,34 +198,41 @@ export class BatchMiningService { * 获取执行记录 */ async getExecution(): Promise { + const url = `${this.miningServiceUrl}/admin/batch-mining/execution`; + this.logger.log(`[getExecution] 开始获取执行记录, URL: ${url}`); + try { - const response = await fetch( - `${this.miningServiceUrl}/admin/batch-mining/execution`, - { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }, - ); + this.logger.log(`[getExecution] 发送 GET 请求...`); + const response = await fetch(url, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }); + + this.logger.log(`[getExecution] 响应状态码: ${response.status}`); if (response.status === 404) { + this.logger.log(`[getExecution] 未找到执行记录 (404)`); return null; } const result = await response.json(); + this.logger.log(`[getExecution] 响应数据概要: id=${result.id}, totalUsers=${result.totalUsers}`); if (!response.ok) { + this.logger.error(`[getExecution] 请求失败: ${result.message || '未知错误'}`); throw new HttpException( result.message || '获取记录失败', response.status, ); } + this.logger.log(`[getExecution] 成功获取执行记录`); return result; } catch (error) { if (error instanceof HttpException) { throw error; } - this.logger.error('Failed to get batch mining execution', error); + this.logger.error(`[getExecution] 调用 mining-service 失败:`, error); throw new HttpException( `调用 mining-service 失败: ${error instanceof Error ? error.message : error}`, HttpStatus.INTERNAL_SERVER_ERROR, @@ -219,6 +246,7 @@ export class BatchMiningService { * 序号 | 注册ID | 认种量(棵)| 挖矿开始时间 | 批次 | 授权提前挖的天数 | 备注 */ parseExcelData(rows: any[]): BatchMiningItem[] { + this.logger.log(`[parseExcelData] 开始解析 Excel 数据, 总行数: ${rows.length}`); const items: BatchMiningItem[] = []; for (let i = 0; i < rows.length; i++) { @@ -226,12 +254,14 @@ export class BatchMiningService { // 跳过标题行和汇总行 if (!row || typeof row[1] !== 'string' || row[1] === '注册ID' || row[1] === '合计') { + this.logger.debug(`[parseExcelData] 跳过行 ${i + 1}: 标题行或汇总行`); continue; } // 跳过认种量为 0 或无效的行 const treeCount = parseInt(row[2], 10); if (isNaN(treeCount) || treeCount <= 0) { + this.logger.debug(`[parseExcelData] 跳过行 ${i + 1}: 认种量无效 (${row[2]})`); continue; } @@ -245,7 +275,7 @@ export class BatchMiningService { const preMineDays = parseInt(row[5], 10); if (isNaN(batch) || isNaN(preMineDays) || preMineDays <= 0) { - this.logger.warn(`Skipping row ${i + 1}: invalid batch or preMineDays`); + this.logger.warn(`[parseExcelData] 跳过行 ${i + 1}: 批次或提前天数无效 (batch=${row[4]}, preMineDays=${row[5]})`); continue; } @@ -259,6 +289,12 @@ export class BatchMiningService { }); } + this.logger.log(`[parseExcelData] 解析完成, 有效数据: ${items.length} 条`); + if (items.length > 0) { + this.logger.log(`[parseExcelData] 第一条数据: ${JSON.stringify(items[0])}`); + this.logger.log(`[parseExcelData] 最后一条数据: ${JSON.stringify(items[items.length - 1])}`); + } + return items; } } 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 541e3d43..89fa0eb2 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 @@ -112,7 +112,9 @@ export class BatchMiningService { * 检查批量补发是否已经执行过 */ async hasExecuted(): Promise { + this.logger.log('[hasExecuted] 检查批量补发是否已执行...'); const record = await this.prisma.batchMiningExecution.findFirst(); + this.logger.log(`[hasExecuted] 结果: ${!!record}`); return !!record; } @@ -120,6 +122,9 @@ export class BatchMiningService { * 预览批量补发(计算但不执行) */ async preview(items: BatchMiningItem[]): Promise { + this.logger.log(`[preview] 开始预览批量补发, 共 ${items.length} 条数据`); + this.logger.debug(`[preview] 数据样例: ${JSON.stringify(items.slice(0, 3))}`); + // 检查是否已执行过 const alreadyExecuted = await this.hasExecuted(); if (alreadyExecuted) { @@ -136,16 +141,20 @@ export class BatchMiningService { } // 获取挖矿配置 + this.logger.log('[preview] 获取挖矿配置...'); const config = await this.miningConfigRepository.getConfig(); if (!config) { + this.logger.error('[preview] 挖矿配置不存在'); throw new BadRequestException('挖矿配置不存在'); } const secondDistribution = config.secondDistribution.value; + this.logger.log(`[preview] 每秒分配量: ${secondDistribution.toString()}`); // 按批次分组并排序 const batchGroups = this.groupByBatch(items); const sortedBatches = Array.from(batchGroups.keys()).sort((a, b) => a - b); + this.logger.log(`[preview] 分组完成, 共 ${sortedBatches.length} 个批次: ${sortedBatches.join(', ')}`); let cumulativeContribution = new Decimal(0); // 累计全网算力 let grandTotalAmount = new Decimal(0); @@ -199,7 +208,7 @@ export class BatchMiningService { grandTotalAmount = grandTotalAmount.plus(batchTotalAmount); } - return { + const result = { canExecute: true, alreadyExecuted: false, totalBatches: sortedBatches.length, @@ -208,6 +217,8 @@ export class BatchMiningService { grandTotalAmount: grandTotalAmount.toFixed(8), message: `预览成功: ${sortedBatches.length} 个批次, ${items.length} 个用户, 总补发金额 ${grandTotalAmount.toFixed(8)}`, }; + this.logger.log(`[preview] 预览完成: ${result.message}`); + return result; } /** @@ -215,6 +226,8 @@ export class BatchMiningService { */ async execute(request: BatchMiningRequest): Promise { const { items, operatorId, operatorName, reason } = request; + this.logger.log(`[execute] 开始执行批量补发, 操作人: ${operatorName}, 共 ${items.length} 条数据`); + this.logger.log(`[execute] 原因: ${reason}`); // 检查是否已执行过 const alreadyExecuted = await this.hasExecuted(); @@ -446,6 +459,7 @@ export class BatchMiningService { * 获取批量补发执行记录 */ async getExecution(): Promise { + this.logger.log('[getExecution] 获取批量补发执行记录...'); const execution = await this.prisma.batchMiningExecution.findFirst({ include: { records: { @@ -455,9 +469,11 @@ export class BatchMiningService { }); if (!execution) { + this.logger.log('[getExecution] 未找到执行记录'); return null; } + this.logger.log(`[getExecution] 找到执行记录: id=${execution.id}, records=${execution.records.length}`); return { id: execution.id, operatorId: execution.operatorId, 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 93bbf344..c35cded9 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 @@ -110,8 +110,15 @@ export default function BatchMiningPage() { const { data: statusData, isLoading: statusLoading } = useQuery({ queryKey: ['batch-mining-status'], queryFn: async () => { - const res = await apiClient.get('/batch-mining/status'); - return res.data; + console.log('[BatchMining] 开始获取批量补发状态...'); + try { + const res = await apiClient.get('/batch-mining/status'); + console.log('[BatchMining] 状态响应:', res.data); + return res.data; + } catch (error) { + console.error('[BatchMining] 获取状态失败:', error); + throw error; + } }, }); @@ -119,10 +126,13 @@ export default function BatchMiningPage() { const { data: executionData, isLoading: executionLoading } = useQuery({ queryKey: ['batch-mining-execution'], queryFn: async () => { + console.log('[BatchMining] 开始获取执行记录...'); try { const res = await apiClient.get('/batch-mining/execution'); + console.log('[BatchMining] 执行记录响应:', res.data); return res.data as BatchExecution; - } catch { + } catch (error) { + console.error('[BatchMining] 获取执行记录失败:', error); return null; } }, @@ -132,22 +142,32 @@ export default function BatchMiningPage() { // 上传预览 const uploadPreviewMutation = useMutation({ mutationFn: async (file: File) => { + console.log('[BatchMining] 开始上传文件预览:', file.name, '大小:', file.size); const formData = new FormData(); formData.append('file', file); - const res = await apiClient.post('/batch-mining/upload-preview', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); - return res.data as BatchPreviewResult; + try { + const res = await apiClient.post('/batch-mining/upload-preview', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + console.log('[BatchMining] 上传预览响应:', res.data); + return res.data as BatchPreviewResult; + } catch (error: any) { + console.error('[BatchMining] 上传预览失败:', error.response?.status, error.response?.data); + throw error; + } }, onSuccess: (data) => { + console.log('[BatchMining] 预览成功, 批次数:', data.totalBatches, '用户数:', data.totalUsers, '总金额:', data.grandTotalAmount); setPreviewResult(data); setParsedItems(data.parsedItems || []); setFileName(data.originalFileName || ''); if (data.alreadyExecuted) { + console.warn('[BatchMining] 批量补发已执行过'); toast({ title: '批量补发已执行过,不能重复执行', variant: 'destructive' }); } }, onError: (error: any) => { + console.error('[BatchMining] 上传预览 mutation 错误:', error); toast({ title: error.response?.data?.message || '上传失败', variant: 'destructive' }); setPreviewResult(null); setParsedItems([]); @@ -157,10 +177,18 @@ export default function BatchMiningPage() { // 执行补发 const executeMutation = useMutation({ mutationFn: async (data: { items: BatchItem[]; reason: string }) => { - const res = await apiClient.post('/batch-mining/execute', data); - return res.data; + console.log('[BatchMining] 开始执行批量补发, 数据条数:', data.items.length, '原因:', data.reason); + try { + const res = await apiClient.post('/batch-mining/execute', data); + console.log('[BatchMining] 执行响应:', res.data); + return res.data; + } catch (error: any) { + console.error('[BatchMining] 执行失败:', error.response?.status, error.response?.data); + throw error; + } }, onSuccess: (data) => { + console.log('[BatchMining] 执行成功, successCount:', data.successCount, 'totalAmount:', data.totalAmount); toast({ title: `批量补发成功`, description: `成功 ${data.successCount} 个,总金额 ${parseFloat(data.totalAmount).toFixed(8)} 积分股`, @@ -175,14 +203,19 @@ export default function BatchMiningPage() { queryClient.invalidateQueries({ queryKey: ['batch-mining-execution'] }); }, onError: (error: any) => { + console.error('[BatchMining] 执行 mutation 错误:', error); toast({ title: error.response?.data?.message || '执行失败', variant: 'destructive' }); }, }); const handleFileSelect = (e: React.ChangeEvent) => { + console.log('[BatchMining] 文件选择事件触发'); const file = e.target.files?.[0]; if (file) { + console.log('[BatchMining] 选中文件:', file.name, '大小:', file.size, '类型:', file.type); uploadPreviewMutation.mutate(file); + } else { + console.log('[BatchMining] 未选择文件'); } // Reset input if (fileInputRef.current) { @@ -191,10 +224,13 @@ export default function BatchMiningPage() { }; const handleExecute = () => { + console.log('[BatchMining] 点击执行按钮, 原因:', reason, '数据条数:', parsedItems.length); if (!reason.trim()) { + console.warn('[BatchMining] 补发原因为空'); toast({ title: '请输入补发原因', variant: 'destructive' }); return; } + console.log('[BatchMining] 开始调用执行 mutation...'); executeMutation.mutate({ items: parsedItems, reason }); };