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>
T
为data
的数据类型,这个类包含了前端常用的字段,还有一些常用的静态初始化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
忽略拦截。 - 判断某些类不进行拦截.
- 判断某些包下所有类不进行拦截。如
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<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容器
最后将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
导入即可。
使用
添加依赖
<dependency>
<groupId>com.purgeteam</groupId>
<artifactId>unified-dispose-deepblueai-starter</artifactId>
<version>0.1.1.RELEASE</version>
</dependency>
启动类开启@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,我们可以通过一定的方式去简化,以达到一定目的减少开发量。
示例代码地址:unified-dispose-springboot
作者GitHub:
Purgeyao 欢迎关注
springboot之全局处理统一返回的更多相关文章
- Springboot项目全局异常统一处理
转自https://blog.csdn.net/hao_kkkkk/article/details/80538955 最近在做项目时需要对异常进行全局统一处理,主要是一些分类入库以及记录日志等,因为项 ...
- spring boot 2 全局统一返回RESTful风格数据、统一异常处理
全局统一返回RESTful风格数据,主要是实现ResponseBodyAdvice接口的方法,对返回值在输出之前进行修改.使用注解@RestControllerAdvice拦截异常并统一处理. 开发环 ...
- SpringBoot处理全局统一异常
在后端发生异常或者是请求出错时,前端通常显示如下 Whitelabel Error Page This application has no explicit mapping for /error, ...
- springboot统一返回json数据格式并配置系统异常拦截
本文链接:https://blog.csdn.net/syystx/article/details/82870217通常进行前后端分离开发时我们需要定义统一的json数据交互格式并对系统未处理异常进行 ...
- SpringBoot(八):系统错误统一拦截器
在日常 web 开发中发生了异常,往往需要通过一个统一的 异常处理,来保证客户端能够收到友好的提示.本文将会介绍 Spring Boot 中的 全局统一异常处理. Springboot的全局异常查是通 ...
- SpringBoot | 第八章:统一异常、数据校验处理
前言 在web应用中,请求处理时,出现异常是非常常见的.所以当应用出现各类异常时,进行异常的捕获或者二次处理(比如sql异常正常是不能外抛)是非常必要的,比如在开发对外api服务时,约定了响应的参数格 ...
- springboot之全局处理异常封装
springboot之全局处理异常封装 简介 在项目中经常出现系统异常的情况,比如NullPointerException等等.如果默认未处理的情况下,springboot会响应默认的错误提示,这样对 ...
- springmvc、 springboot 项目全局异常处理
异常在项目中那是不可避免的,通常情况下,我们需要对全局异常进行处理,下面介绍两种比较常用的情况. 准备工作: 在捕获到异常的时候,我们通常需要返回给前端错误码,错误信息等,所以我们需要手动封装一个js ...
- java统一返回标准类型
一.前言.背景 在如今前后端分离的时代,后端已经由传统的返回view视图转变为返回json数据,此json数据可能包括返回状态.数据.信息等......因为程序猿的习惯不同所以返回json数据的格式也 ...
随机推荐
- odoo通过actions.client进行自定义页面
一.使用原因 由于odoo自带页面在项目开发过程中无法满足使用,需要使用到动作ir.actions.client进行自定义视图的开发,实现自定义的xml视图开发. 二.实现目标 三.开发过程 1.项目 ...
- Python笔记_基础
1.注释 # 单行注释 """ 多行注释,一般用于类说明 """ 或 ''' 多行注释 ''' 2.工作日志 # TODO 说明性文字 记录 ...
- unity编辑器扩展_07(创建对话框,检测按钮的点击,点击按钮后提示信息,保存设置的数据,显示点击按钮后的处理的进度条信息)
代码: using UnityEditor;using UnityEngine; public class ChangeValue : ScriptableWizard { ...
- C++微信网页协议实现和应用
微信推送报警消息实现 目录 1 前言... 2 1.1 背景... 2 1.2 现有技术对比... 2 2 总体流程... 2 3 微信网页接口解析... ...
- MSIL实用指南-位运算
C#支持的位运算是与.或.异或.取反.左移.右移,它们对应的指令是And.Or.Xor.Not.Shl.Shr. 取反运算只需要一个操作数,生成步骤是1.生成加载变量2.生成取反指令实例代码: ilG ...
- 原生js之Math对象
1.比较方法(常用) Math.min() //求一组数中的最小值 不能是数组,和对象等等. Math.max() //求一组数中的最大值eg:Math.min(5,3,5) // 3 2.取整(常用 ...
- React项目升级遇到的问题复盘(2019-09-02)
老铁们,发没发现我换了个贼帅的头像,高端大气上档次,非洲大地我最凶!可把我自己牛逼坏了. 不扯啦不扯啦,抓紧进入今天的正题,从今天开始我会每天写一下每天工作的出现的问题,主要对这些问题出现的原因,以及 ...
- P3084 [USACO13OPEN]照片Photo dp
题意: 有n个区间,每个区间只能有一个斑点奶牛,问最多有几个斑点奶牛. 思路: 首先要处理出每个点的L[i],R[i]. L[i]表示L[i]-i-1之间一定有一个点.i也是选中的. R[i]表示R[ ...
- 一台Linux服务器可以负载多少个连接?
首先我们来看如何标识一个TCP连接?系统是通过一个四元组来识别,(src_ip,src_port,dst_ip,dst_port)即源IP.源端口.目标IP.目标端口.比如我们有一台服务192.168 ...
- 漫谈JavaScript中的提升机制(Hoisting)
前言 刚接触到JavaScript的时候,便知道JavaScript是按顺序执行的,是如浏览器的解析DOM树一样的流程,解析DOM结构的时候,如果遇到JS脚本或者外联脚本便会停止解析,继续下载脚本之后 ...