71 lines
2.7 KiB
TypeScript
71 lines
2.7 KiB
TypeScript
import { Injectable, Logger, Inject } from '@nestjs/common';
|
|
import { Cron, CronExpression } from '@nestjs/schedule';
|
|
import { CommandBus } from '@nestjs/cqrs';
|
|
import { subDays } from 'date-fns';
|
|
import { CalculateDauCommand } from '../commands/calculate-dau/calculate-dau.command';
|
|
import { PresenceRedisRepository } from '../../infrastructure/redis/presence-redis.repository';
|
|
import { OnlineDetectionService } from '../../domain/services/online-detection.service';
|
|
import { OnlineSnapshot } from '../../domain/entities/online-snapshot.entity';
|
|
import { IOnlineSnapshotRepository, ONLINE_SNAPSHOT_REPOSITORY } from '../../domain/repositories/online-snapshot.repository.interface';
|
|
|
|
@Injectable()
|
|
export class AnalyticsScheduler {
|
|
private readonly logger = new Logger(AnalyticsScheduler.name);
|
|
|
|
constructor(
|
|
private readonly commandBus: CommandBus,
|
|
private readonly presenceRedisRepository: PresenceRedisRepository,
|
|
private readonly onlineDetectionService: OnlineDetectionService,
|
|
@Inject(ONLINE_SNAPSHOT_REPOSITORY)
|
|
private readonly snapshotRepository: IOnlineSnapshotRepository,
|
|
) {}
|
|
|
|
@Cron(CronExpression.EVERY_MINUTE)
|
|
async recordOnlineSnapshot(): Promise<void> {
|
|
try {
|
|
const now = new Date();
|
|
const threshold = this.onlineDetectionService.getOnlineThreshold(now);
|
|
const count = await this.presenceRedisRepository.countOnlineUsers(threshold);
|
|
const snapshot = OnlineSnapshot.create({
|
|
ts: now,
|
|
onlineCount: count,
|
|
windowSeconds: this.onlineDetectionService.getWindowSeconds(),
|
|
});
|
|
await this.snapshotRepository.insert(snapshot);
|
|
this.logger.debug(`Online snapshot: ${count} users`);
|
|
} catch (error) {
|
|
this.logger.error('Failed to record online snapshot', error);
|
|
}
|
|
}
|
|
|
|
@Cron(CronExpression.EVERY_HOUR)
|
|
async cleanupExpiredPresence(): Promise<void> {
|
|
try {
|
|
const threshold = Math.floor(Date.now() / 1000) - 86400;
|
|
await this.presenceRedisRepository.removeExpiredUsers(threshold);
|
|
} catch (error) {
|
|
this.logger.error('Failed to cleanup expired presence', error);
|
|
}
|
|
}
|
|
|
|
@Cron('0 0 1 * * *', { timeZone: 'Asia/Shanghai' })
|
|
async calculateYesterdayDau(): Promise<void> {
|
|
try {
|
|
const yesterday = subDays(new Date(), 1);
|
|
await this.commandBus.execute(new CalculateDauCommand(yesterday));
|
|
this.logger.log('Yesterday DAU calculated');
|
|
} catch (error) {
|
|
this.logger.error('Failed to calculate yesterday DAU', error);
|
|
}
|
|
}
|
|
|
|
@Cron(CronExpression.EVERY_HOUR)
|
|
async calculateTodayDauRolling(): Promise<void> {
|
|
try {
|
|
await this.commandBus.execute(new CalculateDauCommand(new Date()));
|
|
} catch (error) {
|
|
this.logger.error('Failed to calculate today DAU', error);
|
|
}
|
|
}
|
|
}
|