using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Distributed; using Microsoft.EntityFrameworkCore; using DramaLing.Api.Data; using DramaLing.Api.Models.Entities; using System.Text.Json; using System.Text; using System.Security.Cryptography; namespace DramaLing.Api.Services.Caching; /// /// 混合快取服務實作,支援記憶體快取和分散式快取的多層架構 /// public class HybridCacheService : ICacheService { private readonly IMemoryCache _memoryCache; private readonly IDistributedCache? _distributedCache; private readonly DramaLingDbContext _dbContext; private readonly ILogger _logger; private readonly CacheStats _stats; private readonly JsonSerializerOptions _jsonOptions; public HybridCacheService( IMemoryCache memoryCache, DramaLingDbContext dbContext, ILogger logger, IDistributedCache? distributedCache = null) { _memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache)); _distributedCache = distributedCache; _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _stats = new CacheStats { LastUpdated = DateTime.UtcNow }; _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false }; _logger.LogInformation("HybridCacheService initialized with Memory Cache and {DistributedCache}", _distributedCache != null ? "Distributed Cache" : "No Distributed Cache"); } #region 基本快取操作 public async Task GetAsync(string key) where T : class { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); try { // L1: 記憶體快取 (最快) if (_memoryCache.TryGetValue(key, out T? memoryResult)) { _stats.HitCount++; _logger.LogDebug("Cache hit from memory for key: {Key}", key); return memoryResult; } // L2: 分散式快取 if (_distributedCache != null) { var distributedData = await _distributedCache.GetAsync(key); if (distributedData != null) { var distributedResult = DeserializeFromBytes(distributedData); if (distributedResult != null) { // 回填到記憶體快取 var memoryExpiry = CalculateMemoryExpiry(key); _memoryCache.Set(key, distributedResult, memoryExpiry); _stats.HitCount++; _logger.LogDebug("Cache hit from distributed cache for key: {Key}", key); return distributedResult; } } } // L3: 資料庫快取 (僅適用於分析結果) if (key.StartsWith("analysis:")) { var dbResult = await GetFromDatabaseCacheAsync(key); if (dbResult != null) { // 回填到上層快取 await SetMultiLevelCacheAsync(key, dbResult); _stats.HitCount++; _logger.LogDebug("Cache hit from database for key: {Key}", key); return dbResult; } } _stats.MissCount++; _logger.LogDebug("Cache miss for key: {Key}", key); return null; } catch (Exception ex) { _logger.LogError(ex, "Error getting cache for key: {Key}", key); _stats.MissCount++; return null; } } public async Task SetAsync(string key, T value, TimeSpan? expiry = null) where T : class { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); try { var smartExpiry = expiry ?? CalculateSmartExpiry(key, value); // 同時設定記憶體和分散式快取 var tasks = new List>(); // L1: 記憶體快取 tasks.Add(Task.Run(() => { try { var memoryExpiry = TimeSpan.FromMinutes(Math.Min(smartExpiry.TotalMinutes, 30)); // 記憶體快取最多30分鐘 _memoryCache.Set(key, value, memoryExpiry); return true; } catch (Exception ex) { _logger.LogError(ex, "Error setting memory cache for key: {Key}", key); return false; } })); // L2: 分散式快取 if (_distributedCache != null) { tasks.Add(SetDistributedCacheAsync(key, value, smartExpiry)); } var results = await Task.WhenAll(tasks); var success = results.Any(r => r); if (success) { _stats.TotalKeys++; _logger.LogDebug("Cache set for key: {Key}, expiry: {Expiry}", key, smartExpiry); } return success; } catch (Exception ex) { _logger.LogError(ex, "Error setting cache for key: {Key}", key); return false; } } public async Task RemoveAsync(string key) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); try { var tasks = new List>(); // 從記憶體快取移除 tasks.Add(Task.Run(() => { try { _memoryCache.Remove(key); return true; } catch (Exception ex) { _logger.LogError(ex, "Error removing from memory cache for key: {Key}", key); return false; } })); // 從分散式快取移除 if (_distributedCache != null) { tasks.Add(Task.Run(async () => { try { await _distributedCache.RemoveAsync(key); return true; } catch (Exception ex) { _logger.LogError(ex, "Error removing from distributed cache for key: {Key}", key); return false; } })); } var results = await Task.WhenAll(tasks); var success = results.Any(r => r); if (success) { _logger.LogDebug("Cache removed for key: {Key}", key); } return success; } catch (Exception ex) { _logger.LogError(ex, "Error removing cache for key: {Key}", key); return false; } } public async Task ExistsAsync(string key) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); try { // 檢查記憶體快取 if (_memoryCache.TryGetValue(key, out _)) { return true; } // 檢查分散式快取 if (_distributedCache != null) { var distributedData = await _distributedCache.GetAsync(key); return distributedData != null; } return false; } catch (Exception ex) { _logger.LogError(ex, "Error checking cache existence for key: {Key}", key); return false; } } public async Task ExpireAsync(string key, TimeSpan expiry) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); try { // 重新設定過期時間(需要重新設定值) var value = await GetAsync(key); if (value != null) { return await SetAsync(key, value, expiry); } return false; } catch (Exception ex) { _logger.LogError(ex, "Error setting expiry for key: {Key}", key); return false; } } public async Task ClearAsync() { try { var tasks = new List(); // 清除記憶體快取(如果支援) if (_memoryCache is MemoryCache memoryCache) { tasks.Add(Task.Run(() => { // MemoryCache 沒有直接清除所有項目的方法 // 這裡只能重新建立或等待自然過期 _logger.LogWarning("Memory cache clear is not directly supported"); })); } // 分散式快取清除(取決於實作) if (_distributedCache != null) { tasks.Add(Task.Run(() => { _logger.LogWarning("Distributed cache clear implementation depends on the provider"); })); } await Task.WhenAll(tasks); _logger.LogInformation("Cache clear operation completed"); return true; } catch (Exception ex) { _logger.LogError(ex, "Error clearing cache"); return false; } } #endregion #region 批次操作 public async Task> GetManyAsync(IEnumerable keys) where T : class { var keyList = keys.ToList(); var result = new Dictionary(); if (!keyList.Any()) return result; try { var tasks = keyList.Select(async key => { var value = await GetAsync(key); return new KeyValuePair(key, value); }); var results = await Task.WhenAll(tasks); return results.ToDictionary(r => r.Key, r => r.Value); } catch (Exception ex) { _logger.LogError(ex, "Error getting multiple cache values"); return result; } } public async Task SetManyAsync(Dictionary keyValuePairs, TimeSpan? expiry = null) where T : class { if (!keyValuePairs.Any()) return true; try { var tasks = keyValuePairs.Select(async kvp => await SetAsync(kvp.Key, kvp.Value, expiry)); var results = await Task.WhenAll(tasks); return results.All(r => r); } catch (Exception ex) { _logger.LogError(ex, "Error setting multiple cache values"); return false; } } #endregion #region 統計資訊 public Task GetStatsAsync() { _stats.LastUpdated = DateTime.UtcNow; return Task.FromResult(_stats); } #endregion #region 私有方法 private async Task SetDistributedCacheAsync(string key, T value, TimeSpan expiry) where T : class { try { var serializedData = SerializeToBytes(value); var options = new DistributedCacheEntryOptions { SlidingExpiration = expiry }; await _distributedCache!.SetAsync(key, serializedData, options); return true; } catch (Exception ex) { _logger.LogError(ex, "Error setting distributed cache for key: {Key}", key); return false; } } private byte[] SerializeToBytes(T value) where T : class { var json = JsonSerializer.Serialize(value, _jsonOptions); return Encoding.UTF8.GetBytes(json); } private T? DeserializeFromBytes(byte[] data) where T : class { try { var json = Encoding.UTF8.GetString(data); return JsonSerializer.Deserialize(json, _jsonOptions); } catch (Exception ex) { _logger.LogError(ex, "Error deserializing cache data"); return null; } } private TimeSpan CalculateSmartExpiry(string key, T value) { // 根據不同的快取類型和鍵的特性計算智能過期時間 return key switch { var k when k.StartsWith("analysis:") => TimeSpan.FromHours(2), // AI 分析結果快取2小時 var k when k.StartsWith("user:") => TimeSpan.FromMinutes(30), // 用戶資料快取30分鐘 var k when k.StartsWith("flashcard:") => TimeSpan.FromMinutes(15), // 詞卡資料快取15分鐘 var k when k.StartsWith("stats:") => TimeSpan.FromMinutes(5), // 統計資料快取5分鐘 _ => TimeSpan.FromMinutes(10) // 預設快取10分鐘 }; } private TimeSpan CalculateMemoryExpiry(string key) { // 記憶體快取時間通常比分散式快取短 return key switch { var k when k.StartsWith("analysis:") => TimeSpan.FromMinutes(30), var k when k.StartsWith("user:") => TimeSpan.FromMinutes(10), var k when k.StartsWith("flashcard:") => TimeSpan.FromMinutes(5), var k when k.StartsWith("stats:") => TimeSpan.FromMinutes(2), _ => TimeSpan.FromMinutes(5) }; } #region 資料庫快取 (L3) private 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 result = JsonSerializer.Deserialize(cached.AnalysisResult, _jsonOptions); return result; } return null; } catch (Exception ex) { _logger.LogError(ex, "Error getting from database cache for key: {Key}", key); return null; } } private 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); if (existing != null) { existing.AnalysisResult = JsonSerializer.Serialize(value, _jsonOptions); existing.ExpiresAt = expiresAt; existing.AccessCount++; existing.LastAccessedAt = DateTime.UtcNow; } else { var cacheItem = new SentenceAnalysisCache { Id = Guid.NewGuid(), InputTextHash = hash, InputText = "", // 需要從其他地方獲取原始文本 AnalysisResult = JsonSerializer.Serialize(value, _jsonOptions), CreatedAt = DateTime.UtcNow, ExpiresAt = expiresAt, AccessCount = 1, LastAccessedAt = DateTime.UtcNow }; _dbContext.SentenceAnalysisCache.Add(cacheItem); } await _dbContext.SaveChangesAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error saving to database cache for key: {Key}", key); } } private async Task SetMultiLevelCacheAsync(string key, T value) where T : class { var expiry = CalculateSmartExpiry(key, value); // 設定記憶體快取 var memoryExpiry = CalculateMemoryExpiry(key); _memoryCache.Set(key, value, memoryExpiry); // 設定分散式快取 if (_distributedCache != null) { await SetDistributedCacheAsync(key, value, expiry); } } #endregion #endregion }