本文目录导读:

- 最基础的实现:通用响应类
- 使用枚举管理状态码
- 利用 Spring Boot 的全局异常处理
- 使用 Spring 的 ResponseBodyAdvice 自动包装
- 结合 DTO 与 Jackson 注解(精细化控制)
- 完整企业级示例(整合以上所有技术)
- 最佳实践建议
最基础的实现:通用响应类
这是最直接的方式,定义一个泛型类包装所有返回数据。
public class ApiResult<T> {
private int code; // 状态码
private String message; // 提示信息
private T data; // 数据主体
// 构造方法私有化,强制使用静态工厂方法
private ApiResult() {}
public static <T> ApiResult<T> success(T data) {
ApiResult<T> result = new ApiResult<>();
result.code = 200;
result.message = "success";
result.data = data;
return result;
}
public static <T> ApiResult<T> success(String message, T data) {
ApiResult<T> result = new ApiResult<>();
result.code = 200;
result.message = message;
result.data = data;
return result;
}
public static <T> ApiResult<T> error(int code, String message) {
ApiResult<T> result = new ApiResult<>();
result.code = code;
result.message = message;
result.data = null;
return result;
}
// getter/setter 省略
}
使用示例:
@RestController
public class UserController {
@GetMapping("/user/{id}")
public ApiResult<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ApiResult.error(404, "用户不存在");
}
return ApiResult.success(user);
}
}
使用枚举管理状态码
定义统一的状态码枚举,避免硬编码数字。
public enum ResultCode {
SUCCESS(200, "操作成功"),
FAIL(500, "操作失败"),
NOT_FOUND(404, "资源不存在"),
UNAUTHORIZED(401, "未授权");
private final int code;
private final String message;
ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
public int code() { return code; }
public String message() { return message; }
}
然后修改 ApiResult:
public static <T> ApiResult<T> success(T data) {
return success(ResultCode.SUCCESS.message(), data);
}
public static <T> ApiResult<T> error(ResultCode resultCode) {
return error(resultCode.code(), resultCode.message());
}
利用 Spring Boot 的全局异常处理
结合 @RestControllerAdvice 和 @ExceptionHandler,实现所有异常统一响应。
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ApiResult<?> handleBusinessException(BusinessException e) {
return ApiResult.error(e.getCode(), e.getMessage());
}
// 处理参数校验失败
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResult<?> handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult()
.getFieldErrors().stream()
.map(er -> er.getField() + ": " + er.getDefaultMessage())
.collect(Collectors.joining(", "));
return ApiResult.error(400, msg);
}
// 兜底处理所有未捕获异常
@ExceptionHandler(Exception.class)
public ApiResult<?> handleException(Exception e) {
log.error("系统异常", e);
return ApiResult.error(500, "服务器内部错误");
}
}
自定义业务异常类:
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() { return code; }
}
这样控制器只需正常抛出异常即可,无需手动包装:
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
throw new BusinessException(404, "用户不存在");
}
return user;
}
Spring 会自动将返回的 User 对象包装进 ApiResult,异常也按配置的格式返回。
使用 Spring 的 ResponseBodyAdvice 自动包装
如果你不想在每个控制器方法上都写 ApiResult.success(...),可以实现 ResponseBodyAdvice 对返回结果统一包装。
@RestControllerAdvice
public class ResponseWrapper implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 对所有返回值都生效(可排除某些类或包)
return !returnType.getParameterType().equals(ApiResult.class);
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
// 如果已经是 ApiResult 类型,不再包装
if (body instanceof ApiResult) {
return body;
}
// 返回 void 的方法 body 为 null,可以改为成功
if (body == null) {
return ApiResult.success(null);
}
return ApiResult.success(body);
}
}
优点: 控制器方法可以返回纯粹的实体对象,框架自动包装。 注意: 需要妥善处理返回 String 类型时的序列化问题(因为 StringHttpMessageConverter 会优先处理)。
结合 DTO 与 Jackson 注解(精细化控制)
可以使用 @JsonInclude、@JsonProperty 控制序列化行为:
@Data
@JsonInclude(JsonInclude.Include.NON_NULL) // null字段不序列化
public class ApiResult<T> {
private int code;
private String message;
private T data;
private Long timestamp; // 可加时间戳
public ApiResult() {
this.timestamp = System.currentTimeMillis();
}
// 静态工厂方法同上...
}
完整企业级示例(整合以上所有技术)
// ========= 1. 状态码枚举 =========
public enum ResultCode {
SUCCESS(200, "成功"),
BAD_REQUEST(400, "请求参数错误"),
UNAUTHORIZED(401, "未登录"),
FORBIDDEN(403, "权限不足"),
NOT_FOUND(404, "资源不存在"),
INTERNAL_ERROR(500, "服务器内部错误");
final int code;
final String message;
// 构造、getter...
}
// ========= 2. 统一响应类 =========
public class R<T> {
private int code;
private String message;
private T data;
private long timestamp;
private R() { this.timestamp = System.currentTimeMillis(); }
public static <T> R<T> ok(T data) {
R<T> r = new R<>();
r.code = ResultCode.SUCCESS.code;
r.message = ResultCode.SUCCESS.message;
r.data = data;
return r;
}
public static <T> R<T> fail(ResultCode resultCode) {
return fail(resultCode.code, resultCode.message);
}
public static <T> R<T> fail(int code, String message) {
R<T> r = new R<>();
r.code = code;
r.message = message;
return r;
}
// getter...
}
// ========= 3. 全局异常处理 =========
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<?> handle(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining("; "));
return R.fail(ResultCode.BAD_REQUEST.code, msg);
}
@ExceptionHandler(BusinessException.class)
public R<?> handle(BusinessException e) {
return R.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public R<?> handle(Exception e) {
log.error("异常", e);
return R.fail(ResultCode.INTERNAL_ERROR);
}
}
// ========= 4. 控制器示例 =========
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public R<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return R.fail(ResultCode.NOT_FOUND);
}
return R.ok(user);
}
@PostMapping
public R<Void> createUser(@Valid @RequestBody UserCreateDTO dto) {
userService.create(dto);
return R.ok(null);
}
}
前端接收到的 JSON 格式统一为:
{
"code": 200,
"message": "成功",
"data": { ... },
"timestamp": 1695000000000
}
最佳实践建议
| 场景 | 推荐方案 |
|---|---|
| 小型项目 | 方案一 + 方案三(纯手工包装) |
| 中型项目 | 方案二(枚举) + 方案三(全局异常处理) |
| 大型微服务 | 方案四(ResponseBodyAdvice自动包装)+ 方案三 |
| 前后端分离 | 方案五(Null控制) + 详细的状态码文档 |
| 需要国际化 | 消息从配置文件读取,结合 locale 动态设置 |
核心原则:
- 所有接口响应格式完全一致(code + message + data)
- 成功和异常都要统一(异常由全局处理器接管)
- 状态码要有明确的业务含义,不要仅用 200/500
- 避免暴露内部实现细节(如数据库错误堆栈)
这样前端可以写一个统一的 request.js 拦截器,根据 code 做出全局响应处理(如 401 跳登录、500 弹错误提示等)。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播