gcx/backend/services/user-service/src/infrastructure/redis/presence-redis.service.ts

60 lines
1.8 KiB
TypeScript

import { Injectable, OnModuleDestroy } from '@nestjs/common';
import Redis from 'ioredis';
const ONLINE_KEY = 'genex:presence:online';
const DAU_KEY_PREFIX = 'genex:dau:';
const ONLINE_WINDOW = 180; // 3 minutes
@Injectable()
export class PresenceRedisService implements OnModuleDestroy {
private readonly redis: Redis;
constructor() {
this.redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379', 10),
password: process.env.REDIS_PASSWORD || undefined,
db: parseInt(process.env.REDIS_DB || '0', 10),
});
}
async onModuleDestroy() {
await this.redis.quit();
}
/** Update user heartbeat timestamp */
async updatePresence(userId: string): Promise<void> {
const now = Math.floor(Date.now() / 1000);
await this.redis.zadd(ONLINE_KEY, now, userId);
}
/** Count users online within window */
async countOnline(): Promise<number> {
const threshold = Math.floor(Date.now() / 1000) - ONLINE_WINDOW;
return this.redis.zcount(ONLINE_KEY, threshold, '+inf');
}
/** Add user/installId to HyperLogLog DAU */
async addDauIdentifier(date: string, identifier: string): Promise<void> {
const key = `${DAU_KEY_PREFIX}${date}`;
await this.redis.pfadd(key, identifier);
// Auto-expire after 48 hours
await this.redis.expire(key, 48 * 3600);
}
/** Get approximate DAU from HyperLogLog */
async getApproxDau(date: string): Promise<number> {
return this.redis.pfcount(`${DAU_KEY_PREFIX}${date}`);
}
/** Clean up expired presence data (>24h old) */
async cleanupExpired(): Promise<number> {
const cutoff = Math.floor(Date.now() / 1000) - 24 * 3600;
return this.redis.zremrangebyscore(ONLINE_KEY, '-inf', cutoff);
}
getWindowSeconds(): number {
return ONLINE_WINDOW;
}
}