欢迎光临
我们一直在努力

打造 Java Spring Boot 标准化 API : 告别请求、响应重复造轮子!ResultVO + RequestVO

告别重复造轮子!ResultVO + RequestVO 打造 Spring Boot 标准化 API 设计体系

前言:每个 Java 项目都在重复的那件事

打开任何一个 Spring Boot 项目的 common 包,你大概率会看到这样的代码:

public class Result<T> {
private Integer code;
private String message;
private T data;
// 省略 getter/setter…
public static <T> Result<T> success(T data) { ... }
public static <T> Result<T> error(String msg) { ... }
}

每个团队都在写,字段名却各不相同:有的叫 code,有的叫 retCode;有的分页数据嵌套在 data 里,有的平铺在顶层。前后端联调时,接口文档写的是 data.list,前端拿到的却是 data.records——这类低效沟通,几乎每天都在发生。

更麻烦的是请求参数的解析:Controller 里 @RequestParam、@RequestBody、@PathVariable 各自为政,遇到 XML、FormData、文件上传,又得重新写一套解析逻辑。

ResultVO + RequestVO 就是为了解决这两个问题而生的。它们一起构成了一个完整的 API 设计规范:

  • ResultVO:统一响应结构,支持状态码自适应(Integer/String)、字段名可配置、分页平铺
  • RequestVO:统一请求封装,支持 JSON/Form/FormData/XML/二进制流全类型,提供嵌套路径取值、对象自动转换

本文将以实际项目场景为线索,带你彻底搞懂这套 API 设计体系。


一、ResultVO:统一响应,一套规范走天下

1.1 核心设计

ResultVO 的响应结构只有 4 个字段,简洁但够用:

{
"code": 200,
"msg": "成功",
"data": { },
"pager": { "page": 1, "pageSize": 10, "total": 100, "totalPages": 10 }
}

字段类型说明
code Object 状态码,支持 Integer(200)或 String(“SYS_OK”)
msg String 响应消息
data T 业务数据,为空时不参与 JSON 序列化
pager Pager 分页信息,与 data 平级,为空时不参与序列化

