springboot之全局处理统一返回

简介

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

接口调用返回类似如下:

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

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

接口调用处理类似如下:

  @GetMapping("hello")
public Result list(){
return Result.ofSuccess("hello");
}

结果:

{
"succ": ture, // 是否成功
"ts": 1566467628851, // 时间戳
"data": "hello", // 数据
"code": null, // 错误类型
"msg": null, // 错误描述
"fail": true
}

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

配置

unified-dispose-springboot-starter

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

完整目录结构如下:

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │   └── purgetiem
│   │   │   └── starter
│   │   │   └── dispose
│   │   │   ├── GlobalDefaultConfiguration.java
│   │   │   ├── GlobalDefaultProperties.java
│   │   │   ├── Interceptors.java
│   │   │   ├── Result.java
│   │   │   ├── advice
│   │   │   │   └── CommonResponseDataAdvice.java
│   │   │   ├── annotation
│   │   │   │   ├── EnableGlobalDispose.java
│   │   │   │   └── IgnorReponseAdvice.java
│   │   │   └── exception
│   │   │   ├── GlobalDefaultExceptionHandler.java
│   │   │   ├── category
│   │   │   │   └── BusinessException.java
│   │   │   └── error
│   │   │   ├── CommonErrorCode.java
│   │   │   └── details
│   │   │   └── BusinessErrorCode.java
│   │   └── resources
│   │   ├── META-INF
│   │   │   └── spring.factories
│   │   └── dispose.properties
│   └── test
│   └── java

统一返回处理

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

Result(返回类):

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

/**
* 返回统一数据结构
*
* @author purgeyao
* @since 1.0
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable { /**
* 是否成功
*/
private Boolean succ; /**
* 服务器当前时间戳
*/
private Long ts = System.currentTimeMillis(); /**
* 成功数据
*/
private T data; /**
* 错误码
*/
private String code; /**
* 错误描述
*/
private String msg; public static Result ofSuccess() {
Result result = new Result();
result.succ = true;
return result;
} public static Result ofSuccess(Object data) {
Result result = new Result();
result.succ = true;
result.setData(data);
return result;
} public static Result ofFail(String code, String msg) {
Result result = new Result();
result.succ = false;
result.code = code;
result.msg = msg;
return result;
} public static Result ofFail(String code, String msg, Object data) {
Result result = new Result();
result.succ = false;
result.code = code;
result.msg = msg;
result.setData(data);
return result;
} public static Result ofFail(CommonErrorCode resultEnum) {
Result result = new Result();
result.succ = false;
result.code = resultEnum.getCode();
result.msg = resultEnum.getMessage();
return result;
} /**
* 获取 json
*/
public String buildResultJson(){
JSONObject jsonObject = new JSONObject();
jsonObject.put("succ", this.succ);
jsonObject.put("code", this.code);
jsonObject.put("ts", this.ts);
jsonObject.put("msg", this.msg);
jsonObject.put("data", this.data);
return JSON.toJSONString(jsonObject, SerializerFeature.DisableCircularReferenceDetect);
}
}

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

  @GetMapping("hello")
public Result list(){
return Result.ofSuccess("hello");
}

当然这样是耦合的使用,每次都需要调用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就不需要进行拦截处理。

  private Boolean filter(MethodParameter methodParameter) {
Class<?> declaringClass = methodParameter.getDeclaringClass();
// 检查过滤包路径
long count = globalDefaultProperties.getAdviceFilterPackage().stream()
.filter(l -> declaringClass.getName().contains(l)).count();
if (count > 0) {
return false;
}
// 检查<类>过滤列表
if (globalDefaultProperties.getAdviceFilterClass().contains(declaringClass.getName())) {
return false;
}
// 检查注解是否存在
if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnorReponseAdvice.class)) {
return false;
}
if (methodParameter.getMethod().isAnnotationPresent(IgnorReponseAdvice.class)) {
return false;
}
return true;
}

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)进行包装处理。
/**
* {@link IgnorReponseAdvice} 处理解析 {@link ResponseBodyAdvice} 统一返回包装器
*
* @author purgeyao
* @since 1.0
*/
@RestControllerAdvice
public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object> { private GlobalDefaultProperties globalDefaultProperties; public CommonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties) {
this.globalDefaultProperties = globalDefaultProperties;
} @Override
@SuppressWarnings("all")
public boolean supports(MethodParameter methodParameter,
Class<? extends HttpMessageConverter<?>> aClass) {
return filter(methodParameter);
} @Nullable
@Override
@SuppressWarnings("all")
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) { // o is null -> return response
if (o == null) {
return Result.ofSuccess();
}
// o is instanceof ConmmonResponse -> return o
if (o instanceof Result) {
return (Result<Object>) o;
}
// string 特殊处理
if (o instanceof String) {
return JSON.toJSON(Result.ofSuccess(o)).toString();
}
return Result.ofSuccess(o);
} private Boolean filter(MethodParameter methodParameter) {
···略
} }

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

