新项目中不希望每次返回参数还要重新封装再返回,希望直接将查询数据或者返回数据统一定义进行返回,所以将新项目中的全局统一返回配置记录,备忘。同时分享给有需要的码友。(例:查询出来的对象集合直接返回,会统一封装在一个返回对象的data中丢给前端,保持返回数据统一)

1.创建统一返回对象:

import lombok.Data;
import java.io.Serializable;

/**
 * 全局统一返回对象
 *
 * @author Tom
 * @date 2020-12-14
 */
@Data
public class ResultMsg implements Serializable {

    /**
     * 返回状态(成功:true 失败:false)
     */
    private boolean success;
    /**
     * 状态码(成功:200 失败:500)
     */
    private Integer code;
    /**
     * 异常提示消息
     */
    private String msg;
    /**
     * 返回参数
     */
    private Object data;

    public ResultMsg() {
    }

    public ResultMsg(boolean success) {
        this.success = success;
    }

    public ResultMsg(boolean success, Integer code) {
        this.success = success;
        this.code = code;
    }

    public ResultMsg(boolean success, Integer code, String msg) {
        this.success = success;
        this.code = code;
        this.msg = msg;
    }

    public ResultMsg(boolean success, Integer code, String msg, Object data) {
        this.success = success;
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public ResultMsg(boolean success, Integer code, Object data) {
        this.success = success;
        this.code = code;
        this.data = data;
    }
}

2.创建全局统一返回启用注解(作用于启动类上)

import com.xiaomi.upupoo.config.GlobalDefaultConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 全局统一返回启用注解(作用于启动类上)
 *
 * @author Tom
 * @date 2020-12-18
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(GlobalDefaultConfig.class)
public @interface EnableGlobalResponse {}

3.创建全局统一返回忽略标识注解(作用于: 类或方法上,标识后的类或方法则不进行全局返回的封装)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 全局统一返回忽略标识注解(作用于: 类或方法上,标识后的类或方法则不进行全局返回的封装)
 *
 * @author Tom
 * @date 2020-12-18
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreGlobalResponse {}

4.创建需要忽略的配置文件dispose.properties

# 全局统一返回忽略配置

# 过滤 swagger(若项目中使用)
dispose.advice-filter-package[0]=springfox.documentation
# 过滤 spring 官方包
dispose.advice-filter-package[1]=org.springframework

5.定义默认过滤配置中的包和类

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.ArrayList;
import java.util.List;

/**
 * 定义默认过滤配置中的包和类
 *
 * @author Tom
 * @date 2020-12-18
 */
@ConfigurationProperties(GlobalDefaultProperties.PREFIX)
public class GlobalDefaultProperties {

    /**
     * 定义过滤拦截头部
     */
    public static final String PREFIX = "dispose";

    /**
     * 统一返回过滤包
     */
    private List<String> adviceFilterPackage = new ArrayList<>();

    /**
     * 统一返回过滤类
     */
    private List<String> adviceFilterClass = new ArrayList<>();

    public List<String> getAdviceFilterPackage() {
        return adviceFilterPackage;
    }

    public void setAdviceFilterPackage(List<String> adviceFilterPackage) {
        this.adviceFilterPackage = adviceFilterPackage;
    }

    public List<String> getAdviceFilterClass() {
        return adviceFilterClass;
    }

    public void setAdviceFilterClass(List<String> adviceFilterClass) {
        this.adviceFilterClass = adviceFilterClass;
    }

}

6.全局统一返回处理类(重要)

import com.alibaba.fastjson.JSON;
import com.xiaomi.upupoo.common.annotation.response.IgnoreGlobalResponse;
import com.xiaomi.upupoo.common.pojo.vo.ResultMsg;
import com.xiaomi.upupoo.config.GlobalDefaultProperties;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * 全局统一返回处理类(重要)
 * {@link IgnoreGlobalResponse} 处理解析 {@link ResponseBodyAdvice} 统一返回包装器
 *
 * @author Tom
 * @date 2020-12-18
 */
@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {

    private GlobalDefaultProperties globalDefaultProperties;

    /**
     * 通过构造器将默认过滤配置注入
     * @param globalDefaultProperties
     */
    public GlobalResponseHandler(GlobalDefaultProperties globalDefaultProperties) {
        this.globalDefaultProperties = globalDefaultProperties;
    }

    /**
     * 重写supports方法,进行自定义规则拦截
     * @param methodParameter
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(MethodParameter methodParameter,Class<? extends HttpMessageConverter<?>> aClass) {
        return filter(methodParameter);
    }

    /**
     * 全局统一返回处理类 - 核心方法
     * 返回值为Object类型并且返回为空  AbstractMessageConverterMethodProcessor#writeWithMessageConverters 方法
     * 无法触发调用本类的 beforeBodyWrite 处理,所以在Controller层尽量避免直接使用 "Object" 类型返回。
     * @param o
     * @param methodParameter
     * @param mediaType
     * @param aClass
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @return
     */
    @Nullable
    @Override
    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) {
            /**当 o 返回类型为 string 并且为null会出现 java.lang.ClassCastException: Result cannot be cast to java.lang.String,则封装ResultMsg对象并转换为String返回*/
            if (methodParameter.getParameterType().getName().equals("java.lang.String")) {
                return JSON.toJSON(new ResultMsg(true,200)).toString();
            }
            return new ResultMsg(true,200);
        }
        /**当 o 返回类型为ResultMsg(统一封装返回对象),则直接返回*/
        if (o instanceof ResultMsg) {
            return o;
        }
        /**当 o 为string 则特殊处理 java.lang.ClassCastException: Result cannot be cast to java.lang.String,封装ResultMsg对象并转换为String返回*/
        if (o instanceof String) {
            return JSON.toJSON(new ResultMsg(true,200,o)).toString();
        }
        return new ResultMsg(true,200,o);
    }

    /**
     * 自定义规则拦截器
     * 过滤: 1.检查过滤包路径 2.检查<类>过滤列表 3.检查忽略注解是否存在于类上 4.检查注解是否存在于方法上
     * @param methodParameter
     * @return
     */
    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(IgnoreGlobalResponse.class)) {
            return false;
        }
        /**检查注解是否存在于方法上*/
        if (methodParameter.getMethod().isAnnotationPresent(IgnoreGlobalResponse.class)) {
            return false;
        }
        return true;
    }
}

7.将全局统一返回注入spring容器(将需要忽略的包或类从配置中获取)

import com.xiaomi.upupoo.common.GlobalResponseHandler;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
 * 全局统一返回注入spring容器(将需要忽略的包或类从配置中获取)
 *
 * @author Tom
 * @date 2020-12-18
 */
@Configuration
@EnableConfigurationProperties(GlobalDefaultProperties.class)
@PropertySource(value = "classpath:config/dispose.properties", encoding = "UTF-8")
public class GlobalDefaultConfig {

    @Bean
    public GlobalResponseHandler commonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties) {
        return new GlobalResponseHandler(globalDefaultProperties);
    }
}

8.完成配置,将@EnableGlobalResponse注解加入启动类上即可。

9.实例显示:

/**
     * 数据字典 - 查询所有类型
     * @return
     */
    @ResponseBody
    @GetMapping(value = "/queryAll")
    public List<DictionaryVo> queryAll() {
        return dictionaryService.queryAll();
    }

返回的List集合对象被统一处理,返回给前端一个ResultMsg Json对象,数据则被封装在data参数中。