两个关键设计点:

  • code 类型自适应:互联网项目用 200(Integer),企业级/银行项目用 "SYS_ORDER_404"(String),一个类全兼容。
  • data 和 pager 为空时不参与序列化(@JsonInclude(NON_EMPTY)):响应干净,不会给前端一堆 "data": null。
  • 1.2 快速上手:suc / fail 全系列

    ResultVO 提供丰富的静态方法,覆盖几乎所有响应场景:

    // ========== 成功响应 ==========

    // 无数据成功(默认 code=200, msg="成功")
    ResultVO<Void> res1 = ResultVO.suc();

    // 仅自定义消息(无数据)
    ResultVO<Void> res2 = ResultVO.sucMsg("操作成功");

    // 携带数据
    ResultVO<User> res3 = ResultVO.suc(user);

    // 携带数据 + 分页
    ResultVO<List<User>> res4 = ResultVO.suc(userList, pager);

    // 自定义状态码 + 消息
    ResultVO<User> res5 = ResultVO.suc(201, "创建成功", user);

    // ========== 失败响应 ==========

    // 默认失败(code=500, msg="失败")
    ResultVO<Void> err1 = ResultVO.fail();

    // 仅自定义消息
    ResultVO<Void> err2 = ResultVO.fail("用户名不能为空");

    // 自定义状态码 + 消息
    ResultVO<Void> err3 = ResultVO.fail(400, "参数错误");

    // 携带错误数据(如校验失败字段)
    ResultVO<Map<String, Object>> err4 = ResultVO.failData(errorMap);

    方法命名约定(看名字就知道用途):

    前缀含义示例
    suc() 成功,无数据 ResultVO.suc()
    suc(T) 成功,携带数据 ResultVO.suc(user)
    sucMsg() 成功,仅消息 ResultVO.sucMsg("操作成功")
    fail() 失败,无数据 ResultVO.fail()
    failData() 失败,携带数据 ResultVO.failData(map)

    1.3 业务状态码管理:ResultMessage 接口

    当项目变复杂后,状态码不能散落在业务代码里。ResultMessage<C> 接口让你统一管理所有业务状态码:

    // 定义业务错误枚举,实现 ResultMessage 接口(String 类型的状态码)
    public enum BizError implements ResultMessage<String> {
    SUCCESS("200", "成功"),
    ORDER_NOT_FOUND("SYS_ORDER_404", "订单不存在"),
    STOCK_INSUFFICIENT("SYS_STOCK_500", "库存不足"),
    USER_DISABLED("SYS_USER_403", "用户已禁用");

    private final String code;
    private final String message;

    BizError(String code, String message) {
    this.code = code;
    this.message = message;
    }

    @Override
    public String getCode() { return code; }

    @Override
    public String getMessage() { return message; }
    }

    使用时,直接把枚举传给 ResultVO,无需每次手写状态码:

    // 成功
    return ResultVO.suc(BizError.SUCCESS);
    // 失败
    return ResultVO.fail(BizError.ORDER_NOT_FOUND);
    // 失败 + 携带数据
    return ResultVO.failData(BizError.STOCK_INSUFFICIENT, orderInfo);

    内置默认状态码(DefaultResultMessage)已覆盖常用 HTTP 状态:

    枚举值codemessage
    SUC 200 成功
    BAD_REQUEST 400 请求参数错误
    UNAUTHORIZED 401 未授权
    FORBIDDEN 403 禁止访问
    NOT_FOUND 404 资源不存在
    ERROR 500 服务器内部错误
    FAIL 500 失败

    1.4 字段名自定义:适配不同团队的规范

    有些团队要求返回 retCode 而不是 code,有些要求 body 而不是 data。不需要改源码,只需实现 ResultFieldMapping 接口并注入 Spring:

    @Component
    public class BankFieldMapping implements ResultFieldMapping {
    @Override public String getCodeFieldName() { return "retCode"; }
    @Override public String getMsgFieldName() { return "retMsg"; }
    @Override public String getDataFieldName() { return "body"; }
    @Override public String getPagerFieldName() { return "pageInfo"; }
    }

    配置生效后,原本返回:

    {"code": 200, "msg": "成功", "data": {}}

    自动变为:

    {"retCode": 200, "retMsg": "成功", "body": {}}

    全局生效,无需修改任何 Controller 代码。 这对多项目/多团队协作非常实用。

    1.5 Pager 分页:与 Vue 生态完美对齐

    Pager 对象与 data 平级返回,前端拿到的结构一目了然:

    {
    "code": 200,
    "msg": "成功",
    "data": [],
    "pager": {
    "startRow": 0,
    "endRow": 10,
    "page": 1,
    "pageSize": 10,
    "total": 100,
    "totalPages": 10
    }
    }

    Pager 提供以下字段:

    字段类型说明
    startRow int 起始行号(用于 SQL LIMIT)
    endRow int 结束行号
    page int 当前页码(默认 1)
    pageSize int 每页条数(默认 10)
    total int 数据总条数
    totalPages int 总页数(自动计算)

    setTotal() 会自动计算总页数:

    Pager pager = new Pager();
    pager.setPage(1);
    pager.setPageSize(20);
    pager.setTotal(95); // 自动计算 totalPages = 5


    二、RequestVO:一个对象搞定所有请求类型

    2.1 核心设计

    RequestVO 配合 @RequestVo 注解,将 HTTP 请求数据自动注入到 Controller 方法参数中。它的设计哲学是:不管前端用什么 Content-Type,后端用同一套 API 取参数。

    支持的请求类型:

    Content-Type场景取值方式
    application/json JSON 请求体 getStr(key) / toBean()
    application/x-www-form-urlencoded Form 表单 getStr(key)
    multipart/form-data 文件上传 + 表单 getWebFile() / getStr(key)
    application/xml XML 请求体 getXml() / getXml(Class)
    text/plain 纯文本 getText()
    application/octet-stream 二进制流 getBinaryStream()
    URL 路径参数 @PathVariable 自动合并到 params
    URL Query 参数 ?key=value getStr(key)

    2.2 快速上手

    @RestController
    public class UserController {

    @PostMapping("/user")
    public ResultVO<?> createUser(@RequestVo RequestVO vo) {
    // 单值参数(类型自动转换)
    String name = vo.getStr("name");
    Integer age = vo.getInt("age");
    Long uid = vo.getLong("uid");
    Boolean vip = vo.getBoolean("vip");

    // 数组参数(逗号分隔自动拆分)
    String[] tags = vo.getStrArray("tags");
    Long[] ids = vo.getLongArray("ids");

    // 整体转换为 JavaBean
    User user = vo.toBean(User.class);

    // 获取分页
    Pager pager = vo.getPager();

    return ResultVO.suc(user);
    }
    }

    2.3 请求头:三种方式

    // 1. 获取请求头对象
    RequestHeader header = vo.getHeader();
    String auth = header.get("Authorization");

    // 2. 直接获取指定请求头(更简洁)
    String auth = vo.getHeader("Authorization");

    // 3. 获取所有请求头
    Map<String, String> allHeaders = vo.getHeaders();

    2.4 类型安全取值:get 方法家族

    RequestVO 提供完整的类型安全取值方法,自动处理类型转换:

    // String 取值(Number 自动 toString)
    String name = vo.getStr("name");

    // Integer 取值(String 自动解析,无效抛 ParseException)
    Integer age = vo.getInt("age");

    // Long 取值
    Long uid = vo.getLong("uid");

    // Boolean 取值(支持 true/false、1/0)
    Boolean vip = vo.getBoolean("vip");

    // 指定类型取值(Map/List 自动通过 JSON 库转换)
    UserDTO dto = vo.get("user", UserDTO.class);

    // 数组取值(逗号分隔自动拆分)
    String[] tags = vo.getStrArray("tags"); // "java,spring" → ["java", "spring"]
    Integer[] ids = vo.getIntArray("ids"); // "1,2,3" → [1, 2, 3]
    Long[] uids = vo.getLongArray("uids"); // "100,200" → [100L, 200L]

    Boolean 转换规则:

    • Boolean 类型:直接返回
    • String 类型:"true" → true,"false" → false
    • Number 类型:非 0 → true,0 → false

    2.5 嵌套路径取值:点号语法

    当请求参数包含嵌套对象时,无需手动层层取值,直接用点号路径:

    // 请求体 JSON:
    // {
    // "user": {
    // "address": {
    // "city": "北京",
    // "street": "长安街"
    // }
    // }
    // }

    // 直接取值
    String city = vo.getPathNode("user.address.city", String.class); // "北京"
    String street = vo.getPathNode("user.address.street", String.class); // "长安街"

    规则:

    • 路径中每层必须是 Map 类型(JSON 对象)
    • 不支持数组索引(如 user.cityList[0].city)
    • 路径不存在时返回 null,不会抛异常

    2.6 对象转换:toBean / toListBean

    // 将全部请求参数转换为 JavaBean(JSON 反序列化)
    User user = vo.toBean(User.class);

    // 将 JSON 数组请求体转换为 List(仅 JSON 请求有效)
    List<User> users = vo.toListBean(User.class);

    2.7 文件上传与二进制流

    // 获取 FormData 中的文件
    WebFile webFile = vo.getWebFile();
    if (webFile != null) {
    MultipartFile avatar = webFile.getFile("avatar");
    // 处理文件…
    }

    // 获取原始二进制流(application/octet-stream)
    InputStream binaryStream = vo.getBinaryStream();
    // 注意:二进制流需要调用方自行关闭

    2.8 XML 处理

    // 获取原始 XML 字符串
    String xml = vo.getXml();

    // XML 自动转 Bean(默认根节点为 <data>)
    // 请求体:<data><name>张三</name><age>25</age></data>
    User user = vo.getXml(User.class);

    // 自定义根节点别名
    // 请求体:<root><name>张三</name></root>
    User user2 = vo.getXml(User.class, "root");

    2.9 自定义分页参数名

    @RequestVo 注解支持自定义分页参数名,适配不同前端团队的命名习惯:

    // 前端传 pageNum / size
    @PostMapping("/list")
    public ResultVO<?> list(
    @RequestVo(page = "pageNum", pageSize = "size", defaultPageSize = 20) RequestVO vo
    ) {
    Pager pager = vo.getPager(); // 自动用 pageNum / size 解析
    List<User> users = userService.list(pager);
    return ResultVO.suc(users, pager);
    }


    三、实战:完整 Controller 示例

    以一个用户管理模块为例,展示 ResultVO + RequestVO 的完整配合使用:

    @RestController
    @RequestMapping("/api/users")
    public class UserController {

    /**
    * 查询用户详情
    * GET /api/users/123
    */

    @GetMapping("/{id}")
    public ResultVO<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    if (user == null) {
    return ResultVO.fail(404, "用户不存在");
    }
    return ResultVO.suc(user);
    }

    /**
    * 分页查询用户列表
    * GET /api/users?page=1&pageSize=20
    */

    @GetMapping
    public ResultVO<List<User>> listUsers(@RequestVo RequestVO requestVO) {
    Pager pager = requestVO.getPager();
    List<User> users = userService.list(pager);
    return ResultVO.suc(users, pager);
    }

    /**
    * 创建用户(JSON 请求)
    * POST /api/users
    * Body: {"name": "张三", "age": 25, "address": {"city": "北京"}}
    */

    @PostMapping
    public ResultVO<User> createUser(@RequestVo RequestVO vo) {
    // 取嵌套字段
    String city = vo.getPathNode("address.city", String.class);

    // 整体转 Bean
    User user = vo.toBean(User.class);
    User created = userService.create(user);

    return ResultVO.suc(201, "创建成功", created);
    }

    /**
    * 更新用户(Form 表单)
    * PUT /api/users/123
    * Content-Type: application/x-www-form-urlencoded
    * Body: name=李四&age=30
    */

    @PutMapping("/{id}")
    public ResultVO<User> updateUser(@PathVariable Long id, @RequestVo RequestVO vo) {
    String name = vo.getStr("name");
    Integer age = vo.getInt("age");
    User updated = userService.update(id, name, age);
    return ResultVO.suc(updated);
    }

    /**
    * 上传头像(FormData)
    * POST /api/users/123/avatar
    * Content-Type: multipart/form-data
    */

    @PostMapping("/{id}/avatar")
    public ResultVO<String> uploadAvatar(@PathVariable Long id, @RequestVo RequestVO vo) {
    WebFile webFile = vo.getWebFile();
    if (webFile == null) {
    return ResultVO.fail(400, "请选择文件");
    }
    MultipartFile file = webFile.getFile("avatar");
    String url = fileService.upload(file);
    return ResultVO.suc(url);
    }

    /**
    * 批量删除
    * DELETE /api/users?ids=1,2,3
    */

    @DeleteMapping
    public ResultVO<Void> deleteUsers(@RequestVo RequestVO vo) {
    Long[] ids = vo.getLongArray("ids");
    if (ids == null || ids.length == 0) {
    return ResultVO.fail(400, "请选择要删除的用户");
    }
    userService.deleteByIds(ids);
    return ResultVO.suc();
    }

    /**
    * 接收 XML 请求
    * POST /api/users/xml
    * Content-Type: application/xml
    * Body: <data><name>王五</name><age>28</age></data>
    */

    @PostMapping("/xml")
    public ResultVO<User> createFromXml(@RequestVo RequestVO vo) {
    User user = vo.getXml(User.class);
    User created = userService.create(user);
    return ResultVO.suc(created);
    }
    }


    四、API 速查表

    ResultVO 方法速查

    场景方法示例
    默认成功 suc() ResultVO.suc()
    仅成功消息 sucMsg(String) ResultVO.sucMsg("操作成功")
    成功携带数据 suc(T) ResultVO.suc(user)
    成功携带数据+分页 suc(T, Pager) ResultVO.suc(list, pager)
    自定义状态码+消息 suc(Object, String) ResultVO.suc(200, "成功")
    自定义状态接口 suc(ResultMessage) ResultVO.suc(BizError.SUCCESS)
    默认失败 fail() ResultVO.fail()
    仅失败消息 fail(String) ResultVO.fail("错误")
    失败携带数据 failData(T) ResultVO.failData(errorMap)
    失败自定义消息+数据 failData(String, T) ResultVO.failData("部分失败", data)
    失败自定义状态码+消息 fail(Object, String) ResultVO.fail(400, "无效请求")
    失败自定义状态接口 fail(ResultMessage) ResultVO.fail(BizError.ORDER_NOT_FOUND)

    RequestVO 方法速查

    分类方法返回类型说明
    请求头 getHeader() RequestHeader 获取请求头对象
    getHeader(String key) String 获取指定请求头的值
    getHeaders() Map<String, String> 获取所有请求头
    单值参数 get(String key) Object 获取原始参数值
    get(String key, Class<T>) T 获取参数并转换为目标类型
    getStr(String key) String 获取字符串参数
    getInt(String key) Integer 获取整型参数
    getLong(String key) Long 获取长整型参数
    getBoolean(String key) Boolean 获取布尔参数
    数组参数 getStrArray(String key) String[] 获取逗号分隔的字符串数组
    getIntArray(String key) Integer[] 获取逗号分隔的整型数组
    getLongArray(String key) Long[] 获取逗号分隔的长整型数组
    嵌套路径 getPathNode(String path, Class<T>) T 点号路径取值
    对象转换 toBean(Class<T>) T 全部参数转 JavaBean
    toListBean(Class<T>) List<T> JSON 数组转 List
    请求体原始内容 getBody() String 原始请求体字符串
    getText() String 纯文本请求体
    getXml() String XML 请求体字符串
    XML 处理 getXml(Class<T>) T XML 转 Bean(默认根节点 <data>)
    getXml(Class<T>, String) T XML 转 Bean(自定义根节点别名)
    分页 getPager() Pager 获取分页对象
    文件与流 getWebFile() WebFile 获取表单上传文件对象
    getBinaryStream() InputStream 获取原始二进制输入流

    五、最佳实践与注意事项

    5.1 响应设计最佳实践

  • 统一使用 ResultVO 封装所有接口返回,包括异常响应。配合 @RestControllerAdvice 全局异常处理:
  • @RestControllerAdvice
    public class GlobalExceptionHandler {

    @ExceptionHandler(BizException.class)
    public ResultVO<?> handleBizException(BizException e) {
    return ResultVO.fail(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultVO<?> handleValidation(MethodArgumentNotValidException e) {
    String msg = e.getBindingResult().getFieldErrors().stream()
    .map(FieldError::getDefaultMessage)
    .collect(Collectors.joining("; "));
    return ResultVO.fail(400, msg);
    }
    }

  • 业务状态码用枚举管理,实现 ResultMessage 接口,避免硬编码。
  • 分页数据与列表数据平铺返回,前端直接取 pager.total,无需嵌套解析。
  • 5.2 请求参数最佳实践

  • 优先使用 toBean() 整体转换,减少逐个字段取值。
  • 嵌套取值用 getPathNode(),避免手写多层 Map.get()。
  • 文件上传和 JSON 不要混用,multipart/form-data 中的 JSON 字段需要特殊处理。
  • 二进制流需要调用方自行关闭,避免资源泄漏。
  • 5.3 注意事项

    问题说明
    code 类型不一致 同一个项目中应保持 code 类型统一(Integer 或 String),不要混用
    toBean() 仅对 JSON 有效 Form 表单参数需逐个 getStr() 取值,或先封装为 Map
    toListBean() 仅对 JSON 数组有效 其他请求类型返回空 List,不会抛异常
    getPathNode() 不支持数组索引 路径中不能包含 [0] 这样的数组下标
    ResultFieldMapping 全局生效 一个 Spring 容器中只能有一个实现,多项目需注意 Bean 名称冲突

    六、总结

    ResultVO + RequestVO 提供了一套完整的 API 设计规范:

    维度ResultVORequestVO
    核心职责 统一响应结构 统一请求封装
    类型支持 code 支持 Integer / String 支持 JSON/Form/FormData/XML/二进制流
    分页设计 Pager 与 data 平铺 自动解析分页参数,支持自定义参数名
    字段自定义 ResultFieldMapping 接口
    状态管理 ResultMessage 接口
    参数取值 类型安全取值 + 嵌套路径 + 对象转换

    两者的配合使用,可以让你的 API 设计更加规范、前后端协作更加高效。无论是互联网项目还是企业级应用,这套设计体系都能很好地适配。

    源码地址:pan-common


    如果这篇文章对你有帮助,欢迎点赞、收藏、关注,后续将持续更新 Java 工具类实战系列。

    赞(0)
    未经允许不得转载:171主机测评 » 打造 Java Spring Boot 标准化 API : 告别请求、响应重复造轮子!ResultVO + RequestVO
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址