Browse Source

自定义ef缓存中间件

懒得勤快 4 years ago
parent
commit
24e88d9864

+ 2 - 2
src/Masuit.MyBlogs.Core/Common/ImagebedClient.cs

@@ -185,7 +185,7 @@ namespace Masuit.MyBlogs.Core.Common
                     using var content = resp.Content;
                     if (resp.IsSuccessStatusCode)
                     {
-                        return (config.RawUrl + path, true);
+                        return (config.RawUrl.Split(',').OrderBy(_ => Guid.NewGuid()).FirstOrDefault() + path, true);
                     }
                 }
 
@@ -207,7 +207,7 @@ namespace Masuit.MyBlogs.Core.Common
                 return (null, false);
             }
 
-            var objectName = DateTime.Now.ToString(@"yyyy\/MM\/dd") + "/" + file;
+            var objectName = $"{DateTime.Now:yyyy\\/MM\\/dd}/{file}";
             var fallbackPolicy = Policy<(string url, bool success)>.Handle<Exception>().Fallback(_ => (null, false));
             var retryPolicy = Policy<(string url, bool success)>.Handle<Exception>().Retry(3);
             return fallbackPolicy.Wrap(retryPolicy).Execute(() =>

+ 2 - 2
src/Masuit.MyBlogs.Core/Controllers/ErrorController.cs

@@ -134,7 +134,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// <param name="email"></param>
         /// <param name="token"></param>
         /// <returns></returns>
-        [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall, ResponseCache(Duration = 115, VaryByQueryKeys = new[] { "email", "token" })]
+        [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall]
         public ActionResult CheckViewToken(string email, string token)
         {
             if (string.IsNullOrEmpty(token))
@@ -167,7 +167,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// <param name="userInfoService"></param>
         /// <param name="email"></param>
         /// <returns></returns>
-        [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall, ResponseCache(Duration = 115, VaryByQueryKeys = new[] { "email" })]
+        [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall, ResponseCache(Duration = 100, VaryByQueryKeys = new[] { "email" })]
         public ActionResult GetViewToken([FromServices] IUserInfoService userInfoService, string email)
         {
             if (string.IsNullOrEmpty(email) || !email.MatchEmail().isMatch)

+ 2 - 1
src/Masuit.MyBlogs.Core/Controllers/MsgController.cs

@@ -1,4 +1,5 @@
 using CacheManager.Core;
+using EFCoreSecondLevelCacheInterceptor;
 using Hangfire;
 using Masuit.MyBlogs.Core.Common;
 using Masuit.MyBlogs.Core.Common.Mails;
@@ -336,7 +337,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         [MyAuthorize]
         public ActionResult GetUnreadMsgs()
         {
-            var msgs = MessageService.GetQueryNoTracking(m => !m.Read, m => m.Time, false).ToList();
+            var msgs = MessageService.GetQueryNoTracking(m => !m.Read, m => m.Time, false).NotCacheable().ToList();
             return ResultData(msgs);
         }
 

+ 2 - 2
src/Masuit.MyBlogs.Core/Controllers/PostController.cs

@@ -376,7 +376,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// <param name="email"></param>
         /// <param name="token"></param>
         /// <returns></returns>
-        [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall, ResponseCache(Duration = 115, VaryByQueryKeys = new[] { "email", "token" })]
+        [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall]
         public ActionResult CheckViewToken(string email, string token)
         {
             if (string.IsNullOrEmpty(token))
@@ -409,7 +409,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         /// <param name="email"></param>
         /// <returns></returns>
-        [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall, ResponseCache(Duration = 115, VaryByQueryKeys = new[] { "email" })]
+        [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall]
         public ActionResult GetViewToken(string email)
         {
             if (string.IsNullOrEmpty(email) || !email.MatchEmail().isMatch)

+ 4 - 5
src/Masuit.MyBlogs.Core/Masuit.MyBlogs.Core.csproj

@@ -39,10 +39,9 @@
         <PackageReference Include="CacheManager.Serialization.Json" Version="2.0.0-beta-1629" />
         <PackageReference Include="CacheManager.StackExchange.Redis" Version="1.2.0" />
         <PackageReference Include="CHTCHSConv" Version="1.0.0" />
-        <PackageReference Include="CLRStats" Version="1.0.0" />
         <PackageReference Include="CSRedisCore" Version="3.6.6" />
         <PackageReference Include="EFCoreSecondLevelCacheInterceptor" Version="2.4.1" />
-        <PackageReference Include="Hangfire" Version="1.7.20" />
+        <PackageReference Include="Hangfire" Version="1.7.21" />
         <PackageReference Include="Hangfire.Autofac" Version="2.3.1" />
         <PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
         <PackageReference Include="htmldiff.net-core" Version="1.3.6" />
@@ -60,14 +59,14 @@
         <PackageReference Include="OpenXmlPowerTools-NetStandard" Version="4.4.21" />
         <PackageReference Include="MiniProfiler.EntityFrameworkCore" Version="4.2.22" />
         <PackageReference Include="PanGu.HighLight" Version="1.0.0" />
-        <PackageReference Include="Polly" Version="7.2.1" />
+        <PackageReference Include="Polly" Version="7.2.2" />
         <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="5.0.0-alpha.2" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="5.0.1" />
         <PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.9" />
-        <PackageReference Include="TimeZoneConverter" Version="3.4.0" />
+        <PackageReference Include="TimeZoneConverter" Version="3.5.0" />
         <PackageReference Include="WilderMinds.RssSyndication" Version="1.7.0" />
         <PackageReference Include="WinInsider.System.Net.Http.Formatting" Version="1.0.14" />
-        <PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="5.1.29" />
+        <PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="5.1.30" />
     </ItemGroup>
     <ItemGroup>
         <Content Update="appsettings.json">

+ 133 - 0
src/Masuit.MyBlogs.Core/MyEFCacheManagerCoreProvider.cs

@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using CacheManager.Core;
+using EFCoreSecondLevelCacheInterceptor;
+
+namespace Masuit.MyBlogs.Core
+{
+    /// <summary>
+    /// Using ICacheManager as a cache service.
+    /// </summary>
+    public class MyEFCacheManagerCoreProvider : IEFCacheServiceProvider
+    {
+        private readonly IReaderWriterLockProvider _readerWriterLockProvider;
+        private readonly ICacheManager<ISet<string>> _dependenciesCacheManager;
+        private readonly ICacheManager<EFCachedData> _valuesCacheManager;
+        private readonly string _keyPrefix = "EFCache:";
+        /// <summary>
+        /// Using IMemoryCache as a cache service.
+        /// </summary>
+        public MyEFCacheManagerCoreProvider(ICacheManager<ISet<string>> dependenciesCacheManager, ICacheManager<EFCachedData> valuesCacheManager, IReaderWriterLockProvider readerWriterLockProvider)
+        {
+            _readerWriterLockProvider = readerWriterLockProvider;
+            _dependenciesCacheManager = dependenciesCacheManager ?? throw new ArgumentNullException(nameof(dependenciesCacheManager), "Please register the `ICacheManager`.");
+            _valuesCacheManager = valuesCacheManager ?? throw new ArgumentNullException(nameof(valuesCacheManager), "Please register the `ICacheManager`.");
+
+            // Occurs when an item was removed by the cache handle due to expiration or e.g. memory pressure eviction.
+            // Without _dependenciesCacheManager items, we can't invalidate cached items on Insert/Update/Delete.
+            // So to prevent stale reads, we have to clear all cached data in this case.
+            _dependenciesCacheManager.OnRemoveByHandle += (sender, args) => ClearAllCachedEntries();
+        }
+
+        /// <summary>
+        /// Adds a new item to the cache.
+        /// </summary>
+        /// <param name="cacheKey">key</param>
+        /// <param name="value">value</param>
+        /// <param name="cachePolicy">Defines the expiration mode of the cache item.</param>
+        public void InsertValue(EFCacheKey cacheKey, EFCachedData value, EFCachePolicy cachePolicy)
+        {
+            _readerWriterLockProvider.TryWriteLocked(() =>
+            {
+                if (value == null)
+                {
+                    value = new EFCachedData
+                    {
+                        IsNull = true
+                    };
+                }
+
+                var keyHash = cacheKey.KeyHash;
+
+                foreach (var rootCacheKey in cacheKey.CacheDependencies)
+                {
+                    _dependenciesCacheManager.AddOrUpdate(_keyPrefix + rootCacheKey, new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+                    {
+                        keyHash
+                    }, updateValue: set =>
+                    {
+                        set.Add(keyHash);
+                        return set;
+                    });
+                }
+
+                if (cachePolicy == null)
+                {
+                    _valuesCacheManager.Add(_keyPrefix + keyHash, value);
+                }
+                else
+                {
+                    _valuesCacheManager.Add(new CacheItem<EFCachedData>(_keyPrefix + keyHash, value, cachePolicy.CacheExpirationMode == CacheExpirationMode.Absolute ? ExpirationMode.Absolute : ExpirationMode.Sliding, cachePolicy.CacheTimeout));
+                }
+            });
+        }
+
+        /// <summary>
+        /// Removes the cached entries added by this library.
+        /// </summary>
+        public void ClearAllCachedEntries()
+        {
+            _readerWriterLockProvider.TryWriteLocked(() =>
+            {
+                _valuesCacheManager.Clear();
+                _dependenciesCacheManager.Clear();
+            });
+        }
+
+        /// <summary>
+        /// Gets a cached entry by key.
+        /// </summary>
+        /// <param name="cacheKey">key to find</param>
+        /// <returns>cached value</returns>
+        /// <param name="cachePolicy">Defines the expiration mode of the cache item.</param>
+        public EFCachedData GetValue(EFCacheKey cacheKey, EFCachePolicy cachePolicy)
+        {
+            return _readerWriterLockProvider.TryReadLocked(() => _valuesCacheManager.Get<EFCachedData>(_keyPrefix + cacheKey.KeyHash));
+        }
+
+        /// <summary>
+        /// Invalidates all of the cache entries which are dependent on any of the specified root keys.
+        /// </summary>
+        /// <param name="cacheKey">Stores information of the computed key of the input LINQ query.</param>
+        public void InvalidateCacheDependencies(EFCacheKey cacheKey)
+        {
+            _readerWriterLockProvider.TryWriteLocked(() =>
+            {
+                foreach (var rootCacheKey in cacheKey.CacheDependencies)
+                {
+                    if (string.IsNullOrWhiteSpace(rootCacheKey))
+                    {
+                        continue;
+                    }
+
+                    clearDependencyValues(rootCacheKey);
+                    _dependenciesCacheManager.Remove(_keyPrefix + rootCacheKey);
+                }
+            });
+        }
+
+        private void clearDependencyValues(string rootCacheKey)
+        {
+            var dependencyKeys = _dependenciesCacheManager.Get(_keyPrefix + rootCacheKey);
+            if (dependencyKeys == null)
+            {
+                return;
+            }
+
+            foreach (var dependencyKey in dependencyKeys)
+            {
+                _valuesCacheManager.Remove(_keyPrefix + dependencyKey);
+            }
+        }
+    }
+}

+ 4 - 3
src/Masuit.MyBlogs.Core/Startup.cs

@@ -1,6 +1,5 @@
 using Autofac;
 using Autofac.Extensions.DependencyInjection;
-using CLRStats;
 using CSRedis;
 using EFCoreSecondLevelCacheInterceptor;
 using Hangfire;
@@ -48,7 +47,9 @@ namespace Masuit.MyBlogs.Core
         /// 配置中心
         /// </summary>
         public IConfiguration Configuration { get; set; }
+
         private readonly IWebHostEnvironment _env;
+
         /// <summary>
         /// asp.net core核心配置
         /// </summary>
@@ -82,7 +83,7 @@ namespace Masuit.MyBlogs.Core
         public void ConfigureServices(IServiceCollection services)
         {
             RedisHelper.Initialization(new CSRedisClient(AppConfig.Redis));
-            services.AddEFSecondLevelCache(options => options.UseMemoryCacheProvider(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(5)).DisableLogging(true));
+            services.AddEFSecondLevelCache(options => options.UseCustomCacheProvider<MyEFCacheManagerCoreProvider>(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(5)).DisableLogging(true));
             services.AddDbContextPool<DataContext>((serviceProvider, opt) =>
             {
                 opt.UseMySql(AppConfig.ConnString, ServerVersion.AutoDetect(AppConfig.ConnString), builder => builder.EnableRetryOnFailure(3)).EnableDetailedErrors().UseLazyLoadingProxies().UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll).AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>());
@@ -149,7 +150,7 @@ namespace Masuit.MyBlogs.Core
             app.SetupHttpsRedirection(Configuration);
             app.UseDefaultFiles().UseStaticFiles();
             app.UseSession().UseCookiePolicy(); //注入Session
-            app.UseWhen(c => c.Session.Get<UserInfoDto>(SessionKey.UserInfo)?.IsAdmin == true, builder => builder.UseMiniProfiler().UseCLRStatsDashboard());
+            app.UseWhen(c => c.Session.Get<UserInfoDto>(SessionKey.UserInfo)?.IsAdmin == true, builder => builder.UseMiniProfiler());
             app.UseWhen(c => !c.Request.Path.StartsWithSegments("/_blazor"), builder => builder.UseMiddleware<RequestInterceptMiddleware>()); //启用网站请求拦截
             app.SetupHangfire();
             app.UseResponseCaching().UseResponseCompression(); //启动Response缓存