Spring Boot接口返回统一格式

统一的标准数据格式好处

SpringBoot返回统一的标准数据格式主要有以下几点好处:

  • 增强接口的可读性和可维护性,使得前端开发人员能够更加清晰地理解接口返回的数据结构,从而提高开发效率。

  • 降低前后端耦合度,当后端需要修改返回数据结构时,只需要调整统一的数据格式,而不用修改大量的前端代码。

  • 有利于统一异常处理,对于出错时的返回数据格式也是一致的,便于前端统一处理错误信息。

  • 当接口版本的升级和兼容时,可以通过统一的数据格式进行向下兼容或者向上兼容,减少接口变更带来的影响

格式不同的痛楚

一般情况下,SpringBoot服务的返回格式有如下几种:

第一种:返回String

复制代码
@GetMapping("/hello")
public String getStr() {
    return "hello";
}

此时调用接口获取到的返回值是这样:

复制代码
hello

第二种:返回自定义对象

复制代码
@GetMapping("/aniaml")
public Aniaml getAniaml(){
  Aniaml aniaml = new Aniaml(1,"pig");
  return aniaml;
}

此时调用接口获取到的返回值是这样:

复制代码
{
  "id": 1,
  "name": "pig"
}

第三种:接口异常

复制代码
@GetMapping("/error")
public int error(){
    int i = 10/0;
    return i;
}

此时调用接口获取到的返回值是这样:

复制代码
{
  "timestamp": "2021-07-08T08:05:15.423+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "path": "/error"
}

看了上边的几种情况,不难发现,一个接口在不同情况下,返回的数据格式竟然截然不同?那作为API的使用方估计就要骂娘了。此时,返回统一的标准数据格式就显得很有必要。

标准格式

一个标准的返回格式应包含以下三部分:

  • Status(状态):由后端统一定义各种返回结果的状态码。

  • Message(描述):描述本次接口调用的结果信息。

  • Data(数据):包含本次返回的具体数据内容。

    {
    "status":"100",
    "message":"操作成功",
    "data":"hello"
    }

当然了这并不是绝对的,在有了主要的字段后,可以按需加入其他扩展值,比如我们就在返回对象中添加了接口调用时间timestamp等。

定义返回对象

复制代码
@Data
public class ResultData<T> {


private int status;
  private String message;
  private T data;
  private long timestamp ;




  public ResultData (){
    this.timestamp = System.currentTimeMillis();
  }




  public static <T> ResultData<T> success(T data) {
    ResultData<T> resultData = new ResultData<>();
    resultData.setStatus(ReturnCode.RC100.getCode());
    resultData.setMessage(ReturnCode.RC100.getMessage());
    resultData.setData(data);
    return resultData;
  }


  public static <T> ResultData<T> fail(int code, String message) {
    ResultData<T> resultData = new ResultData<>();
    resultData.setStatus(code);
    resultData.setMessage(message);
    return resultData;
  }


}

定义状态码

复制代码
public enum ReturnCode {
    /**操作成功**/
    RC100(100,"操作成功"),
    /**操作失败**/
    RC999(999,"操作失败"),
    /**服务限流**/
    RC200(200,"服务开启限流保护,请稍后再试!"),
    /**服务降级**/
    RC201(201,"服务开启降级保护,请稍后再试!"),
    /**热点参数限流**/
    RC202(202,"热点参数限流,请稍后再试!"),
    /**系统规则不满足**/
    RC203(203,"系统规则不满足要求,请稍后再试!"),
    /**授权规则不通过**/
    RC204(204,"授权规则不通过,请稍后再试!"),
    /**access_denied**/
    RC403(403,"无访问权限,请联系管理员授予权限"),
    /**access_denied**/
    RC401(401,"匿名用户访问无权限资源时的异常"),
    /**服务异常**/
    RC500(500,"系统异常,请稍后重试"),


    CLIENT_AUTHENTICATION_FAILED(1001,"客户端认证失败"),
    USERNAME_OR_PASSWORD_ERROR(1002,"用户名或密码错误"),
    UNSUPPORTED_GRANT_TYPE(1003, "不支持的认证模式");


    /**自定义状态码**/
    private final int code;
    /**自定义描述**/
    private final String message;


