📖 本文目录
📖 自定义统一返回类 和 全局异常控制
这里先推荐一个大佬的 自定义统一返回类,绝对清晰明了
缥缈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
你就可以得出如下结果:
当然我们也可以返回一个对象的数据信息:
编写一个用于返回对象的Controller:
@GetMapping("/returnObj")
public ResultData<User> getUser(){
return ResultData.success(new User(101,"Alascanfu"));
}
然后再启动项目通过浏览器进行测试:
http://localhost:8080/returnObj
结果与我们想要获取的数据是一致的:
但是你有没有发现,如果我们每次都对其编写相同的代码
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.直接进行返回数据对象:
2.直接返回字符串数据:
📑 构建全局异常返回类以及自定义客制化异常返回类
步骤4:构建全局异常返回类以及自定义客制化异常返回类
上述统一数据进行返回的格式已经测试成功了的嘛,但是这只是对正常数据测试的返回,那如果是异常错误信息什么的?怎么办?
这时候就要请出咱们的全局异常处理类来完成异常处理了
- 首先咱们来编写用于测试的两个异常Controller
@GetMapping("/exception1")
public void error1(){
throw new RuntimeException("自定义异常");
}
@GetMapping("/exception2")
public void error2(){
int i = 10/0;
}
这里需要先利用postman进行测试,因为前端页面会自动重定向到/error页面
- 创建一个全局异常处理类进行对其进行统一结果返回处理
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
📑 客制化异常处理类
当然我们也可以构建我们自己的异常处理类——来客制化
- 该类需要继承
RuntimeException
就如之前new RuntimeException 那样一致。 - 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,"某不知名异常");
}
okk~大功告成,这就是自定义统一返回类 和 全局异常控制~