Skip to content

Commit 621fa2e

Browse files
committed
feat: 新增Http异常状态码转换
1 parent 298da21 commit 621fa2e

File tree

12 files changed

+318
-54
lines changed

12 files changed

+318
-54
lines changed

framework/src/Bing.AspNetCore.Mvc/Bing/AspNetCore/Mvc/ExceptionHandling/BingExceptionFilter.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
using System.Threading.Tasks;
1+
using System;
2+
using System.Text;
3+
using System.Threading.Tasks;
4+
using Bing.AspNetCore.ExceptionHandling;
25
using Bing.DependencyInjection;
36
using Bing.ExceptionHandling;
7+
using Bing.Helpers;
8+
using Bing.Http;
9+
using Bing.Utils.Json;
410
using Microsoft.AspNetCore.Http;
511
using Microsoft.AspNetCore.Mvc.Abstractions;
612
using Microsoft.AspNetCore.Mvc.Filters;
13+
using Microsoft.Extensions.Logging;
14+
using Microsoft.Extensions.Logging.Abstractions;
15+
using Microsoft.Extensions.Options;
716

817
namespace Bing.AspNetCore.Mvc.ExceptionHandling
918
{
@@ -31,7 +40,7 @@ protected virtual bool ShouldHandleException(ExceptionContext context)
3140
{
3241
if (context.ActionDescriptor.IsControllerAction() && context.ActionDescriptor.HasObjectResult())
3342
return true;
34-
if (context.HttpContext.Request.CanAccept("application/json"))
43+
if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json))
3544
return true;
3645
if (context.HttpContext.Request.IsAjax())
3746
return true;
@@ -44,6 +53,28 @@ protected virtual bool ShouldHandleException(ExceptionContext context)
4453
/// <param name="context">异常上下文</param>
4554
protected virtual async Task HandleAndWrapException(ExceptionContext context)
4655
{
56+
context.HttpContext.Response.Headers.Add(BingHttpConst.BingErrorFormat, "true");
57+
context.HttpContext.Response.StatusCode = (int)context
58+
.GetRequiredService<IHttpExceptionStatusCodeFinder>()
59+
.GetStatusCode(context.HttpContext, context.Exception);
60+
61+
var exceptionHandlingOptions = context.GetRequiredService<IOptions<BingExceptionHandlingOptions>>().Value;
62+
var exceptionToErrorInfoConverter = context.GetRequiredService<IExceptionToErrorInfoConverter>();
63+
var remoteServiceErrorInfo = exceptionToErrorInfoConverter.Convert(context.Exception, exceptionHandlingOptions.SendExceptionDetailsToClients);
64+
65+
// TODO: 此处考虑是否还要抽象个对象存放信息
66+
context.Result = new ApiResult(Conv.ToInt(remoteServiceErrorInfo.Code), remoteServiceErrorInfo.Message);
67+
68+
var logLevel = context.Exception.GetLogLevel();
69+
70+
var remoteServiceErrorInfoBuilder = new StringBuilder();
71+
remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------");
72+
remoteServiceErrorInfoBuilder.AppendLine(JsonHelper.ToJson(remoteServiceErrorInfo, indented: true));
73+
74+
var logger = context.GetService<ILogger<BingExceptionFilter>>(NullLogger<BingExceptionFilter>.Instance);
75+
logger.LogWithLevel(logLevel,remoteServiceErrorInfoBuilder.ToString());
76+
logger.LogException(context.Exception,logLevel);
77+
4778
await context.GetRequiredService<IExceptionNotifier>().NotifyAsync(new ExceptionNotificationContext(context.Exception));
4879
context.Exception = null;
4980
}

framework/src/Bing.AspNetCore/Bing/AspNetCore/ExceptionHandling/BingExceptionHandlingMiddleware.cs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
using System;
22
using System.Threading.Tasks;
3+
using Bing.ExceptionHandling;
4+
using Bing.Helpers;
5+
using Bing.Http;
6+
using Bing.Utils.Json;
37
using Microsoft.AspNetCore.Http;
8+
using Microsoft.Extensions.DependencyInjection;
49
using Microsoft.Extensions.Logging;
510
using Microsoft.Extensions.Options;
611
using Microsoft.Net.Http.Headers;
@@ -17,11 +22,6 @@ public class BingExceptionHandlingMiddleware : IMiddleware
1722
/// </summary>
1823
private readonly RequestDelegate _next;
1924

