Springboot And Vue —— 自定义统一返回类 和 全局异常控制 (囊括项目开发场景~)通俗易懂

📖 自定义统一返回类 和 全局异常控制

这里先推荐一个大佬的 自定义统一返回类,绝对清晰明了

缥缈Jam:【老鸟玩系列】

SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!

这里小付对其细化整理。

众所周知:前后端分离项目中,如果后端猿们传的一些奇葩数据给前端的话,可能会被前端人员暴打至于为什么?不用说咱们都知道——比如说你就传一个data回去,或者就是前端传入的数据是一个非法数据,咱们后端传回去时,确定其合理等等一些列问题都是可能导致后端猿们渐渐灭绝的原因之一

📑 自定义状态码枚举类

步骤1:我们首先需要定义一个状态码枚举类

在我们的 blog-common 中用于存放有关于实体的类

故就有了 blog-web ---- com.alascanfu.utils.resultData.ReturnCode

/***
 * @author: Alascanfu
 * @date : Created in 2022/2/10 17:09
 * @description: 状态码枚举类
 * @modified By: Alascanfu
 **/
public enum ReturnCode {
    /**操作成功**/
    RC200(200,"操作成功"),
    /**操作失败**/
    RC999(999,"操作失败"),
    /**服务限流**/
    RC100(100,"服务器开启限流保护,请稍后再试!"),
    /**服务降级**/
    RC201(201,"服务器开启降级保护,请稍后再试!"),
    /**热点参数限流**/
    RC202(202,"热点参数限流,请稍后再试!"),
    /**系统规则不满足要求**/
    RC203(203,"系统规则不满足要求,请稍后再试!"),
    /**授权规则不通过**/
    RC204(204,"授权规则不通过,请稍后再试!"),
    /**access_denied**/
    RC403(403,"无访问权限,请联系管理员授予权限"),
    /**Not,Found~**/
    RC404(404,"没有找到您所要访问的页面,请重试"),
    /**access_denied**/
    RC401(401,"匿名用户访问无权限资源时的异常"),
    /**服务器异常**/
    RC500(500,"系统异常,请稍后重试~"),
    
    
    /**访问令牌不合法**/
    INVALID_TOKEN(2001,"访问令牌不合法"),
    /**访问令牌不合法**/
    ACCESS_DENIED(2003,"没有权限访问该资源"),
    /**客户端认证出错**/
    CLIENT_AUTHENTICATION_FAILED(1001,"客户端认证失败"),
    /**用户名或者密码错误**/
    USERNAME_OR_PASSWORD(1002,"用户名或者密码错误"),
    /**不支持的用户认证模式**/
    UNSUPPORTED_GRANT_TYPE(1003,"不支持的认证模式");
    ;
    
    /** 自定义状态码 **/
    private final int code;
    /**自定义描述**/
    private final String message;
    
    ReturnCode(int code, String message) {
        this.code = code;
        this.message = message;
    }
    
    public int getCode(){
        return code ;
    }
    
    public String getMessage (){
        return message;
    }
}

这里是根据大佬 缥缈Jam 中的状态码进行改写了一部分,也可以进行对其增加状态情况定义特定的枚举类。

该类中需要有 自定义好构造器与get/set方法 用于获取状态结果枚举返回。

📑 统一结果返回类ResultData

步骤2:我们需要构建统一的结果返回类ResultData

blog-web ---- com.alascanfu.utils.resultData.ResultData

/***
 * @author: 缥缈Jam Alascanfu
 * @date : Created in 2022/2/10 16:37
 * @description: 用于统一返回数据格式
 * @modified By: Alascanfu
 **/
@Data
public class ResultData<T> {
    /** 状态码 */
    private int status ;
    /** 返回的操作信息 */
    private String message ;
    /** 返回的数据信息 */
    private T data ;
    /** 记录当前操作时间 */
    private long timestamp ;
    
    public ResultData() {
        this.timestamp = System.currentTimeMillis();
    }
    
    /**统一结果返回 成功时 */
    public static <T> ResultData<T> success(T data){
        ResultData<T> resultData = new ResultData<>();
        resultData.setStatus(ReturnCode.RC200.getCode());
        resultData.setMessage(ReturnCode.RC200.getMessage());
        resultData.setData(data);
        return resultData;
    }
    
