SpringBoot怎么统一后端返回格式

SpringBoot怎么统一后端返回格式

这篇文章主要介绍“SpringBoot怎么统一后端返回格式”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SpringBoot怎么统一后端返回格式”文章能帮助大家解决问题。

为什么要对SpringBoot返回统一的标准格式

在默认情况下,SpringBoot的返回格式常见的有三种:

第一种:返回 String

@GetMapping("/hello")publicStringgetStr(){return"hello,javadaily";}

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

hello,javadaily

第二种:返回自定义对象

@GetMapping("/aniaml")publicAniamlgetAniaml(){Aniamlaniaml=newAniaml(1,"pig");returnaniaml;}

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

{"id":1,"name":"pig"}

第三种:接口异常

@GetMapping("/error")publicinterror(){inti=9/0;returni;}

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

{"timestamp":"2021-07-08T08:05:15.423+00:00","status":500,"error":"InternalServerError","path":"/wrong"}

基于以上种种情况,如果你和前端开发人员联调接口她们就会很懵逼,由于我们没有给他一个统一的格式,前端人员不知道如何处理返回值。

还有甚者,有的同学比如小张喜欢对结果进行封装,他使用了Result对象,小王也喜欢对结果进行包装,但是他却使用的是Response对象,当出现这种情况时我相信前端人员一定会抓狂的。

所以我们项目中是需要定义一个统一的标准返回格式的。

定义返回标准格式

一个标准的返回格式至少包含3部分:

  • status 状态值:由后端统一定义各种返回结果的状态码

  • message 描述:本次接口调用的结果描述

  • data 数据:本次返回的数据

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

当然也可以按需加入其他扩展值,比如我们就在返回对象中添加了接口调用时间

timestamp: 接口调用时间

定义返回对象

@DatapublicclassResultData<t>{/**结果状态,具体状态码参见ResultData.java*/privateintstatus;privateStringmessage;privateTdata;privatelongtimestamp;publicResultData(){this.timestamp=System.currentTimeMillis();}publicstatic<t>ResultData<t>success(Tdata){ResultData<t>resultData=newResultData<>();resultData.setStatus(ReturnCode.RC100.getCode());resultData.setMessage(ReturnCode.RC100.getMessage());resultData.setData(data);returnresultData;}publicstatic<t>ResultData<t>fail(intcode,Stringmessage){ResultData<t>resultData=newResultData<>();resultData.setStatus(code);resultData.setMessage(message);returnresultData;}}

定义状态码

publicenumReturnCode{/**操作成功**/RC100(100,"操作成功"),/**操作失败**/RC999(999,"操作失败"),/**服务限流**/RC200(200,"服务开启限流保护,请稍后再试!"),/**服务降级**/RC201(201,"服务开启降级保护,请稍后再试!"),/**热点参数限流**/RC202(202,"热点参数限流,请稍后再试!"),/**系统规则不满足**/RC203(203,"系统规则不满足要求,请稍后再试!"),/**授权规则不通过**/RC204(204,"授权规则不通过,请稍后再试!"),/**access_denied**/RC403(403,"无访问权限,请联系管理员授予权限"),/**access_denied**/RC401(401,"匿名用户访问无权限资源时的异常"),/**服务异常**/RC500(500,"系统异常,请稍后重试"),INVALID_TOKEN(2001,"访问令牌不合法"),ACCESS_DENIED(2003,"没有权限访问该资源"),CLIENT_AUTHENTICATION_FAILED(1001,"客户端认证失败"),USERNAME_OR_PASSWORD_ERROR(1002,"用户名或密码错误"),UNSUPPORTED_GRANT_TYPE(1003,"不支持的认证模式");/**自定义状态码**/privatefinalintcode;/**自定义描述**/privatefinalStringmessage;ReturnCode(intcode,Stringmessage){this.code=code;this.message=message;}publicintgetCode(){returncode;}publicStringgetMessage(){returnmessage;}}

统一返回格式

@GetMapping("/hello")publicResultData<string>getStr(){returnResultData.success("hello,javadaily");}

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

{"status":100,"message":"hello,javadaily","data":null,"timestamp":1625736481648,"httpStatus":0}

这样确实已经实现了我们想要的结果,我在很多项目中看到的都是这种写法,在Controller层通过ResultData.success()对返回结果进行包装后返回给前端。

看到这里我们不妨停下来想想,这样做有什么弊端呢?

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

所以呢我们需要对代码进行优化,目标就是不要每个接口都手工制定ResultData返回值。

高级实现方式

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

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

先来看下ResponseBodyAdvice的源码:

publicinterfaceResponseBodyAdvice<t>{/***是否支持advice功能*true支持,false不支持*/booleansupports(MethodParametervar1,Class<!--?extendsHttpMessageConverter<?-->>var2);/***对返回的数据进行处理*/@NullableTbeforeBodyWrite(@NullableTvar1,MethodParametervar2,MediaTypevar3,Class<!--?extendsHttpMessageConverter<?-->>var4,ServerHttpRequestvar5,ServerHttpResponsevar6);}

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

