Spring Boot:统一返回格式,这样搞就对了。

发布于:2025-08-29 ⋅ 阅读:(18) ⋅ 点赞:(0)

Spring统一数据返回格式的核心思想是:强制所有Controller的返回值遵循同一个JSON结构,从而使前端处理响应时有一致的规范。


为什么要统一数据返回格式?

  1. 前端处理方便:前端不需要为每个接口判断不同的响应结构,只需处理一种固定格式。
  2. 便于协作:前后端约定好格式后,可以并行开发。
  3. 标准化:包含状态码、信息、数据等标准字段,易于理解和维护。
  4. 易于扩展:可以统一添加如timestamppath等通用字段。

标准的统一响应体格式

一个通用的响应体通常包含以下字段:

字段名 类型 必须 说明
code Integer 业务状态码(或直接用HTTP状态码)。例如:200成功,500服务器错误。
message String 对本次响应的提示信息,如“操作成功”、“用户不存在”等。
data Object 返回的有效负载数据,可以是任意JSON类型(对象、数组、字符串等)。
timestamp Long 响应时间戳(毫秒),便于调试和记录。

JSON示例:

成功响应:

{
  "code": 200,
  "message": "请求成功",
  "data": {
    "id": 1,
    "name": "张三",
    "age": 25
  },
  "timestamp": 1719371635110
}

失败/异常响应:

{
  "code": 500,
  "message": "系统内部异常,请联系管理员",
  "data": null,
  "timestamp": 1719371635110
}

实现方案(三种,推荐方案三)

方案一:手动封装(不推荐,繁琐)

在每个Controller方法中手动创建响应对象。

// 1. 首先定义一个通用的响应类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp = System.currentTimeMillis();

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "成功", data, System.currentTimeMillis());
    }

    public static <T> Result<T> error(Integer code, String message) {
        return new Result<>(code, message, null, System.currentTimeMillis());
    }
}

// 2. 在Controller中手动使用
@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        User user = userService.getById(id);
        return Result.success(user); // 手动包装
    }

    @PostMapping
    public Result<String> createUser(@RequestBody User user) {
        userService.save(user);
        return Result.success("用户创建成功"); // 手动包装
    }
}

缺点:每个方法都要写Result.success(...),代码重复。


方案二:使用@ControllerAdvice注解(推荐,优雅)

通过实现ResponseBodyAdvice接口,在数据写入Response Body之前进行拦截和包装。

步骤:

  1. 定义统一响应体Result(同上)。
  2. 实现ResponseBodyAdvice
@RestControllerAdvice(basePackages = "com.yourpackage.controller") // 指定要拦截的包
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {

    /**
     * 判断是否支持advice功能
     * 返回true表示支持,会执行beforeBodyWrite方法
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        // 排除掉已经封装成Result的返回值、Swagger的响应等
        return !returnType.getParameterType().isAssignableFrom(Result.class) &&
               !returnType.hasMethodAnnotation(ResponseBody.class); // 通常不需要这个检查,但根据情况定
    }

    /**
     * 对响应体进行实际包装
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 如果返回的是String类型,需要特殊处理(因为String的转换器不同)
        if (body instanceof String) {
            // 通常不会直接返回String,如果返回了,需要手动转成JSON
            // 实际开发中应避免Controller直接返回String
            return JSON.toJSONString(Result.success(body));
        }
        // 如果返回体已经是Result类型(比如全局异常处理器返回的),则直接返回
        if (body instanceof Result) {
            return body;
        }
        // 最普遍的情况:包装成Result格式
        return Result.success(body);
    }
}
  1. Controller保持干净
@RestController
@RequestMapping("/user")
public class UserController {

    // 直接返回数据对象,GlobalResponseHandler会自动帮你包装成Result格式
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getById(id);
    }

    @PostMapping
    public String createUser(@RequestBody User user) {
        userService.save(user);
        return "用户创建成功"; // 不推荐直接返回String,容易出问题
    }
    
    // 返回null也可以
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.removeById(id);
    }
}

注意:直接返回String类型可能会引发异常,因为Spring有专门的StringHttpMessageConverter。建议避免Controller直接返回String,或者在你的beforeBodyWrite方法中做好特殊处理(如上例所示)。


方案三:结合全局异常处理(最完整方案)

通常,方案二会和全局异常处理一起使用,以统一成功和失败的格式。

@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 捕获所有未知异常
     */
    @ExceptionHandler(Exception.class)
    public Result<String> handleException(Exception e) {
        // 记录日志...
        return Result.error(500, "系统繁忙,请稍后再试: " + e.getMessage());
    }

    /**
     * 捕获业务异常(假设你有一个自定义的BusinessException)
     */
    @ExceptionHandler(BusinessException.class)
    public Result<String> handleBusinessException(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }
}

这样,无论是正常响应还是异常抛出,最终返回给前端的都是统一的Result格式。


总结

方案 优点 缺点 推荐度
手动封装 简单直观,控制灵活 代码重复,容易遗漏 ⭐⭐
@ControllerAdvice 自动化,代码整洁,非侵入式 需要处理String类型的特殊情况 ⭐⭐⭐⭐⭐
结合异常处理 最完整的方案,统一所有响应 配置稍复杂 ⭐⭐⭐⭐⭐

最佳实践:采用方案三(@ControllerAdvice + 全局异常处理),这是目前Spring Boot项目中实现统一返回格式最主流、最优雅的方式。


网站公告

今日签到

点亮在社区的每一天
去签到