Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions FastCache.Core/Attributes/CacheableAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
{
private readonly string _key;
private readonly string _expression;
private readonly long _expire;
private readonly TimeSpan _expire;

public sealed override int Order { get; set; }

Expand All @@ -56,8 +56,20 @@
_taskResultMethod = typeof(Task).GetMethods()
.First(p => p.Name == "FromResult" && p.ContainsGenericParameters);
}

// 支持秒数的构造器(保持向后兼容)
public CacheableAttribute(string prefix, string keyPattern, int expirationSeconds)
: this(prefix, keyPattern, TimeSpan.FromSeconds(expirationSeconds))
{
}

// 新增支持TimeSpan的构造器
public CacheableAttribute(string prefix, string keyPattern, double expirationMinutes)
: this(prefix, keyPattern, TimeSpan.FromMinutes(expirationMinutes))
{
}

public CacheableAttribute(string key, string expression, long expireSeconds = 0)
public CacheableAttribute(string key, string expression, TimeSpan expireSeconds = default)
{
_key = key;
_expression = expression;
Expand Down Expand Up @@ -102,7 +114,7 @@

if (canGetCache)
{
var cacheValue = await cacheClient.Get(key);

Check warning on line 117 in FastCache.Core/Attributes/CacheableAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

if (null != cacheValue.Value && cacheValue.AssemblyName != null && cacheValue.Type != null)
{
Expand Down Expand Up @@ -134,11 +146,15 @@

var returnType = value?.GetType();

var expire = 0l;

Check warning on line 149 in FastCache.Core/Attributes/CacheableAttribute.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'expire' is assigned but its value is never used

Check warning on line 149 in FastCache.Core/Attributes/CacheableAttribute.cs

View workflow job for this annotation

GitHub Actions / build

The 'l' suffix is easily confused with the digit '1' -- use 'L' for clarity

await cacheClient.Set(key, new CacheItem

Check warning on line 151 in FastCache.Core/Attributes/CacheableAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
{
Value = value,
CreatedAt = DateTime.UtcNow.Ticks,
Expire = _expire > 0 ? DateTime.UtcNow.AddSeconds(_expire).Ticks : DateTime.UtcNow.AddYears(1).Ticks,
Expire = _expire > TimeSpan.Zero
? DateTime.UtcNow.Add(_expire).Ticks
: DateTime.UtcNow.AddYears(1).Ticks,
AssemblyName = returnType?.Assembly?.GetName()?.FullName ?? typeof(string).Assembly.FullName,
Type = returnType?.FullName ?? string.Empty,
}, _expire);
Expand Down
5 changes: 3 additions & 2 deletions FastCache.Core/Driver/ICacheClient.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
using System;
using System.Threading.Tasks;
using FastCache.Core.Entity;

namespace FastCache.Core.Driver
{
public interface ICacheClient
{
Task Set(string key, CacheItem cacheItem, long expire = 0);
Task<bool> Set(string key, CacheItem cacheItem, TimeSpan expire = default);

Task<CacheItem> Get(string key);

Task Delete(string key, string prefix);

Task Delete(string key);
Task<bool> Delete(string key);
}
}
28 changes: 24 additions & 4 deletions FastCache.Core/Driver/IRedisCache.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FastCache.Core.Entity;
using RedLockNet.SERedis;
using StackExchange.Redis;

namespace FastCache.Core.Driver
{
public interface IRedisCache : ICacheClient
{
Task<bool> ExecuteWithRedisLockAsync(string lockKey,
// void Dispose();

ConnectionMultiplexer GetConnectionMultiplexer();

RedLockFactory GetRedLockFactory();

Task<DistributedLockResult> ExecuteWithRedisLockAsync(string lockKey,
Func<Task> operation,
int msTimeout = 100,
int msExpire = 1000,
bool throwOnFailure = false);
DistributedLockOptions? options = null,
CancellationToken cancellationToken = default);

Task<List<string>> FuzzySearchAsync(
AdvancedSearchModel advancedSearchModel,
CancellationToken cancellationToken = default);

Task<long> BatchDeleteKeysWithPipelineAsync(
IEnumerable<string> keys,
int batchSize = 200);

Task<long> TryRemove(string[] keys, int doubleDeleteDelayedMs = 0);
}
}
20 changes: 20 additions & 0 deletions FastCache.Core/Entity/AdvancedSearchModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace FastCache.Core.Entity
{
public class AdvancedSearchModel
{
/// <summary>匹配模式(支持*和?)</summary>
public string Pattern { get; set; } = "*";

/// <summary>每批次扫描数量</summary>
public int PageSize { get; set; } = 200;

/// <summary>最大返回结果数(0表示无限制)</summary>
public int MaxResults { get; set; } = 1000;

/// <summary>是否包含值内容(启用时会额外执行GET操作)</summary>
public bool IncludeValues { get; set; }

/// <summary>结果过滤条件(Lua脚本片段)</summary>
public string? FilterScript { get; set; }
}
}
39 changes: 39 additions & 0 deletions FastCache.Core/Entity/DistributedLockOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;

namespace FastCache.Core.Entity
{
public class DistributedLockOptions
{
/// <summary>
/// 锁的自动释放时间(默认30秒)
/// </summary>
public TimeSpan ExpiryTime { get; set; } = TimeSpan.FromSeconds(30);

/// <summary>
/// 最大等待获取锁时间(默认10秒)
/// </summary>
public TimeSpan WaitTime { get; set; } = TimeSpan.FromSeconds(10);

/// <summary>
/// 重试间隔时间(默认200毫秒)
/// </summary>
public TimeSpan RetryInterval { get; set; } = TimeSpan.FromMilliseconds(200);

/// <summary>
/// 获取锁失败时是否抛出异常(默认true)
/// </summary>
public bool ThrowOnLockFailure { get; set; } = true;

/// <summary>
/// 业务操作失败时是否抛出异常(默认false)
/// </summary>
public bool ThrowOnOperationFailure { get; set; } = false;

// 可扩展的Builder模式
public DistributedLockOptions WithExpiry(TimeSpan expiry)
{
ExpiryTime = expiry;
return this;
}
}
}
12 changes: 12 additions & 0 deletions FastCache.Core/Entity/DistributedLockResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using FastCache.Core.Enums;

namespace FastCache.Core.Entity
{
public class DistributedLockResult
{
public bool IsSuccess { get; set; }
public LockStatus Status { get; set; }
public Exception? Exception { get; set; }
}
}
29 changes: 29 additions & 0 deletions FastCache.Core/Entity/MemoryCacheOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using FastCache.Core.Enums;

namespace FastCache.Core.Entity
{
public class MemoryCacheOptions
{
/// <summary>
/// 最大缓存项数量(默认:1,000,000)
/// </summary>
public int MaxCapacity { get; set; } = 1000000;

/// <summary>
/// 内存淘汰策略(默认:LRU - 最近最少使用)
/// </summary>
public MaxMemoryPolicy MemoryPolicy { get; set; } = MaxMemoryPolicy.LRU;

/// <summary>
/// 内存清理百分比(范围:1-100,默认:10%)
/// </summary>
public int CleanUpPercentage { get; set; } = 10;

/// <summary>
/// 延迟秒数
/// </summary>
public int DelaySeconds { get; set; } = 2;

public uint Buckets { get; set; } = 5;
}
}
27 changes: 27 additions & 0 deletions FastCache.Core/Entity/RedisCacheOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using StackExchange.Redis;

namespace FastCache.Core.Entity
{
public class RedisCacheOptions
{
/// <summary>
/// Quorum 重试次数(默认: 3)
/// </summary>
public int QuorumRetryCount { get; set; } = 3;

/// <summary>
/// Quorum 重试延迟基准值(默认: 400ms)
/// </summary>
public int QuorumRetryDelayMs { get; set; } = 400;

/// <summary>
/// 全局延迟删除时间(ms)
/// </summary>
public int DoubleDeleteDelayedMs { get; set; } = 2000;

public Action<object?, ConnectionFailedEventArgs>? ConnectionFailureHandler { get; set; } = null;

public Action<object?, ConnectionFailedEventArgs>? ConnectionRestoredHandler { get; set; } = null;
}
}
9 changes: 9 additions & 0 deletions FastCache.Core/Enums/LockStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace FastCache.Core.Enums
{
public enum LockStatus
{
AcquiredAndCompleted, // 成功获取并执行
LockNotAcquired, // 锁获取失败
OperationFailed // 获取锁后执行失败
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
namespace FastCache.InMemory.Enum
namespace FastCache.Core.Enums
{
public enum MaxMemoryPolicy
{
LRU, // Least Recently Used
RANDOM, // RANDOM
TTL // Time To Live
}
}

}
35 changes: 35 additions & 0 deletions FastCache.Core/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;

namespace FastCache.Core.Extensions
{
public static class EnumerableExtensions
{
/// <summary>
/// 将序列按指定大小分块(兼容 netstandard2.1)
/// </summary>
/// <typeparam name="T">元素类型</typeparam>
/// <param name="source">输入序列</param>
/// <param name="size">每块大小</param>
/// <returns>分块后的序列</returns>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int size)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));

using var enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
yield return GetChunk(enumerator, size);
}
}

private static IEnumerable<T> GetChunk<T>(IEnumerator<T> enumerator, int size)
{
do
{
yield return enumerator.Current;
} while (--size > 0 && enumerator.MoveNext());
}
}
}
1 change: 1 addition & 0 deletions FastCache.Core/FastCache.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RedLock.net" Version="2.3.2" />
</ItemGroup>

</Project>
12 changes: 6 additions & 6 deletions FastCache.InMemory/Drivers/MemoryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Threading.Tasks;
using FastCache.Core.Driver;
using FastCache.Core.Entity;
using FastCache.InMemory.Enum;
using FastCache.Core.Enums;
using FastCache.InMemory.Extension;
using Newtonsoft.Json;

Expand All @@ -31,9 +31,9 @@
_dist = new ConcurrentDictionary<string, CacheItem>(Environment.ProcessorCount * 2, _maxCapacity);
}

public Task Set(string key, CacheItem cacheItem, long _ = 0)
public Task<bool> Set(string key, CacheItem cacheItem, TimeSpan expire = default)
{
if (_dist.ContainsKey(key)) return Task.CompletedTask;
if (_dist.ContainsKey(key)) return Task.FromResult(true);
if (_dist.Count >= _maxCapacity)
{
ReleaseCached();
Expand All @@ -46,7 +46,7 @@

_dist.AddOrUpdate(key, cacheItem, (k, v) => cacheItem);

return Task.CompletedTask;
return Task.FromResult(true);
}

public Task<CacheItem> Get(string key)
Expand All @@ -68,7 +68,7 @@
var valueType = assembly.GetType(cacheItem.Type, true, true);
value = cacheItem.Value == null
? null
: JsonConvert.DeserializeObject(cacheItem.Value as string, valueType);

Check warning on line 71 in FastCache.InMemory/Drivers/MemoryCache.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'value' in 'object? JsonConvert.DeserializeObject(string value, Type type)'.
}

return Task.FromResult(new CacheItem()
Expand Down Expand Up @@ -118,10 +118,10 @@
return Task.CompletedTask;
}

public Task Delete(string key)
public Task<bool> Delete(string key)
{
_dist.TryRemove(key, out _, _delaySeconds);
return Task.CompletedTask;
return Task.FromResult(true);
}

private void ReleaseCached()
Expand Down
Loading
Loading