Skip to content

Commit

Permalink
自定义ef缓存中间件
Browse files Browse the repository at this point in the history
  • Loading branch information
ldqk committed Apr 13, 2021
1 parent c578fd7 commit 24e88d9
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 15 deletions.
4 changes: 2 additions & 2 deletions src/Masuit.MyBlogs.Core/Common/ImagebedClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public ImagebedClient(HttpClient httpClient, IConfiguration config)
using var content = resp.Content;
if (resp.IsSuccessStatusCode)
{
return (config.RawUrl + path, true);
return (config.RawUrl.Split(',').OrderBy(_ => Guid.NewGuid()).FirstOrDefault() + path, true);
}
}

Expand All @@ -207,7 +207,7 @@ public ImagebedClient(HttpClient httpClient, IConfiguration config)
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(() =>
Expand Down
4 changes: 2 additions & 2 deletions src/Masuit.MyBlogs.Core/Controllers/ErrorController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public ActionResult ComingSoon()
/// <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))
Expand Down Expand Up @@ -167,7 +167,7 @@ public ActionResult CheckViewToken(string email, string token)
/// <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)
Expand Down
3 changes: 2 additions & 1 deletion src/Masuit.MyBlogs.Core/Controllers/MsgController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CacheManager.Core;
using EFCoreSecondLevelCacheInterceptor;
using Hangfire;
using Masuit.MyBlogs.Core.Common;
using Masuit.MyBlogs.Core.Common.Mails;
Expand Down Expand Up @@ -336,7 +337,7 @@ public ActionResult GetInternalMsgs([Range(1, int.MaxValue, ErrorMessage = "页
[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);
}

Expand Down
4 changes: 2 additions & 2 deletions src/Masuit.MyBlogs.Core/Controllers/PostController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ public async Task<ActionResult> All()
/// <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))
Expand Down Expand Up @@ -409,7 +409,7 @@ public ActionResult CheckViewToken(string email, string token)
/// </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)
Expand Down
9 changes: 4 additions & 5 deletions src/Masuit.MyBlogs.Core/Masuit.MyBlogs.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand All @@ -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">
Expand Down
133 changes: 133 additions & 0 deletions src/Masuit.MyBlogs.Core/MyEFCacheManagerCoreProvider.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
7 changes: 4 additions & 3 deletions src/Masuit.MyBlogs.Core/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using CLRStats;
using CSRedis;
using EFCoreSecondLevelCacheInterceptor;
using Hangfire;
Expand Down Expand Up @@ -48,7 +47,9 @@ public class Startup
/// 配置中心
/// </summary>
public IConfiguration Configuration { get; set; }

private readonly IWebHostEnvironment _env;

/// <summary>
/// asp.net core核心配置
/// </summary>
Expand Down Expand Up @@ -82,7 +83,7 @@ void BindConfig()
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>());
Expand Down Expand Up @@ -149,7 +150,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataCont
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缓存
Expand Down

0 comments on commit 24e88d9

Please sign in to comment.