Spring Boot:统一返回格式,这样搞就对了。
Spring统一数据返回格式的核心思想是:强制所有Controller的返回值遵循同一个JSON结构,从而使前端处理响应时有一致的规范。
为什么要统一数据返回格式?
- 前端处理方便:前端不需要为每个接口判断不同的响应结构,只需处理一种固定格式。
- 便于协作:前后端约定好格式后,可以并行开发。
- 标准化:包含状态码、信息、数据等标准字段,易于理解和维护。
- 易于扩展:可以统一添加如
timestamp
、path
等通用字段。
标准的统一响应体格式
一个通用的响应体通常包含以下字段:
字段名 | 类型 | 必须 | 说明 |
---|---|---|---|
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); // 手动包装}@PostMappingpublic Result<String> createUser(@RequestBody User user) {userService.save(user);return Result.success("用户创建成功"); // 手动包装}
}
缺点:每个方法都要写Result.success(...)
,代码重复。
方案二:使用@ControllerAdvice注解(推荐,优雅)
通过实现ResponseBodyAdvice
接口,在数据写入Response Body之前进行拦截和包装。
步骤:
- 定义统一响应体
Result
(同上)。 - 实现
ResponseBodyAdvice
:
@RestControllerAdvice(basePackages = "com.yourpackage.controller") // 指定要拦截的包
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {/*** 判断是否支持advice功能* 返回true表示支持,会执行beforeBodyWrite方法*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {// 排除掉已经封装成Result的返回值、Swagger的响应等return !returnType.getParameterType().isAssignableFrom(Result.class) &&!returnType.hasMethodAnnotation(ResponseBody.class); // 通常不需要这个检查,但根据情况定}/*** 对响应体进行实际包装*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 如果返回的是String类型,需要特殊处理(因为String的转换器不同)if (body instanceof String) {// 通常不会直接返回String,如果返回了,需要手动转成JSON// 实际开发中应避免Controller直接返回Stringreturn JSON.toJSONString(Result.success(body));}// 如果返回体已经是Result类型(比如全局异常处理器返回的),则直接返回if (body instanceof Result) {return body;}// 最普遍的情况:包装成Result格式return Result.success(body);}
}
- Controller保持干净:
@RestController
@RequestMapping("/user")
public class UserController {// 直接返回数据对象,GlobalResponseHandler会自动帮你包装成Result格式@GetMapping("/{id}")public User getUser(@PathVariable Long id) {return userService.getById(id);}@PostMappingpublic 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项目中实现统一返回格式最主流、最优雅的方式。