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 _logger; private bool _disposed; public RedisCacheService( IRedisConnectionManager connectionManager, IRedisSerializer serializer, IOptions options, ILogger 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 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(string key) where T : class { var fullKey = BuildKey(key); var value = Db.StringGet(fullKey); if (!value.IsNullOrEmpty) { Db.KeyDelete(fullKey); return _serializer.Deserialize(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 keys) { if (keys == null) return 0; var redisKeys = keys.Select(k => (RedisKey)BuildKey(k)).ToArray(); return (int)Db.KeyDelete(redisKeys); } public int RemoveWhere(Func 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 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 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(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(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(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(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(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(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(string key) where T : class { var value = Db.StringGet(BuildKey(key)); if (value.IsNullOrEmpty) return default; return _serializer.Deserialize(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(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(string key, out T? value) where T : class { var val = Db.StringGet(BuildKey(key)); if (val.IsNullOrEmpty) { value = default; return false; } value = _serializer.Deserialize(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(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( string key, T newValue, object? expectedVersion, Func 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(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 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(string key, Func valueFactory, int expireSeconds = -1) where T : class { var fullKey = BuildKey(key); var existing = Db.StringGet(fullKey); if (!existing.IsNullOrEmpty) return _serializer.Deserialize(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; } } }