using MAX.Framework.Dtos; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.IO; using System; using System.Collections.Generic; using System.Text; using System.Text.Json; namespace MAX.Framework.Commons { /// /// 统一状态码中间件 /// 统一返回200 固定格式 /// internal class UnifyStatusCodeMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; // 静态复用,全局唯一的内存池管理器 private static readonly RecyclableMemoryStreamManager _streamManager = new(); // 需要直接透传、不做缓冲的 Content-Type 前缀 private static readonly string[] _passthroughPrefixes = { "image/", "audio/", "video/", "application/octet-stream", "application/zip", "application/pdf", "application/x-msdownload", "text/event-stream" }; public UnifyStatusCodeMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { // 1. 只处理 /api 路径(按需调整或删除) if (!context.Request.Path.StartsWithSegments("/api")) { await _next(context); return; } // 2. 二进制 / 流式响应:不缓冲,直接透传 var accept = context.Request.Headers.Accept.ToString(); if (IsPassthroughByRequest(context) || IsPassthroughByAccept(accept)) { await _next(context); return; } var originalBody = context.Response.Body; using var buffer = _streamManager.GetStream(); context.Response.Body = buffer; try { await _next(context); } catch (Exception ex) { // 兜底:Filter 没抓到的异常 _logger.LogError(ex, "未处理异常: {Path}", context.Request.Path); // 清空已写入的内容,准备返回 500 统一格式 buffer.SetLength(0); context.Response.StatusCode = StatusCodes.Status500InternalServerError; } var statusCode = context.Response.StatusCode; var contentType = context.Response.ContentType ?? string.Empty; // 3. 响应体如果本身就是二进制/流式,透传不处理 if (IsPassthroughByResponseContentType(contentType)) { buffer.Position = 0; context.Response.Body = originalBody; await buffer.CopyToAsync(originalBody); return; } // 4. 2xx 且已有响应体:透传(已被 UnifyResultFilter 包装过,不再包一层) if (statusCode >= 200 && statusCode < 300 && buffer.Length > 0) { buffer.Position = 0; context.Response.Body = originalBody; await buffer.CopyToAsync(originalBody); return; } // 5. 非 2xx:统一改写为 200 + ApiResult var (code, msg) = MapStatusCode(statusCode); // 如果是 2xx 但响应体为空(少见,例如 204),也当作成功 if (statusCode >= 200 && statusCode < 300) { code = (int)ResultCode.Success; msg = "success"; } var result = statusCode >= 200 && statusCode < 300 ? ApiResult.Ok() : ApiResult.Fail(msg, code); // 恢复原始流并写出 context.Response.Body = originalBody; context.Response.StatusCode = StatusCodes.Status200OK; context.Response.ContentType = "application/json; charset=utf-8"; context.Response.ContentLength = null; // 清理可能存在的旧响应头 context.Response.Headers.Remove("WWW-Authenticate"); await context.Response.WriteAsJsonAsync(result, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } private static bool IsPassthroughByRequest(HttpContext context) { // 上传大文件等场景,请求体大,避免缓冲 var contentLength = context.Request.ContentLength; if (contentLength.HasValue && contentLength.Value > 1_000_000) // >1MB { return true; } // Range 请求(断点续传)不缓冲 if (context.Request.Headers.ContainsKey("Range")) { return true; } return false; } private static bool IsPassthroughByAccept(string accept) { if (string.IsNullOrEmpty(accept)) return false; foreach (var prefix in _passthroughPrefixes) { if (accept.Contains(prefix, StringComparison.OrdinalIgnoreCase)) return true; } return false; } private static bool IsPassthroughByResponseContentType(string contentType) { if (string.IsNullOrEmpty(contentType)) return false; foreach (var prefix in _passthroughPrefixes) { if (contentType.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return true; } return false; } private static (int code, string msg) MapStatusCode(int statusCode) => statusCode switch { 400 => ((int)ResultCode.ValidationError, "请求参数错误"), 401 => ((int)ResultCode.Unauthorized, "未授权,请先登录"), 403 => ((int)ResultCode.Forbidden, "禁止访问"), 404 => ((int)ResultCode.NotFound, "资源不存在"), 405 => ((int)ResultCode.Fail, "请求方法不允许"), 429 => ((int)ResultCode.Fail, "请求过于频繁"), 500 => ((int)ResultCode.ServerError, "服务器内部错误"), 502 => ((int)ResultCode.ServerError, "网关错误"), 503 => ((int)ResultCode.ServerError, "服务暂不可用"), 504 => ((int)ResultCode.ServerError, "网关超时"), _ => ((int)ResultCode.Fail, $"请求失败({statusCode})") }; } }