using DramaLing.Api.Data; using DramaLing.Api.Models.Entities; using Microsoft.EntityFrameworkCore; using System.Text.Json; namespace DramaLing.Api.Services.Infrastructure.Caching; public class DatabaseCacheManager : IDatabaseCacheManager { private readonly DramaLingDbContext _dbContext; private readonly ICacheSerializer _serializer; private readonly ILogger _logger; public DatabaseCacheManager( DramaLingDbContext dbContext, ICacheSerializer serializer, ILogger logger) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task GetFromDatabaseCacheAsync(string key) where T : class { try { if (!key.StartsWith("analysis:")) return null; var hash = key.Replace("analysis:", ""); var cached = await _dbContext.SentenceAnalysisCache .AsNoTracking() .FirstOrDefaultAsync(c => c.InputTextHash == hash && c.ExpiresAt > DateTime.UtcNow); if (cached != null) { // 更新訪問統計 cached.AccessCount++; cached.LastAccessedAt = DateTime.UtcNow; await _dbContext.SaveChangesAsync(); var jsonBytes = System.Text.Encoding.UTF8.GetBytes(cached.AnalysisResult); var result = _serializer.Deserialize(jsonBytes); _logger.LogDebug("Database cache hit for key: {Key}", key); return result; } _logger.LogDebug("Database cache miss for key: {Key}", key); return null; } catch (Exception ex) { _logger.LogError(ex, "Error getting from database cache for key: {Key}", key); return null; } } public async Task SaveToDatabaseCacheAsync(string key, T value, TimeSpan expiry) where T : class { try { if (!key.StartsWith("analysis:")) return; var hash = key.Replace("analysis:", ""); var expiresAt = DateTime.UtcNow.Add(expiry); var existing = await _dbContext.SentenceAnalysisCache .FirstOrDefaultAsync(c => c.InputTextHash == hash); var jsonBytes = _serializer.Serialize(value); var jsonString = System.Text.Encoding.UTF8.GetString(jsonBytes); if (existing != null) { existing.AnalysisResult = jsonString; existing.ExpiresAt = expiresAt; existing.AccessCount++; existing.LastAccessedAt = DateTime.UtcNow; } else { var cacheItem = new SentenceAnalysisCache { Id = Guid.NewGuid(), InputTextHash = hash, InputText = "", AnalysisResult = jsonString, CreatedAt = DateTime.UtcNow, ExpiresAt = expiresAt, AccessCount = 1, LastAccessedAt = DateTime.UtcNow }; _dbContext.SentenceAnalysisCache.Add(cacheItem); } await _dbContext.SaveChangesAsync(); _logger.LogDebug("Database cache saved for key: {Key}", key); } catch (Exception ex) { _logger.LogError(ex, "Error saving to database cache for key: {Key}", key); } } }