java purge_springboot之全局处理统一返回

springboot之全局处理统一返回

简介

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

接口调用返回类似如下:

{

"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为data的数据类型,这个类包含了前端常用的字段,还有一些常用的静态初始化Result对象的方法。

/**

* 返回统一数据结构

*

* @author purgeyao

* @since 1.0

*/

@Data

@ToString

@NoArgsConstructor

@AllArgsConstructor

public class Result 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忽略拦截。

判断某些类不进行拦截.

判断某些包下所有类不进行拦截。如swagger的springfox.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方法处理里。

判断Object o是否为null,为null构建Result对象进行返回。

判断Object o是否是Result子类或其本身,该情况下,可能是接口返回时创建了Result,为了避免再次封装一次,判断是Result子类或其本身就返回Object o本身。

判断Object o是否是为String,在测试的过程中发现String的特殊情况,在这里做了一次判断操作,如果为String就进行JSON.toJSON(Result.ofSuccess(o)).toString()序列号操作。

其他情况默认返回Result.ofSuccess(o)进行包装处理。

/**

* {@link IgnorReponseAdvice} 处理解析 {@link ResponseBodyAdvice} 统一返回包装器

*

* @author purgeyao

* @since 1.0

*/

@RestControllerAdvice

public class CommonResponseDataAdvice implements ResponseBodyAdvice {

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) 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容器

最后将GlobalDefaultExceptionHandler以bean的方式注入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);

}

}

将GlobalDefaultConfiguration在resources/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 {

}

使用@Import将GlobalDefaultConfiguration导入即可。

使用

添加依赖

com.purgeteam

unified-dispose-deepblueai-starter

0.1.1.RELEASE

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

业务使用

接口:

@GetMapping("test")

public String test(){

return "test";

}

返回

{

"succ": true, // 是否成功

"ts": 1566386951005, // 时间戳

"data": "test", // 数据

"code": null, // 错误类型

"msg": null, // 错误描述

"fail": false

}

忽略封装注解:@IgnorReponseAdvice

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

接口:

@IgnorReponseAdvice // 忽略数据包装 可添加到类、方法上

@GetMapping("test")

public String test(){

return "test";

}

返回 test

总结

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

作者GitHub:

Purgeyao 欢迎关注

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值