diff --git a/FastCache.Core/Attributes/CacheableAttribute.cs b/FastCache.Core/Attributes/CacheableAttribute.cs
index f7d7be2..3302a91 100644
--- a/FastCache.Core/Attributes/CacheableAttribute.cs
+++ b/FastCache.Core/Attributes/CacheableAttribute.cs
@@ -12,17 +12,43 @@
namespace FastCache.Core.Attributes
{
+ ///
+ /// 规则支持说明
+ /// 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.
+ ///
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
TypeofTaskResultMethod = new ConcurrentDictionary();
-
+
private static readonly MethodInfo _taskResultMethod;
static CacheableAttribute()
@@ -84,9 +110,11 @@ public override async Task Invoke(AspectContext context, AspectDelegate next)
? 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;
}
}
@@ -103,7 +131,7 @@ public override async Task Invoke(AspectContext context, AspectDelegate next)
{
value = context.ReturnValue;
}
-
+
var returnType = value?.GetType();
await cacheClient.Set(key, new CacheItem
diff --git a/FastCache.Core/Attributes/EvictableAttribute.cs b/FastCache.Core/Attributes/EvictableAttribute.cs
index 7b2f3a6..b12a05c 100644
--- a/FastCache.Core/Attributes/EvictableAttribute.cs
+++ b/FastCache.Core/Attributes/EvictableAttribute.cs
@@ -7,6 +7,32 @@
namespace FastCache.Core.Attributes
{
+ ///
+ /// 规则支持说明
+ /// 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.
+ ///
public class EvictableAttribute : AbstractInterceptorAttribute
{
private readonly string[] _keys;
diff --git a/FastCache.Core/Utils/KeyGenerateHelper.cs b/FastCache.Core/Utils/KeyGenerateHelper.cs
index 05b829e..be0142c 100644
--- a/FastCache.Core/Utils/KeyGenerateHelper.cs
+++ b/FastCache.Core/Utils/KeyGenerateHelper.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -8,40 +9,41 @@
namespace FastCache.Core.Utils
{
+ ///
+ /// 规则支持说明
+ /// 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.
+ ///
public static class KeyGenerateHelper
{
public static string GetKey(string name, string originKey, IDictionary? 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? parameters)
{
var values = new ConfigurationBuilder()
@@ -54,20 +56,49 @@ public static string GetKey(string originKey, IDictionary? 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]);
+ }
}
}
diff --git a/FastCache.MultiSource/Attributes/MultiSourceCacheableAttribute.cs b/FastCache.MultiSource/Attributes/MultiSourceCacheableAttribute.cs
index 225e6ea..5f59d16 100644
--- a/FastCache.MultiSource/Attributes/MultiSourceCacheableAttribute.cs
+++ b/FastCache.MultiSource/Attributes/MultiSourceCacheableAttribute.cs
@@ -13,6 +13,32 @@
namespace FastCache.MultiSource.Attributes
{
+ ///
+ /// 规则支持说明
+ /// 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.
+ ///
public class MultiSourceCacheableAttribute : AbstractInterceptorAttribute
{
private readonly string _key;
diff --git a/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs b/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs
index 05975f4..f541b45 100644
--- a/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs
+++ b/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs
@@ -11,6 +11,32 @@
namespace FastCache.MultiSource.Attributes
{
+ ///
+ /// 规则支持说明
+ /// 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.
+ ///
public class MultiSourceEvictableAttribute : AbstractInterceptorAttribute
{
private readonly string[] _keys;
diff --git a/TestApi/Entity/Company.cs b/TestApi/Entity/Company.cs
index adae5f6..2735f08 100644
--- a/TestApi/Entity/Company.cs
+++ b/TestApi/Entity/Company.cs
@@ -2,20 +2,23 @@
namespace TestApi.Entity;
-
public record Company : IEntity
{
public string Id { get; set; }
public string Name { get; set; }
- [NotMapped]public List? Menus { get; set; }
-
- [NotMapped]public List? ThirdPartyIds { get; set; }
-
- [NotMapped]public List? Merchants { get; set; }
+ [NotMapped] public List? Menus { get; set; }
+
+ [NotMapped] public List? ThirdPartyIds { get; set; }
+
+ [NotMapped] public List? Merchants { get; set; }
}
public class CompanyMenu
{
+ public string Id { get; set; }
+
+ public List MenuSettings { get; set; }
+
public DateTimeOffset openTime { get; set; }
public DateTimeOffset endTime { get; set; }
}
@@ -23,4 +26,9 @@ public class CompanyMenu
public class CompanyMerchant
{
public List MerchantIds { get; set; }
+}
+
+public class MenuSetting
+{
+ public string Id { get; set; }
}
\ No newline at end of file
diff --git a/UnitTests/KeyGenerateHelperTests.cs b/UnitTests/KeyGenerateHelperTests.cs
index a310c25..b142b0d 100644
--- a/UnitTests/KeyGenerateHelperTests.cs
+++ b/UnitTests/KeyGenerateHelperTests.cs
@@ -9,22 +9,47 @@ namespace UnitTests;
public class KeyGenerateHelperTests
{
+ private const string Prefix = "single";
+ private readonly string _defaultKey = $"{Prefix}:";
+
+ private readonly Company _companyThirdPartyIds = new()
+ {
+ Id = "3",
+ Name = "anson33",
+ ThirdPartyIds = new List() { 123, 456, 789 },
+ Menus = new List()
+ {
+ new()
+ {
+ Id = "1",
+ MenuSettings =
+ [new MenuSetting { Id = "menu_setting_id_1" }, new MenuSetting() { Id = "menu_setting_id_2" }]
+ },
+ new()
+ {
+ Id = "2",
+ MenuSettings = []
+ }
+ }
+ };
+
[Fact]
public void GetCacheKey()
{
- const string prefix = "single";
- const string defaultKey = $"{prefix}:";
- var companyThirdPartyIds = new Company()
- {
- Id = "3",
- Name = "anson33",
- ThirdPartyIds = new List() { 123, 456, 789 }
- };
+ // 规则:{company:name}:{company:status}
+ // 输出: Prefix:anson33:
+
+ var key1 =
+ KeyGenerateHelper.GetKey(Prefix, "{company:name}:{company:status}",
+ new Dictionary() { { "company", _companyThirdPartyIds } });
+
+ Assert.Equal(key1, $"{Prefix}:{_companyThirdPartyIds.Name}:");
+
var arrayKey =
- KeyGenerateHelper.GetKey(prefix, "{company:thirdPartyIds}",
- new Dictionary() { { "company", companyThirdPartyIds } });
-
- Assert.Equal(arrayKey, $"{prefix}:{string.Join(",", companyThirdPartyIds.ThirdPartyIds)}");
+ KeyGenerateHelper.GetKey(Prefix, "{company:thirdPartyIds}",
+ new Dictionary() { { "company", _companyThirdPartyIds } });
+
+ Assert.Equal(arrayKey, $"{Prefix}:{string.Join(",", _companyThirdPartyIds.ThirdPartyIds!)}");
var companyMenuOpenTime = DateTimeOffset.UtcNow;
var companyMenus = new Company()
@@ -36,19 +61,19 @@ public void GetCacheKey()
new CompanyMenu() { openTime = companyMenuOpenTime, endTime = DateTimeOffset.Now.AddHours(1) }
}
};
-
+
var companyMenusKey =
- KeyGenerateHelper.GetKey(prefix, "{company:menus}",
+ KeyGenerateHelper.GetKey(Prefix, "{company:menus}",
new Dictionary() { { "company", companyMenus } });
-
- Assert.Equal(companyMenusKey, defaultKey);
-
+
+ Assert.Equal(companyMenusKey, _defaultKey);
+
var companyMenusFirstKey =
- KeyGenerateHelper.GetKey(prefix, "{company:menus:0:openTime}",
+ KeyGenerateHelper.GetKey(Prefix, "{company:menus:0:openTime}",
new Dictionary() { { "company", companyMenus } });
-
+
var milliseconds = companyMenuOpenTime.ToString("fffffff").TrimEnd('0');
- Assert.Equal(companyMenusFirstKey, $"{prefix}:{companyMenuOpenTime:yyyy-MM-ddTHH:mm:ss}.{milliseconds}+00:00");
+ Assert.Equal(companyMenusFirstKey, $"{Prefix}:{companyMenuOpenTime:yyyy-MM-ddTHH:mm:ss}.{milliseconds}+00:00");
var companyMerchants = new Company()
{
@@ -56,21 +81,71 @@ public void GetCacheKey()
Name = "company 1",
Merchants = new List()
{
- new CompanyMerchant() { MerchantIds = new List() { "m11", "m12" } },
- new CompanyMerchant() { MerchantIds = new List(){ "m21", "m22" }}
+ new() { MerchantIds = ["m11", "m12"] },
+ new() { MerchantIds = ["m21", "m22"] }
}
};
var companyMerchantsKey =
- KeyGenerateHelper.GetKey(prefix, "{company:merchants}",
+ KeyGenerateHelper.GetKey(Prefix, "{company:merchants}",
new Dictionary() { { "company", companyMerchants } });
- Assert.Equal(companyMerchantsKey, defaultKey);
-
+ Assert.Equal(companyMerchantsKey, _defaultKey);
+
var companyMerchantsFirstKey =
- KeyGenerateHelper.GetKey(prefix, "{company:merchants:0:merchantIds:0}",
+ KeyGenerateHelper.GetKey(Prefix, "{company:merchants:0:merchantIds:0}",
new Dictionary() { { "company", companyMerchants } });
- Assert.Equal(companyMerchantsFirstKey, $"{prefix}:{companyMerchants.Merchants.First().MerchantIds.First()}");
+ Assert.Equal(companyMerchantsFirstKey, $"{Prefix}:{companyMerchants.Merchants.First().MerchantIds.First()}");
+
+ // 新增规则:company:menus:id:all
+ // 输出: Prefix:1,2
+
+ var allKeysRule =
+ KeyGenerateHelper.GetKey(Prefix, "{company:menus:id:all}",
+ new Dictionary() { { "company", _companyThirdPartyIds } });
+
+ Assert.Equal(allKeysRule,
+ $"{Prefix}:{string.Join(",", _companyThirdPartyIds.Menus!.Select(x => x.Id).ToList())}");
+
+ // 新增规则:{company:menus:all}
+ // 输出: Prefix:
+
+ var allKeysRule2 =
+ KeyGenerateHelper.GetKey(Prefix, "{company:menus:all}",
+ new Dictionary() { { "company", _companyThirdPartyIds } });
+
+ Assert.Equal($"{Prefix}:", allKeysRule2);
+
+ // 新增规则:{company:menus:id:all}:{company:id}
+ // 输出: Prefix:1,2:3
+
+ var allKeysRule3 =
+ KeyGenerateHelper.GetKey(Prefix, "{company:menus:id:all}:{company:id}",
+ new Dictionary() { { "company", _companyThirdPartyIds } });
+
+ Assert.Equal(
+ $"{Prefix}:{string.Join(",", _companyThirdPartyIds.Menus!.Select(x => x.Id).ToList())}:{_companyThirdPartyIds.Id}",
+ allKeysRule3);
+
+ // 新增规则:{company:menus:0:menuSettings:id:all}:{company:id}
+ // 输出: Prefix:menu_setting_id_1,menu_setting_id_2:3
+
+ var allKeysRule4 =
+ KeyGenerateHelper.GetKey(Prefix, "{company:menus:0:menuSettings:id:all}:{company:id}",
+ new Dictionary { { "company", _companyThirdPartyIds } });
+
+ Assert.Equal(
+ $"{Prefix}:{string.Join(",", _companyThirdPartyIds.Menus!.First().MenuSettings.Select(x => x.Id).ToList())}:{_companyThirdPartyIds.Id}",
+ allKeysRule4);
+
+ // 新增规则:{company:all}:{company:id}
+ // 输出: Prefix::3
+
+ var allKeysRule5 =
+ KeyGenerateHelper.GetKey(Prefix, "{company:all}:{company:id}",
+ new Dictionary { { "company", _companyThirdPartyIds } });
+
+ Assert.Equal($"{Prefix}::{_companyThirdPartyIds.Id}", allKeysRule5);
}
}
\ No newline at end of file