用NetCore + ReactJS 实现一个前后端分离的网站 (6) 缓存机制
1. 前言
对于实时性要求不高的资源,我们一般可以利用缓存机制来降低数据库的请求压力。
轻量级的应用可以用自带的MemoryCache,如果对缓存的高并发、持久化有要求的可以用Redis。
本节用MemoryCache来实现缓存机制。
2. 自定义属性
先创建一个ApiCacheAttribute
,接受一个缓存时间。
给方法添加属性,这里传了一个参数60,意思是返回值会在缓存里存在60秒。
ApiCacheAttribute.cs
using Microsoft.AspNetCore.Mvc.Filters;
namespace NovelTogether.Core.API.Attributes
{
// IFilterMetadata 是为了在过滤器中能够获取到这个自定义属性
public class ApiCacheAttribute: Attribute, IFilterMetadata
{
public int DurationSeconds { get; set; }
public ApiCacheAttribute(int durationSeconds)
{
DurationSeconds = durationSeconds;
}
}
}
NovelController.cs
[HttpGet("{id}")]
[ApiAuthorize]
[ApiCache(60)]
public async Task<ResponseModel<Novel>> Get(int id)
{
var novel = await _novelService.SelectAsync(x => x.ID == id);
return new ResponseModel<Novel>().Ok(novel);
}
3. 缓存的存取实现
ICacheHelper.cs
namespace NovelTogether.Core.API.Helpers.Cache
{
public interface ICacheHelper
{
public object? Get(object key);
public void Set(object key, object value, TimeSpan relativeTimeToNow);
}
}
MemoryCacheHelper
using Microsoft.Extensions.Caching.Memory;
namespace NovelTogether.Core.API.Helpers.Cache
{
public class MemoryCacheHelper : ICacheHelper
{
private readonly IMemoryCache _cache;
public MemoryCacheHelper(IMemoryCache cache)
{
_cache = cache;
}
public object? Get(object key)
{
return _cache.Get(key);
}
public void Set(object key, object value, TimeSpan relativeTimeToNow)
{
_cache.Set(key, value, relativeTimeToNow);
}
}
}
3. 过滤器(MemoryCache)
通过过滤器
过滤请求,把方法名和参数值作为key,返回值作为value存入缓存中。
当过滤器收到相同请求,并且缓存没有超时,那么就直接返回缓存中的value,并且记log。
CacheFilter.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using NovelTogether.Core.API.Attributes;
using NovelTogether.Core.API.Helpers.Cache;
using NovelTogether.Core.API.Utils;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
namespace NovelTogether.Core.API.Filters
{
public class CacheFilter : IActionFilter
{
private readonly ICacheHelper _cache;
private readonly ILogger<CacheFilter> _logger;
public CacheFilter(ICacheHelper cache, ILogger<CacheFilter> logger)
{
_cache = cache;
_logger = logger;
}
public void OnActionExecuted(ActionExecutedContext context)
{
var cacheKey = context.HttpContext.Items[Consts.CACHE_KEY];
if (cacheKey != null)
{
cacheKey = cacheKey.ObjToString();
var duration = int.Parse(context.HttpContext.Items[Consts.CACHE_DURATION_SECONDS].ObjToString());
_cache.Set(cacheKey, context.Result, new TimeSpan(0, 0, duration));
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
// 如果方法声明了属性[ApiCache],就把资源加入缓存。
var filters = context.ActionDescriptor.FilterDescriptors;
foreach (var filter in filters)
{
if (filter.Filter is ApiCacheAttribute)
{
var cacheAttribute = (ApiCacheAttribute)filter.Filter;
var action = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
var controllerName = action.ControllerTypeInfo.FullName;
var actionName = action.ActionName;
var option = new JsonSerializerOptions()
{
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
};
var parameters = JsonSerializer.Serialize(context.ActionArguments, option);
var cacheKey = $"{controllerName}.{actionName}_{parameters}";
var result = _cache.Get(cacheKey);
if (result == null)
{
// 提取方法名和参数,给OnActionExecuted使用
context.HttpContext.Items.Add(Consts.CACHE_KEY, cacheKey);
context.HttpContext.Items.Add(Consts.CACHE_DURATION_SECONDS, cacheAttribute.DurationSeconds);
}
else
{
// 从缓存中取出数据,直接返回
context.Result = (IActionResult)result;
var uniqueId = context.HttpContext.Items[Consts.UNIQUE_ID].ObjToString();
_logger.LogInformation($"Action Executing\r\n唯一标识:{uniqueId}\r\n触发缓存,键值为: {cacheKey}");
}
return;
}
}
}
}
}
4. 添加服务
Program.cs
builder.Services.AddControllers(option =>
{
// 添加日志过滤器
option.Filters.Add(typeof(LogFilter));
// 添加缓存过滤器
option.Filters.Add(typeof(CacheFilter));
// 添加全局异常过滤器
option.Filters.Add(typeof(GlobalExceptionFilter));
});
// 添加缓存服务
builder.Services.AddMemoryCache();
// 其他代码
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory(containerBuilder =>
{
// 其他代码
// 注册自定义缓存服务
containerBuilder.RegisterType<MemoryCacheHelper>().As<ICacheHelper>().SingleInstance();
// 其他代码
}));
5. 执行方法
我执行三次方法,前两次参数一样,从图中可以看出,第二次是从缓存中获取的。第三次换了参数,就没有从缓存中取值。