using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Distributed; using System.Text.Json; using System.Text; namespace DramaLing.Api.Services.Caching; /// /// 混合快取服務實作,支援記憶體快取和分散式快取的多層架構 /// public class HybridCacheService : ICacheService { private readonly IMemoryCache _memoryCache; private readonly IDistributedCache? _distributedCache; private readonly ILogger _logger; private readonly CacheStats _stats; private readonly JsonSerializerOptions _jsonOptions; public HybridCacheService( IMemoryCache memoryCache, ILogger logger, IDistributedCache? distributedCache = null) { _memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache)); _distributedCache = distributedCache; _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; } } } _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) }; } #endregion }