using Microsoft.AspNetCore.Http; using System.Buffers; using System.Text; using System.Text.Json; namespace YLErp.Web.App { /// /// 异常处理中间件 /// internal class ExceptionMiddleware { private readonly RequestDelegate _next; public ExceptionMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { if (IsSwapTradeEditRequest(context.Request)) { context.Request.EnableBuffering(); } try { await _next.Invoke(context); } catch (Exception ex) { await HandleExceptionMessageAsync(context, ex); } } private static async Task HandleExceptionMessageAsync(HttpContext context, Exception exception) { var message = string.Empty; var serviceExpcetion = exception as ServiceException; var request = context.Request; try { message = GetInnerExceptionMessage(exception); if (serviceExpcetion == null || serviceExpcetion.IsFaultError) { if (IsSwapTradeEditRequest(request)) { var diagnostic = await GetSwapIntervalDiagnosticAsync(request); LogFactory.GetLogger(context.Request.Path.Value).Error(serviceExpcetion ?? exception, $"[query]:{request.QueryString.Value};{diagnostic}"); } else { var result = await request.BodyReader.ReadAsync(); var reqBody = ConvertBufferToString(result.Buffer); LogFactory.GetLogger(context.Request.Path.Value).Error(serviceExpcetion ?? exception, $"[query]:{request.QueryString.Value};[body]:{reqBody}"); } } } catch (Exception ex) { message += "\r\n\r\n" + ex.Message; } if (request.Path.StartsWithSegments("/m/api", StringComparison.OrdinalIgnoreCase) || request.Headers["X-Requested-With"] == "XMLHttpRequest" && !request.Headers["Accept"].Contains("text/html")) { await context.Response.WriteAsJsonAsync(new { code = -1, success = false, msg = message, Data = serviceExpcetion?.Tag }); } else { context.Response.ContentType = "text/html"; var resultHtml = File.ReadAllText(Server.MapPath("Statics/html/ShowError.html")); resultHtml = resultHtml.Replace("{{ErrorMsg}}", message); await context.Response.WriteAsync(resultHtml); } } private static string ConvertBufferToString(in ReadOnlySequence readOnlySequence) { // Separate method because Span/ReadOnlySpan cannot be used in async methods ReadOnlySpan span = readOnlySequence.IsSingleSegment ? readOnlySequence.First.Span : readOnlySequence.ToArray().AsSpan(); return System.Text.Encoding.UTF8.GetString(span); } private static bool IsSwapTradeEditRequest(HttpRequest request) { return string.Equals(request.Path.Value, "/swaptrade2/tradeEditJson", StringComparison.OrdinalIgnoreCase); } private static async Task GetSwapIntervalDiagnosticAsync(HttpRequest request) { if (!request.Body.CanSeek) { return "[swap-interval-diagnostic]:request-body-unavailable"; } request.Body.Position = 0; using var reader = new StreamReader(request.Body, Encoding.UTF8, false, 1024, leaveOpen: true); var requestBody = await reader.ReadToEndAsync(); request.Body.Position = 0; if (string.IsNullOrWhiteSpace(requestBody)) { return "[swap-interval-diagnostic]:request-body-empty"; } try { using var document = JsonDocument.Parse(requestBody); if (!document.RootElement.TryGetProperty("swap_positions", out var positions) || positions.ValueKind != JsonValueKind.Array) { return "[swap-interval-diagnostic]:swap_positions-missing"; } var invalidRates = new List(); var positionIndex = 0; foreach (var position in positions.EnumerateArray()) { var positionId = position.TryGetProperty("id", out var id) ? id.ToString() : "missing"; AddInvalidRateDiagnostics(position, "SwapIntervalList", false, positionIndex, positionId, invalidRates); AddInvalidRateDiagnostics(position, "InterestSwapInterval", true, positionIndex, positionId, invalidRates); if (position.TryGetProperty("Obervation", out var observation)) { AddInvalidRateDiagnostics(observation, "Obervation.ObservationInterval", true, positionIndex, positionId, invalidRates); } if (invalidRates.Count >= 10) { break; } positionIndex++; } return invalidRates.Count == 0 ? "[swap-interval-diagnostic]:no-invalid-rate-in-payload" : $"[swap-interval-diagnostic]:{string.Join(";", invalidRates)}"; } catch (JsonException) { return "[swap-interval-diagnostic]:request-json-invalid"; } } private static void AddInvalidRateDiagnostics(JsonElement position, string source, bool serializedJson, int positionIndex, string positionId, List invalidRates) { if (!position.TryGetProperty(source, out var intervals)) { return; } if (serializedJson) { if (intervals.ValueKind != JsonValueKind.String) { return; } try { using var document = JsonDocument.Parse(intervals.GetString()); intervals = document.RootElement.Clone(); } catch (JsonException) { invalidRates.Add($"positionIndex={positionIndex},positionId={positionId},source={source},interval-json-invalid"); return; } } if (intervals.ValueKind != JsonValueKind.Array) { return; } var intervalIndex = 0; foreach (var interval in intervals.EnumerateArray()) { if ((!interval.TryGetProperty("Rate", out var rate) || rate.ValueKind == JsonValueKind.Null) && invalidRates.Count < 10) { invalidRates.Add($"positionIndex={positionIndex},positionId={positionId},source={source},intervalIndex={intervalIndex},rate={(rate.ValueKind == JsonValueKind.Null ? "null" : "missing")}"); } intervalIndex++; } } private static string GetInnerExceptionMessage(Exception ex) { var exceptionStr = ex.Message; while (ex.InnerException != null) { exceptionStr = ex.InnerException.Message; ex = ex.InnerException; } return exceptionStr; } } }