using System.Security.Cryptography; using System.Text; using Microsoft.EntityFrameworkCore; using DramaLing.Api.Data; using DramaLing.Api.Models.Entities; using DramaLing.Api.Models.Dtos; namespace DramaLing.Api.Services; public interface IAudioCacheService { Task GetOrCreateAudioAsync(TTSRequest request); Task GenerateCacheKeyAsync(string text, string accent, string voice); Task UpdateAccessTimeAsync(string cacheKey); Task CleanupOldCacheAsync(); } public class AudioCacheService : IAudioCacheService { private readonly DramaLingDbContext _context; private readonly IAzureSpeechService _speechService; private readonly ILogger _logger; public AudioCacheService( DramaLingDbContext context, IAzureSpeechService speechService, ILogger logger) { _context = context; _speechService = speechService; _logger = logger; } public async Task GetOrCreateAudioAsync(TTSRequest request) { try { var cacheKey = await GenerateCacheKeyAsync(request.Text, request.Accent, request.Voice); // 檢查快取 var cachedAudio = await _context.AudioCaches .FirstOrDefaultAsync(a => a.TextHash == cacheKey); if (cachedAudio != null) { // 更新訪問時間 await UpdateAccessTimeAsync(cacheKey); return new TTSResponse { AudioUrl = cachedAudio.AudioUrl, Duration = cachedAudio.DurationMs.HasValue ? cachedAudio.DurationMs.Value / 1000.0f : 0, CacheHit = true }; } // 生成新音頻 var response = await _speechService.GenerateAudioAsync(request); if (!string.IsNullOrEmpty(response.Error)) { return response; } // 存入快取 var audioCache = new AudioCache { TextHash = cacheKey, TextContent = request.Text, Accent = request.Accent, VoiceId = request.Voice, AudioUrl = response.AudioUrl, DurationMs = (int)(response.Duration * 1000), CreatedAt = DateTime.UtcNow, LastAccessed = DateTime.UtcNow, AccessCount = 1 }; _context.AudioCaches.Add(audioCache); await _context.SaveChangesAsync(); _logger.LogInformation("Created new audio cache entry for text: {Text}", request.Text); return response; } catch (Exception ex) { _logger.LogError(ex, "Error in GetOrCreateAudioAsync for text: {Text}", request.Text); return new TTSResponse { Error = "Internal error processing audio request" }; } } public async Task GenerateCacheKeyAsync(string text, string accent, string voice) { var combined = $"{text}|{accent}|{voice}"; using var sha256 = SHA256.Create(); var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(combined)); return Convert.ToHexString(hash).ToLowerInvariant(); } public async Task UpdateAccessTimeAsync(string cacheKey) { try { var audioCache = await _context.AudioCaches .FirstOrDefaultAsync(a => a.TextHash == cacheKey); if (audioCache != null) { audioCache.LastAccessed = DateTime.UtcNow; audioCache.AccessCount++; await _context.SaveChangesAsync(); } } catch (Exception ex) { _logger.LogWarning(ex, "Failed to update access time for cache key: {CacheKey}", cacheKey); } } public async Task CleanupOldCacheAsync() { try { var cutoffDate = DateTime.UtcNow.AddDays(-30); var oldEntries = await _context.AudioCaches .Where(a => a.LastAccessed < cutoffDate) .ToListAsync(); if (oldEntries.Any()) { _context.AudioCaches.RemoveRange(oldEntries); await _context.SaveChangesAsync(); _logger.LogInformation("Cleaned up {Count} old audio cache entries", oldEntries.Count); } } catch (Exception ex) { _logger.LogError(ex, "Error during audio cache cleanup"); } } }