feat(batch-mining): 添加详细的调试日志
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
cb9831f2fc
commit
7a4f5591b7
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,30 +47,35 @@ export class BatchMiningService {
|
|||
* 获取批量补发状态
|
||||
*/
|
||||
async getStatus(): Promise<any> {
|
||||
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<any> {
|
||||
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<any> {
|
||||
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<any> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ export class BatchMiningService {
|
|||
* 检查批量补发是否已经执行过
|
||||
*/
|
||||
async hasExecuted(): Promise<boolean> {
|
||||
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<BatchMiningPreviewResult> {
|
||||
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<BatchMiningResult> {
|
||||
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<any | null> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<HTMLInputElement>) => {
|
||||
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 });
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue