Skip to content
Merged
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
42 changes: 35 additions & 7 deletions FastCache.Core/Attributes/CacheableAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,43 @@

namespace FastCache.Core.Attributes
{
/// <summary>
/// 规则支持说明
/// Based on the provided test cases, the `KeyGenerateHelper.GetKey` method supports the following rules for generating cache keys:
/// Supported Rules
/// 1. **Basic Property Access**:
/// - `{company:name}`: Accesses the `Name` property of the `Company` object.
/// - `{company:id}`: Accesses the `Id` property.
/// 2. **List and Array Indexing**:
/// - `{company:menus:0:openTime}`: Accesses the `openTime` of the first menu in the `Menus` list.
/// - `{company:merchants:0:merchantIds:0}`: Accesses the first `MerchantId` of the first merchant.
/// 3. **Iterating All Elements**:
/// - `{company:menus:id:all}`: Joins all `Id`s from the `Menus` list.
/// - `{company:menus:0:menuSettings:id:all}`: Joins all `Id`s from `MenuSettings` of the first menu.
/// 4. **Combining Multiple Properties**:
/// - `{company:name}:{company:id}`: Combines multiple properties into a single key.
/// - `{company:menus:id:all}:{company:id}`: Combines all menu IDs with company ID.
/// 5. **Wildcard for All Elements**:
/// - `{company:all}:{company:id}` or similar patterns can be used to indicate special handling, though specifics depend on implementation details not provided here.
/// General Structure
/// - Prefix is added at the beginning (`single:`).
/// - Patterns within curly braces are replaced with corresponding values from object properties or collections.
/// - Supports indexing and iteration over lists/arrays using specific indices or "all" for concatenation.
/// - Combinations of different patterns are supported to form complex keys.
/// Implementation Notes
/// Ensure that your method correctly interprets these patterns and handles edge cases, such as missing data or empty lists, to prevent errors like null reference exceptions.
/// </summary>
public class CacheableAttribute : AbstractInterceptorAttribute
{
private readonly string _key;
private readonly string _expression;
private readonly long _expire;

public sealed override int Order { get; set; }

private static readonly ConcurrentDictionary<Type, MethodInfo>
TypeofTaskResultMethod = new ConcurrentDictionary<Type, MethodInfo>();

private static readonly MethodInfo _taskResultMethod;

static CacheableAttribute()
Expand Down Expand Up @@ -76,7 +102,7 @@

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

Check warning on line 105 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 All @@ -84,9 +110,11 @@
? context.ServiceMethod.ReturnType.GetGenericArguments().First()
: context.ServiceMethod.ReturnType;

context.ReturnValue = context.IsAsync() ? TypeofTaskResultMethod.GetOrAdd(returnTypeBefore,
t => _taskResultMethod.MakeGenericMethod(returnTypeBefore))
.Invoke(null, new [] { cacheValue.Value }) : cacheValue.Value;
context.ReturnValue = context.IsAsync()
? TypeofTaskResultMethod.GetOrAdd(returnTypeBefore,
t => _taskResultMethod.MakeGenericMethod(returnTypeBefore))
.Invoke(null, new[] { cacheValue.Value })
: cacheValue.Value;
return;
}
}
Expand All @@ -103,10 +131,10 @@
{
value = context.ReturnValue;
}

var returnType = value?.GetType();

await cacheClient.Set(key, new CacheItem

Check warning on line 137 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,
Expand Down
26 changes: 26 additions & 0 deletions FastCache.Core/Attributes/EvictableAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@

namespace FastCache.Core.Attributes
{
/// <summary>
/// 规则支持说明
/// Based on the provided test cases, the `KeyGenerateHelper.GetKey` method supports the following rules for generating cache keys:
/// Supported Rules
/// 1. **Basic Property Access**:
/// - `{company:name}`: Accesses the `Name` property of the `Company` object.
/// - `{company:id}`: Accesses the `Id` property.
/// 2. **List and Array Indexing**:
/// - `{company:menus:0:openTime}`: Accesses the `openTime` of the first menu in the `Menus` list.
/// - `{company:merchants:0:merchantIds:0}`: Accesses the first `MerchantId` of the first merchant.
/// 3. **Iterating All Elements**:
/// - `{company:menus:id:all}`: Joins all `Id`s from the `Menus` list.
/// - `{company:menus:0:menuSettings:id:all}`: Joins all `Id`s from `MenuSettings` of the first menu.
/// 4. **Combining Multiple Properties**:
/// - `{company:name}:{company:id}`: Combines multiple properties into a single key.
/// - `{company:menus:id:all}:{company:id}`: Combines all menu IDs with company ID.
/// 5. **Wildcard for All Elements**:
/// - `{company:all}:{company:id}` or similar patterns can be used to indicate special handling, though specifics depend on implementation details not provided here.
/// General Structure
/// - Prefix is added at the beginning (`single:`).
/// - Patterns within curly braces are replaced with corresponding values from object properties or collections.
/// - Supports indexing and iteration over lists/arrays using specific indices or "all" for concatenation.
/// - Combinations of different patterns are supported to form complex keys.
/// Implementation Notes
/// Ensure that your method correctly interprets these patterns and handles edge cases, such as missing data or empty lists, to prevent errors like null reference exceptions.
/// </summary>
public class EvictableAttribute : AbstractInterceptorAttribute
{
private readonly string[] _keys;
Expand Down Expand Up @@ -36,7 +62,7 @@

foreach (var s in _keys)
{
await cacheClient.Delete(KeyGenerateHelper.GetKey(_expression, dictionary), s);

Check warning on line 65 in FastCache.Core/Attributes/EvictableAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
}
}
}
Expand Down
105 changes: 68 additions & 37 deletions FastCache.Core/Utils/KeyGenerateHelper.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -8,40 +9,41 @@

namespace FastCache.Core.Utils
{
/// <summary>
/// 规则支持说明
/// Based on the provided test cases, the `KeyGenerateHelper.GetKey` method supports the following rules for generating cache keys:
/// Supported Rules
/// 1. **Basic Property Access**:
/// - `{company:name}`: Accesses the `Name` property of the `Company` object.
/// - `{company:id}`: Accesses the `Id` property.
/// 2. **List and Array Indexing**:
/// - `{company:menus:0:openTime}`: Accesses the `openTime` of the first menu in the `Menus` list.
/// - `{company:merchants:0:merchantIds:0}`: Accesses the first `MerchantId` of the first merchant.
/// 3. **Iterating All Elements**:
/// - `{company:menus:id:all}`: Joins all `Id`s from the `Menus` list.
/// - `{company:menus:0:menuSettings:id:all}`: Joins all `Id`s from `MenuSettings` of the first menu.
/// 4. **Combining Multiple Properties**:
/// - `{company:name}:{company:id}`: Combines multiple properties into a single key.
/// - `{company:menus:id:all}:{company:id}`: Combines all menu IDs with company ID.
/// 5. **Wildcard for All Elements**:
/// - `{company:all}:{company:id}` or similar patterns can be used to indicate special handling, though specifics depend on implementation details not provided here.
/// General Structure
/// - Prefix is added at the beginning (`single:`).
/// - Patterns within curly braces are replaced with corresponding values from object properties or collections.
/// - Supports indexing and iteration over lists/arrays using specific indices or "all" for concatenation.
/// - Combinations of different patterns are supported to form complex keys.
/// Implementation Notes
/// Ensure that your method correctly interprets these patterns and handles edge cases, such as missing data or empty lists, to prevent errors like null reference exceptions.
/// </summary>
public static class KeyGenerateHelper
{
public static string GetKey(string name, string originKey, IDictionary<string, object>? parameters)
{
var values = new ConfigurationBuilder()
.AddJsonStream(
new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(parameters))))
.Build();

var reg = new Regex(@"\{([^\}]*)\}");
var matches = reg.Matches(originKey);
var valueKey = GetKey(originKey, parameters);

foreach (Match match in matches)
{
var valueName = match.Value.Replace(@"{", "").Replace(@"}", "");
var sections = values.GetSection(valueName).GetChildren()
.Where(x => !string.IsNullOrEmpty(x.Value))
.ToList();

if (sections.Any())
{
var valuesList = sections.Select(keyValuePair => keyValuePair.Value).ToList();

originKey = originKey.Replace(match.Value, string.Join(",", valuesList));
}
else
{
originKey = originKey.Replace(match.Value, values[valueName]);
}
}

return $"{name}:{originKey}";
return $"{name}:{valueKey}";
}

public static string GetKey(string originKey, IDictionary<string, object>? parameters)
{
var values = new ConfigurationBuilder()
Expand All @@ -54,20 +56,49 @@ public static string GetKey(string originKey, IDictionary<string, object>? param

foreach (Match match in matches)
{
var valueName = match.Value.Replace(@"{", "").Replace(@"}", "");
var sections = values.GetSection(valueName).GetChildren()
.Where(x => !string.IsNullOrEmpty(x.Value))
.ToList();

if (sections.Any())
var valueName = match.Value.Replace("{", "").Replace("}", "");
if (valueName.Contains(":all"))
{
var valuesList = sections.Select(keyValuePair => keyValuePair.Value).ToList();
var modifiedPattern = valueName.Replace(":all", "");

var lastColonIndex = modifiedPattern.LastIndexOf(":", StringComparison.Ordinal);

if (lastColonIndex == -1)
{
originKey = originKey.Replace(match.Value, "");
continue;
}

originKey = originKey.Replace(match.Value, string.Join(",", valuesList));
var regexPattern = "^" + Regex.Escape(modifiedPattern[..lastColonIndex])
+ @":[^:]+:" // 确保中间只允许一个非冒号的值
+ Regex.Escape(modifiedPattern[(lastColonIndex + 1)..]) + "$";

// 获取所有符合条件的值
var matchValues = values.AsEnumerable().Reverse().ToList()
.Where(x => Regex.IsMatch(x.Key, regexPattern, RegexOptions.IgnoreCase)) // 匹配路径
.Where(x => !string.IsNullOrWhiteSpace(x.Value)) // 确保值不为空白
.Select(x => x.Value) // 提取值
.ToList();

// 用匹配到的值更新 originKey
originKey = originKey.Replace(match.Value, matchValues.Any() ? string.Join(",", matchValues) : "");
}
else
{
originKey = originKey.Replace(match.Value, values[valueName]);
// Handle normal case
var sections = values.GetSection(valueName).GetChildren()
.Where(x => !string.IsNullOrEmpty(x.Value))
.ToList();

if (sections.Any())
{
var valuesList = sections.Select(keyValuePair => keyValuePair.Value).ToList();
originKey = originKey.Replace(match.Value, string.Join(",", valuesList));
}
else
{
originKey = originKey.Replace(match.Value, values[valueName]);
}
}
}

Expand Down
26 changes: 26 additions & 0 deletions FastCache.MultiSource/Attributes/MultiSourceCacheableAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,32 @@

namespace FastCache.MultiSource.Attributes
{
/// <summary>
/// 规则支持说明
/// Based on the provided test cases, the `KeyGenerateHelper.GetKey` method supports the following rules for generating cache keys:
/// Supported Rules
/// 1. **Basic Property Access**:
/// - `{company:name}`: Accesses the `Name` property of the `Company` object.
/// - `{company:id}`: Accesses the `Id` property.
/// 2. **List and Array Indexing**:
/// - `{company:menus:0:openTime}`: Accesses the `openTime` of the first menu in the `Menus` list.
/// - `{company:merchants:0:merchantIds:0}`: Accesses the first `MerchantId` of the first merchant.
/// 3. **Iterating All Elements**:
/// - `{company:menus:id:all}`: Joins all `Id`s from the `Menus` list.
/// - `{company:menus:0:menuSettings:id:all}`: Joins all `Id`s from `MenuSettings` of the first menu.
/// 4. **Combining Multiple Properties**:
/// - `{company:name}:{company:id}`: Combines multiple properties into a single key.
/// - `{company:menus:id:all}:{company:id}`: Combines all menu IDs with company ID.
/// 5. **Wildcard for All Elements**:
/// - `{company:all}:{company:id}` or similar patterns can be used to indicate special handling, though specifics depend on implementation details not provided here.
/// General Structure
/// - Prefix is added at the beginning (`single:`).
/// - Patterns within curly braces are replaced with corresponding values from object properties or collections.
/// - Supports indexing and iteration over lists/arrays using specific indices or "all" for concatenation.
/// - Combinations of different patterns are supported to form complex keys.
/// Implementation Notes
/// Ensure that your method correctly interprets these patterns and handles edge cases, such as missing data or empty lists, to prevent errors like null reference exceptions.
/// </summary>
public class MultiSourceCacheableAttribute : AbstractInterceptorAttribute
{
private readonly string _key;
Expand Down
26 changes: 26 additions & 0 deletions FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,32 @@

namespace FastCache.MultiSource.Attributes
{
/// <summary>
/// 规则支持说明
/// Based on the provided test cases, the `KeyGenerateHelper.GetKey` method supports the following rules for generating cache keys:
/// Supported Rules
/// 1. **Basic Property Access**:
/// - `{company:name}`: Accesses the `Name` property of the `Company` object.
/// - `{company:id}`: Accesses the `Id` property.
/// 2. **List and Array Indexing**:
/// - `{company:menus:0:openTime}`: Accesses the `openTime` of the first menu in the `Menus` list.
/// - `{company:merchants:0:merchantIds:0}`: Accesses the first `MerchantId` of the first merchant.
/// 3. **Iterating All Elements**:
/// - `{company:menus:id:all}`: Joins all `Id`s from the `Menus` list.
/// - `{company:menus:0:menuSettings:id:all}`: Joins all `Id`s from `MenuSettings` of the first menu.
/// 4. **Combining Multiple Properties**:
/// - `{company:name}:{company:id}`: Combines multiple properties into a single key.
/// - `{company:menus:id:all}:{company:id}`: Combines all menu IDs with company ID.
/// 5. **Wildcard for All Elements**:
/// - `{company:all}:{company:id}` or similar patterns can be used to indicate special handling, though specifics depend on implementation details not provided here.
/// General Structure
/// - Prefix is added at the beginning (`single:`).
/// - Patterns within curly braces are replaced with corresponding values from object properties or collections.
/// - Supports indexing and iteration over lists/arrays using specific indices or "all" for concatenation.
/// - Combinations of different patterns are supported to form complex keys.
/// Implementation Notes
/// Ensure that your method correctly interprets these patterns and handles edge cases, such as missing data or empty lists, to prevent errors like null reference exceptions.
/// </summary>
public class MultiSourceEvictableAttribute : AbstractInterceptorAttribute
{
private readonly string[] _keys;
Expand Down
20 changes: 14 additions & 6 deletions TestApi/Entity/Company.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,33 @@

namespace TestApi.Entity;


public record Company : IEntity
{
public string Id { get; set; }
public string Name { get; set; }
[NotMapped]public List<CompanyMenu>? Menus { get; set; }
[NotMapped]public List<long>? ThirdPartyIds { get; set; }
[NotMapped]public List<CompanyMerchant>? Merchants { get; set; }
[NotMapped] public List<CompanyMenu>? Menus { get; set; }

[NotMapped] public List<long>? ThirdPartyIds { get; set; }

[NotMapped] public List<CompanyMerchant>? Merchants { get; set; }
}

public class CompanyMenu
{
public string Id { get; set; }

public List<MenuSetting> MenuSettings { get; set; }

public DateTimeOffset openTime { get; set; }
public DateTimeOffset endTime { get; set; }
}

public class CompanyMerchant
{
public List<string> MerchantIds { get; set; }
}

public class MenuSetting
{
public string Id { get; set; }
}
Loading
Loading