refactor
This commit is contained in:
parent
4489ca3c74
commit
0bdbdba9f8
|
@ -15,7 +15,7 @@ const GLOBAL_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
|
||||||
export class FeaturedService {
|
export class FeaturedService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,103 +26,75 @@ export class FeaturedService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private getCurrentGlobalNotesRankingWindow(): number {
|
private getCurrentWindow(windowRange: number): number {
|
||||||
const passed = new Date().getTime() - new Date(new Date().getFullYear(), 0, 1).getTime();
|
const passed = new Date().getTime() - new Date(new Date().getFullYear(), 0, 1).getTime();
|
||||||
return Math.floor(passed / GLOBAL_NOTES_RANKING_WINDOW);
|
return Math.floor(passed / windowRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async updateRankingOf(name: string, windowRange: number, element: string, score = 1): Promise<void> {
|
||||||
|
const currentWindow = this.getCurrentWindow(windowRange);
|
||||||
|
const redisTransaction = this.redisClient.multi();
|
||||||
|
redisTransaction.zincrby(
|
||||||
|
`${name}:${currentWindow}`,
|
||||||
|
score,
|
||||||
|
element);
|
||||||
|
redisTransaction.expire(
|
||||||
|
`${name}:${currentWindow}`,
|
||||||
|
(windowRange * 3) / 1000,
|
||||||
|
'NX'); // "NX -- Set expiry only when the key has no expiry" = 有効期限がないときだけ設定
|
||||||
|
await redisTransaction.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async getRankingOf(name: string, windowRange: number, limit: number): Promise<string[]> {
|
||||||
|
const currentWindow = this.getCurrentWindow(windowRange);
|
||||||
|
const previousWindow = currentWindow - 1;
|
||||||
|
|
||||||
|
const [currentRankingResult, previousRankingResult] = await Promise.all([
|
||||||
|
this.redisClient.zrange(
|
||||||
|
`${name}:${currentWindow}`, 0, limit, 'REV', 'WITHSCORES'),
|
||||||
|
this.redisClient.zrange(
|
||||||
|
`${name}:${previousWindow}`, 0, limit, 'REV', 'WITHSCORES'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const ranking = new Map<string, number>();
|
||||||
|
for (let i = 0; i < currentRankingResult.length; i += 2) {
|
||||||
|
const noteId = currentRankingResult[i];
|
||||||
|
const score = parseInt(currentRankingResult[i + 1], 10);
|
||||||
|
ranking.set(noteId, score);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < previousRankingResult.length; i += 2) {
|
||||||
|
const noteId = previousRankingResult[i];
|
||||||
|
const score = parseInt(previousRankingResult[i + 1], 10);
|
||||||
|
const exist = ranking.get(noteId);
|
||||||
|
if (exist != null) {
|
||||||
|
ranking.set(noteId, (exist + score) / 2);
|
||||||
|
} else {
|
||||||
|
ranking.set(noteId, score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(ranking.keys());
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async updateGlobalNotesRanking(noteId: MiNote['id'], score = 1): Promise<void> {
|
public async updateGlobalNotesRanking(noteId: MiNote['id'], score = 1): Promise<void> {
|
||||||
// TODO: フォロワー数の多い人が常にランキング上位になるのを防ぎたい
|
return this.updateRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
|
||||||
const currentWindow = this.getCurrentGlobalNotesRankingWindow();
|
|
||||||
const redisTransaction = this.redisClient.multi();
|
|
||||||
redisTransaction.zincrby(
|
|
||||||
`featuredGlobalNotesRanking:${currentWindow}`,
|
|
||||||
score.toString(),
|
|
||||||
noteId);
|
|
||||||
redisTransaction.expire(
|
|
||||||
`featuredGlobalNotesRanking:${currentWindow}`,
|
|
||||||
(GLOBAL_NOTES_RANKING_WINDOW * 3) / 1000,
|
|
||||||
'NX'); // "NX -- Set expiry only when the key has no expiry" = 有効期限がないときだけ設定
|
|
||||||
await redisTransaction.exec();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async updateInChannelNotesRanking(noteId: MiNote['id'], channelId: MiNote['channelId'], score = 1): Promise<void> {
|
public async updateInChannelNotesRanking(noteId: MiNote['id'], channelId: MiNote['channelId'], score = 1): Promise<void> {
|
||||||
const currentWindow = this.getCurrentGlobalNotesRankingWindow();
|
return this.updateRankingOf(`featuredInChannelNotesRanking:${channelId}`, GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
|
||||||
const redisTransaction = this.redisClient.multi();
|
|
||||||
redisTransaction.zincrby(
|
|
||||||
`featuredInChannelNotesRanking:${channelId}:${currentWindow}`,
|
|
||||||
score.toString(),
|
|
||||||
noteId);
|
|
||||||
redisTransaction.expire(
|
|
||||||
`featuredInChannelNotesRanking:${channelId}:${currentWindow}`,
|
|
||||||
(GLOBAL_NOTES_RANKING_WINDOW * 3) / 1000,
|
|
||||||
'NX'); // "NX -- Set expiry only when the key has no expiry" = 有効期限がないときだけ設定
|
|
||||||
await redisTransaction.exec();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getGlobalNotesRanking(limit: number): Promise<MiNote['id'][]> {
|
public async getGlobalNotesRanking(limit: number): Promise<MiNote['id'][]> {
|
||||||
const currentWindow = this.getCurrentGlobalNotesRankingWindow();
|
return this.getRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, limit);
|
||||||
const previousWindow = currentWindow - 1;
|
|
||||||
|
|
||||||
const [currentRankingResult, previousRankingResult] = await Promise.all([
|
|
||||||
this.redisClient.zrange(
|
|
||||||
`featuredGlobalNotesRanking:${currentWindow}`, 0, limit, 'REV', 'WITHSCORES'),
|
|
||||||
this.redisClient.zrange(
|
|
||||||
`featuredGlobalNotesRanking:${previousWindow}`, 0, limit, 'REV', 'WITHSCORES'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ranking = new Map<MiNote['id'], number>();
|
|
||||||
for (let i = 0; i < currentRankingResult.length; i += 2) {
|
|
||||||
const noteId = currentRankingResult[i];
|
|
||||||
const score = parseInt(currentRankingResult[i + 1], 10);
|
|
||||||
ranking.set(noteId, score);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < previousRankingResult.length; i += 2) {
|
|
||||||
const noteId = previousRankingResult[i];
|
|
||||||
const score = parseInt(previousRankingResult[i + 1], 10);
|
|
||||||
const exist = ranking.get(noteId);
|
|
||||||
if (exist != null) {
|
|
||||||
ranking.set(noteId, (exist + score) / 2);
|
|
||||||
} else {
|
|
||||||
ranking.set(noteId, score);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(ranking.keys());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getInChannelNotesRanking(channelId: MiNote['channelId'], limit: number): Promise<MiNote['id'][]> {
|
public async getInChannelNotesRanking(channelId: MiNote['channelId'], limit: number): Promise<MiNote['id'][]> {
|
||||||
const currentWindow = this.getCurrentGlobalNotesRankingWindow();
|
return this.getRankingOf(`featuredInChannelNotesRanking:${channelId}`, GLOBAL_NOTES_RANKING_WINDOW, limit);
|
||||||
const previousWindow = currentWindow - 1;
|
|
||||||
|
|
||||||
const [currentRankingResult, previousRankingResult] = await Promise.all([
|
|
||||||
this.redisClient.zrange(
|
|
||||||
`featuredInChannelNotesRanking:${channelId}:${currentWindow}`, 0, limit, 'REV', 'WITHSCORES'),
|
|
||||||
this.redisClient.zrange(
|
|
||||||
`featuredInChannelNotesRanking:${channelId}:${previousWindow}`, 0, limit, 'REV', 'WITHSCORES'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ranking = new Map<MiNote['id'], number>();
|
|
||||||
for (let i = 0; i < currentRankingResult.length; i += 2) {
|
|
||||||
const noteId = currentRankingResult[i];
|
|
||||||
const score = parseInt(currentRankingResult[i + 1], 10);
|
|
||||||
ranking.set(noteId, score);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < previousRankingResult.length; i += 2) {
|
|
||||||
const noteId = previousRankingResult[i];
|
|
||||||
const score = parseInt(previousRankingResult[i + 1], 10);
|
|
||||||
const exist = ranking.get(noteId);
|
|
||||||
if (exist != null) {
|
|
||||||
ranking.set(noteId, (exist + score) / 2);
|
|
||||||
} else {
|
|
||||||
ranking.set(noteId, score);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(ranking.keys());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue