Files
zszq-trs/YLErpWeb/App/ExceptionMiddleware.cs
张名锐 2e75e62bbc feat(exception): 优化异常中间件的日志记录功能
- 添加了对 Swap Trade 编辑请求的特殊处理逻辑
- 实现了针对掉期交易的诊断功能,包括 JSON 解析和验证
- 添加了专门用于检测无效利率的诊断方法
- 优化了异常日志的输出格式和内容
- 集成了缓冲区管理和流位置重置功能
- 增强了错误信息的详细程度和可读性
2026-08-10 17:21:46 +08:00

208 lines
7.8 KiB
C#

using Microsoft.AspNetCore.Http;
using System.Buffers;
using System.Text;
using System.Text.Json;
namespace YLErp.Web.App
{
/// <summary>
/// 异常处理中间件
/// </summary>
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<byte> readOnlySequence)
{
// Separate method because Span/ReadOnlySpan cannot be used in async methods
ReadOnlySpan<byte> 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<string> 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<string>();
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<string> 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;
}
}
}