@IgnorReponseAdvice:

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

/**
* 统一返回包装标识注解
*
* @author purgeyao
* @since 1.0
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnorReponseAdvice { }

加入spring容器

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

@Configuration
@EnableConfigurationProperties(GlobalDefaultProperties.class)
@PropertySource(value = "classpath:dispose.properties", encoding = "UTF-8")
public class GlobalDefaultConfiguration { ···略 @Bean
public CommonResponseDataAdvice commonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties){
return new CommonResponseDataAdvice(globalDefaultProperties);
} }

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

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

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

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(GlobalDefaultConfiguration.class)
public @interface EnableGlobalDispose { }

使用@ImportGlobalDefaultConfiguration导入即可。

使用

添加依赖

<dependency>
<groupId>com.purgeteam</groupId>
<artifactId>unified-dispose-deepblueai-starter</artifactId>
<version>0.1.1.RELEASE</version>
</dependency>

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

  1. 业务使用

接口:

@GetMapping("test")
public String test(){
return "test";
}

返回

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

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

接口:

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

返回 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. [程序人生]那些IT界“活久见”的奇葩现象

    常言道,人活久了什么稀奇古怪的事都会见到.本文盘点几件刚毕业工作时想当然,工作若干年后啪啪打脸的“奇葩”事. (1)去年推荐一朋友来我们公司面试时,朋友说起当年她去某游戏公司时,那公司HR说这家公司是 ...

  2. StackOverflow 周报 - 这些高关注的问题你是否都会

    我从 Stack Overflow 上找的了一些高关注度且高赞的问题.这些问题可能平时我们遇不到,但既然是高关注的问题和高点赞的回答说明是被大家普遍认可的,如果我们提前学到了以后不管工作中还是面试中处 ...

  3. 分布式日志收集系统 —— Flume

    一.Flume简介 Apache Flume 是一个分布式,高可用的数据收集系统.它可以从不同的数据源收集数据,经过聚合后发送到存储系统中,通常用于日志数据的收集.Flume 分为 NG 和 OG ( ...

  4. Android进阶之路(2)-详解MVP

    ### MVP简介 >MVP 全称:Model-View-Presenter :MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的[地方](https://baike.baidu.co ...

  5. 第7章 使用springMVC构建Web应用程序 7.1 springMVC配置

    最近在看spring in action第3版,这里搭建一个简单的spring mvc,也算书没白看,当然老鸟可以不屑了,这里只是给自己做个笔记,配置也尽量删烦就简, Spring MVC的核心是Di ...

  6. NLP(二十一)根据已有文本LSTM自动生成文本

    根据已有文本LSTM自动生成文本 原理 与股票预测类似,用前面的n个字符预测下一个字符 https://www.cnblogs.com/peng8098/p/keras_5.html 代码 from ...

  7. android CTS 介绍

    [转]http://blog.csdn.net/pugongying1988/article/details/6976091 一.为什么需要兼容性测试(以下称CTS)? 1.1.让APP提供更好的用户 ...

  8. 在Linux查看版本命令

    1.在终端中执行下列指令: cat /etc/issue 可以查看当前正在运行的 Ubuntu 的版本号. 2. 使用 lsb_release 命令也可以查看 Ubuntu 的版本号,与方法一相比,内 ...

  9. eql框架。

    在刚进入公司的时候,在service层的框架用的是eql,是公司内的大佬封装的,作为一个小白,真的是折磨.公司内没有任何的文档,只能靠着自己一步一步的摸索. 后来用习惯了,发现这个框架确实有自己的独到 ...

  10. 接口请求失败处理,重新请求并限制请求次数.自己封装搞定retry函数

    最近开发一款小程序的时候想到一个问题,如果接口突然挂掉怎么办呢,于是乎想到一个解决办法.接口请求重试功能.并限制请求次数 用最新的async函数语法实现.代码简洁明了. 测试代码如下: functio ...