    /**统一结果返回 失败时 */
    public static <T> ResultData<T> fail(int code,String message){
        ResultData<T> resultData = new ResultData<>();
        resultData.setStatus(code);
        resultData.setMessage(message);
        return resultData;
    }
}

统一结果的返回类:当我们需要进行返回的时候就可以直接通过如下方式:

@GetMapping("/returnStr")
public ResultData<String> hello(){
    return ResultData.success("Hello,World!~");
}

然后咱们启动项目通过浏览器测试:

http://localhost:8080/returnStr

你就可以得出如下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TiuWB8nk-1651765118024)(H:/csdn%E5%8D%9A%E6%96%87%E8%AE%B0%E5%BD%95/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/image-20220211145927934.png)]

当然我们也可以返回一个对象的数据信息:

编写一个用于返回对象的Controller:

@GetMapping("/returnObj")
public ResultData<User> getUser(){
    return ResultData.success(new User(101,"Alascanfu"));
}

然后再启动项目通过浏览器进行测试:

http://localhost:8080/returnObj

结果与我们想要获取的数据是一致的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2X7fLtSN-1651765118027)(C:/Users/Alascanfu/AppData/Roaming/Typora/typora-user-images/image-20220211150359558.png)]

但是你有没有发现,如果我们每次都对其编写相同的代码

ResultData.success(xxx)

📑 实现ResponseBodyAdvice接口对全局控制管理数据统一格式进行返回

步骤3:通过实现ResponseBodyAdvice接口对全局控制管理数据统一格式进行返回

如果业务庞大的话代码冗余程度就会非常的高,所以我们是否通过某些特性将其封装统一返回只需要写一次,而每个Controller就专心用于返回数据就可以了呢?

咱们可以通过一个类标注@Controller的增强注解@ControllerAdvice 的同时使得该类去实现ResponseBodyAdvice这个接口。

正如大佬所说知其然知其所以然。

窥探ResponseBodyAdvice这个接口源码:

public interface ResponseBodyAdvice<T> {

	/**
	 * Whether this component supports the given controller method return type
	 * and the selected {@code HttpMessageConverter} type.
	 * @return {@code true} if {@link #beforeBodyWrite} should be invoked;
	 * {@code false} otherwise
	 */
	boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

	/**
	 * Invoked after an {@code HttpMessageConverter} is selected and just before
	 * its write method is invoked.
	 * @param body the body to be written
	 */
	@Nullable
	T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType,
			ServerHttpRequest request, ServerHttpResponse response);

}

该接口的第一个方法是supports:是否支持对控制层返回值数据进行封装等操作

第二个方法 beforeBodyWrite:在执行数据返回时之前的一个方法,可以对数据进行处理,用来对其进行统一处理或者加密等等…

@ControllerAdvice注解是干什么的?

  • 全局异常处理
  • 全局数据绑定
  • 全局数据预处理

如果上面看不懂,你就可以简单理解该注解就是为了实现全局控制管理的注解。

因为其是属于对Controller层的对数据统一封装的控制拦截功能

blog-web ---- com.alascanfu.interceptor.ResponseAdvice.java

/***
 * @author: Alascanfu
 * @date : Created in 2022/2/10 17:21
 * @description: 用于拦截Controller层数据返回时进行数据格式统一封装
 * @modified By: Alascanfu
 **/
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    ObjectMapper objectMapper;
    /** 是否开启拦截Controller的返回值,进行统一数据格式的封装 */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
    
    /** 对Controller层数据返回数据进行统一处理*/
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //如果数据返回的是字符串类型,我们需要通过objectMapper将其转换为JSON数据传回给前端进行控制
        if (body instanceof String){
            return objectMapper.writeValueAsString(ResultData.success(body));
        }
        //如果数据返回的是ResultData类型直接返回无需继续封装
        if (body instanceof ResultData){
            return body;
        }
        return ResultData.success(body);
    }
}

这样一来简单的统一结果返回类,以及全局控制统一结果进行返回就完成啦~

此时来进行编写代码进行测试,是否达到我们的设想需求:

TestController.java

@GetMapping("/helloWorld")
public String helloWorld(){
    return "Hello,World!~";
}

@GetMapping("/helloUser")
public User helloUser(){
    return new User(101,"Alascanfu");
}

启动项目进入浏览器进行测试,是否符合我们所预想那样

1.直接进行返回数据对象:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WGZZb2xj-1651765118028)(H:/csdn%E5%8D%9A%E6%96%87%E8%AE%B0%E5%BD%95/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/image-20220211155503670.png)]

