using Microsoft.EntityFrameworkCore; using DramaLing.Api.Data; using DramaLing.Api.Models.Entities; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace DramaLing.Api.Services; public interface IAnalysisCacheService { Task GetCachedAnalysisAsync(string inputText); Task SetCachedAnalysisAsync(string inputText, object analysisResult, TimeSpan ttl); Task InvalidateCacheAsync(string textHash); Task GetCacheHitCountAsync(); Task CleanExpiredCacheAsync(); } public class AnalysisCacheService : IAnalysisCacheService { private readonly DramaLingDbContext _context; private readonly ILogger _logger; public AnalysisCacheService(DramaLingDbContext context, ILogger logger) { _context = context; _logger = logger; } /// /// 獲取快取的分析結果 /// public async Task GetCachedAnalysisAsync(string inputText) { try { var textHash = GenerateTextHash(inputText); var cached = await _context.SentenceAnalysisCache .FirstOrDefaultAsync(c => c.InputTextHash == textHash && c.ExpiresAt > DateTime.UtcNow); if (cached != null) { // 更新訪問統計 cached.AccessCount++; cached.LastAccessedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Cache hit for text hash: {TextHash}", textHash); return cached; } _logger.LogInformation("Cache miss for text hash: {TextHash}", textHash); return null; } catch (Exception ex) { _logger.LogError(ex, "Error getting cached analysis for text: {InputText}", inputText); return null; } } /// /// 設定快取分析結果 /// public async Task SetCachedAnalysisAsync(string inputText, object analysisResult, TimeSpan ttl) { try { var textHash = GenerateTextHash(inputText); var expiresAt = DateTime.UtcNow.Add(ttl); // 檢查是否已存在 var existing = await _context.SentenceAnalysisCache .FirstOrDefaultAsync(c => c.InputTextHash == textHash); if (existing != null) { // 更新現有快取 - 使用一致的命名策略 var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; existing.AnalysisResult = JsonSerializer.Serialize(analysisResult, options); existing.ExpiresAt = expiresAt; existing.AccessCount++; existing.LastAccessedAt = DateTime.UtcNow; } else { // 創建新快取項目 var cacheItem = new SentenceAnalysisCache { Id = Guid.NewGuid(), InputTextHash = textHash, InputText = inputText, AnalysisResult = JsonSerializer.Serialize(analysisResult, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }), CreatedAt = DateTime.UtcNow, ExpiresAt = expiresAt, AccessCount = 1, LastAccessedAt = DateTime.UtcNow }; _context.SentenceAnalysisCache.Add(cacheItem); } await _context.SaveChangesAsync(); _logger.LogInformation("Cached analysis for text hash: {TextHash}, expires at: {ExpiresAt}", textHash, expiresAt); return textHash; } catch (Exception ex) { _logger.LogError(ex, "Error setting cached analysis for text: {InputText}", inputText); throw; } } /// /// 使快取失效 /// public async Task InvalidateCacheAsync(string textHash) { try { var cached = await _context.SentenceAnalysisCache .FirstOrDefaultAsync(c => c.InputTextHash == textHash); if (cached != null) { _context.SentenceAnalysisCache.Remove(cached); await _context.SaveChangesAsync(); _logger.LogInformation("Invalidated cache for text hash: {TextHash}", textHash); return true; } return false; } catch (Exception ex) { _logger.LogError(ex, "Error invalidating cache for text hash: {TextHash}", textHash); return false; } } /// /// 獲取快取命中次數 /// public async Task GetCacheHitCountAsync() { try { return await _context.SentenceAnalysisCache .Where(c => c.ExpiresAt > DateTime.UtcNow) .SumAsync(c => c.AccessCount); } catch (Exception ex) { _logger.LogError(ex, "Error getting cache hit count"); return 0; } } /// /// 清理過期的快取 /// public async Task CleanExpiredCacheAsync() { try { var expiredItems = await _context.SentenceAnalysisCache .Where(c => c.ExpiresAt <= DateTime.UtcNow) .ToListAsync(); if (expiredItems.Any()) { _context.SentenceAnalysisCache.RemoveRange(expiredItems); await _context.SaveChangesAsync(); _logger.LogInformation("Cleaned {Count} expired cache items", expiredItems.Count); } } catch (Exception ex) { _logger.LogError(ex, "Error cleaning expired cache"); } } /// /// 生成文本哈希值 /// private string GenerateTextHash(string inputText) { using var sha256 = SHA256.Create(); var bytes = Encoding.UTF8.GetBytes(inputText.Trim().ToLower()); var hash = sha256.ComputeHash(bytes); return Convert.ToHexString(hash).ToLower(); } }