泛型类 Result<T> 封装 RPC 响应体,仅保留 code(业务码)、message(纯文本提示)、data(泛型业务数据)三字段;通过泛型静态工厂方法、Controller 强制声明、OpenAPI Schema 与客户端代码生成,确保前后端类型一致、不破型、可直接使用。

用泛型类封装 RPC 响应体,核心是让 code、message、data 三字段稳定可预期,同时确保 T data 在任意业务场景下不破型、不丢失语义——不是“能塞进去”,而是“塞进去后前端敢直接用、后端敢直接序列化、IDE 敢直接跳转”。
只保留三个必选字段,拒绝语义污染
响应体结构必须精简且无歧义:
- int code:自定义业务码(如 10000=成功,40001=参数缺失),不用 HTTP 状态码;网关或监控系统需靠它做路由与告警,不能和 HTTP 层混用
- String message:纯文本提示,禁止拼接堆栈、路径、JSON 片段;前端 toast、日志归集、错误上报都依赖它,一串乱码或空字符串会直接阻断问题定位
-
T data:唯一承载业务数据的泛型字段,类型由接口声明决定(如
Result<User>或Result<List<Order>>),不允许出现Object、Map<String, Object>这类擦除型返回
删掉 timestamp、success、extend 等字段。它们要么该由网关统一注入,要么属于特定子域扩展(如审计场景可继承 ResultAudit<T>),不应污染通用契约。
泛型静态工厂方法必须支持类型推导
所有构造入口必须是泛型静态方法,否则编译器无法绑定 T,前端收到 data: {} 或后端反序列化为 LinkedHashMap 就是典型破型表现:
-
public static <T> Result<T> success(T data)—— 传入new User("Alice"),返回即为Result<User> -
public static <T> Result<T> fail(int code, String message)—— 即使data为 null,也维持Result<Product>类型,避免 JSON 反序列化失败 -
public static <T> Result<T> empty()—— 显式表达“有结果、但 data 为空”,比null更安全,前端可统一判空而无需 try-catch
禁用 Result.of(Object) 这类非泛型方法,它会让 Result<Order> result = Result.of(order) 编译报错或隐式擦除为 Result<Object>。
Controller 层强制声明泛型返回类型
所有 REST 接口必须写成 Result<X>,这是堵住字段黑洞的第一道闸门:
- 前端 Axios 拦截器可统一提取
response.data.data,无需每个接口写不同路径 - Swagger 自动生成的 Schema 是确定的:
data字段类型永远等于你写的<User>或<Page<Product>>,而不是object - 如果已有老接口未改造,可用
@RestControllerAdvice拦截非Result返回值,自动包装为Result.success(value),但要排除已返回Result或抛异常的情况,避免二次封装
配合 JSON Schema 与强类型客户端生成
光有 Java 泛型不够,字段黑洞常发生在前后端类型不同步时。需配套落地:
- 在 OpenAPI YAML 中明确定义
Result的 Schema,将data描述为$ref: '#/components/schemas/User',而非type: object - 用
openapi-generator生成 TypeScript 客户端,得到ResultUser类型,字段名、是否可选、嵌套结构全部与后端一致 - 对
data字段启用严格校验:如后端声明Result<BigDecimal>,前端生成类型就是string(因 JSON 无 BigDecimal),并配正则校验格式,杜绝 “123.00” 被当成整数解析

















