using Microsoft.Extensions.Logging;
|
using StackExchange.Redis;
|
using WIDESEAWCS_Core.Caches;
|
using WIDESEAWCS_RedisService.Connection;
|
using WIDESEAWCS_RedisService.Options;
|
using WIDESEAWCS_RedisService.Serialization;
|
using Microsoft.Extensions.Options;
|
|
namespace WIDESEAWCS_RedisService.Cache
|
{
|
public class RedisCacheService : ICacheService
|
{
|
private readonly IRedisConnectionManager _connectionManager;
|
private readonly IRedisSerializer _serializer;
|
private readonly RedisOptions _options;
|
private readonly ILogger<RedisCacheService> _logger;
|
private bool _disposed;
|
|
public RedisCacheService(
|
IRedisConnectionManager connectionManager,
|
IRedisSerializer serializer,
|
IOptions<RedisOptions> options,
|
ILogger<RedisCacheService> logger)
|
{
|
_connectionManager = connectionManager;
|
_serializer = serializer;
|
_options = options.Value;
|
_logger = logger;
|
}
|
|
private string BuildKey(string key) => $"{_options.KeyPrefix}{key}";
|
|
private IDatabase Db => _connectionManager.GetDatabase();
|
|
public bool Exists(string key)
|
{
|
return Db.KeyExists(BuildKey(key));
|
}
|
|
public bool Add(string key, string value, int expireSeconds = -1, bool isSliding = false)
|
{
|
var fullKey = BuildKey(key);
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
return Db.StringSet(fullKey, value, expiry);
|
}
|
|
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)
|
{
|
return Db.KeyDelete(BuildKey(key));
|
}
|
|
public void Remove(IEnumerable<string> keys)
|
{
|
var redisKeys = keys.Select(k => (RedisKey)BuildKey(k)).ToArray();
|
Db.KeyDelete(redisKeys);
|
}
|
|
#region 删除扩展方法
|
|
public string? RemoveAndGet(string key)
|
{
|
var fullKey = BuildKey(key);
|
var value = Db.StringGet(fullKey);
|
if (!value.IsNullOrEmpty)
|
{
|
Db.KeyDelete(fullKey);
|
return value.ToString();
|
}
|
return null;
|
}
|
|
public T? RemoveAndGet<T>(string key) where T : class
|
{
|
var fullKey = BuildKey(key);
|
var value = Db.StringGet(fullKey);
|
if (!value.IsNullOrEmpty)
|
{
|
Db.KeyDelete(fullKey);
|
return _serializer.Deserialize<T>(value!);
|
}
|
return default;
|
}
|
|
public int RemoveByPrefix(string prefix)
|
{
|
var fullPrefix = BuildKey(prefix);
|
var server = Db.Multiplexer.GetServer(Db.Multiplexer.GetEndPoints().First());
|
var keys = server.Keys(pattern: $"{fullPrefix}*").ToArray();
|
if (keys.Length == 0) return 0;
|
return (int)Db.KeyDelete(keys);
|
}
|
|
public int RemoveByPattern(string pattern)
|
{
|
var fullPattern = BuildKey(pattern).Replace("*", ""); // 保留用户传入的通配符
|
var server = Db.Multiplexer.GetServer(Db.Multiplexer.GetEndPoints().First());
|
var keys = server.Keys(pattern: $"{_options.KeyPrefix}{pattern}").ToArray();
|
if (keys.Length == 0) return 0;
|
return (int)Db.KeyDelete(keys);
|
}
|
|
public int RemoveAll(IEnumerable<string> keys)
|
{
|
if (keys == null) return 0;
|
var redisKeys = keys.Select(k => (RedisKey)BuildKey(k)).ToArray();
|
return (int)Db.KeyDelete(redisKeys);
|
}
|
|
public int RemoveWhere(Func<string, bool> predicate)
|
{
|
if (predicate == null) return 0;
|
var server = Db.Multiplexer.GetServer(Db.Multiplexer.GetEndPoints().First());
|
var keys = server.Keys(pattern: $"{_options.KeyPrefix}*")
|
.Where(k => predicate(k.ToString().Replace(_options.KeyPrefix, "")))
|
.ToArray();
|
if (keys.Length == 0) return 0;
|
return (int)Db.KeyDelete(keys);
|
}
|
|
#endregion
|
|
#region 添加和修改扩展方法
|
|
public void AddAll(IDictionary<string, string> items, int expireSeconds = -1)
|
{
|
if (items == null) return;
|
var batch = Db.CreateBatch();
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
foreach (var item in items)
|
{
|
batch.StringSetAsync(BuildKey(item.Key), item.Value, expiry);
|
}
|
batch.Execute();
|
}
|
|
public void AddAllObjects(IDictionary<string, object> items, int expireSeconds = -1)
|
{
|
if (items == null) return;
|
var batch = Db.CreateBatch();
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
foreach (var item in items)
|
{
|
batch.StringSetAsync(BuildKey(item.Key), _serializer.Serialize(item.Value), expiry);
|
}
|
batch.Execute();
|
}
|
|
public bool Replace(string key, string newValue, int expireSeconds = -1)
|
{
|
return TryUpdate(key, newValue, expireSeconds);
|
}
|
|
public bool Replace<T>(string key, T newValue, int expireSeconds = -1) where T : class
|
{
|
var fullKey = BuildKey(key);
|
if (!Db.KeyExists(fullKey)) return false;
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
return Db.StringSet(fullKey, _serializer.Serialize(newValue), expiry, When.Exists);
|
}
|
|
public string? GetAndRefresh(string key, int expireSeconds)
|
{
|
var fullKey = BuildKey(key);
|
var value = Db.StringGet(fullKey);
|
if (!value.IsNullOrEmpty)
|
{
|
Db.KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
|
return value.ToString();
|
}
|
return null;
|
}
|
|
public T? GetAndRefresh<T>(string key, int expireSeconds) where T : class
|
{
|
var fullKey = BuildKey(key);
|
var value = Db.StringGet(fullKey);
|
if (!value.IsNullOrEmpty)
|
{
|
Db.KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
|
return _serializer.Deserialize<T>(value!);
|
}
|
return default;
|
}
|
|
public bool RefreshExpire(string key, int expireSeconds)
|
{
|
return Db.KeyExpire(BuildKey(key), TimeSpan.FromSeconds(expireSeconds));
|
}
|
|
public bool ExpireIn(string key, int seconds)
|
{
|
return Db.KeyExpire(BuildKey(key), TimeSpan.FromSeconds(seconds));
|
}
|
|
public bool ExpireAt(string key, DateTime expireTime)
|
{
|
return Db.KeyExpire(BuildKey(key), expireTime);
|
}
|
|
public long? GetExpire(string key)
|
{
|
var ttl = Db.KeyTimeToLive(BuildKey(key));
|
return ttl.HasValue ? (long)ttl.Value.TotalSeconds : null;
|
}
|
|
public bool AddIfNotExists(string key, string value, int expireSeconds = -1)
|
{
|
var fullKey = BuildKey(key);
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
return Db.StringSet(fullKey, value, expiry, When.NotExists);
|
}
|
|
public bool AddIfNotExists<T>(string key, T value, int expireSeconds = -1) where T : class
|
{
|
var fullKey = BuildKey(key);
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
return Db.StringSet(fullKey, _serializer.Serialize(value), expiry, When.NotExists);
|
}
|
|
public string? GetAndSet(string key, string newValue, int expireSeconds = -1)
|
{
|
var fullKey = BuildKey(key);
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
var oldValue = Db.StringGetSet(fullKey, newValue);
|
if (expireSeconds > 0)
|
{
|
Db.KeyExpire(fullKey, expiry);
|
}
|
return oldValue.IsNullOrEmpty ? null : oldValue.ToString();
|
}
|
|
public T? GetAndSet<T>(string key, T newValue, int expireSeconds = -1) where T : class
|
{
|
var fullKey = BuildKey(key);
|
var serialized = _serializer.Serialize(newValue);
|
var oldValue = Db.StringGetSet(fullKey, serialized);
|
if (expireSeconds > 0)
|
{
|
Db.KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
|
}
|
return oldValue.IsNullOrEmpty ? default : _serializer.Deserialize<T>(oldValue!);
|
}
|
|
public long Increment(string key, long value = 1)
|
{
|
return Db.StringIncrement(BuildKey(key), value);
|
}
|
|
public long Decrement(string key, long value = 1)
|
{
|
return Db.StringDecrement(BuildKey(key), value);
|
}
|
|
public long Append(string key, string value)
|
{
|
return Db.StringAppend(BuildKey(key), value);
|
}
|
|
#endregion
|
|
public T? Get<T>(string key) where T : class
|
{
|
var value = Db.StringGet(BuildKey(key));
|
if (value.IsNullOrEmpty) return default;
|
return _serializer.Deserialize<T>(value!);
|
}
|
|
public object? Get(Type type, string key)
|
{
|
var value = Db.StringGet(BuildKey(key));
|
if (value.IsNullOrEmpty) return null;
|
return _serializer.Deserialize(value!, type);
|
}
|
|
public string? Get(string key)
|
{
|
var value = Db.StringGet(BuildKey(key));
|
return value.IsNullOrEmpty ? null : value.ToString();
|
}
|
|
public void Dispose()
|
{
|
if (_disposed) return;
|
_disposed = true;
|
}
|
|
public bool TryAdd(string key, string value, int expireSeconds = -1)
|
{
|
var fullKey = BuildKey(key);
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
return Db.StringSet(fullKey, value, expiry, When.NotExists);
|
}
|
|
public bool TryAdd<T>(string key, T value, int expireSeconds = -1) where T : class
|
{
|
return TryAdd(key, _serializer.Serialize(value), expireSeconds);
|
}
|
|
public bool TryGetValue(string key, out string? value)
|
{
|
var val = Db.StringGet(BuildKey(key));
|
value = val.IsNullOrEmpty ? null : val.ToString();
|
return !val.IsNullOrEmpty;
|
}
|
|
public bool TryGetValue<T>(string key, out T? value) where T : class
|
{
|
var val = Db.StringGet(BuildKey(key));
|
if (val.IsNullOrEmpty) { value = default; return false; }
|
value = _serializer.Deserialize<T>(val!);
|
return value != null;
|
}
|
|
public bool TryRemove(string key, out string? value)
|
{
|
var fullKey = BuildKey(key);
|
value = Db.StringGet(fullKey).ToString();
|
if (value == null) return false;
|
return Db.KeyDelete(fullKey);
|
}
|
|
public bool TryUpdate(string key, string newValue, int expireSeconds = -1)
|
{
|
var fullKey = BuildKey(key);
|
if (!Db.KeyExists(fullKey)) return false;
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
return Db.StringSet(fullKey, newValue, expiry, When.Exists);
|
}
|
|
public bool TryUpdateIfChanged(string key, string newValue, int expireSeconds = -1)
|
{
|
var fullKey = BuildKey(key);
|
var existing = Db.StringGet(fullKey);
|
if (existing.IsNullOrEmpty) return false;
|
if (existing.ToString() == newValue) return false; // 值相同,不更新
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
return Db.StringSet(fullKey, newValue, expiry, When.Exists);
|
}
|
|
public bool TryUpdateIfChanged<T>(string key, T newValue, int expireSeconds = -1) where T : class
|
{
|
var fullKey = BuildKey(key);
|
var existing = Db.StringGet(fullKey);
|
if (existing.IsNullOrEmpty) return false;
|
|
var newJson = _serializer.Serialize(newValue);
|
if (existing.ToString() == newJson) return false; // JSON字符串相同,不更新
|
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
return Db.StringSet(fullKey, newJson, expiry, When.Exists);
|
}
|
|
public bool TrySafeUpdate<T>(
|
string key,
|
T newValue,
|
object? expectedVersion,
|
Func<T, object?> versionExtractor,
|
int expireSeconds = -1) where T : class
|
{
|
var fullKey = BuildKey(key);
|
var existing = Db.StringGet(fullKey);
|
if (existing.IsNullOrEmpty) return false;
|
|
// 反序列化现有值
|
var existingValue = _serializer.Deserialize<T>(existing.ToString()!);
|
if (existingValue == null) return false;
|
|
// 检查版本是否匹配
|
var currentVersion = versionExtractor(existingValue);
|
if (!Equals(currentVersion, expectedVersion))
|
{
|
return false; // 版本不匹配,拒绝更新
|
}
|
|
// 版本匹配,执行更新
|
var newJson = _serializer.Serialize(newValue);
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
return Db.StringSet(fullKey, newJson, expiry, When.Exists);
|
}
|
|
public string GetOrAdd(string key, string value, int expireSeconds = -1)
|
{
|
var fullKey = BuildKey(key);
|
var existing = Db.StringGet(fullKey);
|
if (!existing.IsNullOrEmpty) return existing.ToString();
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
Db.StringSet(fullKey, value, expiry, When.NotExists);
|
return Db.StringGet(fullKey).ToString();
|
}
|
|
public string GetOrAdd(string key, Func<string, string> valueFactory, int expireSeconds = -1)
|
{
|
var fullKey = BuildKey(key);
|
var existing = Db.StringGet(fullKey);
|
if (!existing.IsNullOrEmpty) return existing.ToString();
|
var value = valueFactory(key);
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
Db.StringSet(fullKey, value, expiry, When.NotExists);
|
return Db.StringGet(fullKey).ToString();
|
}
|
|
public T GetOrAdd<T>(string key, Func<string, T> valueFactory, int expireSeconds = -1) where T : class
|
{
|
var fullKey = BuildKey(key);
|
var existing = Db.StringGet(fullKey);
|
if (!existing.IsNullOrEmpty) return _serializer.Deserialize<T>(existing!)!;
|
var value = valueFactory(key);
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
Db.StringSet(fullKey, _serializer.Serialize(value), expiry, When.NotExists);
|
return value;
|
}
|
}
|
}
|