本节介绍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应用框架基础(五) - 异常处理的更多相关文章

  1. 爬虫基础(五)-----scrapy框架简介

    ---------------------------------------------------摆脱穷人思维 <五> :拓展自己的视野,适当做一些眼前''无用''的事情,防止进入只关 ...

  2. Hadoop框架基础(五)

    ** Hadoop框架基础(五) 已经部署了Hadoop的完全分布式集群,我们知道NameNode节点的正常运行对于整个HDFS系统来说非常重要,如果NameNode宕掉了,那么整个HDFS就要整段垮 ...

  3. HBase框架基础(五)

    * HBase框架基础(五) 本节主要介绍HBase中关于分区的一些知识. * HBase的RowKey设计 我们为什么要讨论rowKey的设计?或者说为什么很多工作岗位要求有rowKey的优化设计经 ...

  4. Hibernatel框架基础使用

    Hibernatel框架基础使用 1.简介 1.1.Hibernate框架由来 Struts:基于MVC模式的应用层框架技术 Hibernate:基于持久层的框架(数据访问层使用)! Spring:创 ...

  5. .NET面试题系列[1] - .NET框架基础知识(1)

    很明显,CLS是CTS的一个子集,而且是最小的子集. - 张子阳 .NET框架基础知识(1) 参考资料: http://www.tracefact.net/CLR-and-Framework/DotN ...

  6. 框架基础之Hibernate简介

    框架基础之Hibernate简介 1.什么是Hibernate? Hibernate是一个开发源代码的对象关系映射框架,它对JDBC进行非常轻量级的对象封装,使得程序员可以随心所欲地使用对象编程思维来 ...

  7. 游戏UI框架设计(五): 配置管理与应用

    游戏UI框架设计(五) --配置管理与应用 在开发企业级游戏/VR/AR产品时候,我们总是希望可以总结出一些通用的技术体系,框架结构等,为简化我们的开发起到"四两拨千金"的作用.所 ...

  8. c#基础之异常处理

    在开发过程中,经常遇到各种各样的小问题,很多是由于基础不牢固,没有经常总结导致的.遇到重复的问题可能可根据以往经验处理,但是对问题本身引发的原因进行深入的了解.工作很多年,但是c#基础像一层冰一样,可 ...

  9. c#基础之异常处理及自定义异常 从SQLServer转储数据到MySQL

    c#基础之异常处理及自定义异常 一.什么是c#中的异常? 异常是程序运行中发生的错误,异常处理是程序的一部分.c#中的异常类主要是直接或者间接的派生于 System.Exception类 ,也就是说S ...

  10. Hibernate框架基础

    Hibernate框架基础 Hibernate框架 ORM概念 O, Object 对象 R, Realtion 关系 (关系型数据库: MySQL, Oracle…) M,Mapping 映射 OR ...

随机推荐

  1. java 线程等待和唤醒方法

    java线程状态变迁图 从图中可以看出Java 线程等待方法是将线程从Runnable状态转换为Waiting状态,Java线程的唤醒方法是将线程从Waiting状态唤醒进入Runnable状态 在J ...

  2. 从 iOS App 启动速度看如何为基础性能保驾护航

    1 前言 启动是App给用户的第一印象,一款App的启动速度,不单单是用户体验的事情,往往还决定了它能否获取更多的用户.所以到了一定阶段App的启动优化是必须要做的事情.App启动基本分为以下两种 1 ...

  3. Trackbar调色板

    我们将会建立一个简单的应用,显示我们指定的颜色.将会建立一个窗口,显示三个trackbar指定RGB三个颜色通道值.可以滑动trackbar来改变相应的颜色.默认情况下,初始颜色为黑色. cv2.ge ...

  4. Java日志系列:日志门面JCL、SLF4J

    目录 一.日志门面说明 二.JCL 使用 JCL 入门 JCL 原理 三.SLF4J 使用 配合自身简单日志实现(slf4j-simple) 配置logback日志实现 配置Log4J日志实现(需适配 ...

  5. Kioptrix: Level 1 (#1) 古老的Apache Samba VULN

    0×01 Vulnhub靶机渗透总结之 Kioptrix: Level 1 (#1) 系列专栏:Vulnhub靶机渗透系列 欢迎大佬:点赞️收藏关注 首发时间: 2023年8月20日 如有错误 还望告 ...

  6. Unity UGUI的Toggle(复选框)组件的介绍及使用

    Unity UGUI的Toggle(复选框)组件的介绍及使用 1. 什么是Toggle组件? Toggle(复选框)是Unity UGUI中的一个常用组件,用于实现复选框的功能.它可以被选中或取消选中 ...

  7. 【渗透测试】Vulnhub EMPIRE BREAKOUT

    渗透环境 攻击机:   IP: 192.168.149.128(Kali) 靶机:     IP:192.168.149.130 靶机下载地址:https://www.vulnhub.com/entr ...

  8. 《Kali渗透基础》09. 漏洞利用、后渗透

    @ 目录 1:漏洞基本介绍 1.1:漏洞从哪里来 1.2:缓冲区溢出 1.3:如何发现漏洞 2:漏洞利用 2.1:EXP 选择与修改 2.2:避免有害的 EXP 3:后渗透阶段 3.1:Linux 上 ...

  9. BeanUtils.copyProperties:曾经是我的女神,现在是我的毒药。

    前言 BeanUtils.copyProperties十有八九是你这些年工作中用的很多的其中一个,不管是Apache的还是Spring的. 网上的解释浩如烟海,我这边用一个超简单的例子直观展示给你看. ...

  10. Nomad 系列-安装

    系列文章 Nomad 系列文章 Nomad 简介 开新坑!近期算是把自己的家庭实验室环境初步搞好了,终于可以开始进入正题研究了. 首先开始的是 HashiCorp Nomad 系列,欢迎阅读. 关于 ...