90 lines
2.9 KiB
TypeScript
90 lines
2.9 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { ContributionAccountRepository } from '../../infrastructure/persistence/repositories/contribution-account.repository';
|
|
import { RedisService } from '../../infrastructure/redis/redis.service';
|
|
|
|
export interface ContributionRankingDto {
|
|
rank: number;
|
|
accountSequence: string;
|
|
effectiveContribution: string;
|
|
personalContribution: string;
|
|
totalPending: string;
|
|
totalUnlocked: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class GetContributionRankingQuery {
|
|
private readonly RANKING_CACHE_KEY = 'contribution:ranking';
|
|
private readonly CACHE_TTL = 300; // 5分钟缓存
|
|
|
|
constructor(
|
|
private readonly accountRepository: ContributionAccountRepository,
|
|
private readonly redis: RedisService,
|
|
) {}
|
|
|
|
/**
|
|
* 获取算力排行榜
|
|
*/
|
|
async execute(limit: number = 100): Promise<ContributionRankingDto[]> {
|
|
// 尝试从缓存获取
|
|
const cached = await this.redis.getJson<ContributionRankingDto[]>(`${this.RANKING_CACHE_KEY}:${limit}`);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
|
|
// 从数据库获取
|
|
const topContributors = await this.accountRepository.findTopContributors(limit);
|
|
|
|
const ranking: ContributionRankingDto[] = topContributors.map((account, index) => ({
|
|
rank: index + 1,
|
|
accountSequence: account.accountSequence,
|
|
effectiveContribution: account.effectiveContribution.value.toString(),
|
|
personalContribution: account.personalContribution.value.toString(),
|
|
totalPending: account.totalPending.value.toString(),
|
|
totalUnlocked: account.totalUnlocked.value.toString(),
|
|
}));
|
|
|
|
// 缓存结果
|
|
await this.redis.setJson(`${this.RANKING_CACHE_KEY}:${limit}`, ranking, this.CACHE_TTL);
|
|
|
|
return ranking;
|
|
}
|
|
|
|
/**
|
|
* 获取指定用户的排名
|
|
*/
|
|
async getUserRank(accountSequence: string): Promise<{
|
|
rank: number | null;
|
|
effectiveContribution: string;
|
|
percentile: number | null;
|
|
} | null> {
|
|
const account = await this.accountRepository.findByAccountSequence(accountSequence);
|
|
if (!account) {
|
|
return null;
|
|
}
|
|
|
|
// 使用 Redis 有序集合来快速获取排名
|
|
// 这需要在算力变化时同步更新 Redis
|
|
const rank = await this.redis.zrevrank('contribution:leaderboard', accountSequence);
|
|
const totalAccounts = await this.accountRepository.countAccountsWithContribution();
|
|
|
|
return {
|
|
rank: rank !== null ? rank + 1 : null,
|
|
effectiveContribution: account.effectiveContribution.value.toString(),
|
|
percentile: rank !== null && totalAccounts > 0 ? ((totalAccounts - rank) / totalAccounts) * 100 : null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 刷新排行榜缓存
|
|
*/
|
|
async refreshRankingCache(): Promise<void> {
|
|
// 清除旧缓存
|
|
await this.redis.del(`${this.RANKING_CACHE_KEY}:100`);
|
|
await this.redis.del(`${this.RANKING_CACHE_KEY}:50`);
|
|
await this.redis.del(`${this.RANKING_CACHE_KEY}:10`);
|
|
|
|
// 重新生成缓存
|
|
await this.execute(100);
|
|
}
|
|
}
|