20-
/// <summary>
21-
/// 异常处理选项配置
22-
/// </summary>
23-
private readonly BingExceptionHandlingOptions _options;
24-
2525
/// <summary>
2626
/// 日志
2727
/// </summary>
@@ -36,12 +36,10 @@ public class BingExceptionHandlingMiddleware : IMiddleware
3636
/// 初始化一个<see cref="BingExceptionHandlingMiddleware"/>类型的实例
3737
/// </summary>
3838
/// <param name="next">方法</param>
39-
/// <param name="options">异常处理选项配置</param>
4039
/// <param name="logger">日志</param>
41-
public BingExceptionHandlingMiddleware(RequestDelegate next, IOptions<BingExceptionHandlingOptions> options, ILogger<BingExceptionHandlingMiddleware> logger)
40+
public BingExceptionHandlingMiddleware(RequestDelegate next, ILogger<BingExceptionHandlingMiddleware> logger)
4241
{
4342
_next = next;
44-
_options = options.Value;
4543
_logger = logger;
4644
_clearCacheHeaderDelegate = ClearCacheHeaders;
4745
}
@@ -64,10 +62,7 @@ public async Task InvokeAsync(HttpContext context)
6462
_logger.LogWarning("An exception occurred, but response has already started!");
6563
throw;
6664
}
67-
if (context.RequestAborted.IsCancellationRequested && (e is TaskCanceledException || e is OperationCanceledException))
68-
_options.OnRequestAborted?.Invoke(context, _logger);
69-
else
70-
_options.OnException?.Invoke(context, _logger, e);
65+
await HandleAndWarpException(context, e);
7166
}
7267
}
7368

