EnvelopMessage
本页聚合 AgileLabs Framework 中 EnvelopMessage 的字段语义、过滤器接法、异常链路和项目级扩展方式。它解决的是“统一封包具体怎么接”,而不是整个 WebAPI 宿主怎么组织。更高层的总览请先看 WebAPI。
适用场景
ApiController默认对前端返回统一协议的项目。- 需要把成功响应、模型校验错误和业务错误统一映射到
{ data, code, msg, errMsg, tid }的项目。 - 需要保留个别裸响应接口,但又不希望大多数接口手工拼 JSON 的项目。
必须遵守
- 成功响应默认由统一过滤器封包,不在每个 Action 手工拼
{ data, code, msg, errMsg, tid }。 - 模型验证错误、业务错误、数据库错误和兜底异常统一走
ExceptionHandlerFilter+IApiExceptionProcessor。 msg只承载可展示给最终用户的信息,errMsg只承载排障或调试信息。tid作为请求追踪字段稳定回传,不在控制器里手工补不同名字的 TraceId 字段。- 只有明确标记为例外的接口才跳过封包,不能把“局部裸响应”当默认接法。
推荐做法
- 在
AddControllers(...)阶段统一注册ExceptionHandlerFilter和EnvelopFilterAttribute。 - Action 只返回业务对象、
ObjectResult、JsonResult或EmptyResult,让过滤器做最终包装。 - 项目级业务异常映射通过
IApiExceptionProcessor或继承DefaultApiExceptionProcessor承接。 - 对匿名接口、回调接口和下载接口单独说明是否跳过封包,并在文档中显式标注。
核心类型与入口
AgileLabs/ComponentModels/EnvelopMessage.cs:基础封包对象,包含code、msg、errMsg、tid。AgileLabs/ComponentModels/EnvelopMessage~T.cs:泛型封包对象,增加data。AgileLabs.WebApp/AspNet/WebApis/Filters/EnvelopFilterAttribute.cs:成功响应统一封包。AgileLabs.WebApp/AspNet/WebApis/Filters/ExceptionHandlerFilter.cs:模型校验和执行异常的总入口。AgileLabs.WebApp/AspNet/WebApis/Filters/IApiExceptionProcessor.cs:项目级异常映射扩展点。AgileLabs.WebApp/AspNet/WebApis/Filters/IgnoreEnvelopAttribute.cs:显式跳过统一封包。
从源码语义看,这组字段的职责是固定的:
| 字段 | 含义 | 使用方式 |
|---|---|---|
data |
成功响应的业务对象 | 由 EnvelopMessage<T> 承载 |
code |
处理状态码 | 成功通常为 200,业务错误保留项目码 |
msg |
可展示给最终用户的提示 | 前端可用于弹窗、提示文案 |
errMsg |
调试或排障信息 | 不作为最终用户展示文案 |
tid |
请求追踪标识 | 由当前 Activity 自动回填 |
成功返回链路
典型接法里,Controller 不自己拼封包,而是返回真实业务对象,交给 EnvelopFilterAttribute 在 Action 结束后统一转换。
sequenceDiagram
participant Client as Client
participant Controller as ApiController
participant Action as Action
participant Envelop as EnvelopFilterAttribute
Client->>Controller: HTTP Request
Controller->>Action: 执行业务逻辑
Action-->>Controller: 返回 ObjectResult / JsonResult / EmptyResult
Controller->>Envelop: OnActionExecuted()
Envelop->>Envelop: 检查 IgnoreEnvelopAttribute
Envelop->>Envelop: 检查是否为 ApiController
Envelop-->>Client: OkObjectResult(EnvelopMessage)
最小配置示例:
using AgileLabs.AspNet.WebApis.Filters;
services.AddControllers(options =>
{
options.Filters.Add<ExceptionHandlerFilter>();
options.Filters.Add<EnvelopFilterAttribute>();
});
最小控制器示例:
[ApiController]
[Route("api/orders")]
public sealed class OrdersController(IOrderQueryService orderQueryService) : ControllerBase
{
[HttpGet("{id:guid}")]
public async Task<OrderDto> GetAsync(Guid id)
{
return await orderQueryService.GetAsync(id);
}
}
只要宿主已经挂上 EnvelopFilterAttribute,最终对前端暴露的响应会保持类似结构:
{
"data": {
"id": "f1f7c56b-49b8-4cc7-bc22-2ab1cf74b7ae",
"orderNo": "SO-20260412-001"
},
"code": 200,
"msg": null,
"errMsg": null,
"tid": "trace-id"
}
错误返回链路
ExceptionHandlerFilter 同时承担两件事:Action 执行前先把 ModelState 失败转换成 ModelValidateException;执行中如果出现异常,再交给 IApiExceptionProcessor 做统一映射。
sequenceDiagram
participant Client as Client
participant Filter as ExceptionHandlerFilter
participant Action as Action
participant Processor as IApiExceptionProcessor
participant Result as JsonResult
Client->>Filter: 请求进入 Action Filter
Filter->>Filter: 检查 ModelState
alt 模型校验失败
Filter->>Processor: Process(ModelValidateException)
Processor-->>Result: EnvelopMessage(code/msg/errMsg)
Result-->>Client: 200 + 规范错误封包
else Action 抛出业务/系统异常
Filter->>Action: 执行 Action
Action-->>Filter: 抛出 ApiException / DbException / Exception
Filter->>Processor: Process(exception)
Processor-->>Result: JsonResult(EnvelopMessage)
Result-->>Client: 200 + 规范错误封包
end
默认处理器的行为可以概括成这张表:
| 异常类型 | 处理器行为 | 对前端的结果 |
|---|---|---|
ModelValidateException |
不输出错误日志,提取字段错误 | 返回参数错误封包 |
ApiException |
不输出错误日志,保留业务码 | 返回业务错误封包 |
DbException |
映射为数据库错误 | 返回统一数据库错误码 |
其他 Exception |
兜底处理 | 返回统一 500 封包 |
项目级扩展示例
如果项目要保留自己的业务码、用户提示语或 errMsg 生成逻辑,推荐继承 DefaultApiExceptionProcessor,而不是在每个 Action 里 catch (Exception) 后重拼 JSON。
using AgileLabs.AspNet.WebApis.Exceptions;
using AgileLabs.AspNet.WebApis.Filters;
using AgileLabs.ComponentModels;
public class ProjectApiExceptionProcessor : DefaultApiExceptionProcessor
{
protected override EnvelopMessage ProcessException(ApiException exception, ExceptionProcessContext processContext)
{
return new EnvelopMessage((int)exception.Code, exception.HintMessage, exception.Message);
}
}
如果项目注册了自己的处理器,就让 DI 统一托管,不要局部接口自己改错误返回结构。
例外接口示例
IgnoreEnvelopAttribute 只适合明确的特例接口,例如第三方回调、文件流下载或必须返回裸响应的兼容接口。
[ApiController]
[Route("api/callbacks")]
public sealed class CallbacksController : ControllerBase
{
[IgnoreEnvelop]
[HttpPost("partner-a")]
public IActionResult Receive([FromBody] PartnerCallbackDto dto)
{
return Ok(new { accepted = true });
}
}
这类接口需要一起满足两个条件:
- 在代码上显式标记
IgnoreEnvelopAttribute。 - 在接口文档里说明它不是默认封包协议的一部分。
常见坑
- Controller 已经手工返回封包对象,外层又挂了
EnvelopFilterAttribute,导致结构重复嵌套。 - Action 里
catch (Exception)后自己返回 JSON,绕开统一异常链。 - 某些接口返回
camelCase,某些接口返回PascalCase,让前端不得不写兼容判断。 - 把
errMsg当成直接展示给最终用户的文案。 - 把
IgnoreEnvelopAttribute当成常规扩展点,结果系统里同时存在多套默认返回协议。
示例与落地对照
niusys-webapi:EnvelopMessage、统一过滤器、Swagger 对齐。woscm:项目级异常处理器、业务错误码和 TraceId 回写。