/***@authorjam*@date2021/7/810:10上午*/@RestControllerAdvicepublicclassResponseAdviceimplementsResponseBodyAdvice<object>{@AutowiredprivateObjectMapperobjectMapper;@Overridepublicbooleansupports(MethodParametermethodParameter,Class<!--?extendsHttpMessageConverter<?-->>aClass){returntrue;}@SneakyThrows@OverridepublicObjectbeforeBodyWrite(Objecto,MethodParametermethodParameter,MediaTypemediaType,Class<!--?extendsHttpMessageConverter<?-->>aClass,ServerHttpRequestserverHttpRequest,ServerHttpResponseserverHttpResponse){if(oinstanceofString){returnobjectMapper.writeValueAsString(ResultData.success(o));}returnResultData.success(o);}}

需要注意两个地方:

@RestControllerAdvice注解

@RestControllerAdvice@RestController注解的增强,可以实现三个方面的功能:

  • 全局异常处理

  • 全局数据绑定全

  • 局数据预处理

String类型判断

if(oinstanceofString){returnobjectMapper.writeValueAsString(ResultData.success(o));}

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

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

@GetMapping("/hello")publicStringgetStr(){return"hello,javadaily";}

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

@GetMapping("/hello")publicStringgetStr(){return"hello,javadaily";}

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

接口异常问题

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

@GetMapping("/wrong")publicinterror(){inti=9/0;returni;}

返回的结果为:

这显然不是我们想要的结果,接口都报错了还返回操作成功的响应码,前端看了会打人的。

别急,接下来我们进入第二个议题,如何优雅的处理全局异常。

SpringBoot为什么需要全局异常处理器

不用手写try...catch,由全局异常处理器统一捕获

使用全局异常处理器最大的便利就是程序员在写代码时不再需要手写try...catch了,前面我们讲过,默认情况下SpringBoot出现异常时返回的结果是这样:

{"timestamp":"2021-07-08T08:05:15.423+00:00","status":500,"error":"InternalServerError","path":"/wrong"}

这种数据格式返回给前端,前端是看不懂的,所以这时候我们一般通过try...catch来处理异常

@GetMapping("/wrong")publicinterror(){inti;try{i=9/0;}catch(Exceptione){log.error("error:{}",e);i=0;}returni;}

我们追求的目标肯定是不需要再手动写try...catch了,而是希望由全局异常处理器处理。

对于自定义异常,只能通过全局异常处理器来处理

@GetMapping("error1")publicvoidempty(){thrownewRuntimeException("自定义异常");}

当我们引入Validator参数校验器的时候,参数校验不通过会抛出异常,此时是无法用try...catch捕获的,只能使用全局异常处理器。

SpringBoot集成参数校验请参考这篇文章SpringBoot开发秘籍 - 集成参数校验及高阶技巧

如何实现全局异常处理器

@Slf4j@RestControllerAdvicepublicclassRestExceptionHandler{/***默认全局异常处理。*@paramethee*@returnResultData*/@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)publicResultData<string>exception(Exceptione){log.error("全局异常信息ex={}",e.getMessage(),e);returnResultData.fail(ReturnCode.RC500.getCode(),e.getMessage());}}

有三个细节需要说明一下:

  • @RestControllerAdvice,RestController的增强类,可用于实现全局异常处理器

  • @ExceptionHandler,统一处理某一类异常,从而减少代码重复率和复杂度,比如要获取自定义异常可以@ExceptionHandler(BusinessException.class)

  • @ResponseStatus指定客户端收到的http状态码

体验效果

这时候我们调用如下接口:

@GetMapping("error1")publicvoidempty(){thrownewRuntimeException("自定义异常");}

返回的结果如下:

{"status":500,"message":"自定义异常","data":null,"timestamp":1625795902556}

基本满足我们的需求了。

但是当我们同时启用统一标准格式封装功能ResponseAdviceRestExceptionHandler全局异常处理器时又出现了新的问题:

{"status":100,"message":"操作成功","data":{"status":500,"message":"自定义异常","data":null,"timestamp":1625796167986},"timestamp":1625796168008}

此时返回的结果是这样,统一格式增强功能会给返回的异常结果再次封装,所以接下来我们需要解决这个问题。

全局异常接入返回的标准格式

要让全局异常接入标准格式很简单,因为全局异常处理器已经帮我们封装好了标准格式,我们只需要直接返回给客户端即可。

@SneakyThrows@OverridepublicObjectbeforeBodyWrite(Objecto,MethodParametermethodParameter,MediaTypemediaType,Class<!--?extendsHttpMessageConverter<?-->>aClass,ServerHttpRequestserverHttpRequest,ServerHttpResponseserverHttpResponse){if(oinstanceofString){returnobjectMapper.writeValueAsString(ResultData.success(o));}if(oinstanceofResultData){returno;}returnResultData.success(o);}

关键代码:

if(oinstanceofResultData){returno;}

如果返回的结果是ResultData对象,直接返回即可。

这时候我们再调用上面的错误方法,返回的结果就符合我们的要求了。

{"status":500,"message":"自定义异常","data":null,"timestamp":1625796580778}

关于“SpringBoot怎么统一后端返回格式”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注恰卡编程网行业资讯频道,小编每天都会为大家更新不同的知识点。

发布于 2022-04-11 21:13:11
收藏
分享
海报
0 条评论
22
上一篇:springboot怎么通过注解、接口创建定时任务 下一篇:springboot jpa延迟加载问题怎么解决
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码