| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
- import { CacheKeys } from '@box/common/cache/cache-keys';
- import { RedisService } from '@box/db/redis/redis.service';
- import { MongoPrismaService } from '@box/db/prisma/mongo-prisma.service';
- import type { AdType } from '@box/common/ads/ad-types';
- interface CachedAd {
- id: string;
- channelId: string;
- adsModuleId: string;
- advertiser: string;
- title: string;
- adsContent: string | null;
- adsCoverImg: string | null;
- adsUrl: string | null;
- adType: string;
- }
- /**
- * AdCacheWarmupService
- *
- * Responsible for warming up individual ad caches (app:ad:by-id:${adId})
- * on application startup or on-demand.
- *
- * This complements AdPoolWarmupService which handles ad pools.
- */
- @Injectable()
- export class AdCacheWarmupService implements OnModuleInit {
- private readonly logger = new Logger(AdCacheWarmupService.name);
- private readonly AD_CACHE_TTL = 300; // 5 minutes
- constructor(
- private readonly redis: RedisService,
- private readonly mongoPrisma: MongoPrismaService,
- ) {}
- async onModuleInit(): Promise<void> {
- try {
- this.logger.log('Individual ad cache warmup starting...');
- await this.warmupAllAdCaches();
- } catch (err) {
- this.logger.error(
- 'Individual ad cache warmup encountered an error but will not block startup',
- err instanceof Error ? err.stack : String(err),
- );
- }
- }
- /**
- * Warm up all active ads' individual caches.
- * Only caches ads that are enabled and within their date range.
- */
- async warmupAllAdCaches(): Promise<void> {
- const startTime = Date.now();
- const now = BigInt(Date.now());
- try {
- // Fetch all active ads
- const ads = await this.mongoPrisma.ads.findMany({
- where: {
- status: 1, // enabled
- startDt: { lte: now },
- OR: [{ expiryDt: BigInt(0) }, { expiryDt: { gte: now } }],
- },
- include: {
- adsModule: { select: { adType: true } },
- },
- });
- this.logger.log(`Found ${ads.length} active ads to cache`);
- let successCount = 0;
- let errorCount = 0;
- // Cache each ad individually
- for (const ad of ads) {
- try {
- await this.cacheAd(ad.id, {
- id: ad.id,
- channelId: ad.channelId,
- adsModuleId: ad.adsModuleId,
- advertiser: ad.advertiser,
- title: ad.title,
- adsContent: ad.adsContent ?? null,
- adsCoverImg: ad.adsCoverImg ?? null,
- adsUrl: ad.adsUrl ?? null,
- adType: ad.adsModule.adType,
- });
- successCount++;
- } catch (err) {
- errorCount++;
- this.logger.warn(
- `Failed to cache ad ${ad.id}: ${err instanceof Error ? err.message : String(err)}`,
- );
- }
- }
- const duration = Date.now() - startTime;
- this.logger.log(
- `Ad cache warmup completed: ${successCount} cached, ${errorCount} errors, ${duration}ms`,
- );
- } catch (err) {
- this.logger.error(
- 'Ad cache warmup failed',
- err instanceof Error ? err.stack : String(err),
- );
- throw err;
- }
- }
- /**
- * Cache a single ad by ID
- */
- private async cacheAd(adId: string, cachedAd: CachedAd): Promise<void> {
- const key = CacheKeys.appAdById(adId);
- await this.redis.setJson(key, cachedAd, this.AD_CACHE_TTL);
- }
- /**
- * Warm up a single ad cache (used for on-demand refresh)
- */
- async warmupSingleAd(adId: string): Promise<void> {
- const now = BigInt(Date.now());
- const ad = await this.mongoPrisma.ads.findUnique({
- where: { id: adId },
- include: {
- adsModule: { select: { adType: true } },
- },
- });
- if (!ad) {
- // Ad doesn't exist - remove from cache
- const key = CacheKeys.appAdById(adId);
- await this.redis.del(key);
- this.logger.debug(`Ad ${adId} not found, removed from cache`);
- return;
- }
- // Check if ad is active
- const isActive =
- ad.status === 1 &&
- ad.startDt <= now &&
- (ad.expiryDt === BigInt(0) || ad.expiryDt >= now);
- if (!isActive) {
- // Ad is not active - remove from cache
- const key = CacheKeys.appAdById(adId);
- await this.redis.del(key);
- this.logger.debug(`Ad ${adId} is not active, removed from cache`);
- return;
- }
- // Cache the ad
- await this.cacheAd(adId, {
- id: ad.id,
- channelId: ad.channelId,
- adsModuleId: ad.adsModuleId,
- advertiser: ad.advertiser,
- title: ad.title,
- adsContent: ad.adsContent ?? null,
- adsCoverImg: ad.adsCoverImg ?? null,
- adsUrl: ad.adsUrl ?? null,
- adType: ad.adsModule.adType,
- });
- this.logger.debug(`Cached ad ${adId}`);
- }
- }
|