using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using WIDESEAWCS_Core.Caches; using WIDESEAWCS_RedisService.Connection; using WIDESEAWCS_RedisService.Options; using WIDESEAWCS_RedisService.Serialization; namespace WIDESEAWCS_RedisService.Cache { public class HybridCacheService : ICacheService { private readonly IMemoryCache _memoryCache; private readonly IRedisConnectionManager _connectionManager; private readonly IRedisSerializer _serializer; private readonly RedisOptions _options; private readonly ILogger _logger; private bool _disposed; private bool RedisAvailable => _connectionManager.IsConnected; public HybridCacheService( IMemoryCache memoryCache, IRedisConnectionManager connectionManager, IRedisSerializer serializer, IOptions options, ILogger logger) { _memoryCache = memoryCache; _connectionManager = connectionManager; _serializer = serializer; _options = options.Value; _logger = logger; } private string BuildKey(string key) => $"{_options.KeyPrefix}{key}"; public bool Exists(string key) { if (_memoryCache.TryGetValue(BuildKey(key), out _)) return true; if (!RedisAvailable) return false; try { return _connectionManager.GetDatabase().KeyExists(BuildKey(key)); } catch (Exception ex) { _logger.LogWarning(ex, "Redis Exists失败, key={Key}", key); return false; } } public bool Add(string key, string value, int expireSeconds = -1, bool isSliding = false) { var fullKey = BuildKey(key); SetMemoryCache(fullKey, value, expireSeconds, isSliding); if (!RedisAvailable) { _logger.LogWarning("Redis不可用,仅使用内存缓存, key={Key}", key); return true; } try { var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null; var result = _connectionManager.GetDatabase().StringSet(fullKey, value, expiry); _logger.LogInformation("Redis写入成功: key={Key}, result={Result}", fullKey, result); return result; } catch (Exception ex) { _logger.LogWarning(ex, "Redis Add失败, key={Key}", key); return _options.FallbackToMemory; } } public bool AddObject(string key, object value, int expireSeconds = -1, bool isSliding = false) { return Add(key, _serializer.Serialize(value), expireSeconds, isSliding); } public void AddOrUpdate(string key, string value, int expireSeconds = -1, bool isSliding = false) { Add(key, value, expireSeconds, isSliding); } public void AddOrUpdate(string key, object value, int expireSeconds = -1, bool isSliding = false) { AddObject(key, value, expireSeconds, isSliding); } public bool Remove(string key) { var fullKey = BuildKey(key); _memoryCache.Remove(fullKey); if (!RedisAvailable) return true; try { return _connectionManager.GetDatabase().KeyDelete(fullKey); } catch (Exception ex) { _logger.LogWarning(ex, "Redis Remove失败, key={Key}", key); return true; } } public void Remove(IEnumerable keys) { foreach (var key in keys) Remove(key); } public T? Get(string key) where T : class { var fullKey = BuildKey(key); if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null) return _serializer.Deserialize(cached); if (!RedisAvailable) return default; try { var value = _connectionManager.GetDatabase().StringGet(fullKey); if (value.IsNullOrEmpty) return default; var str = value.ToString(); SetMemoryCache(fullKey, str, 300, false); return _serializer.Deserialize(str); } catch (Exception ex) { _logger.LogWarning(ex, "Redis Get失败, key={Key}", key); return default; } } public object? Get(Type type, string key) { var fullKey = BuildKey(key); if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null) return _serializer.Deserialize(cached, type); if (!RedisAvailable) return null; try { var value = _connectionManager.GetDatabase().StringGet(fullKey); if (value.IsNullOrEmpty) return null; var str = value.ToString(); SetMemoryCache(fullKey, str, 300, false); return _serializer.Deserialize(str, type); } catch (Exception ex) { _logger.LogWarning(ex, "Redis Get(Type)失败, key={Key}", key); return null; } } public string? Get(string key) { var fullKey = BuildKey(key); if (_memoryCache.TryGetValue(fullKey, out string? cached)) return cached; if (!RedisAvailable) return null; try { var value = _connectionManager.GetDatabase().StringGet(fullKey); if (value.IsNullOrEmpty) return null; var str = value.ToString(); SetMemoryCache(fullKey, str, 300, false); return str; } catch (Exception ex) { _logger.LogWarning(ex, "Redis Get失败, key={Key}", key); return null; } } private void SetMemoryCache(string fullKey, string value, int expireSeconds, bool isSliding) { var entryOptions = new MemoryCacheEntryOptions(); if (expireSeconds > 0) { if (isSliding) entryOptions.SetSlidingExpiration(TimeSpan.FromSeconds(expireSeconds)); else entryOptions.SetAbsoluteExpiration(TimeSpan.FromSeconds(expireSeconds)); } _memoryCache.Set(fullKey, value, entryOptions); } #region ConcurrentDictionary风格方法 public bool TryAdd(string key, string value, int expireSeconds = -1) { if (Exists(key)) return false; return Add(key, value, expireSeconds); } public bool TryAdd(string key, T value, int expireSeconds = -1) where T : class { if (Exists(key)) return false; return AddObject(key, value, expireSeconds); } public bool TryGetValue(string key, out string? value) { value = Get(key); return value != null; } public bool TryGetValue(string key, out T? value) where T : class { value = Get(key); return value != null; } public bool TryRemove(string key, out string? value) { value = Get(key); if (value == null) return false; Remove(key); return true; } public bool TryUpdate(string key, string newValue, int expireSeconds = -1) { if (!Exists(key)) return false; Add(key, newValue, expireSeconds); return true; } public string GetOrAdd(string key, string value, int expireSeconds = -1) { var existing = Get(key); if (existing != null) return existing; Add(key, value, expireSeconds); return value; } public string GetOrAdd(string key, Func valueFactory, int expireSeconds = -1) { var existing = Get(key); if (existing != null) return existing; var value = valueFactory(key); Add(key, value, expireSeconds); return value; } public T GetOrAdd(string key, Func valueFactory, int expireSeconds = -1) where T : class { var existing = Get(key); if (existing != null) return existing; var value = valueFactory(key); AddObject(key, value, expireSeconds); return value; } #endregion public void Dispose() { if (_disposed) return; _disposed = true; } } }