2.直接返回字符串数据:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13crK5sw-1651765118030)(H:/csdn%E5%8D%9A%E6%96%87%E8%AE%B0%E5%BD%95/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/image-20220211155642064.png)]

📑 构建全局异常返回类以及自定义客制化异常返回类

步骤4:构建全局异常返回类以及自定义客制化异常返回类

上述统一数据进行返回的格式已经测试成功了的嘛,但是这只是对正常数据测试的返回,那如果是异常错误信息什么的?怎么办?

这时候就要请出咱们的全局异常处理类来完成异常处理了

  • 首先咱们来编写用于测试的两个异常Controller
@GetMapping("/exception1")
public void error1(){
    throw new RuntimeException("自定义异常");
}
@GetMapping("/exception2")
public void error2(){
    int i = 10/0;
}

这里需要先利用postman进行测试,因为前端页面会自动重定向到/error页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q1wnaRDT-1651765118031)(H:/csdn%E5%8D%9A%E6%96%87%E8%AE%B0%E5%BD%95/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/image-20220211161038214.png)]

  • 创建一个全局异常处理类进行对其进行统一结果返回处理

blog-web ---- com.alascanfu.handler.exception.RestExceptionHandler

/***
 * @author:缥缈Jam Alascanfu
 * @date : Created in 2022/2/10 17:47
 * @description: 用于统一控制全局异常返回数据
 * @modified By: Alascanfu
 **/
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler<T> {
    
    /** 默认定义服务器的内部异常处理 */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//状态码500
    public ResultData<String> exception(Exception e){
        log.error("服务器内部异常 ex=>{}",e.getMessage(),e);
        return ResultData.fail(ReturnCode.RC500.getCode(),e.getMessage());
    }
    
}

@RestControllerAdvice 也是一个组合注解:@ControllerAdvice 和 @Response

@ExceptionHandler: 针对异常处理类,这里是Exception 那么当前就是配置的默认全局异常的处理类。

@ResponseStatus: 针对于HttpStatus这个枚举类中出现的状态码进行处理 这里是对 服务器内部出现异常就进行处理。


当然需要注意的是:全局异常处理需要返回的是一个也是全局统一返回格式的数据,这里返回的数据并非由Controller代为返回所以只能通过调用ResultData.fail来进行返回异常处理的情况…


随后启动项目,通过浏览器对异常处理进行测试:

http://localhost:8080/exception2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HjyUfMmm-1651765118033)(C:/Users/Alascanfu/AppData/Roaming/Typora/typora-user-images/image-20220211162122160.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQ7igZ1w-1651765118035)(H:/csdn%E5%8D%9A%E6%96%87%E8%AE%B0%E5%BD%95/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/image-20220211162007457.png)]


📑 客制化异常处理类

当然我们也可以构建我们自己的异常处理类——来客制化

  1. 该类需要继承RuntimeException就如之前new RuntimeException 那样一致。
  2. blog-web ---- com.alascanfu.handler.exception.CustomExceptionHandler
/***
 * @author: Alascanfu
 * @date : Created in 2022/2/11 1:39
 * @description: 用户登录的异常处理类
 * @modified By: Alascanfu
 **/
@Data
public class CustomExceptionHandler extends RuntimeException{
    
    private int status ;
    private String message ;
    private String data ;
    private long timestamp;
    
    public CustomExceptionHandler(int status ,String message){
        this.status = status;
        this.message = message;
    }
    
}

随后需要将自定义的异常处理类交付给全局异常处理进行控制

blog-web ---- com.alascanfu.handler.exception.RestExceptionHandler中追加自定义全局异常处理

/** 测试自定义异常处理 */
@ExceptionHandler(CustomExceptionHandler.class)
public ResultData<T> customException(CustomExceptionHandler e){
    log.error("自定义异常 ex=>{}",e.getMessage(),e);
    return ResultData.fail(e.getStatus(),e.getMessage());
}

这样一来咱们就可以去测试自定义异常类啦~

@GetMapping("/customException")
public void error3(){
    throw new CustomExceptionHandler(1234,"某不知名异常");
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zS2tvtu3-1651765118036)(H:/csdn%E5%8D%9A%E6%96%87%E8%AE%B0%E5%BD%95/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/image-20220211164227910.png)]

okk~大功告成,这就是自定义统一返回类 和 全局异常控制~

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alascanfu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值