using System.Collections; using Fake.Application; using Fake.Application.Dtos; using Fake.AspNetCore.ExceptionHandling; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Oms.Application.Dtos.Orders; namespace SimpleAdmin.Api.ExceptionHandling; /// /// 在 Fake 默认异常转换之上,把草稿已落库失败的结构化信息写入 Details,并清理 msg 中的机器标记。 /// public class OmsException2ResponseConverter : IException2ResponseConverter { private static readonly JsonSerializerSettings JsonSettings = new() { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, }; private const string DraftSavedMarkerPrefix = " [[omsDraft:"; private readonly DefaultException2ResponseConverter _inner; public OmsException2ResponseConverter(DefaultException2ResponseConverter inner) { _inner = inner; } public ApplicationExceptionResponse Convert(Exception exception, FakeExceptionHandlingOptions options) { var response = _inner.Convert(exception, options); response.Message = StripDraftSavedMarker(response.Message); if (TryBuildDraftFailData(exception, out var failData)) { if (string.IsNullOrWhiteSpace(response.Message)) { response.Message = failData.Reason; } response.Details = JsonConvert.SerializeObject(failData, JsonSettings); } return response; } private static bool TryBuildDraftFailData(Exception exception, out OrderParcelDraftFailData failData) { failData = new OrderParcelDraftFailData(); if (exception is not BusinessException business) { return false; } if (business.Data[OrderParcelDraftFailData.DataKey] is string json && !string.IsNullOrWhiteSpace(json)) { try { var parsed = JsonConvert.DeserializeObject(json); if (parsed != null && parsed.DraftSaved && parsed.OrderId > 0) { failData = parsed; return true; } } catch { // fall through to key-based parse } } if (!HasDraftSavedFlag(business.Data)) { // 兼容旧客户端:仅从 message 尾部标记解析 return TryParseMarker(business.Message, out failData); } failData.DraftSaved = true; failData.OrderId = ToInt(business.Data["orderId"] ?? business.Data["OrderId"]); failData.ServiceNo = ToStringValue(business.Data["serviceNo"] ?? business.Data["ServiceNo"]); failData.Reason = StripDraftSavedMarker(business.Message); failData.ErrorCode = OrderParcelDraftFailData.ErrorCodeValue; return failData.OrderId > 0; } private static bool HasDraftSavedFlag(IDictionary data) { if (data.Contains("draftSaved") && IsTruthy(data["draftSaved"])) return true; if (data.Contains("DraftSaved") && IsTruthy(data["DraftSaved"])) return true; return false; } private static bool TryParseMarker(string? message, out OrderParcelDraftFailData failData) { failData = new OrderParcelDraftFailData(); if (string.IsNullOrEmpty(message)) return false; var idx = message.IndexOf(DraftSavedMarkerPrefix, StringComparison.Ordinal); if (idx < 0) return false; var jsonStart = idx + DraftSavedMarkerPrefix.Length; var jsonEnd = message.LastIndexOf("]]", StringComparison.Ordinal); if (jsonEnd <= jsonStart) return false; try { var parsed = JsonConvert.DeserializeObject(message[jsonStart..jsonEnd]); if (parsed == null || !parsed.DraftSaved || parsed.OrderId <= 0) return false; parsed.Reason = message[..idx].TrimEnd(); parsed.ErrorCode = OrderParcelDraftFailData.ErrorCodeValue; failData = parsed; return true; } catch { return false; } } private static string StripDraftSavedMarker(string? message) { if (string.IsNullOrEmpty(message)) return string.Empty; var idx = message.IndexOf(DraftSavedMarkerPrefix, StringComparison.Ordinal); return idx < 0 ? message : message[..idx].TrimEnd(); } private static bool IsTruthy(object? value) => value is true || (value is bool b && b) || string.Equals(global::System.Convert.ToString(value), "true", StringComparison.OrdinalIgnoreCase) || string.Equals(global::System.Convert.ToString(value), "1", StringComparison.OrdinalIgnoreCase); private static int ToInt(object? value) { if (value == null) return 0; return int.TryParse(global::System.Convert.ToString(value), out var n) ? n : 0; } private static string ToStringValue(object? value) => global::System.Convert.ToString(value)?.Trim() ?? string.Empty; }