springboot之全局处理统一返回

简介

在REST风格的开发中,避免通常会告知前台返回是否成功以及状态码等信息。这里我们通常返回的时候做一次util的包装处理工作,如:Result类似的类,里面包含succcodemsgdata等字段。

接口调用返回类似如下:

  1. {
  2. "succ": false, // 是否成功
  3. "ts": 1566467628851, // 时间戳
  4. "data": null, // 数据
  5. "code": "CLOUD800", // 错误类型
  6. "msg": "业务异常", // 错误描述
  7. "fail": true
  8. }

当然在每个接口里返回要通过Result的工具类将这些信息给封装一下,这样导致业务和技术类的代码耦合在一起。

接口调用处理类似如下:

  1. @GetMapping("hello")
  2. public Result list(){
  3. return Result.ofSuccess("hello");
  4. }

结果:

  1. {
  2. "succ": ture, // 是否成功
  3. "ts": 1566467628851, // 时间戳
  4. "data": "hello", // 数据
  5. "code": null, // 错误类型
  6. "msg": null, // 错误描述
  7. "fail": true
  8. }

我们将这些操抽出一个公共starter包,各个服务依赖即可,做一层统一拦截处理的工作,进行技术解耦。

配置

unified-dispose-springboot-starter

这个模块里包含异常处理以及全局返回封装等功能,下面。

完整目录结构如下:

  1. ├── pom.xml
  2. ├── src
  3.    ├── main
  4.       ├── java
  5.          └── com
  6.          └── purgetiem
  7.          └── starter
  8.          └── dispose
  9.          ├── GlobalDefaultConfiguration.java
  10.          ├── GlobalDefaultProperties.java
  11.          ├── Interceptors.java
  12.          ├── Result.java
  13.          ├── advice
  14.             └── CommonResponseDataAdvice.java
  15.          ├── annotation
  16.             ├── EnableGlobalDispose.java
  17.             └── IgnorReponseAdvice.java
  18.          └── exception
  19.          ├── GlobalDefaultExceptionHandler.java
  20.          ├── category
  21.             └── BusinessException.java
  22.          └── error
  23.          ├── CommonErrorCode.java
  24.          └── details
  25.          └── BusinessErrorCode.java
  26.       └── resources
  27.       ├── META-INF
  28.          └── spring.factories
  29.       └── dispose.properties
  30.    └── test
  31.    └── java

统一返回处理

按照一般的模式,我们都需要创建一个可以进行处理包装的工具类以及一个返回对象。

Result(返回类):

创建Result<T> Tdata的数据类型,这个类包含了前端常用的字段,还有一些常用的静态初始化Result对象的方法。

  1. /**
  2. * 返回统一数据结构
  3. *
  4. * @author purgeyao
  5. * @since 1.0
  6. */
  7. @Data
  8. @ToString
  9. @NoArgsConstructor
  10. @AllArgsConstructor
  11. public class Result<T> implements Serializable {
  12. /**
  13. * 是否成功
  14. */
  15. private Boolean succ;
  16. /**
  17. * 服务器当前时间戳
  18. */
  19. private Long ts = System.currentTimeMillis();
  20. /**
  21. * 成功数据
  22. */
  23. private T data;
  24. /**
  25. * 错误码
  26. */
  27. private String code;
  28. /**
  29. * 错误描述
  30. */
  31. private String msg;
  32. public static Result ofSuccess() {
  33. Result result = new Result();
  34. result.succ = true;
  35. return result;
  36. }
  37. public static Result ofSuccess(Object data) {
  38. Result result = new Result();
  39. result.succ = true;
  40. result.setData(data);
  41. return result;
  42. }
  43. public static Result ofFail(String code, String msg) {
  44. Result result = new Result();
  45. result.succ = false;
  46. result.code = code;
  47. result.msg = msg;
  48. return result;
  49. }
  50. public static Result ofFail(String code, String msg, Object data) {
  51. Result result = new Result();
  52. result.succ = false;
  53. result.code = code;
  54. result.msg = msg;
  55. result.setData(data);
  56. return result;
  57. }
  58. public static Result ofFail(CommonErrorCode resultEnum) {
  59. Result result = new Result();
  60. result.succ = false;
  61. result.code = resultEnum.getCode();
  62. result.msg = resultEnum.getMessage();
  63. return result;
  64. }
  65. /**
  66. * 获取 json
  67. */
  68. public String buildResultJson(){
  69. JSONObject jsonObject = new JSONObject();
  70. jsonObject.put("succ", this.succ);
  71. jsonObject.put("code", this.code);
  72. jsonObject.put("ts", this.ts);
  73. jsonObject.put("msg", this.msg);
  74. jsonObject.put("data", this.data);
  75. return JSON.toJSONString(jsonObject, SerializerFeature.DisableCircularReferenceDetect);
  76. }
  77. }

这样已经满足一般返回处理的需求了,在接口可以这样使用:

  1. @GetMapping("hello")
  2. public Result list(){
  3. return Result.ofSuccess("hello");
  4. }

当然这样是耦合的使用,每次都需要调用Result里的包装方法。


ResponseBodyAdvice 返回统一拦截处理

ResponseBodyAdvice在 spring 4.1 新加入的一个接口,在消息体被HttpMessageConverter写入之前允许Controller@ResponseBody修饰的方法或ResponseEntity调整响应中的内容,比如做一些返回处理。

ResponseBodyAdvice接口里一共包含了两个方法

  • supports:该组件是否支持给定的控制器方法返回类型和选择的{@code HttpMessageConverter}类型

  • beforeBodyWrite:在选择{@code HttpMessageConverter}之后调用,在调用其写方法之前调用。

那么我们就可以在这两个方法做一些手脚。

  • supports用于判断是否需要做处理。

  • beforeBodyWrite用于做返回处理。

CommonResponseDataAdvice类实现ResponseBodyAdvice两个方法。

filter(MethodParameter methodParameter) 私有方法里进行判断是否要进行拦截统一返回处理。

如:

  • 添加自定义注解@IgnorReponseAdvice忽略拦截。
  • 判断某些类不进行拦截.
  • 判断某些包下所有类不进行拦截。如swaggerspringfox.documentation包下的接口忽略拦截等。

filter方法:

判断为false就不需要进行拦截处理。

  1. private Boolean filter(MethodParameter methodParameter) {
  2. Class<?> declaringClass = methodParameter.getDeclaringClass();
  3. // 检查过滤包路径
  4. long count = globalDefaultProperties.getAdviceFilterPackage().stream()
  5. .filter(l -> declaringClass.getName().contains(l)).count();
  6. if (count > 0) {
  7. return false;
  8. }
  9. // 检查<类>过滤列表
  10. if (globalDefaultProperties.getAdviceFilterClass().contains(declaringClass.getName())) {
  11. return false;
  12. }
  13. // 检查注解是否存在
  14. if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnorReponseAdvice.class)) {
  15. return false;
  16. }
  17. if (methodParameter.getMethod().isAnnotationPresent(IgnorReponseAdvice.class)) {
  18. return false;
  19. }
  20. return true;
  21. }

CommonResponseDataAdvice类:

最核心的就在beforeBodyWrite方法处理里。

  1. 判断Object o是否为null,为null构建Result对象进行返回。
  2. 判断Object o是否是Result子类或其本身,该情况下,可能是接口返回时创建了Result,为了避免再次封装一次,判断是Result子类或其本身就返回Object o本身。
  3. 判断Object o是否是为String,在测试的过程中发现String的特殊情况,在这里做了一次判断操作,如果为String就进行JSON.toJSON(Result.ofSuccess(o)).toString()序列号操作。
  4. 其他情况默认返回Result.ofSuccess(o)进行包装处理。
  1. /**
  2. * {@link IgnorReponseAdvice} 处理解析 {@link ResponseBodyAdvice} 统一返回包装器
  3. *
  4. * @author purgeyao
  5. * @since 1.0
  6. */
  7. @RestControllerAdvice
  8. public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object> {
  9. private GlobalDefaultProperties globalDefaultProperties;
  10. public CommonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties) {
  11. this.globalDefaultProperties = globalDefaultProperties;
  12. }
  13. @Override
  14. @SuppressWarnings("all")
  15. public boolean supports(MethodParameter methodParameter,
  16. Class<? extends HttpMessageConverter<?>> aClass) {
  17. return filter(methodParameter);
  18. }
  19. @Nullable
  20. @Override
  21. @SuppressWarnings("all")
  22. public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
  23. Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
  24. ServerHttpResponse serverHttpResponse) {
  25. // o is null -> return response
  26. if (o == null) {
  27. return Result.ofSuccess();
  28. }
  29. // o is instanceof ConmmonResponse -> return o
  30. if (o instanceof Result) {
  31. return (Result<Object>) o;
  32. }
  33. // string 特殊处理
  34. if (o instanceof String) {
  35. return JSON.toJSON(Result.ofSuccess(o)).toString();
  36. }
  37. return Result.ofSuccess(o);
  38. }
  39. private Boolean filter(MethodParameter methodParameter) {
  40. ···略
  41. }
  42. }

