From 9aa2aa9b90a3b8bd385551cda2270324ab9413d3 Mon Sep 17 00:00:00 2001 From: "https://gitlab.sjfood.us/Joe.G" Date: Wed, 9 Apr 2025 22:09:47 +0800 Subject: [PATCH 1/4] =?UTF-8?q?MultiSourceEvictable=20=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E5=A4=9A=E4=B8=AA=E8=A7=84=E5=88=99=E6=B8=85?= =?UTF-8?q?=E9=99=A4=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MultiSourceEvictableAttribute.cs | 25 ++++++++++--- .../MultiSourceApiRequestCacheTests.cs | 35 ++++++++++++++++++- TestApi/Controllers/MultiSourceController.cs | 10 ++++-- .../MultiSourceInMemoryController.cs | 4 +-- TestApi/Controllers/UseLockController.cs | 6 ++-- TestApi/Service/IMultiSourceService.cs | 2 ++ TestApi/Service/MultiSourceService.cs | 6 ++++ 7 files changed, 75 insertions(+), 13 deletions(-) diff --git a/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs b/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs index 5556431..ba1044e 100644 --- a/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs +++ b/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using AspectCore.DynamicProxy; using FastCache.Core.Driver; @@ -12,12 +14,14 @@ namespace FastCache.MultiSource.Attributes public class MultiSourceEvictableAttribute : AbstractInterceptorAttribute { private readonly string[] _keys; - private readonly string _expression; + private readonly string[] _expression; private readonly Target _target; public sealed override int Order { get; set; } - public MultiSourceEvictableAttribute(string[] keys, string expression, Target target) + public override bool AllowMultiple { get; } = true; + + public MultiSourceEvictableAttribute(string[] keys, string[] expression, Target target) { _keys = keys; _expression = expression; @@ -43,6 +47,8 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) throw new ArgumentOutOfRangeException(); } + var tasks = new ConcurrentBag(); + var dictionary = new Dictionary(); var parameterInfos = context.ImplementationMethod.GetParameters(); for (var i = 0; i < context.Parameters.Length; i++) @@ -50,11 +56,20 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) dictionary.Add(parameterInfos[i].Name, context.Parameters[i]); } - foreach (var s in _keys) + var keys = _keys.Select(x => x.Trim()).Distinct().ToList(); + var expressions = _expression.Select(x => x.Trim()).Distinct().ToList(); + + foreach (var key in keys) { - var key = KeyGenerateHelper.GetKey(_expression, dictionary); + foreach (var expression in expressions) + { + tasks.Add(cacheClient.Delete(KeyGenerateHelper.GetKey(expression, dictionary), key)); + } + } - await cacheClient.Delete(key, s); + if (tasks.Count > 0) + { + await Task.WhenAll(tasks); } } } diff --git a/IntegrationTests/MultiSourceApiRequestCacheTests.cs b/IntegrationTests/MultiSourceApiRequestCacheTests.cs index 4e58d01..5d60e1d 100644 --- a/IntegrationTests/MultiSourceApiRequestCacheTests.cs +++ b/IntegrationTests/MultiSourceApiRequestCacheTests.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; +using FastCache.Core.Driver; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using TestApi.DB; @@ -148,7 +149,7 @@ public async void CacheAndEvictOther(string baseUrl) var timeResult = end - start; Assert.True(timeResult < 500000); } - + [Theory] [InlineData("/MultiSource")] public async Task TestUpdated(string baseUrl) @@ -186,4 +187,36 @@ public async void CacheResultTaskNull(string baseUrl) var result1 = await resp1.Content.ReadAsStringAsync(); Assert.Equal("", result1); } + + [Theory] + [InlineData("/MultiSource")] + public async void TestRemove(string baseUrl) + { + var responseMessage = await _httpClient.GetAsync($"{baseUrl}?id=1"); + Assert.Equal(responseMessage.StatusCode, HttpStatusCode.OK); + + var user = await responseMessage.Content.ReadFromJsonAsync(); + + var responseMessageBySearchName = await _httpClient.GetAsync($"{baseUrl}/get/name?name=anson1"); + Assert.Equal(responseMessageBySearchName.StatusCode, HttpStatusCode.OK); + + var responseMessageBySearchNameDeleted = await _httpClient.GetAsync($"{baseUrl}/get/name?name=anson1"); + Assert.Equal(responseMessageBySearchName.StatusCode, HttpStatusCode.OK); + + var userDeleted = await responseMessageBySearchNameDeleted.Content.ReadFromJsonAsync(); + + Assert.Equal(userDeleted.Name, "anson1"); + + user.Name = "joe"; + + var updateAfter = await _httpClient.PutAsJsonAsync(baseUrl, user); + Assert.Equal(updateAfter.StatusCode, HttpStatusCode.OK); + + var responseMessageBySearchNameUpdated = await _httpClient.GetAsync($"{baseUrl}/get/name?name=joe"); + Assert.Equal(responseMessageBySearchNameUpdated.StatusCode, HttpStatusCode.OK); + + var userUpdated = await responseMessageBySearchNameUpdated.Content.ReadFromJsonAsync(); + + Assert.Equal(userUpdated.Name, "joe"); + } } \ No newline at end of file diff --git a/TestApi/Controllers/MultiSourceController.cs b/TestApi/Controllers/MultiSourceController.cs index de6d900..a91ed88 100644 --- a/TestApi/Controllers/MultiSourceController.cs +++ b/TestApi/Controllers/MultiSourceController.cs @@ -32,14 +32,14 @@ public User Add(User user) [HttpPut] [MultiSourceCacheable("MultiSource-single", "{user:id}", Target.Redis, 5)] - [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "{user:id}", Target.Redis)] + [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["{user:id}", "{user:name}"], Target.Redis)] public virtual async Task Update(User user) { return await _userService.Update(user); } [HttpDelete] - [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "{id}", Target.Redis)] + [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["{id}"], Target.Redis)] public virtual bool Delete(string id) { return _userService.Delete(id); @@ -57,4 +57,10 @@ public virtual IEnumerable Users(string page) { return await _userService.SingleOrDefault(id); } + + [HttpGet("get/name")] + public virtual async Task SearchName(string name) + { + return await _userService.SingleOrDefaultByName(name); + } } \ No newline at end of file diff --git a/TestApi/Controllers/MultiSourceInMemoryController.cs b/TestApi/Controllers/MultiSourceInMemoryController.cs index 2f4eba6..9968c41 100644 --- a/TestApi/Controllers/MultiSourceInMemoryController.cs +++ b/TestApi/Controllers/MultiSourceInMemoryController.cs @@ -32,14 +32,14 @@ public User Add(User user) [HttpPut] [MultiSourceCacheable("MultiSource-single", "{user:id}", Target.InMemory, 5)] - [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "{user:id}", Target.InMemory)] + [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["{user:id}"], Target.InMemory)] public virtual async Task Update(User user) { return await _userService.Update(user); } [HttpDelete] - [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "{id}", Target.InMemory)] + [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["{id}"], Target.InMemory)] public virtual bool Delete(string id) { return _userService.Delete(id); diff --git a/TestApi/Controllers/UseLockController.cs b/TestApi/Controllers/UseLockController.cs index 0afbacc..862ea81 100644 --- a/TestApi/Controllers/UseLockController.cs +++ b/TestApi/Controllers/UseLockController.cs @@ -13,7 +13,7 @@ public class UseLockController(ILockUserService lockUserService) [HttpPost("add-with-cache")] [DistributedLock("user-add")] [MultiSourceCacheable("MultiSource-single", "{user:id}", Target.Redis, 5)] - [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "{user:id}", Target.Redis)] + [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["{user:id}"], Target.Redis)] public virtual async Task AddWithCache(User user, int delayMs = 0) { return await lockUserService.Add(user, delayMs); @@ -32,7 +32,7 @@ public virtual async Task Get(string id) { return await lockUserService.Single(id); } - + [HttpGet("users")] [MultiSourceCacheable("MultiSource-single", "{id}", Target.Redis, 5)] public virtual IEnumerable Users(string page) @@ -41,7 +41,7 @@ public virtual IEnumerable Users(string page) } [HttpDelete] - [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "*{id}*", Target.Redis)] + [MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["*{id}*"], Target.Redis)] public virtual bool Delete(string id) { return lockUserService.Delete(id); diff --git a/TestApi/Service/IMultiSourceService.cs b/TestApi/Service/IMultiSourceService.cs index bee9ca5..62a3f47 100644 --- a/TestApi/Service/IMultiSourceService.cs +++ b/TestApi/Service/IMultiSourceService.cs @@ -10,6 +10,8 @@ public interface IMultiSourceService Task SingleOrDefault(string id); + Task SingleOrDefaultByName(string name); + Task Update(User user); bool Delete(string id); diff --git a/TestApi/Service/MultiSourceService.cs b/TestApi/Service/MultiSourceService.cs index 8e529b6..409b111 100644 --- a/TestApi/Service/MultiSourceService.cs +++ b/TestApi/Service/MultiSourceService.cs @@ -52,6 +52,12 @@ public virtual IEnumerable List(string page) return await dbContext.Set().SingleOrDefaultAsync(x => x.Id == id); } + [MultiSourceCacheable("MultiSource-single", "{name}", Target.Redis, 60)] + public async Task SingleOrDefaultByName(string name) + { + return await dbContext.Set().SingleOrDefaultAsync(x => x.Name == name); + } + public virtual Task TestReturnNull() { return null; From 017334a7627d7075655153fed6f47cd5fa291f11 Mon Sep 17 00:00:00 2001 From: "https://gitlab.sjfood.us/Joe.G" Date: Wed, 9 Apr 2025 22:12:02 +0800 Subject: [PATCH 2/4] update test name --- IntegrationTests/MultiSourceApiRequestCacheTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IntegrationTests/MultiSourceApiRequestCacheTests.cs b/IntegrationTests/MultiSourceApiRequestCacheTests.cs index 5d60e1d..1d9c7c0 100644 --- a/IntegrationTests/MultiSourceApiRequestCacheTests.cs +++ b/IntegrationTests/MultiSourceApiRequestCacheTests.cs @@ -190,7 +190,7 @@ public async void CacheResultTaskNull(string baseUrl) [Theory] [InlineData("/MultiSource")] - public async void TestRemove(string baseUrl) + public async void TestMultiSourceEvictableAllowsEvictionByMultipleRules(string baseUrl) { var responseMessage = await _httpClient.GetAsync($"{baseUrl}?id=1"); Assert.Equal(responseMessage.StatusCode, HttpStatusCode.OK); From 4d9d17f0147fdfe47df623acf386461e4cbd8bda Mon Sep 17 00:00:00 2001 From: "https://gitlab.sjfood.us/Joe.G" Date: Wed, 9 Apr 2025 22:57:37 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E5=BC=BA=E5=8C=96=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MultiSourceEvictableAttribute.cs | 4 +- .../MultiSourceApiRequestCacheTests.cs | 40 +++++++++++++------ TestApi/Entity/User.cs | 2 + TestApi/Service/MultiSourceService.cs | 1 + 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs b/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs index ba1044e..b8197d2 100644 --- a/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs +++ b/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs @@ -63,7 +63,9 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) { foreach (var expression in expressions) { - tasks.Add(cacheClient.Delete(KeyGenerateHelper.GetKey(expression, dictionary), key)); + var deleteKey = KeyGenerateHelper.GetKey(expression, dictionary); + await cacheClient.Delete(deleteKey, key); + // tasks.Add(cacheClient.Delete(deleteKey, key)); } } diff --git a/IntegrationTests/MultiSourceApiRequestCacheTests.cs b/IntegrationTests/MultiSourceApiRequestCacheTests.cs index 1d9c7c0..db3fe9a 100644 --- a/IntegrationTests/MultiSourceApiRequestCacheTests.cs +++ b/IntegrationTests/MultiSourceApiRequestCacheTests.cs @@ -7,6 +7,7 @@ using System.Net.Http.Json; using System.Threading.Tasks; using FastCache.Core.Driver; +using FastCache.Redis.Driver; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using TestApi.DB; @@ -18,10 +19,12 @@ namespace IntegrationTests; [Collection("Sequential")] public class MultiSourceApiRequestCacheTests : IClassFixture> { + private readonly RedisCache _redisCache; private readonly HttpClient _httpClient; - public MultiSourceApiRequestCacheTests(WebApplicationFactory factory) + public MultiSourceApiRequestCacheTests(WebApplicationFactory factory, RedisCache redisCache) { + _redisCache = redisCache; _httpClient = factory.CreateClient(); var memoryDbContext = factory.Services.GetService(); var list = memoryDbContext.Set().ToList(); @@ -32,7 +35,8 @@ public MultiSourceApiRequestCacheTests(WebApplicationFactory factory) new() { Id = "1", - Name = "anson1" + Name = "anson1", + Age = 10 }, new() { @@ -57,6 +61,8 @@ public MultiSourceApiRequestCacheTests(WebApplicationFactory factory) [InlineData("/MultiSourceInMemory")] public async void RequestCanCache(string baseUrl) { + _redisCache.GetRedisClient()!.Clear(); + var stopwatch = new Stopwatch(); // 第一次请求 @@ -196,27 +202,37 @@ public async void TestMultiSourceEvictableAllowsEvictionByMultipleRules(string b Assert.Equal(responseMessage.StatusCode, HttpStatusCode.OK); var user = await responseMessage.Content.ReadFromJsonAsync(); + Assert.Equal(user.Name, "anson1"); + Assert.Equal(user.Age, 10); var responseMessageBySearchName = await _httpClient.GetAsync($"{baseUrl}/get/name?name=anson1"); Assert.Equal(responseMessageBySearchName.StatusCode, HttpStatusCode.OK); - var responseMessageBySearchNameDeleted = await _httpClient.GetAsync($"{baseUrl}/get/name?name=anson1"); - Assert.Equal(responseMessageBySearchName.StatusCode, HttpStatusCode.OK); - - var userDeleted = await responseMessageBySearchNameDeleted.Content.ReadFromJsonAsync(); + var userBySearchName = await responseMessageBySearchName.Content.ReadFromJsonAsync(); - Assert.Equal(userDeleted.Name, "anson1"); + Assert.Equal(userBySearchName.Name, "anson1"); + Assert.Equal(userBySearchName.Age, 10); - user.Name = "joe"; + user.Age = 1; var updateAfter = await _httpClient.PutAsJsonAsync(baseUrl, user); Assert.Equal(updateAfter.StatusCode, HttpStatusCode.OK); - var responseMessageBySearchNameUpdated = await _httpClient.GetAsync($"{baseUrl}/get/name?name=joe"); + var responseMessageBySearchNameUpdated = await _httpClient.GetAsync($"{baseUrl}/get/name?name=anson1"); Assert.Equal(responseMessageBySearchNameUpdated.StatusCode, HttpStatusCode.OK); - + var userUpdated = await responseMessageBySearchNameUpdated.Content.ReadFromJsonAsync(); - - Assert.Equal(userUpdated.Name, "joe"); + Assert.Equal("anson1", userUpdated.Name); + Assert.Equal(1, userUpdated.Age); + + var deleted = await _httpClient.DeleteAsync($"{baseUrl}?id=1"); + Assert.True(deleted.StatusCode == HttpStatusCode.OK); + + var responseMessageBySearchNameDeleted = await _httpClient.GetAsync($"{baseUrl}/get/name?name=anson1"); + Assert.Equal(responseMessageBySearchNameDeleted.StatusCode, HttpStatusCode.OK); + + var userDeleted = await responseMessageBySearchNameDeleted.Content.ReadFromJsonAsync(); + Assert.Equal("anson1", userDeleted.Name); + Assert.Equal(1, userDeleted.Age); } } \ No newline at end of file diff --git a/TestApi/Entity/User.cs b/TestApi/Entity/User.cs index cf1b5f8..20e8ca8 100644 --- a/TestApi/Entity/User.cs +++ b/TestApi/Entity/User.cs @@ -7,5 +7,7 @@ public record User : IEntity public string Id { get; set; } public string Name { get; set; } + public int Age { get; set; } + [NotMapped]public List? ThirdPartyIds { get; set; } } \ No newline at end of file diff --git a/TestApi/Service/MultiSourceService.cs b/TestApi/Service/MultiSourceService.cs index 409b111..743ab41 100644 --- a/TestApi/Service/MultiSourceService.cs +++ b/TestApi/Service/MultiSourceService.cs @@ -26,6 +26,7 @@ public virtual async Task Update(User user) { var first = await dbContext.Set().FirstAsync(x => x.Id == user.Id); first.Name = user.Name; + first.Age = user.Age; dbContext.Set().Update(first); await dbContext.SaveChangesAsync(); return first; From 3a5ee24133e4df801edcd273c005a42cb396e4b6 Mon Sep 17 00:00:00 2001 From: "https://gitlab.sjfood.us/Joe.G" Date: Wed, 9 Apr 2025 22:59:30 +0800 Subject: [PATCH 4/4] update --- .../Attributes/MultiSourceEvictableAttribute.cs | 3 +-- IntegrationTests/MultiSourceApiRequestCacheTests.cs | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs b/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs index b8197d2..05975f4 100644 --- a/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs +++ b/FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs @@ -64,8 +64,7 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) foreach (var expression in expressions) { var deleteKey = KeyGenerateHelper.GetKey(expression, dictionary); - await cacheClient.Delete(deleteKey, key); - // tasks.Add(cacheClient.Delete(deleteKey, key)); + tasks.Add(cacheClient.Delete(deleteKey, key)); } } diff --git a/IntegrationTests/MultiSourceApiRequestCacheTests.cs b/IntegrationTests/MultiSourceApiRequestCacheTests.cs index db3fe9a..d1e28f1 100644 --- a/IntegrationTests/MultiSourceApiRequestCacheTests.cs +++ b/IntegrationTests/MultiSourceApiRequestCacheTests.cs @@ -19,12 +19,10 @@ namespace IntegrationTests; [Collection("Sequential")] public class MultiSourceApiRequestCacheTests : IClassFixture> { - private readonly RedisCache _redisCache; private readonly HttpClient _httpClient; - public MultiSourceApiRequestCacheTests(WebApplicationFactory factory, RedisCache redisCache) + public MultiSourceApiRequestCacheTests(WebApplicationFactory factory) { - _redisCache = redisCache; _httpClient = factory.CreateClient(); var memoryDbContext = factory.Services.GetService(); var list = memoryDbContext.Set().ToList(); @@ -61,8 +59,6 @@ public MultiSourceApiRequestCacheTests(WebApplicationFactory factory, R [InlineData("/MultiSourceInMemory")] public async void RequestCanCache(string baseUrl) { - _redisCache.GetRedisClient()!.Clear(); - var stopwatch = new Stopwatch(); // 第一次请求