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;
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.Success()
: 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
});
buffer.Dispose();
}
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})")
};
}
}