@@ -80,11 +75,31 @@ private async Task HandleAndWarpException(HttpContext httpContext, Exception exc
8075
{
8176
_logger.LogException(exception);
8277

78+
var errorInfoConverter = httpContext.RequestServices.GetRequiredService<IExceptionToErrorInfoConverter>();
79+
var statusCodeFinder = httpContext.RequestServices.GetRequiredService<IHttpExceptionStatusCodeFinder>();
80+
var options = httpContext.RequestServices.GetRequiredService<IOptions<BingExceptionHandlingOptions>>().Value;
81+
8382
httpContext.Response.Clear();
84-
httpContext.Response.StatusCode = 200;
83+
httpContext.Response.StatusCode = (int)statusCodeFinder.GetStatusCode(httpContext, exception);
8584
httpContext.Response.OnStarting(_clearCacheHeaderDelegate, httpContext.Response);
86-
httpContext.Response.Headers.Add("_BingErrorFormat", "true");
85+
httpContext.Response.Headers.Add(BingHttpConst.BingErrorFormat, "true");
86+
87+
var remoteServiceErrorInfo = errorInfoConverter.Convert(exception, options.SendExceptionDetailsToClients);
88+
await WriteJsonAsync(httpContext.Response, new {code = Conv.ToInt(remoteServiceErrorInfo.Code), message = remoteServiceErrorInfo.Message});
8789

90+
await httpContext.RequestServices.GetRequiredService<IExceptionNotifier>().NotifyAsync(new ExceptionNotificationContext(exception));
91+
}
92+
93+
/// <summary>
94+
/// 写入Json
95+
/// </summary>
96+
/// <param name="response">Http响应</param>
97+
/// <param name="obj">对象</param>
98+
private static async Task WriteJsonAsync(HttpResponse response, object obj)
99+
{
100+
var json = JsonHelper.ToJson(obj);
101+
response.ContentType = "application/json; charset=utf-8";
102+
await response.WriteAsync(json);
88103
}
89104

90105
/// <summary>

framework/src/Bing.AspNetCore/Bing/AspNetCore/ExceptionHandling/BingExceptionHttpStatusCodeOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ public class BingExceptionHttpStatusCodeOptions
1313
/// </summary>
1414
public IDictionary<string, HttpStatusCode> ErrorCodeToHttpStatusCodeMappings { get; }
1515

16+
/// <summary>
17+
/// 全局状态码200
18+
/// </summary>
19+
public bool GlobalHttpStatusCode200 { get; set; } = true;
20+
1621
/// <summary>
1722
/// 初始化一个<see cref="BingExceptionHttpStatusCodeOptions"/>类型的实例
1823
/// </summary>
Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
2+
using Bing.AspNetCore.Mvc;
23
using Bing.DependencyInjection;
3-
using Bing.ExceptionHandling;
4+
using Bing.Exceptions;
45
using Bing.Http;
56

67
namespace Bing.AspNetCore.ExceptionHandling
@@ -15,41 +16,21 @@ public class DefaultExceptionToErrorInfoConverter : IExceptionToErrorInfoConvert
1516
/// </summary>
1617
/// <param name="exception">异常</param>
1718
/// <param name="includeSensitiveDetails">是否包含敏感信息</param>
18-
public RemoteServiceErrorInfo Convert(Exception exception, bool includeSensitiveDetails)
19+
public virtual RemoteServiceErrorInfo Convert(Exception exception, bool includeSensitiveDetails)
1920
{
20-
var errorInfo = CreateErrorInfoWithoutCode(exception, includeSensitiveDetails);
21-
if (exception is IHasErrorCode hasErrorCodeException)
22-
errorInfo.Code = hasErrorCodeException.Code;
23-
return errorInfo;
24-
}
25-
26-
/// <summary>
27-
/// 创建错误信息(无错误码)
28-
/// </summary>
29-
/// <param name="exception">异常</param>
30-
/// <param name="includeSensitiveDetails">是否包含敏感信息</param>
31-
protected virtual RemoteServiceErrorInfo CreateErrorInfoWithoutCode(Exception exception, bool includeSensitiveDetails)
32-
{
33-
exception = TryToGetActualException(exception);
34-
35-
var errorInfo = new RemoteServiceErrorInfo();
36-
errorInfo.Data = exception.Data;
37-
38-
return errorInfo;
39-
}
40-
41-
/// <summary>
42-
/// 尝试获取实际异常
43-
/// </summary>
44-
/// <param name="exception">异常</param>
45-
protected virtual Exception TryToGetActualException(Exception exception)
46-
{
47-
if (exception is AggregateException && exception.InnerException != null)
21+
if (exception is ConcurrencyException)
4822
{
49-
var aggException = exception as AggregateException;
50-
23+
return new RemoteServiceErrorInfo {Code = "400001", Message = exception.GetPrompt()};
5124
}
52-
return exception;
25+
26+
if (exception is Warning warning)
27+
return new RemoteServiceErrorInfo
28+
{
29+
Code = string.IsNullOrWhiteSpace(warning.Code) ? StatusCode.Fail.ToString() : warning.Code,
30+
Message = warning.GetPrompt()
31+
};
32+
33+
return new RemoteServiceErrorInfo {Code = StatusCode.Fail.ToString(), Message = exception.GetPrompt()};
5334
}
5435
}
5536
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Net;
3+
using Bing.DependencyInjection;
4+
using Bing.ExceptionHandling;
5+
using Bing.Exceptions;
6+
using Bing.Text;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.Extensions.Options;
9+
10+
namespace Bing.AspNetCore.ExceptionHandling
11+
{
12+
/// <summary>
13+
/// Http异常状态码查找器
14+
/// </summary>
15+
public class DefaultHttpExceptionStatusCodeFinder : IHttpExceptionStatusCodeFinder, ITransientDependency
16+
{
17+
/// <summary>
18+
/// 异常Http状态码选项配置
19+
/// </summary>
20+
protected BingExceptionHttpStatusCodeOptions Options { get; }
21+
22+
/// <summary>
23+
/// 初始化一个<see cref="DefaultHttpExceptionStatusCodeFinder"/>类型的实例
24+
/// </summary>
25+
/// <param name="options">异常Http状态码选项配置</param>
26+
public DefaultHttpExceptionStatusCodeFinder(IOptions<BingExceptionHttpStatusCodeOptions> options)
27+
{
28+
Options = options.Value;
29+
}
30+
31+
/// <summary>
32+
/// 获取状态码
33+
/// </summary>
34+
/// <param name="httpContext">Http上下文</param>
35+
/// <param name="exception">异常</param>
36+
public virtual HttpStatusCode GetStatusCode(HttpContext httpContext, Exception exception)
37+
{
38+
// 如果设置了Http状态码,则返回指定Http状态码
39+
if (exception is IHasHttpStatusCode exceptionWithHttpStatusCode && exceptionWithHttpStatusCode.HttpStatusCode > 0)
40+
return (HttpStatusCode)exceptionWithHttpStatusCode.HttpStatusCode;
41+
42+
// 如果已配置字典【错误码 - Http状态码】,则返回指定Http状态码
43+
if (exception is IHasErrorCode exceptionWithErrorCode && !exceptionWithErrorCode.Code.IsNullOrWhiteSpace())
44+
{
45+
if (Options.ErrorCodeToHttpStatusCodeMappings.TryGetValue(exceptionWithErrorCode.Code, out var status))
46+
return status;
47+
}
48+
49+
if (Options.GlobalHttpStatusCode200)
50+
return HttpStatusCode.OK;
51+
52+
if (exception is ConcurrencyException)
53+
return HttpStatusCode.Conflict;
54+
55+
if (exception is NotImplementedException)
56+
return HttpStatusCode.NotImplemented;
57+
58+
if (exception is IBusinessException)
59+
return HttpStatusCode.Forbidden;
60+
61+
return HttpStatusCode.InternalServerError;
62+
}
63+
}
64+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Net;
3+
using Microsoft.AspNetCore.Http;
4+
5+
namespace Bing.AspNetCore.ExceptionHandling
6+
{
7+
/// <summary>
8+
/// Http异常状态码查找器
9+
/// </summary>
10+
public interface IHttpExceptionStatusCodeFinder
11+
{
12+
/// <summary>
13+
/// 获取状态码
14+
/// </summary>
15+
/// <param name="httpContext">Http上下文</param>
16+
/// <param name="exception">异常</param>
17+
HttpStatusCode GetStatusCode(HttpContext httpContext, Exception exception);
18+
}
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Bing.Http
2+
{
3+
/// <summary>
4+
/// Http常量
5+
/// </summary>
6+
public static class BingHttpConst
7+
{
8+
/// <summary>
9+
/// 错误格式
10+
/// </summary>
11+
public const string BingErrorFormat = "_BingErrorFormat";
12+
}
13+
}

0 commit comments

Comments
 (0)