using Application.Abstractions.Data; using Application.Abstractions.YouTube; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Infrastructure.YouTube; /// /// YouTube API Key 캐시 싱글톤 — 5분 TTL + 수동 Invalidate 지원. /// DelegatingHandler는 풀링되어 instance 상태 공유가 불안정하므로 별도 싱글톤에 캐시 분리. /// internal sealed class YouTubeApiKeyProvider( IServiceProvider serviceProvider, ILogger logger ) : IYouTubeApiKeyProvider { private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5); private readonly SemaphoreSlim _lock = new(1, 1); private string? _cachedApiKey; private DateTime _cachedAt = DateTime.MinValue; private bool _hasCachedValue; public async Task GetAsync(CancellationToken ct) { if (_hasCachedValue && DateTime.UtcNow - _cachedAt < CacheDuration) { return _cachedApiKey; } await _lock.WaitAsync(ct); try { // double-check after acquiring lock if (_hasCachedValue && DateTime.UtcNow - _cachedAt < CacheDuration) { return _cachedApiKey; } try { using var scope = serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var config = await db.Config.AsNoTracking().FirstOrDefaultAsync(ct); _cachedApiKey = config?.External?.YouTubeApiKeyEnc; _cachedAt = DateTime.UtcNow; _hasCachedValue = true; return _cachedApiKey; } catch (Exception ex) { logger.LogWarning(ex, "[YouTube] API Key 조회 실패"); return _cachedApiKey; } } finally { _lock.Release(); } } public void Invalidate() { _hasCachedValue = false; _cachedApiKey = null; _cachedAt = DateTime.MinValue; } }