跳转至

后端统一接口返回

在前后端分离的项目中一般都会对后端提供的接口作出统一接口返回的要求,而不同的统一接口返回也会使得返回规范难度及代码优雅程度出现差异。

目前来讲在后端中统一接口返回的方式一般有以下两种:

1. 统一返回工具类

项目中最常能见到的方式,便是单独写一个工具类或返回信息类,在该类中封装好各类返回信息从而统一返回的格式。

返回工具类的参数

  • code:返回码
  • message:返回的描述信息
  • data:返回的数据

示例

以下为统一返回工具类的示例。一般该类会使用静态方法从而方便实际的使用;另需要注意的是该示例中的返回码较为简单且直接写在工具类中,实际项目中应用时也会考虑实现一个枚举类提供作为状态码使用

@Data
@Accessors(chain = true)
public class ResultCommon implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    public static int SUCCESS = 200;

    public static int ERROR = 404;

    @ApiModelProperty("返回码")
    private int code;

    @ApiModelProperty("返回信息")
    private String message;

    @ApiModelProperty("返回数据")
    private Map<String, Object> data = new HashMap<>();

    public ResultCommon() {
    }

    public static ResultCommon success() {
        ResultCommon resultCommon = new ResultCommon();
        resultCommon.setCode(SUCCESS);
        resultCommon.setMessage("成功");
        return resultCommon;
    }

    public static ResultCommon fail() {
        ResultCommon resultCommon = new ResultCommon();
        resultCommon.setCode(ERROR);
        resultCommon.setMessage("失败");
        return resultCommon;
    }

    public ResultCommon setData(String key, Object value) {
        this.getData().put(key, value);
        return this;
    }
}

使用

    return ResultCommon.success().setData("items", items);

2. ResponseAdvice

可以知道,上述统一返回的方法很有可能使得 Controller 中出现大量重复的使用代码,从而使得代码冗余。

针对这个问题,SpringMVC 4.1 提供的一个以 AOP 为原理的解决方法:ResponseBodyAdvice接口。

该接口会保证在 Controller 处理方法返回时处理返回体 ResponseBody 的切面中最后进行,因此也可以用来封装自定义 GlobalExceptionHandler 的 Advice 返回。

示例

使用 ResponseBodyAdvice 时需要自行实现该接口,实现其 supports()、beforeBodyWrite() 方法,并在实现类上添加 @ControllerAdvice 或 @RestControllerAdvice 注解。

其中,supports() 方法返回一个布尔值,每个返回在被拦截时执行该方法从而判断是否需要执行 beforeBodyWrite() 方法,因此可以用以规避像 swagger 的接口文档中的返回遭 ResponseAdvice 封装;而 beforeBodyWrite() 方法则是对通过 supports() 方法验证的返回进行封装,需留意的是若返回体(也即本次请求 Controller 中的处理方法返回值) body 为 String 类型时,默认会将其转换为 "text/plain" 类型,因此需要通过SpringBoot内置提供Jackson序列化ObjectMapper实现实体信息的序列化,将其转换为 "application/json;charset=utf-8" 类型。

@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 是否开启Advice
     * @param methodParameter
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        // 放行swagger
        return !methodParameter.getDeclaringClass().getName().contains("springfox");
    }

    /**
     * 统一处理返回结果,减少如ResultCommon.success().setData("items", obj)的代码
     * @param body
     * @param returnType
     * @param mediaType
     * @param aClass
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if (body instanceof String) {
            // String类单独处理
            try {
                return objectMapper.writeValueAsString(ResultCommon.success().setData("msg", body));
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }

        // 对于已封装的ResultCommon不进行处理,避免嵌套重复处理并提高灵活性
        if (body instanceof ResultCommon) {
            return body;
        }

        if (body instanceof Boolean) {
            return (Boolean) body ? ResultCommon.success() : ResultCommon.fail();
        }

        if (body == null) {
            return ResultCommon.success();
        }

        // 封装为ResultCommon,其中载荷为items
        return ResultCommon.success().setData("items", body);
    }
}