114 lines
3.3 KiB
TypeScript
114 lines
3.3 KiB
TypeScript
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||
import { MiningDistributionService } from '../services/mining-distribution.service';
|
||
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
||
import { RedisService } from '../../infrastructure/redis/redis.service';
|
||
|
||
@Injectable()
|
||
export class MiningScheduler implements OnModuleInit {
|
||
private readonly logger = new Logger(MiningScheduler.name);
|
||
|
||
constructor(
|
||
private readonly distributionService: MiningDistributionService,
|
||
private readonly prisma: PrismaService,
|
||
private readonly redis: RedisService,
|
||
) {}
|
||
|
||
onModuleInit() {
|
||
this.logger.log('Mining scheduler initialized');
|
||
}
|
||
|
||
/**
|
||
* 每分钟执行挖矿分配
|
||
*/
|
||
@Cron(CronExpression.EVERY_MINUTE)
|
||
async executeMinuteDistribution(): Promise<void> {
|
||
try {
|
||
await this.distributionService.executeMinuteDistribution();
|
||
} catch (error) {
|
||
this.logger.error('Failed to execute minute distribution', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 每天凌晨0点生成每日统计
|
||
*/
|
||
@Cron('0 0 * * *')
|
||
async generateDailyStats(): Promise<void> {
|
||
const lockValue = await this.redis.acquireLock('mining:daily-stats:lock', 300);
|
||
if (!lockValue) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const yesterday = new Date();
|
||
yesterday.setDate(yesterday.getDate() - 1);
|
||
yesterday.setHours(0, 0, 0, 0);
|
||
|
||
const endOfYesterday = new Date(yesterday);
|
||
endOfYesterday.setHours(23, 59, 59, 999);
|
||
|
||
// 聚合昨天的分钟统计
|
||
const minuteStats = await this.prisma.minuteMiningStat.aggregate({
|
||
where: {
|
||
minute: {
|
||
gte: yesterday,
|
||
lte: endOfYesterday,
|
||
},
|
||
},
|
||
_sum: {
|
||
totalDistributed: true,
|
||
burnAmount: true,
|
||
},
|
||
_avg: {
|
||
totalContribution: true,
|
||
},
|
||
_max: {
|
||
participantCount: true,
|
||
},
|
||
});
|
||
|
||
// 创建每日统计
|
||
await this.prisma.dailyMiningStat.create({
|
||
data: {
|
||
date: yesterday,
|
||
totalContribution: minuteStats._avg.totalContribution || 0,
|
||
totalDistributed: minuteStats._sum.totalDistributed || 0,
|
||
totalBurned: minuteStats._sum.burnAmount || 0,
|
||
participantCount: minuteStats._max.participantCount || 0,
|
||
avgContributionRate: 0, // TODO: 计算
|
||
},
|
||
});
|
||
|
||
this.logger.log(`Daily stats generated for ${yesterday.toISOString().split('T')[0]}`);
|
||
} catch (error) {
|
||
this.logger.error('Failed to generate daily stats', error);
|
||
} finally {
|
||
await this.redis.releaseLock('mining:daily-stats:lock', lockValue);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 每小时清理旧的分钟统计(保留7天)
|
||
*/
|
||
@Cron('0 * * * *')
|
||
async cleanupOldMinuteStats(): Promise<void> {
|
||
try {
|
||
const sevenDaysAgo = new Date();
|
||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||
|
||
const result = await this.prisma.minuteMiningStat.deleteMany({
|
||
where: {
|
||
minute: { lt: sevenDaysAgo },
|
||
},
|
||
});
|
||
|
||
if (result.count > 0) {
|
||
this.logger.log(`Cleaned up ${result.count} old minute stats`);
|
||
}
|
||
} catch (error) {
|
||
this.logger.error('Failed to cleanup old minute stats', error);
|
||
}
|
||
}
|
||
}
|