    ReturnCode(int code, String message){
        this.code = code;
        this.message = message;
    }




    public int getCode() {
        return code;
    }


    public String getMessage() {
        return message;
    }
}

统一返回格式

经过上边我们定义的数据格式,再次调用API,在Controller层通过ResultData.success()对返回结果进行包装后返回给前端。

复制代码
@GetMapping("/hello")
public String getStr() {
    return ResultData.success("hello");
}

此时调用接口获取到的返回值已经是标准的格式,符合我们预期的效果。

复制代码
{
  "status": 100,
  "message": "hello",
  "data": null,
  "timestamp": 1625736481648
}

最大的弊端就是我们后面每写一个接口都需要调用ResultData.success()这行代码对结果进行包装,重复劳动,浪费体力;而且还很容易被其他老鸟给嘲笑。所以呢我们需要对代码进行优化,目标就是不要每个接口都手工制定ResultData返回值。

高级实现方式

要优化这段代码很简单,我们只需要借助SpringBoot提供的ResponseBodyAdvice即可。

"

ResponseBodyAdvice的作用:拦截Controller方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等等。

"

先来看下ResponseBodyAdvice的源码:

复制代码
public interface ResponseBodyAdvice<T> {
  /**
  * 是否支持advice功能
  * true 支持,false 不支持
  */
    boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2);


   /**
  * 对返回的数据进行处理
  */
    @Nullable
    T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4, ServerHttpRequest var5, ServerHttpResponse var6);
}

我们只需要编写一个具体实现类即可

复制代码
/**
 * @author jam
 * @date 2021/7/8 10:10 上午
 */
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
    @Autowired
    private ObjectMapper objectMapper;


    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }


    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if(o instanceof String){
            return objectMapper.writeValueAsString(ResultData.success(o));
        }        
        return ResultData.success(o);
    }
}

需要注意两个地方:
@RestControllerAdvice注解

  • @RestControllerAdvice@RestController注解的增强,可以实现三个方面的功能:
  1. 全局异常处理

  2. 全局数据绑定

  3. 全局数据预处理

  • String类型判断

    if(o instanceof String){
    return objectMapper.writeValueAsString(ResultData.success(o));
    }

这段代码一定要加,如果Controller直接返回String的话,SpringBoot是直接返回,故我们需要手动转换成json。

经过上面的处理我们就再也不需要通过ResultData.success()来进行转换了,直接返回原始数据格式,SpringBoot自动帮我们实现包装类的封装。

复制代码
@GetMapping("/hello")
public String getStr(){
    return "hello";
}

此时我们调用接口返回的数据结果为:

复制代码
@GetMapping("/hello")
public String getStr(){
  return "hello";
}

是不是感觉很完美,别急,还有个问题在等着你呢。

接口异常问题

此时有个问题,由于我们没对Controller的异常进行处理,当我们调用的方法一旦出现异常,就会出现问题,比如下面这个接口:

复制代码
@GetMapping("/wrong")
public int error(){
    int i = 9/0;
    return i;
}
相关推荐
二哈喇子!1 小时前
SpringBoot项目右上角选择ProjectNameApplication的配置
java·spring boot
二哈喇子!1 小时前
基于Spring Boot框架的车库停车管理系统的设计与实现
java·spring boot·后端·计算机毕业设计
二哈喇子!1 小时前
基于SpringBoot框架的水之森海底世界游玩系统
spring boot·旅游
二哈喇子!1 小时前
Java框架精品项目【用于个人学习】
java·spring boot·学习
二哈喇子!2 小时前
基于SpringBoot框架的网上购书系统的设计与实现
java·大数据·spring boot
二哈喇子!3 小时前
基于JavaSE的淘宝卖鞋后端管理系统的设计与实现
java·spring boot·spring
Coder_Boy_3 小时前
基于SpringAI的在线考试系统-智能考试系统-学习分析模块
java·开发语言·数据库·spring boot·ddd·tdd
高山上有一只小老虎4 小时前
mybatisplus实现分页查询
java·spring boot·mybatis
罗伯特_十三4 小时前
Spring AI ChatModel 使用记录
java·人工智能·spring
毕设源码-朱学姐4 小时前
【开题答辩全过程】以 基于SpringBoot的律师事务所管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端