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;
}