125 lines
4.2 KiB
C#
125 lines
4.2 KiB
C#
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Http.Features;
|
|
using Microsoft.Extensions.Caching.Distributed;
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CloudGaming.Code.MiddlewareExtend
|
|
{
|
|
/// <summary>
|
|
/// Redis 缓存中间件
|
|
/// </summary>
|
|
public class RedisCacheMiddleware
|
|
{
|
|
private readonly RequestDelegate _next;
|
|
|
|
private static readonly ConcurrentDictionary<Endpoint, RedisCacheAttribute> _attributeCache = new();
|
|
|
|
public RedisCacheMiddleware(RequestDelegate next)
|
|
{
|
|
_next = next;
|
|
|
|
}
|
|
|
|
public async Task InvokeAsync(HttpContext context,
|
|
IServiceProvider _serviceProvider,
|
|
AppConfig appConfig
|
|
)
|
|
{
|
|
// 检查当前请求是否需要缓存
|
|
var cacheAttribute = GetCacheAttribute(context);
|
|
if (cacheAttribute == null)
|
|
{
|
|
await _next(context);
|
|
return;
|
|
}
|
|
var _cache = appConfig.GetRedisDataBase();
|
|
|
|
// 生成缓存键(基于请求路径和查询字符串)
|
|
var cacheKey = GenerateCacheKey(context.Request, appConfig);
|
|
|
|
// 尝试从 Redis 缓存中获取数据
|
|
var cachedResponse = await _cache.StringGetAsync(cacheKey);
|
|
if (!cachedResponse.IsNullOrEmpty)
|
|
{
|
|
context.Response.StatusCode = StatusCodes.Status200OK;
|
|
context.Response.ContentType = "application/json";
|
|
await context.Response.WriteAsync(cachedResponse);
|
|
return;
|
|
}
|
|
|
|
// 捕获原始响应流
|
|
var originalResponseStream = context.Response.Body;
|
|
|
|
try
|
|
{
|
|
using (var memoryStream = new MemoryStream())
|
|
{
|
|
context.Response.Body = memoryStream;
|
|
|
|
// 调用下一个中间件
|
|
await _next(context);
|
|
|
|
// 将响应流重新定位到起始位置
|
|
memoryStream.Position = 0;
|
|
|
|
// 读取响应内容
|
|
var responseBody = await new StreamReader(memoryStream).ReadToEndAsync();
|
|
|
|
await _cache.StringSetAsync(cacheKey, responseBody, cacheAttribute.TimeSpan);
|
|
|
|
// 将内容写回原始响应流
|
|
memoryStream.Position = 0;
|
|
await memoryStream.CopyToAsync(originalResponseStream);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
context.Response.Body = originalResponseStream;
|
|
}
|
|
}
|
|
|
|
private RedisCacheAttribute GetCacheAttribute(HttpContext context)
|
|
{
|
|
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
|
|
if (endpoint == null) return null;
|
|
|
|
if (_attributeCache.TryGetValue(endpoint, out var cachedAttribute))
|
|
{
|
|
return cachedAttribute;
|
|
}
|
|
|
|
var attribute = endpoint.Metadata.GetMetadata<RedisCacheAttribute>();
|
|
if (attribute != null)
|
|
{
|
|
_attributeCache.TryAdd(endpoint, attribute);
|
|
}
|
|
|
|
return attribute;
|
|
}
|
|
|
|
private string GenerateCacheKey(HttpRequest request, AppConfig appConfig)
|
|
{
|
|
var appRequestInfo = new AppRequestConfig(request);
|
|
var cacheKey = $"cache:api:{request.Path.Value.Replace('/', '.').TrimStart('.')}:{appRequestInfo.Key}:{(string.IsNullOrEmpty(request.QueryString.Value) ? "default" : request.QueryString)}";
|
|
return cacheKey;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redis 缓存特性类
|
|
/// </summary>
|
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
|
public class RedisCacheAttribute : Attribute
|
|
{
|
|
public TimeSpan TimeSpan { get; }
|
|
public RedisCacheAttribute(int durationInSeconds) => TimeSpan = TimeSpan.FromSeconds(durationInSeconds);
|
|
public RedisCacheAttribute(int hours, int minutes, int seconds) => TimeSpan = new TimeSpan(hours, minutes, seconds);
|
|
public RedisCacheAttribute(int minutes, int seconds) => TimeSpan = new TimeSpan(0, minutes, seconds);
|
|
}
|
|
}
|