using LiveForum.Code.AttributeExtend; using LiveForum.Code.Base; using LiveForum.Code.Redis.Contract; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LiveForum.Code.MiddlewareExtend { /// /// 响应缓存中间件 /// 通过特性标记需要缓存的Action,使用Redis进行缓存 /// public class ResponseCacheMiddleware { private readonly RequestDelegate _next; private readonly IRedisService _redisService; private readonly ILogger _logger; private const string CACHE_KEY_PREFIX = "cache:api:"; public ResponseCacheMiddleware( RequestDelegate next, IRedisService redisService, ILogger logger) { _next = next; _redisService = redisService; _logger = logger; } public async Task Invoke(HttpContext context) { // 1. 仅处理GET请求 if (!string.Equals(context.Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { await _next(context); return; } // 2. 获取路由信息(从路径解析) var path = context.Request.Path.Value ?? ""; // 路径格式:/api/{Controller}/{Action} if (!path.StartsWith("/api/", StringComparison.OrdinalIgnoreCase)) { await _next(context); return; } var pathParts = path.TrimStart('/').Split('/', StringSplitOptions.RemoveEmptyEntries); if (pathParts.Length < 3) { await _next(context); return; } var controller = pathParts[1]; // api/{controller}/... var action = pathParts[2]; // api/{controller}/{action} // 移除Controller后缀(如果有) if (controller.EndsWith("controller", StringComparison.OrdinalIgnoreCase)) { controller = controller.Substring(0, controller.Length - 10); } // 3. 尝试获取Action的ResponseCacheAttribute特性 var cacheAttribute = await GetResponseCacheAttributeAsync(context, controller, action); if (cacheAttribute == null || !cacheAttribute.Enabled) { // 没有缓存特性或已禁用,继续执行 await _next(context); return; } // 4. 生成缓存Key var cacheKey = GenerateCacheKey(controller, action, context.Request.Query, cacheAttribute); try { // 5. 查询Redis缓存 var cachedResponse = await _redisService.GetAsync(cacheKey); if (!string.IsNullOrEmpty(cachedResponse)) { // 缓存命中,直接返回 context.Response.ContentType = "application/json; charset=utf-8"; context.Response.StatusCode = StatusCodes.Status200OK; await context.Response.WriteAsync(cachedResponse, Encoding.UTF8); _logger.LogInformation("[ResponseCache] 缓存命中。Key: {CacheKey}", cacheKey); return; } // 6. 缓存未命中,启用响应缓冲以便捕获响应 var originalBodyStream = context.Response.Body; using (var responseBody = new MemoryStream()) { context.Response.Body = responseBody; // 继续执行管道 await _next(context); // 7. 读取响应内容 responseBody.Seek(0, SeekOrigin.Begin); var responseBodyText = await new StreamReader(responseBody).ReadToEndAsync(); // 将响应写回原始流 responseBody.Seek(0, SeekOrigin.Begin); await responseBody.CopyToAsync(originalBodyStream); // 8. 检查响应状态码和业务代码 if (context.Response.StatusCode == StatusCodes.Status200OK && !string.IsNullOrEmpty(responseBodyText)) { try { // 解析JSON响应,检查Code字段 var responseJson = JObject.Parse(responseBodyText); var codeValue = responseJson["code"]?.Value(); // 只有Code=0(Success)才缓存 if (codeValue.HasValue && codeValue.Value == (int)ResponseCode.Success) { // 9. 写入Redis缓存(添加1-100秒随机数,防止缓存同时失效) var random = new Random(); var randomSeconds = random.Next(1, 101); // 1-100秒随机数 var actualDuration = cacheAttribute.Duration + randomSeconds; await _redisService.SetAsync(cacheKey, responseBodyText, TimeSpan.FromSeconds(actualDuration)); _logger.LogInformation( "[ResponseCache] 响应已缓存。Key: {CacheKey}, 基础Duration: {Duration}秒, 随机数: {RandomSeconds}秒, 实际Duration: {ActualDuration}秒", cacheKey, cacheAttribute.Duration, randomSeconds, actualDuration); } else { _logger.LogInformation( "[ResponseCache] 响应Code不为0,不缓存。Key: {CacheKey}, Code: {Code}", cacheKey, codeValue); } } catch (JsonException ex) { _logger.LogWarning(ex, "[ResponseCache] 解析响应JSON失败,不缓存。Key: {CacheKey}", cacheKey); } } } } catch (Exception ex) { _logger.LogError(ex, "[ResponseCache] 缓存处理异常。Key: {CacheKey}", cacheKey); // 发生异常时继续正常流程 } } /// /// 获取Action的ResponseCacheAttribute特性 /// private async Task GetResponseCacheAttributeAsync( HttpContext context, string controller, string action) { try { // 通过Endpoint获取Action描述符 var endpoint = context.GetEndpoint(); if (endpoint?.Metadata != null) { var attribute = endpoint.Metadata .OfType() .FirstOrDefault(); if (attribute != null) { return attribute; } } // 如果Endpoint中没有,尝试通过反射获取(备用方案) var controllerType = GetControllerType(controller); if (controllerType != null) { var methodInfo = controllerType.GetMethod(action, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase); if (methodInfo != null) { var attr = methodInfo.GetCustomAttributes(typeof(ResponseCacheExtendAttribute), false) .FirstOrDefault() as ResponseCacheExtendAttribute; if (attr != null) { return attr; } } } } catch (Exception ex) { _logger.LogWarning(ex, "[ResponseCache] 获取ResponseCacheAttribute特性失败。Controller: {Controller}, Action: {Action}", controller, action); } return null; } /// /// 获取Controller类型(通过反射) /// private Type GetControllerType(string controllerName) { try { // 尝试查找Controller类型 var controllerFullName = $"LiveForum.WebApi.Controllers.{controllerName}Controller"; var controllerType = Type.GetType(controllerFullName); if (controllerType == null) { // 尝试从所有已加载的程序集中查找 foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { controllerType = assembly.GetType(controllerFullName); if (controllerType != null) break; } } return controllerType; } catch { return null; } } /// /// 生成缓存Key /// private string GenerateCacheKey( string controller, string action, IQueryCollection query, ResponseCacheExtendAttribute attribute) { // 如果指定了自定义前缀,使用自定义前缀 if (!string.IsNullOrWhiteSpace(attribute.CacheKeyPrefix)) { var baseKey = attribute.CacheKeyPrefix; // 如果有VaryByQueryKeys,追加参数 if (attribute.VaryByQueryKeys != null && attribute.VaryByQueryKeys.Length > 0) { var paramPairs = attribute.VaryByQueryKeys .Where(key => query.ContainsKey(key)) .OrderBy(key => key) // 排序确保一致性 .Select(key => $"{key}={query[key]}") .ToList(); if (paramPairs.Any()) { baseKey += ":" + string.Join("&", paramPairs); } } return $"{CACHE_KEY_PREFIX}{baseKey}"; } // 使用默认格式:cache:api:{Controller}:{Action} var key = $"{CACHE_KEY_PREFIX}{controller}:{action}"; // 如果有VaryByQueryKeys,追加参数 if (attribute.VaryByQueryKeys != null && attribute.VaryByQueryKeys.Length > 0) { var paramPairs = attribute.VaryByQueryKeys .Where(key => query.ContainsKey(key)) .OrderBy(key => key) // 排序确保一致性 .Select(key => $"{key}={query[key]}") .ToList(); if (paramPairs.Any()) { key += ":" + string.Join("&", paramPairs); } } return key; } } }