Util應(yīng)用框架基礎(chǔ)(五) - 異常處理
本節(jié)介紹Util應(yīng)用框架如何處理系統(tǒng)錯(cuò)誤.
概述
系統(tǒng)在運(yùn)行過(guò)程中可能發(fā)生錯(cuò)誤.
系統(tǒng)錯(cuò)誤可以簡(jiǎn)單分為兩類:
-
系統(tǒng)異常
系統(tǒng)本身出現(xiàn)的錯(cuò)誤.
-
業(yè)務(wù)異常
不滿足業(yè)務(wù)規(guī)則出現(xiàn)的錯(cuò)誤.
如何處理系統(tǒng)異常
如果發(fā)生系統(tǒng)異常,大多數(shù)情況下,你除了記錄異常日志外,可能無(wú)法處理它們.
一個(gè)例外是并發(fā)異常.
當(dāng)發(fā)生并發(fā)異常,可以通過(guò)重試再次提交,有可能成功處理.
另外一個(gè)問(wèn)題,是否應(yīng)該將系統(tǒng)異常消息返回給客戶端?
系統(tǒng)異常消息與技術(shù)相關(guān),客戶無(wú)法理解它們.
而且系統(tǒng)異常消息可能包含敏感信息,返回給客戶端可能更易受到攻擊.
如何處理業(yè)務(wù)異常
業(yè)務(wù)異常表明沒(méi)有滿足某些業(yè)務(wù)規(guī)則,通常也無(wú)法自動(dòng)處理.
如果能夠自動(dòng)處理的業(yè)務(wù)異常,應(yīng)定義專用異常類型.
對(duì)于業(yè)務(wù)異常,除了記錄異常日志外,還應(yīng)把業(yè)務(wù)異常消息返回給客戶端,以指示用戶調(diào)整操作.
基礎(chǔ)用法
.Net 使用異常 Exception 及派生異常來(lái)處理系統(tǒng)異常,但沒(méi)有明確規(guī)定處理業(yè)務(wù)異常的類型.
Warning 業(yè)務(wù)異常
Util應(yīng)用框架定義了 Util.Exceptions.Warning 異常類型,Warning 從 Exception 派生,代表業(yè)務(wù)異常.
當(dāng)你拋出 Exception 或派生異常類型時(shí),異常消息僅在開(kāi)發(fā)階段返回給客戶端.
一旦發(fā)布到生產(chǎn)環(huán)境,系統(tǒng)異常消息將被屏蔽,客戶端收到消息: 系統(tǒng)忙,請(qǐng)稍后再試 .
throw new Exception( "未將對(duì)象引用設(shè)置到對(duì)象的實(shí)例" );
對(duì)于業(yè)務(wù)規(guī)則導(dǎo)致的錯(cuò)誤,你需要拋出 Warning 異常.
Warning 拋出的異常消息將返回到客戶端,提示用戶進(jìn)行修改.
throw new Warning( "必須填寫(xiě)姓名" );
GetMessage 工具方法
Warning 除了代表業(yè)務(wù)異常外,還提供了一個(gè)靜態(tài)工具方法 GetMessage.
異常可能被其它異常包裹,要獲得異常真正的消息,需要使用遞歸.
Warning.GetMessage 工具方法傳入異常實(shí)例,遞歸獲取異常消息,
var message = Warning.GetMessage( exception );
ConcurrencyException 并發(fā)異常
Util應(yīng)用框架定義了并發(fā)異常 Util.Exceptions.ConcurrencyException.
不同的 .Net 組件拋出的并發(fā)異常類型可能不同, Util使用 ConcurrencyException 進(jìn)行統(tǒng)一包裝.
可以通過(guò)重試的方式來(lái)解決并發(fā)異常.
下面是Util應(yīng)用框架Dapr集成事件增加計(jì)數(shù)時(shí)并發(fā)處理的代碼片斷.
public virtual async Task IncrementAsync( CancellationToken cancellationToken = default ) {
try {
await Store.IncrementAsync( cancellationToken );
}
catch ( ConcurrencyException ) {
Log.LogDebug( "更新集成事件計(jì)數(shù)出現(xiàn)并發(fā)異常,即將重試" );
await IncrementAsync( cancellationToken );
}
catch ( Exception exception ) {
Log.LogError( exception, "更新集成事件計(jì)數(shù)失敗" );
}
}
全局錯(cuò)誤日志記錄
Util應(yīng)用框架使用 ErrorLogFilterAttribute 過(guò)濾器來(lái)記錄全局錯(cuò)誤日志.
已在 Web Api控制器基類 WebApiControllerBase 設(shè)置 [ErrorLogFilter] 過(guò)濾器.
全局異常處理
Util應(yīng)用框架使用 ExceptionHandlerAttribute 過(guò)濾器來(lái)處理全局異常.
已在 Web Api控制器基類 WebApiControllerBase 設(shè)置 [ExceptionHandler] 過(guò)濾器.
[ExceptionHandler] 過(guò)濾器對(duì)異常消息進(jìn)行處理,只有 Warning 異常消息才會(huì)返回給客戶端.
源碼解析
Warning 業(yè)務(wù)異常
Warning 代表業(yè)務(wù)異常,它的異常消息會(huì)返回給客戶端.
GetMessage 方法使用遞歸獲取內(nèi)部異常消息.
/// <summary>
/// 應(yīng)用程序異常
/// </summary>
public class Warning : Exception {
/// <summary>
/// 初始化應(yīng)用程序異常
/// </summary>
/// <param name="exception">異常</param>
public Warning( Exception exception )
: this( null, exception ) {
}
/// <summary>
/// 初始化應(yīng)用程序異常
/// </summary>
/// <param name="message">錯(cuò)誤消息</param>
/// <param name="exception">異常</param>
/// <param name="code">錯(cuò)誤碼</param>
/// <param name="httpStatusCode">Http狀態(tài)碼</param>
public Warning( string message, Exception exception = null, string code = null, int? httpStatusCode = null )
: base( message ?? "", exception ) {
Code = code;
HttpStatusCode = httpStatusCode;
IsLocalization = true;
}
/// <summary>
/// 錯(cuò)誤碼
/// </summary>
public string Code { get; set; }
/// <summary>
/// Http狀態(tài)碼
/// </summary>
public int? HttpStatusCode { get; set; }
/// <summary>
/// 是否本地化異常消息
/// </summary>
public bool IsLocalization { get; set; }
/// <summary>
/// 獲取錯(cuò)誤消息
/// </summary>
/// <param name="isProduction">是否生產(chǎn)環(huán)境</param>
public virtual string GetMessage( bool isProduction = false ) {
return GetMessage( this );
}
/// <summary>
/// 獲取錯(cuò)誤消息
/// </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>
/// 添加內(nèi)部異常
/// </summary>
private static void AddException( List<Exception> result, Exception exception ) {
if( exception == null )
return;
result.Add( exception );
AddException( result, exception.InnerException );
}
}
ConcurrencyException 并發(fā)異常
ConcurrencyException 表示并發(fā)異常,統(tǒng)一包裝其它組件產(chǎn)生的并發(fā)異常,并處理異常消息.
/// <summary>
/// 并發(fā)異常
/// </summary>
public class ConcurrencyException : Warning {
/// <summary>
/// 消息
/// </summary>
private readonly string _message;
/// <summary>
/// 初始化并發(fā)異常
/// </summary>
public ConcurrencyException()
: this( "" ) {
}
/// <summary>
/// 初始化并發(fā)異常
/// </summary>
/// <param name="exception">異常</param>
public ConcurrencyException( Exception exception )
: this( "", exception ) {
}
/// <summary>
/// 初始化并發(fā)異常
/// </summary>
/// <param name="message">錯(cuò)誤消息</param>
/// <param name="exception">異常</param>
/// <param name="code">錯(cuò)誤碼</param>
/// <param name="httpStatusCode">Http狀態(tài)碼</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 錯(cuò)誤日志過(guò)濾器
[ErrorLogFilter] 錯(cuò)誤日志過(guò)濾器記錄全局異常日志.
/// <summary>
/// 錯(cuò)誤日志過(guò)濾器
/// </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 異常處理過(guò)濾器
[ExceptionHandler] 過(guò)濾器處理全局異常.
Exception 的擴(kuò)展方法 GetPrompt 獲取客戶端友好的異常消息.
對(duì)于生產(chǎn)環(huán)境, Exception 異常消息將被替換為 系統(tǒng)忙,請(qǐng)稍后再試.
[ExceptionHandler] 過(guò)濾器還對(duì)異常消息的本地化進(jìn)行了處理.
/// <summary>
/// 異常處理過(guò)濾器
/// </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>
/// 獲取結(jié)果
/// </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>
/// 異常擴(kuò)展
/// </summary>
public static class ExceptionExtensions {
/// <summary>
/// 獲取異常提示
/// </summary>
/// <param name="exception">異常</param>
/// <param name="isProduction">是否生產(chǎn)環(huán)境</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狀態(tài)碼
/// </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>
/// 獲取錯(cuò)誤碼
/// </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;
}
}

浙公網(wǎng)安備 33010602011771號(hào)