这样基本完成了核心的处理工作。当然还少了上文提到的@IgnorReponseAdvice注解。

@IgnorReponseAdvice:

比较简单点,只作为一个标识的作用。

  1. /**
  2. * 统一返回包装标识注解
  3. *
  4. * @author purgeyao
  5. * @since 1.0
  6. */
  7. @Target({ElementType.TYPE, ElementType.METHOD})
  8. @Retention(RetentionPolicy.RUNTIME)
  9. public @interface IgnorReponseAdvice {
  10. }

加入spring容器

最后将GlobalDefaultExceptionHandlerbean的方式注入spring容器。

  1. @Configuration
  2. @EnableConfigurationProperties(GlobalDefaultProperties.class)
  3. @PropertySource(value = "classpath:dispose.properties", encoding = "UTF-8")
  4. public class GlobalDefaultConfiguration {
  5. ···略
  6. @Bean
  7. public CommonResponseDataAdvice commonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties){
  8. return new CommonResponseDataAdvice(globalDefaultProperties);
  9. }
  10. }

GlobalDefaultConfigurationresources/META-INF/spring.factories文件下加载。

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2. com.purgetime.starter.dispose.GlobalDefaultConfiguration

不过我们这次使用注解方式开启。其他项目依赖包后,需要添加@EnableGlobalDispose才可以将全局拦截的特性开启。

将刚刚创建的spring.factories注释掉,创建EnableGlobalDispose注解。

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Import(GlobalDefaultConfiguration.class)
  4. public @interface EnableGlobalDispose {
  5. }

使用@ImportGlobalDefaultConfiguration导入即可。

使用

添加依赖

  1. <dependency>
  2. <groupId>com.purgeteam</groupId>
  3. <artifactId>unified-dispose-deepblueai-starter</artifactId>
  4. <version>0.1.1.RELEASE</version>
  5. </dependency>

启动类开启@EnableGlobalDispose注解即可。

  1. 业务使用

接口:

  1. @GetMapping("test")
  2. public String test(){
  3. return "test";
  4. }

返回

  1. {
  2. "succ": true, // 是否成功
  3. "ts": 1566386951005, // 时间戳
  4. "data": "test", // 数据
  5. "code": null, // 错误类型
  6. "msg": null, // 错误描述
  7. "fail": false
  8. }
  1. 忽略封装注解:@IgnorReponseAdvice

@IgnorReponseAdvice允许范围为:类 + 方法,标识在类上这个类下的说有方法的返回都将忽略返回封装。

接口:

  1. @IgnorReponseAdvice // 忽略数据包装 可添加到类、方法上
  2. @GetMapping("test")
  3. public String test(){
  4. return "test";
  5. }

返回 test

总结

项目里很多重复的code,我们可以通过一定的方式去简化,以达到一定目的减少开发量。

示例代码地址:unified-dispose-springboot

作者GitHub:

Purgeyao 欢迎关注

