本节介绍Util应用框架如何处理系统错误.概述系统在运行过程中可能发生错误.系统错误可以简单分为两类:系统异常系统本身出现的错误.业务异常不满足业务规则出现的错误.如何处理系统异常如果发生系统异常,大多数情况下,你除了记录异常日志外,可能无法处理它们.一个例外是并发异常.当发生并发异常,可以通过重试再次提交,有可能成功处理.另外一个问题,是否应该将系统异常消息返回给客户端?系统异常消息与技术相关,客户无法理解它们.而且系统异常消息可能包含敏感信息,返回给客户端可能更易受到攻击.如何处理业务异常业务异常表明没有满足某些业务规则,通常也无法自动处理.如果能够自动处理的业务异常,应定义专用异常类型.对于业务异常,除了记录异常日志外,还应把业务异常消息返回给客户端,以指示用户调整操作.基础用法.Net 使用异常 Exception 及派生异常来处理系统异常,但没有明确规定处理业务异常的类型.Warning 业务异常Util应用框架定义了 Util.Exceptions.Warning 异常类型,Warning 从 Exception 派生,代表业务异常.当你抛出 Exception 或派生异常类型时,异常消息仅在开发阶段返回给客户端.一旦发布到生产环境,系统异常消息将被屏蔽,客户端收到消息: 系统忙,请稍后再试 .throw new Exception( "未将对象引用设置到对象的实例" );
对于业务规则导致的错误,你需要抛出 Warning 异常.Warning 抛出的异常消息将返回到客户端,提示用户进行修改.throw new Warning( "必须填写姓名" );
GetMessage 工具方法Warning 除了代表业务异常外,还提供了一个静态工具方法 GetMessage.异常可能被其它异常包裹,要获得异常真正的消息,需要使用递归.Warning.GetMessage 工具方法传入异常实例,递归获取异常消息,var message = Warning.GetMessage( exception );
ConcurrencyException 并发异常Util应用框架定义了并发异常 Util.Exceptions.ConcurrencyException.不同的 .Net 组件抛出的并发异常类型可能不同, Util使用 ConcurrencyException 进行统一包装.可以通过重试的方式来解决并发异常.下面是Util应用框架Dapr集成事件增加计数时并发处理的代码片断.public virtual async Task IncrementAsync( CancellationToken cancellationToken = default ) { try { await Store.IncrementAsync( cancellationToken ); } catch ( ConcurrencyException ) { Log.LogDebug( "更新集成事件计数出现并发异常,即将重试" ); await IncrementAsync( cancellationToken ); } catch ( Exception exception ) { Log.LogError( exception, "更新集成事件计数失败" ); }}
全局错误日志记录Util应用框架使用 ErrorLogFilterAttribute 过滤器来记录全局错误日志.已在 Web Api控制器基类 WebApiControllerBase 设置 [ErrorLogFilter] 过滤器.全局异常处理Util应用框架使用 ExceptionHandlerAttribute 过滤器来处理全局异常.已在 Web Api控制器基类 WebApiControllerBase 设置 [ExceptionHandler] 过滤器.[ExceptionHandler] 过滤器对异常消息进行处理,只有 Warning 异常消息才会返回给客户端.源码解析Warning 业务异常Warning 代表业务异常,它的异常消息会返回给客户端.GetMessage 方法使用递归获取内部异常消息./// <summary>/// 应用程序异常/// </summary>public class Warning : Exception {/// <summary>/// 初始化应用程序异常/// </summary>/// <param name="exception">异常</param>public Warning( Exception exception ): this( null, exception ) {}/// <summary>/// 初始化应用程序异常/// </summary>/// <param name="message">错误消息</param>/// <param name="exception">异常</param>/// <param name="code">错误码</param>/// <param name="httpStatusCode">Http状态码</param>public Warning( string message, Exception exception = null, string code = null, int? httpStatusCode = null ): base( message ?? "", exception ) {Code = code;HttpStatusCode = httpStatusCode;IsLocalization = true;}/// <summary>/// 错误码/// </summary>public string Code { get; set; }/// <summary>/// Http状态码/// </summary>public int? HttpStatusCode { get; set; }/// <summary>/// 是否本地化异常消息/// </summary>public bool IsLocalization { get; set; }/// <summary>/// 获取错误消息/// </summary>/// <param name="isProduction">是否生产环境</param>public virtual string GetMessage( bool isProduction = false ) {return GetMessage( this );}/// <summary>/// 获取错误消息/// </summary>public static string GetMessage( Exception ex ) {var result = new StringBuilder();var list = GetExceptions( ex );foreach( var exception in list )AppendMessage( result, exception );return result.ToString().Trim( Environment.NewLine.ToCharArray() );}/// <summary>/// 添加异常消息/// </summary>private static void AppendMessage( StringBuilder result, Exception exception ) {if( exception == null )return;result.AppendLine( exception.Message );}/// <summary>/// 获取异常列表/// </summary>public IList<Exception> GetExceptions() {return GetExceptions( this );}/// <summary>/// 获取异常列表/// </summary>/// <param name="ex">异常</param>public static IList<Exception> GetExceptions( Exception ex ) {var result = new List<Exception>();AddException( result, ex );return result;}/// <summary>/// 添加内部异常/// </summary>private static void AddException( List<Exception> result, Exception exception ) {if( exception == null )return;result.Add( exception );AddException( result, exception.InnerException );}}
ConcurrencyException 并发异常ConcurrencyException 表示并发异常,统一包装其它组件产生的并发异常,并处理异常消息./// <summary>/// 并发异常/// </summary>public class ConcurrencyException : Warning { /// <summary> /// 消息 /// </summary> private readonly string _message; /// <summary> /// 初始化并发异常 /// </summary> public ConcurrencyException() : this( "" ) { } /// <summary> /// 初始化并发异常 /// </summary> /// <param name="exception">异常</param> public ConcurrencyException( Exception exception ) : this( "", exception ) { } /// <summary> /// 初始化并发异常 /// </summary> /// <param name="message">错误消息</param> /// <param name="exception">异常</param> /// <param name="code">错误码</param> /// <param name="httpStatusCode">Http状态码</param> public ConcurrencyException( string message, Exception exception = null, string code = null, int? httpStatusCode = null ) : base( message, exception, code, httpStatusCode ) { _message = message; } /// <inheritdoc /> public override string Message => $"{R.ConcurrencyExceptionMessage}.{_message}"; /// <inheritdoc /> public override string GetMessage( bool isProduction = false ) { if( isProduction ) return R.ConcurrencyExceptionMessage; return GetMessage(this); }}
ErrorLogFilterAttribute 错误日志过滤器[ErrorLogFilter] 错误日志过滤器记录全局异常日志./// <summary>/// 错误日志过滤器/// </summary>public class ErrorLogFilterAttribute : ExceptionFilterAttribute { /// <summary> /// 异常处理 /// </summary> public override void OnException( ExceptionContext context ) { if( context == null ) return; var log = context.HttpContext.RequestServices.GetService<ILogger<ErrorLogFilterAttribute>>(); var exception = context.Exception.GetRawException(); if( exception is Warning warning ) { log.LogWarning( warning, exception.Message ); return; } log.LogError( exception, exception.Message ); }}
ExceptionHandlerAttribute 异常处理过滤器[ExceptionHandler] 过滤器处理全局异常.Exception 的扩展方法 GetPrompt 获取客户端友好的异常消息.对于生产环境, Exception 异常消息将被替换为 系统忙,请稍后再试.[ExceptionHandler] 过滤器还对异常消息的本地化进行了处理./// <summary>/// 异常处理过滤器/// </summary>public class ExceptionHandlerAttribute : ExceptionFilterAttribute { /// <summary> /// 异常处理 /// </summary> public override void OnException( ExceptionContext context ) { context.ExceptionHandled = true; var message = context.Exception.GetPrompt( Web.Environment.IsProduction() ); message = GetLocalizedMessages( context, message ); var errorCode = context.Exception.GetErrorCode() ?? StateCode.Fail; var httpStatusCode = context.Exception.GetHttpStatusCode() ?? 200; context.Result = GetResult( context, errorCode, message, httpStatusCode ); } /// <summary> /// 获取本地化异常消息 /// </summary> protected virtual string GetLocalizedMessages( ExceptionContext context, string message ) { var exception = context.Exception.GetRawException(); if ( exception is Warning { IsLocalization: false } ) return message; var stringLocalizerFactory = context.HttpContext.RequestServices.GetService<IStringLocalizerFactory>(); if ( stringLocalizerFactory == null ) return message; var stringLocalizer = stringLocalizerFactory.Create( "Warning",null ); var localizedString = stringLocalizer[message]; if ( localizedString.ResourceNotFound == false ) return localizedString.Value; stringLocalizer = context.HttpContext.RequestServices.GetService<IStringLocalizer>(); if ( stringLocalizer == null ) return message; return stringLocalizer[message]; } /// <summary> /// 获取结果 /// </summary> protected virtual IActionResult GetResult( ExceptionContext context, string code, string message, int? httpStatusCode ) { var options = GetJsonSerializerOptions( context ); var resultFactory = context.HttpContext.RequestServices.GetService<IResultFactory>(); if ( resultFactory == null ) return new Result( code, message, null, httpStatusCode, options ); return resultFactory.CreateResult( code, message, null, httpStatusCode, options ); } /// <summary> /// 获取Json序列化配置 /// </summary> private JsonSerializerOptions GetJsonSerializerOptions( ExceptionContext context ) { var factory = context.HttpContext.RequestServices.GetService<IJsonSerializerOptionsFactory>(); if( factory != null ) return factory.CreateOptions(); return new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Encoder = JavaScriptEncoder.Create( UnicodeRanges.All ), DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Converters = { new DateTimeJsonConverter(), new NullableDateTimeJsonConverter() } }; }}/// <summary>/// 异常扩展/// </summary>public static class ExceptionExtensions { /// <summary> /// 获取异常提示 /// </summary> /// <param name="exception">异常</param> /// <param name="isProduction">是否生产环境</param> public static string GetPrompt( this Exception exception, bool isProduction = false ) { if( exception == null ) return null; exception = exception.GetRawException(); if( exception == null ) return null; if( exception is Warning warning ) return warning.GetMessage( isProduction ); return isProduction ? R.SystemError : exception.Message; } /// <summary> /// 获取Http状态码 /// </summary> /// <param name="exception">异常</param> public static int? GetHttpStatusCode( this Exception exception ) { if ( exception == null ) return null; exception = exception.GetRawException(); if ( exception == null ) return null; if ( exception is Warning warning ) return warning.HttpStatusCode; return null; } /// <summary> /// 获取错误码 /// </summary> /// <param name="exception">异常</param> public static string GetErrorCode( this Exception exception ) { if ( exception == null ) return null; exception = exception.GetRawException(); if ( exception == null ) return null; if ( exception is Warning warning ) return warning.Code; return null; }}
(图片来源网络,侵删)
异常处理Util应用框架异常处理方法
0 评论