using NetAdmin.Host.Utils; namespace NetAdmin.Host.Middlewares; /// /// 请求审计中间件 /// /// /// 放在所有中间件最前面 /// public sealed class RequestAuditMiddleware( RequestDelegate next , IOptions dynamicApiControllerSettingsOptions , RequestLogger requestLogger) { private readonly PathString _defaultRoutePrefix = new($"/{dynamicApiControllerSettingsOptions.Value.DefaultRoutePrefix}"); private readonly PathString _healthCheckRoutePrefix = new($"/{dynamicApiControllerSettingsOptions.Value.DefaultRoutePrefix}/health/check"); /// /// 主函数 /// public async Task InvokeAsync(HttpContext context) { // 跳过处理的情况: if (!context.Request.Path.StartsWithSegments(_defaultRoutePrefix) // 非api请求 || context.Request.Path.StartsWithSegments(_healthCheckRoutePrefix) // 健康检查 || context.Request.Method == Chars.FLG_HTTP_METHOD_OPTIONS) { // is options 请求 await next(context).ConfigureAwait(false); return; } // Response.Body流默认是不可读的,将Response.Body流替换成MemoryStream 使其可读 await using var ms = new MemoryStream(); var stream = context.Response.Body; context.Response.Body = ms; // 在控制台上输出分割线,区分不同请求 // 调用下一个中间件 var sw = Stopwatch.StartNew(); await next(context).ConfigureAwait(false); sw.Stop(); _ = ms.Seek(0, SeekOrigin.Begin); using var sr = new StreamReader(ms); var responseBody = await sr.ReadToEndAsync().ConfigureAwait(false); _ = ms.Seek(0, SeekOrigin.Begin); await ms.CopyToAsync(stream).ConfigureAwait(false); context.Response.Body = stream; var exception = context.Features.Get(); var errorCode = context.Response.Headers[nameof(ErrorCodes)] // .FirstOrDefault() ?.Enum() ?? 0; _ = await requestLogger.LogAsync(context, (long)sw.Elapsed.TotalMicroseconds, responseBody, errorCode , exception) .ConfigureAwait(false); } }