| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- using System.Reflection;
- using Microsoft.AspNetCore.Http;
- using Microsoft.AspNetCore.Mvc.Filters;
- using Microsoft.Extensions.Logging;
- namespace Admin.NET.Plugin.AiDOP.Infrastructure;
- public sealed class AdoS0ApiErrorResponse
- {
- public string Code { get; init; } = string.Empty;
- public string Message { get; init; } = string.Empty;
- }
- public static class AdoS0ApiErrors
- {
- private static readonly (string Prefix, string Code, string Message)[] DuplicateCodeMappings =
- [
- ("/api/s0/sales/customers", AdoS0ErrorCodes.CustomerCodeExists, "客户编码已存在"),
- ("/api/s0/sales/materials", AdoS0ErrorCodes.MaterialCodeExists, "物料编码已存在"),
- ("/api/s0/supply/suppliers", AdoS0ErrorCodes.SupplierCodeExists, "供应商编码已存在"),
- ("/api/s0/manufacturing/standard-operations", AdoS0ErrorCodes.DuplicateCode, "标准工序名称已存在"),
- ("/api/s0/manufacturing/production-lines", AdoS0ErrorCodes.DuplicateCode, "生产线编码已存在"),
- ("/api/s0/manufacturing/routings", AdoS0ErrorCodes.DuplicateCode, "工艺路线编码已存在"),
- ("/api/s0/manufacturing/work-order-controls", AdoS0ErrorCodes.DuplicateCode, "工单控制参数编码已存在"),
- ("/api/s0/manufacturing/person-skills", AdoS0ErrorCodes.DuplicateCode, "人员技能编码已存在"),
- ("/api/s0/manufacturing/line-posts", AdoS0ErrorCodes.DuplicateCode, "线体岗位编码已存在"),
- ("/api/s0/manufacturing/work-centers", AdoS0ErrorCodes.DuplicateCode, "工作中心编码已存在"),
- ("/api/s0/manufacturing/preprocess-elements", AdoS0ErrorCodes.DuplicateCode, "前处理要素编码已存在"),
- ("/api/s0/manufacturing/sop-file-types", AdoS0ErrorCodes.DuplicateCode, "SOP 文件类型编码已存在"),
- ("/api/s0/manufacturing/sop-documents", AdoS0ErrorCodes.DuplicateCode, "SOP 文档编码已存在"),
- ("/api/s0/warehouse/departments", AdoS0ErrorCodes.DepartmentCodeExists, "部门编码已存在"),
- ("/api/s0/warehouse/employees", AdoS0ErrorCodes.EmployeeCodeExists, "员工编码已存在"),
- ("/api/s0/warehouse/cost-centers", AdoS0ErrorCodes.CostCtrCodeExists, "成本中心编码已存在"),
- ("/api/s0/warehouse/locations", AdoS0ErrorCodes.LocationCodeExists, "库位编码已存在"),
- ("/api/s0/warehouse/location-shelves", AdoS0ErrorCodes.LocationShelfCodeExists, "货架编码已存在"),
- ("/api/s0/warehouse/barcode-rules", AdoS0ErrorCodes.BarcodeRuleCodeExists, "条码规则已存在"),
- ("/api/s0/warehouse/label-types", AdoS0ErrorCodes.LabelTypeCodeExists, "标签类型编码已存在"),
- ("/api/s0/warehouse/nbr-types", AdoS0ErrorCodes.NbrTypeCodeExists, "单号类型编码已存在"),
- ("/api/s0/warehouse/nbr-controls", AdoS0ErrorCodes.NbrControlCodeExists, "单号规则编码已存在"),
- ("/api/s0/warehouse/item-packs", AdoS0ErrorCodes.ItemPackCodeExists, "物料包装规格已存在"),
- ("/api/s0/warehouse/emp-work-duties", AdoS0ErrorCodes.DuplicateCode, "物料职责记录已存在"),
- ("/api/s0/warehouse/task-assignments", AdoS0ErrorCodes.TaskAssignmentCodeExists, "任务指派记录已存在")
- ];
- public static bool IsS0Request(PathString path) =>
- path.Value?.StartsWith("/api/s0/", StringComparison.OrdinalIgnoreCase) == true;
- public static ObjectResult InvalidRequest(string message) =>
- Create(StatusCodes.Status400BadRequest, AdoS0ErrorCodes.InvalidRequest, string.IsNullOrWhiteSpace(message) ? "请求参数非法" : message);
- public static ObjectResult InvalidReference(string code, string message) =>
- Create(StatusCodes.Status400BadRequest, code, message);
- public static ObjectResult Conflict(string code, string message) =>
- Create(StatusCodes.Status409Conflict, code, message);
- public static ObjectResult NotFound(string message = "记录不存在") =>
- Create(StatusCodes.Status404NotFound, AdoS0ErrorCodes.RecordNotFound, message);
- public static ObjectResult InternalServerError(string message = "系统繁忙,请稍后再试") =>
- Create(StatusCodes.Status500InternalServerError, AdoS0ErrorCodes.InternalServerError, message);
- public static IActionResult WrapResult(IActionResult result)
- {
- if (result is ObjectResult { Value: AdoS0ApiErrorResponse })
- return result;
- return result switch
- {
- NotFoundResult => NotFound(),
- NotFoundObjectResult notFound => NotFound(ExtractMessage(notFound.Value) ?? "记录不存在"),
- BadRequestObjectResult badRequest => WrapBadRequest(badRequest),
- ObjectResult { StatusCode: StatusCodes.Status400BadRequest } objectResult => WrapBadRequest(objectResult),
- _ => result
- };
- }
- public static bool TryMapUnhandledException(PathString path, Exception exception, out ObjectResult? result)
- {
- result = null;
- if (!IsS0Request(path))
- return false;
- if (ContainsDuplicateKey(exception))
- {
- var mapping = ResolveDuplicateCode(path);
- result = Conflict(mapping.Code, mapping.Message);
- return true;
- }
- result = InternalServerError();
- return true;
- }
- private static ObjectResult WrapBadRequest(ObjectResult result)
- {
- var message = ExtractMessage(result.Value) ?? "请求参数非法";
- var mapping = ResolveBadRequest(message);
- return Create(StatusCodes.Status400BadRequest, mapping.Code, mapping.Message);
- }
- private static (string Code, string Message) ResolveBadRequest(string message)
- {
- var normalized = message.Trim();
- return normalized switch
- {
- "BOM 至少包含一行子项" => (AdoS0ErrorCodes.BomItemRequired, normalized),
- "同一 BOM 下子项物料不能重复" => (AdoS0ErrorCodes.BomItemDuplicate, normalized),
- "父项物料不能同时作为本子项出现" => (AdoS0ErrorCodes.BomParentConflict, normalized),
- "数量分母不能为 0" => (AdoS0ErrorCodes.BomQtyDenominatorZero, normalized),
- "工艺路线至少包含一道工序" => (AdoS0ErrorCodes.RoutingOperationRequired, normalized),
- "存在无效的标准工序引用" => (AdoS0ErrorCodes.StandardOperationReferenceInvalid, normalized),
- "物料主数据引用无效" => (AdoS0ErrorCodes.MaterialReferenceInvalid, normalized),
- "前处理要素引用无效" => (AdoS0ErrorCodes.PreprocessElementReferenceInvalid, normalized),
- "生产要素参数引用无效" => (AdoS0ErrorCodes.ElementParamReferenceInvalid, normalized),
- "存在无效的生产要素参数引用" => (AdoS0ErrorCodes.ElementParamReferenceInvalid, normalized),
- "存在无效的人员技能主数据引用" => (AdoS0ErrorCodes.PersonSkillReferenceInvalid, normalized),
- _ when normalized.Contains("引用无效", StringComparison.Ordinal) =>
- (AdoS0ErrorCodes.InvalidReference, normalized),
- _ => (AdoS0ErrorCodes.InvalidRequest, normalized)
- };
- }
- private static (string Code, string Message) ResolveDuplicateCode(PathString path)
- {
- var requestPath = path.Value ?? string.Empty;
- foreach (var mapping in DuplicateCodeMappings)
- {
- if (requestPath.StartsWith(mapping.Prefix, StringComparison.OrdinalIgnoreCase))
- return (mapping.Code, mapping.Message);
- }
- return (AdoS0ErrorCodes.DuplicateCode, "编码已存在");
- }
- private static string? ExtractMessage(object? value)
- {
- if (value == null) return null;
- if (value is string text) return text;
- if (value is ValidationProblemDetails validationProblem)
- return FlattenErrors(validationProblem.Errors);
- if (value is ProblemDetails problemDetails)
- return problemDetails.Detail ?? problemDetails.Title;
- if (value is IDictionary<string, string[]> stringArrayDictionary)
- return FlattenErrors(stringArrayDictionary);
- if (value is IDictionary<string, object?> objectDictionary)
- {
- foreach (var item in objectDictionary)
- {
- if (item.Key.Equals("message", StringComparison.OrdinalIgnoreCase))
- return item.Value?.ToString();
- }
- }
- var messageProperty = value.GetType().GetProperty("message", BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
- return messageProperty?.GetValue(value)?.ToString();
- }
- private static string FlattenErrors(IDictionary<string, string[]> errors)
- {
- var parts = errors
- .SelectMany(pair => pair.Value.Select(message => string.IsNullOrWhiteSpace(pair.Key) ? message : $"{pair.Key}: {message}"))
- .Where(message => !string.IsNullOrWhiteSpace(message))
- .ToList();
- return parts.Count == 0 ? "请求参数非法" : string.Join(";", parts);
- }
- private static bool ContainsDuplicateKey(Exception exception)
- {
- for (var current = exception; current != null; current = current.InnerException)
- {
- var message = current.Message;
- if (string.IsNullOrWhiteSpace(message))
- continue;
- if (message.Contains("Duplicate entry", StringComparison.OrdinalIgnoreCase) ||
- message.Contains("UNIQUE constraint failed", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
- return false;
- }
- private static ObjectResult Create(int statusCode, string code, string message)
- {
- return new ObjectResult(new AdoS0ApiErrorResponse
- {
- Code = code,
- Message = message
- })
- {
- StatusCode = statusCode
- };
- }
- }
- public sealed class AdoS0ResultFilter : IAsyncAlwaysRunResultFilter
- {
- public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
- {
- if (AdoS0ApiErrors.IsS0Request(context.HttpContext.Request.Path))
- context.Result = AdoS0ApiErrors.WrapResult(context.Result);
- await next();
- }
- }
- public sealed class AdoS0ExceptionFilter : IAsyncExceptionFilter
- {
- private readonly ILogger<AdoS0ExceptionFilter> _logger;
- public AdoS0ExceptionFilter(ILogger<AdoS0ExceptionFilter> logger)
- {
- _logger = logger;
- }
- public Task OnExceptionAsync(ExceptionContext context)
- {
- if (!AdoS0ApiErrors.TryMapUnhandledException(context.HttpContext.Request.Path, context.Exception, out var result) || result == null)
- return Task.CompletedTask;
- if (result.StatusCode == StatusCodes.Status409Conflict)
- _logger.LogWarning(context.Exception, "S0 duplicate conflict: {Path}", context.HttpContext.Request.Path);
- else
- _logger.LogError(context.Exception, "S0 unhandled exception: {Path}", context.HttpContext.Request.Path);
- context.Result = result;
- context.ExceptionHandled = true;
- return Task.CompletedTask;
- }
- }
|