3.8统一响应格式

分类: 搭建单体商城服务

统一响应格式

统一的响应格式能够提高 API 的一致性和可维护性,方便前端处理。本节将学习如何实现统一的响应格式。

本节将学习:Result 封装类、状态码定义、统一异常处理,以及 @ControllerAdvice 使用。

Result 封装类

Result 类设计

package com.example.ecommerce.common; import lombok.Data; @Data public class Result<T> { /** * 状态码 */ private Integer code; /** * 消息 */ private String message; /** * 数据 */ private T data; /** * 时间戳 */ private Long timestamp; /** * 成功响应 */ public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("Success"); result.setData(data); result.setTimestamp(System.currentTimeMillis()); return result; } /** * 成功响应(无数据) */ public static <T> Result<T> success() { return success(null); } /** * 成功响应(自定义消息) */ public static <T> Result<T> success(String message, T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage(message); result.setData(data); result.setTimestamp(System.currentTimeMillis()); return result; } /** * 错误响应 */ public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); result.setTimestamp(System.currentTimeMillis()); return result; } /** * 错误响应(默认状态码) */ public static <T> Result<T> error(String message) { return error(500, message); } }

Result 使用示例

@RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") public Result<User> getUser(@PathVariable Long id) { User user = userService.getById(id); return Result.success(user); } @PostMapping public Result<User> createUser(@RequestBody User user) { User created = userService.register(user); return Result.success("User created successfully", created); } }

状态码定义

HTTP 状态码

HTTP 状态码使用:

  • 200 OK:请求成功
  • 201 Created:资源创建成功
  • 400 Bad Request:请求参数错误
  • 401 Unauthorized:未授权
  • 403 Forbidden:禁止访问
  • 404 Not Found:资源不存在
  • 500 Internal Server Error:服务器内部错误

业务状态码

package com.example.ecommerce.common; public class ResultCode { /** * 成功 */ public static final Integer SUCCESS = 200; /** * 参数错误 */ public static final Integer BAD_REQUEST = 400; /** * 未授权 */ public static final Integer UNAUTHORIZED = 401; /** * 禁止访问 */ public static final Integer FORBIDDEN = 403; /** * 资源不存在 */ public static final Integer NOT_FOUND = 404; /** * 服务器错误 */ public static final Integer INTERNAL_SERVER_ERROR = 500; /** * 业务错误 */ public static final Integer BUSINESS_ERROR = 1000; }

状态码使用

统一异常处理

自定义异常类

package com.example.ecommerce.exception; import com.example.ecommerce.common.ResultCode; public class BusinessException extends RuntimeException { private Integer code; public BusinessException(String message) { super(message); this.code = ResultCode.BUSINESS_ERROR; } public BusinessException(Integer code, String message) { super(message); this.code = code; } public Integer getCode() { return code; } }

全局异常处理器

package com.example.ecommerce.exception; import com.example.ecommerce.common.Result; import com.example.ecommerce.common.ResultCode; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { /** * 业务异常 */ @ExceptionHandler(BusinessException.class) @ResponseStatus(HttpStatus.OK) public Result<?> handleBusinessException(BusinessException e) { return Result.error(e.getCode(), e.getMessage()); } /** * 参数校验异常 */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { String message = e.getBindingResult().getFieldErrors().stream() .map(FieldError::getDefaultMessage) .collect(Collectors.joining(", ")); return Result.error(ResultCode.BAD_REQUEST, message); } /** * 绑定异常 */ @ExceptionHandler(BindException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Result<?> handleBindException(BindException e) { String message = e.getBindingResult().getFieldErrors().stream() .map(FieldError::getDefaultMessage) .collect(Collectors.joining(", ")); return Result.error(ResultCode.BAD_REQUEST, message); } /** * 其他异常 */ @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Result<?> handleException(Exception e) { e.printStackTrace(); return Result.error(ResultCode.INTERNAL_SERVER_ERROR, "Internal server error"); } }

@ControllerAdvice 使用

@ControllerAdvice 说明

@ControllerAdvice 是一个全局异常处理注解,可以处理所有 Controller 抛出的异常。

异常处理流程

响应拦截器

ResponseBodyAdvice

package com.example.ecommerce.config; import com.example.ecommerce.common.Result; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @RestControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { // 如果返回类型已经是 Result,则不处理 return !returnType.getParameterType().equals(Result.class); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 如果返回类型是 Result,直接返回 if (body instanceof Result) { return body; } // 否则包装成 Result return Result.success(body); } }

完整示例

使用统一响应格式

@RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public Result<User> getUser(@PathVariable Long id) { User user = userService.getById(id); if (user == null) { throw new BusinessException(ResultCode.NOT_FOUND, "User not found"); } return Result.success(user); } @PostMapping public Result<User> createUser(@RequestBody @Valid User user) { User created = userService.register(user); return Result.success("User created successfully", created); } }

响应格式示例

成功响应

{ "code": 200, "message": "Success", "data": { "id": 1, "username": "admin", "email": "admin@example.com" }, "timestamp": 1703123456789 }

错误响应

{ "code": 1000, "message": "Username already exists", "data": null, "timestamp": 1703123456789 }

官方资源

本节小结

在本节中,我们学习了:

第一个是 Result 封装类。 统一封装响应结果,包含状态码、消息、数据和时间戳。

第二个是状态码定义。 定义 HTTP 状态码和业务状态码。

第三个是统一异常处理。 使用 @RestControllerAdvice 实现全局异常处理。

第四个是 @ControllerAdvice 使用。 处理所有 Controller 抛出的异常。

这就是统一响应格式。统一的响应格式提高了 API 的一致性和可维护性。

在下一节,我们将学习如何实现参数校验,确保请求参数的有效性。