首页  编辑  

SpringBoot对API返回结果统一包装

Tags: /Java/   Date Created:
springboot对返回值作统一处理方式(@RestControllerAdvice+ResponseBodyAdvice接口)
前后端分离的情况下,后端API返回的结果,一般都是一个JSON,类似下面:
{
    "code": 0,
    "msg": "message",
    "data": object
}
如果我们按上面统一的格式处理返回值,那么可以按下面方法:
  • 定义统一的类
public class Response<T> {
    private int code;
    private String msg;
    private T data;

    public Response(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> Response<T> success(T data) {
        return new Response<>(0, "success", data);
    }

    public static <T> Response<T> error(String msg) {
        return new Response<>(-1, msg, null);
    }

    // Getters and Setters
}
  • 实现@ControllerAdvice包装返回值
统一处理异常的返回情况:
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Response<Object> handleException(Exception e) {
        // 你可以自定义不同的异常处理逻辑
        return Response.error(e.getMessage());
    }
}
正常结果包装处理:
@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 这里可以根据需要进行条件判断
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {

        // 如果返回值已经是Response类型,则直接返回
        if (body instanceof Response) {
            return body;
        }

        // 统一包装返回值
        return Response.success(body);
    }
}
  • 在类方法中,直接返回业务类结果即可
@RestController
public class TestController {

    @GetMapping("/user/get")
    public User get() {
        return new User("小明","男");
    }

}
上面的方法会对所有Controller所有方法,均按统一的格式返回,但如果不想对所有的都做类似处理,而是希望有选择性对某些类或者方法返回,可以考虑使用注解的方式实现:
  • 定义自定义注解
首先,定义一个自定义注解,比如 @ApiResponseWrapper,用于标记需要统一包装返回值的类或方法。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiResponseWrapper {
}
  • 修改全局返回值处理器
在全局返回值处理器中,通过检查方法或类上是否存在 @ApiResponseWrapper 注解,来决定是否进行包装。
@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 检查方法或类上是否存在@ApiResponseWrapper注解
        return returnType.getContainingClass().isAnnotationPresent(ApiResponseWrapper.class) ||
               returnType.hasMethodAnnotation(ApiResponseWrapper.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {

        // 如果返回值已经是Response类型,则直接返回
        if (body instanceof Response) {
            return body;
        }

        // 统一包装返回值
        return Response.success(body);
    }
}
  • 使用示例
现在,你可以在需要统一包装返回值的类或方法上使用 @ApiResponseWrapper 注解。
在类上使用注解:
如果你在类上使用注解,类中的所有方法的返回值都会被包装。
@RestController
@RequestMapping("/api")
@ApiResponseWrapper
public class UserController {

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
        return new User(id, "John Doe");
    }
}
在方法上使用注解:
如果你只想对特定方法进行包装,可以在方法上使用注解。
@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/user/{id}")
    @ApiResponseWrapper
    public User getUser(@PathVariable Long id) {
        return new User(id, "John Doe");
    }

    @GetMapping("/anotherEndpoint")
    public String anotherEndpoint() {
        return "This response will not be wrapped";
    }
}
当然我们也可以使用请求、响应包装器来实现:
如果你需要对请求的body或者是响应的body进行拦截,Spring已经为我们提供了这样的包装器,可直接使用,如下示例:
@Component
public class ContentFilter extends OncePerRequestFilter {

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
    ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request) ;
    ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response) ;
    filterChain.doFilter(requestWrapper, responseWrapper) ;
    byte[] responseBody = responseWrapper.getContentAsByteArray();
    MessageDigest md5Digest = MessageDigest.getInstance("MD5");
    byte[] md5Hash = md5Digest.digest(responseBody);
    String md5HashString = DatatypeConverter.printHexBinary(md5Hash);
    responseWrapper.setHeader("X-API-SIGN", md5HashString) ;
    // 必须调用,否则响应无法输出到客户端
    responseWrapper.copyBodyToResponse();
  }
}
上面示例中将响应body内容生成签名信息设置到响应header中,响应结果: