agilelabs-fx-docs main topics/envelop-message.md

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(...) 阶段统一注册 ExceptionHandlerFilterEnvelopFilterAttribute
  • Action 只返回业务对象、ObjectResultJsonResultEmptyResult,让过滤器做最终包装。
  • 项目级业务异常映射通过 IApiExceptionProcessor 或继承 DefaultApiExceptionProcessor 承接。
  • 对匿名接口、回调接口和下载接口单独说明是否跳过封包,并在文档中显式标注。

核心类型与入口

  • AgileLabs/ComponentModels/EnvelopMessage.cs:基础封包对象,包含 codemsgerrMsgtid
  • 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-webapiEnvelopMessage、统一过滤器、Swagger 对齐。
  • woscm:项目级异常处理器、业务错误码和 TraceId 回写。

相关页面