• Util应用框架基础(五) - 异常处理


    本节介绍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 方法使用递归获取内部异常消息.

    /// 
    /// 应用程序异常
    /// 
    public class Warning : Exception {
        /// 
        /// 初始化应用程序异常
        /// 
        /// 异常
        public Warning( Exception exception )
            : this( null, exception ) {
        }
    
        /// 
        /// 初始化应用程序异常
        /// 
        /// 错误消息
        /// 异常
        /// 错误码
        /// Http状态码
        public Warning( string message, Exception exception = null, string code = null, int? httpStatusCode = null )
            : base( message ?? "", exception ) {
            Code = code;
            HttpStatusCode = httpStatusCode;
            IsLocalization = true;
        }
    
        /// 
        /// 错误码
        /// 
        public string Code { get; set; }
    
        /// 
        /// Http状态码
        /// 
        public int? HttpStatusCode { get; set; }
    
        /// 
        /// 是否本地化异常消息
        /// 
        public bool IsLocalization { get; set; }
    
        /// 
        /// 获取错误消息
        /// 
        /// 是否生产环境
        public virtual string GetMessage( bool isProduction = false ) {
            return GetMessage( this );
        }
    
        /// 
        /// 获取错误消息
        /// 
        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() );
        }
    
        /// 
        /// 添加异常消息
        /// 
        private static void AppendMessage( StringBuilder result, Exception exception ) {
            if( exception == null )
                return;
            result.AppendLine( exception.Message );
        }
    
        /// 
        /// 获取异常列表
        /// 
        public IList GetExceptions() {
            return GetExceptions( this );
        }
    
        /// 
        /// 获取异常列表
        /// 
        /// 异常
        public static IList GetExceptions( Exception ex ) {
            var result = new List();
            AddException( result, ex );
            return result;
        }
    
        /// 
        /// 添加内部异常
        /// 
        private static void AddException( List result, Exception exception ) {
            if( exception == null )
                return;
            result.Add( exception );
            AddException( result, exception.InnerException );
        }
    }
    

    ConcurrencyException 并发异常

    ConcurrencyException 表示并发异常,统一包装其它组件产生的并发异常,并处理异常消息.

    /// 
    /// 并发异常
    /// 
    public class ConcurrencyException : Warning {
        /// 
        /// 消息
        /// 
        private readonly string _message;
    
        /// 
        /// 初始化并发异常
        /// 
        public ConcurrencyException()
            : this( "" ) {
        }
    
        /// 
        /// 初始化并发异常
        /// 
        /// 异常
        public ConcurrencyException( Exception exception )
            : this( "", exception ) {
        }
    
        /// 
        /// 初始化并发异常
        /// 
        /// 错误消息
        /// 异常
        /// 错误码
        /// Http状态码
        public ConcurrencyException( string message, Exception exception = null, string code = null, int? httpStatusCode = null )
            : base( message, exception, code, httpStatusCode ) {
            _message = message;
        }
    
        /// 
        public override string Message => $"{R.ConcurrencyExceptionMessage}.{_message}";
    
        /// 
        public override string GetMessage( bool isProduction = false ) {
            if( isProduction )
                return R.ConcurrencyExceptionMessage;
            return GetMessage(this);
        }
    }
    

    ErrorLogFilterAttribute 错误日志过滤器

    [ErrorLogFilter] 错误日志过滤器记录全局异常日志.

    /// 
    /// 错误日志过滤器
    /// 
    public class ErrorLogFilterAttribute : ExceptionFilterAttribute {
        /// 
        /// 异常处理
        /// 
        public override void OnException( ExceptionContext context ) {
            if( context == null )
                return;
            var log = context.HttpContext.RequestServices.GetService>();
            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] 过滤器还对异常消息的本地化进行了处理.

    /// 
    /// 异常处理过滤器
    /// 
    public class ExceptionHandlerAttribute : ExceptionFilterAttribute {
        /// 
        /// 异常处理
        /// 
        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 );
        }
    
        /// 
        /// 获取本地化异常消息
        /// 
        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();
            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();
            if ( stringLocalizer == null )
                return message;
            return stringLocalizer[message];
        }
    
        /// 
        /// 获取结果
        /// 
        protected virtual IActionResult GetResult( ExceptionContext context, string code, string message, int? httpStatusCode ) {
            var options = GetJsonSerializerOptions( context );
            var resultFactory = context.HttpContext.RequestServices.GetService();
            if ( resultFactory == null )
                return new Result( code, message, null, httpStatusCode, options );
            return resultFactory.CreateResult( code, message, null, httpStatusCode, options );
        }
    
        /// 
        /// 获取Json序列化配置
        /// 
        private JsonSerializerOptions GetJsonSerializerOptions( ExceptionContext context ) {
            var factory = context.HttpContext.RequestServices.GetService();
            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()
                }
            };
        }
    }
    
    /// 
    /// 异常扩展
    /// 
    public static class ExceptionExtensions {
        /// 
        /// 获取异常提示
        /// 
        /// 异常
        /// 是否生产环境
        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;
        }
    
        /// 
        /// 获取Http状态码
        /// 
        /// 异常
        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;
        }
    
        /// 
        /// 获取错误码
        /// 
        /// 异常
        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;
        }
    }
    
  • 相关阅读:
    云计算场景下,如何快速定位出虚拟机reboot/shutdown引发的故障
    SpringCloud之Ribbon负载均衡解读
    UI组件DevExpress ASP.NET Core v22.1亮点 - 甘特图、UI组件全新升级
    base64图片的优缺点(雪碧图)
    web大作业 静态网页(地下城与勇士 10页 带视频)
    Spring Webflux 后端处理前端请求的 4 种方式
    Python的pytest框架(3)--fixtrue固件
    2022-11-16 几种三角函数的图形
    Ubuntu LabelMe AI 识别
    easyexcel的使用
  • 原文地址:https://www.cnblogs.com/xiadao521/p/17814734.html