前言
项目开发中,经常需要后端通常要给前端返回数据,后端程序员也经常会对数据进行自行封装,如果没有一个统一的规范,每个人都会自定义返回格式,这样一整,前端就会懵逼,骂娘,为了规避这种情况,就需要统一规范,统一给出相应数据格式,本文带你来看看springboot是如何实现的。
附录
参考的博客:SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的! - 掘金
springboot常见返回格式
第一种,String
@GetMapping(value = "/hello1")
public String test5(){
return "hello world";
}
http://localhost:8080/hello1
hello world
第二种,json
@GetMapping(value = "/hello2")
public ValidVO test6(){
ValidVO vo = new ValidVO();
vo.setName("zhangsan");
vo.setSex("男");
return vo;
}
http://localhost:8080/hello2
{
"id": null,
"appId": null,
"name": "zhangsan",
"email": null,
"sex": "男",
"level": null
}
第三种,异常形式
@GetMapping(value = "/hello3")
public void test7(){
throw new RuntimeException("asdf");
}
http://localhost:8080/hello3
{
"timestamp": "2021-12-08T09:11:51.371+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/hello3"
}
如何统一返回格式
这里我们采用spring里面的@RestControllerAdvice+自定义注解的形式来实现。
具体思路如下。
第一步,定义统一响应实体类
这里我们就要思考,一个标准的响应都有哪些信息。
这里我们采用大多数公司采用的格式。
- code响应码:由后端统一定义各种返回结果的状态码
- msg描述:本次接口调用的结果描述
- data 数据:本次返回的数据
{
"code":"0",
"msg":"成功",
"data":[{
"name":"张三",
"age":"31"
}]
}
第二步,定义统一响应码
响应码的作用是让人快速知道返回的结果状态,见名知意。通常分为三大类,成功,失败,异常。
一些项目会有更专业的响应码,会对异常信息和相对应的错误码绑定,通过报出的错误码,查询错误码手册来获知项目的报错内容,从而定位问题,避免盲目。这样也使代码更优雅美观。
@Data
public class CommonResp<T> implements Serializable {
private static final long serivalVersionUID = 1L;
/**
* 响应码
*/
private String code = "0";
/**
* 响应信息
*/
private String msg = "success";
/**
* 响应消息体
*/
private T data;
private CommonResp(){
this.code = ResponseCodeEnum.SUCCESS.getCode();
this.msg = ResponseCodeEnum.SUCCESS.getMsg();
}
private static CommonResp success(Object data){
CommonResp result = new CommonResp();
result.setData(data);
return result;
}
}
第三步,把统一返回体封装成注解
springboot提供了一个接口ResponseBodyAdvice。
这个接口是起什么作用呢?
先看一波源码,如下
/**
*Allows customizing the response after the execution of an @ResponseBody or a
*ResponseEntity controller method but before the body is written with an
*HttpMessageConverter.
*Implementations may be registered directly with RequestMappingHandlerAdapter and
*ExceptionHandlerExceptionResolver or more likely annotated with @ControllerAdvice in
*which case they will be auto-detected by both.
*Since:
*4.1
*Author:
*Rossen Stoyanchev
*Type parameters:
*<T> – the body type
*/
public interface ResponseBodyAdvice<T> {
/**
* Whether this component supports the given controller method return type
* and the selected {@code HttpMessageConverter} type.
* @param returnType the return type
* @param converterType the selected converter type
* @return {@code true} if {@link #beforeBodyWrite} should be invoked;
* {@code false} otherwise
*/
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
/**
* Invoked after an {@code HttpMessageConverter} is selected and just before
* its write method is invoked.
* @param body the body to be written
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @param request the current request
* @param response the current response
* @return the body that was passed in or a modified (possibly new) instance
*/
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}
来我给你翻译翻译:拦截Controller方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等等。
那么这个玩意怎么用呢?
我们只需实现这个接口,即可。
@RestControllerAdvice
public class CommonRespAdvice implements ResponseBodyAdvice<Object>{
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
boolean hasMethodAnnotation = returnType.hasMethodAnnotation(WrapCommonResp.class);
if(!hasMethodAnnotation){
WrapCommonResp wrapCommonResp = returnType.getMethod() == null ? null : returnType.getMethod().getDeclaringClass().getAnnotation(WrapCommonResp.class);
if(wrapCommonResp != null){
hasMethodAnnotation = true;
}
}
return hasMethodAnnotation;
}
@Override
public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(obj instanceof String){
return JSON.toJSONString(CommonResp.success(obj));
}
return CommonResp.success(obj);
}
}
以上还涉及到一个注解:@RestControllerAdvice
这个注解是是 @RestController注解的增强,可以实现三个方面的功能:
- 全局异常处理
- 全局数据绑定
- 全局数据预处理
if(obj instanceof String){
return JSON.toJSONString(CommonResp.success(obj));
}
注:这段代码一定要加,因为springboot对于string类型是直接返回的。
统一格式后的返回结果
{
"code": "0",
"msg": "success",
"data": {
"id": null,
"appId": null,
"name": "zhangsan",
"email": null,
"sex": "男",
"level": null
}
}