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<HybridCacheService> _logger;
|
private bool _disposed;
|
|
private bool RedisAvailable => _connectionManager.IsConnected;
|
|
public HybridCacheService(
|
IMemoryCache memoryCache,
|
IRedisConnectionManager connectionManager,
|
IRedisSerializer serializer,
|
IOptions<RedisOptions> options,
|
ILogger<HybridCacheService> 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<string> keys)
|
{
|
foreach (var key in keys) Remove(key);
|
}
|
|
public T? Get<T>(string key) where T : class
|
{
|
var fullKey = BuildKey(key);
|
if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
|
return _serializer.Deserialize<T>(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<T>(str);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis Get<T>失败, 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<T>(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<T>(string key, out T? value) where T : class
|
{
|
value = Get<T>(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<string, string> 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<T>(string key, Func<string, T> valueFactory, int expireSeconds = -1) where T : class
|
{
|
var existing = Get<T>(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;
|
}
|
}
|
}
|