springboot之全局处理统一返回的更多相关文章

  1. Springboot项目全局异常统一处理

    转自https://blog.csdn.net/hao_kkkkk/article/details/80538955 最近在做项目时需要对异常进行全局统一处理,主要是一些分类入库以及记录日志等,因为项 ...

  2. spring boot 2 全局统一返回RESTful风格数据、统一异常处理

    全局统一返回RESTful风格数据,主要是实现ResponseBodyAdvice接口的方法,对返回值在输出之前进行修改.使用注解@RestControllerAdvice拦截异常并统一处理. 开发环 ...

  3. SpringBoot处理全局统一异常

    在后端发生异常或者是请求出错时,前端通常显示如下 Whitelabel Error Page This application has no explicit mapping for /error, ...

  4. springboot统一返回json数据格式并配置系统异常拦截

    本文链接:https://blog.csdn.net/syystx/article/details/82870217通常进行前后端分离开发时我们需要定义统一的json数据交互格式并对系统未处理异常进行 ...

  5. SpringBoot(八):系统错误统一拦截器

    在日常 web 开发中发生了异常,往往需要通过一个统一的 异常处理,来保证客户端能够收到友好的提示.本文将会介绍 Spring Boot 中的 全局统一异常处理. Springboot的全局异常查是通 ...

  6. SpringBoot | 第八章:统一异常、数据校验处理

    前言 在web应用中,请求处理时,出现异常是非常常见的.所以当应用出现各类异常时,进行异常的捕获或者二次处理(比如sql异常正常是不能外抛)是非常必要的,比如在开发对外api服务时,约定了响应的参数格 ...

  7. springboot之全局处理异常封装

    springboot之全局处理异常封装 简介 在项目中经常出现系统异常的情况,比如NullPointerException等等.如果默认未处理的情况下,springboot会响应默认的错误提示,这样对 ...

  8. springmvc、 springboot 项目全局异常处理

    异常在项目中那是不可避免的,通常情况下,我们需要对全局异常进行处理,下面介绍两种比较常用的情况. 准备工作: 在捕获到异常的时候,我们通常需要返回给前端错误码,错误信息等,所以我们需要手动封装一个js ...

  9. java统一返回标准类型

    一.前言.背景 在如今前后端分离的时代,后端已经由传统的返回view视图转变为返回json数据,此json数据可能包括返回状态.数据.信息等......因为程序猿的习惯不同所以返回json数据的格式也 ...

随机推荐

  1. 在.net core web 项目中使用Nlog记录日志

    第1步,添加NLog.Web.AspNetCore包引用 方法1 在项目上右击“依赖项”---“管理Nuget程序包(N)…”,然后在浏览对话框中输入“NLog.Web.AspNetCore”查找包, ...

  2. Win10安装Linux系统

    windows系统安装虚拟机,常见的是利用VMware Workstation这款软件来进行安装.在未接触Docker之前,我一直通过这款软件来进行管理的.docker是运行在linux环境下的,那怎 ...

  3. hdu 5495 LCS(并查集)

    Problem Description You are given two sequence {a1,a2,...,an} and {b1,b2,...,bn}. Both sequences are ...

  4. CodeForces 311 B Cats Transport 斜率优化DP

    题目传送门 题意:现在有n座山峰,现在 i-1 与 i 座山峰有 di长的路,现在有m个宠物, 分别在hi座山峰,第ti秒之后可以被带走,现在有p个人,每个人会从1号山峰走到n号山峰,速度1m/s.现 ...

  5. codeforces 459 D. Pashmak and Parmida's problem(思维+线段树)

    题目链接:http://codeforces.com/contest/459/problem/D 题意:给出数组a,定义f(l,r,x)为a[]的下标l到r之间,等于x的元素数.i和j符合f(1,i, ...

  6. codeforces E. Phone Talks(dp)

    题目链接:http://codeforces.com/contest/158/problem/E 题意:给出一些电话,有打进来的时间和持续的时间,如果人在打电话,那么新打进来的电话入队,如果人没有打电 ...

  7. Redis与Queue

    Redis有多种数据结构,适合多种不同的应用场景 1. 使用Redis做缓存 Redis的字符串.哈希表两种数据结构适合用来储存大量的键值对信息,从而实现高速缓存. 2. 使用Redis做队列 Red ...

  8. python 整型、字符串常用方法、for循环

    整型--int 定义:用于比较和计算 python2和python3: python2:python2中油int(整型)和long(长整型):1231312L+ 进制转换: 十进制转二进制:正除2,获 ...

  9. springmvc使用JSR-303对复杂对象进行校验

    JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,官方参考实现是Hibernate Validator.此实现与Hibernate ORM 没有任何关系.JSR ...

  10. 卸载VMware

    最近使用ubuntu的时候操作不当直接卡死了,然后强制关闭VMware软件,之后再打开时出现本文中的 “Vmware启动ubuntu 出现错误 ”这个情况,具体请看链接:https://www.cnb ...