# 异常处理
Web 服务的异常处理由 FriendlyException、全局异常过滤器和统一返回共同完成。业务代码负责抛出稳定异常,全局异常过滤器负责捕获和提取元数据,IUnifyResultProvider 负责输出最终 JSON。
# 处理链路
启用 AddWebApp() 后,异常处理链路通常是:
- 业务代码抛出
Oops.Bah(...)、Oops.Oh(...)或普通异常。 - 全局友好异常过滤器捕获异常。
- 统一返回异常元数据解析器提取状态码、错误码、错误消息和验证错误。
IUnifyResultProvider包装为统一返回结构。- 已注册的
IGlobalExceptionHandler执行日志、监控或审计逻辑。
如果只启用 AddWebAppCore(),友好异常能力仍然可用,但是否包装为统一返回取决于是否额外启用 AddWebAppUnifyResult()。
# 配置全局异常过滤器
FriendlyExceptionConfigure 挂在 AddWebAppCore() 选项中。
builder.Services
.AddControllers()
.AddWebAppCore(options =>
{
options.FriendlyExceptionConfigure = friendly =>
{
friendly.EnabledGlobalFriendlyException = true;
};
});
| 配置项 | 默认值 | 说明 |
|---|---|---|
EnabledGlobalFriendlyException | true | 是否启用全局友好异常过滤器 |
# IfExceptionAttribute
IfExceptionAttribute 用于动作方法级异常消息覆盖。它适合把内部异常详情转换为稳定、可暴露给前端的业务提示。
[IfException(typeof(InvalidOperationException), ErrorMessage = "当前操作不可用")]
[HttpPost("submit")]
public object Submit()
{
throw new InvalidOperationException("内部异常详情");
}
最终对外消息会使用 当前操作不可用。
也可以定义默认兜底消息:
[IfException(ErrorMessage = "请求处理失败")]
[HttpPost("submit")]
public object Submit()
{
throw new Exception("内部异常详情");
}
注意:
- 类型匹配优先于默认兜底。
- 对普通异常优先比较异常本身类型。
- 对
AppFriendlyException优先比较内部异常类型。 - 异常类型匹配是精确类型匹配,不按继承关系匹配。
- 带错误码的
IfExceptionAttribute主要服务于错误码消息解析,不参与全局异常类型映射。
# 错误码消息覆盖
IfExceptionAttribute 也可以给当前方法或类覆盖指定错误码的消息。
[IfException("USER_NAME_RESERVED", ErrorMessage = "该用户名不可使用")]
public object Create(CreateUserRequest request)
{
throw Oops.Bah("USER_NAME_RESERVED");
}
这类带错误码的特性只参与 Oops.Oh(errorCode) 和 Oops.Bah(errorCode) 的消息解析。
# 统一返回结构
启用 AddWebApp() 后,默认 RESTfulResultProvider 会包装异常。
普通异常示例:
{
"code": 500,
"data": null,
"succeeded": false,
"errors": "Internal Server Error",
"extras": null,
"timestamp": 1760000000000,
"message": null
}
业务验证异常示例:
{
"code": 400,
"data": null,
"succeeded": false,
"errors": {
"statusCode": 400,
"errorCode": "USER_NAME_RESERVED",
"message": "用户名已被保留",
"fields": {},
"errors": [
"用户名已被保留"
]
},
"extras": null,
"timestamp": 1760000000000,
"message": "用户名已被保留"
}
最终 JSON 结构由 IUnifyResultProvider 决定。如果项目要调整字段名称、层级或错误体结构,请看 自定义重写。
# 全局异常处理器
如需记录日志、上报监控或做审计,实现 IGlobalExceptionHandler。
using Air.Cloud.WebApp.FriendlyException.Handlers;
using Microsoft.AspNetCore.Mvc.Filters;
public sealed class AuditExceptionHandler : IGlobalExceptionHandler
{
public Task OnExceptionAsync(ExceptionContext context)
{
// 记录日志、链路追踪、告警等
return Task.CompletedTask;
}
}
builder.Services.AddSingleton<IGlobalExceptionHandler, AuditExceptionHandler>();
建议分工:
Oops:业务代码中的异常创建。IfExceptionAttribute:当前接口上的异常消息映射。IGlobalExceptionHandler:日志、监控、审计。IUnifyResultProvider:最终响应结构。
# 推荐实践
- 业务代码只抛异常,不手工拼最终 JSON。
- 对外错误消息保持稳定,不暴露内部异常堆栈、数据库错误或第三方响应明细。
- 需要前端稳定识别业务错误时,使用
errorCode。 - 日志、告警、审计放到
IGlobalExceptionHandler,不要塞进 Controller。 - 返回结构不符合项目规范时,优先自定义
IUnifyResultProvider。