统一返回格式
身为一名后端开发者,少不了前后端的信息交互,这个时候如果你传给前端的信息乱七八糟,往往少不了挨一顿揍。所以统一一下返回格式还是很有必要的。有点经验的开发者一般都会养成这个习惯,所以今天这篇文章的目的并不在于讲解如何统一返回信息。现在有这么一个问题,你已经封装好了统一的返回信息,按照标准给前端返回了code,msg和data,但是你每次写接口都要重新写一遍生成这个对象,再包装,让代码变得十分冗余并且不美观。接下来就记录我个人在自己的项目上对这个现象进行优化的过程。
@RestControllerAdvice注解
首先他是一个组合注解,由@ControllerAdvice、@ResponseBody组成,而@ControllerAdvice继承了@Component。个人理解他的作用是,在Controller层return完数据,在最终返回给前端信息前对数据进行拦截并自定义处理。看一下ResponseBodyAdvice的接口源码。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface ResponseBodyAdvice<T> {
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
@Nullable T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response); }
|
接口源码很简单,接下来就是自己写一个实现类去实现这个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @RestControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; }
@SneakyThrows @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof ResponseResult){ return body; } return ResponseResult.success(body); } }
|
因为我个人项目是微服务的架构,所以这个实现类配置在公共库library中,还要记得再具体启动类上配置一下扫描路径,单体应用可以略过这个步骤
1 2 3 4
|
@ComponentScan(basePackages = {"com.xxx.xxx","com.xxx.library"})
|
这样就已经配置好了,写一个接口测试一下
1 2 3 4 5 6 7 8
| @RestControllerAdvice @RequestMapping("/user") public class UserController { @RequestMapping(value = "/exposure/test",method = RequestMethod.POST) public Integer dele() throws ExceptionVo { return 2; } }
|
测试结果
1 2 3 4 5
| { "code": 200, "msg": "SUCCESS", "data": 2 }
|
这样我们就再也不用不断创建ResponseResult啦,省去了很多代码。
然而别高兴的太早,我们调用的方法一旦出现异常,返回的信息就会变成这样。
1 2 3 4 5 6 7 8 9 10
| { "code": 200, "msg": "SUCCESS", "data": { "timestamp": 1668752852033, "status": 500, "error": "Internal Server Error", "path": "/user/exposure/test" } }
|
这下又会被前端揍了,所以我们需要做点措施,以防挨揍。
全局异常捕获与处理
你也很烦每次涉及到数据库什么操作都要加个try-catch吧?我的评价是不如直接抛出异常让全局异常处理器去干这个活。他还有个好处,try-catch捕获不了的异常,它也能捕获,例如参数注解校验上报的错。按照我的想法,我想直接抛出错误枚举类(就是你自己定义的状态码+信息)中的返回信息,那么就要自定义异常,再全局捕获这个异常,再直接返回异常信息。配置步骤如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public class ExceptionVo extends RuntimeException{
private RespBeanEnum e;
public ExceptionVo() { super(); }
public ExceptionVo(RespBeanEnum res) { super(res.getMessage()); this.e = res; }
public RespBeanEnum getE() { return e; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@Slf4j @RestControllerAdvice public class RestExceptionHandler {
@ExceptionHandler(ExceptionVo.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseResult exception(ExceptionVo e) { log.error("运行时异常:{}", e.getE().getMessage(), e); return ResponseResult.error(e.getE()); } }
|
测试时间到!
1 2 3 4 5 6 7 8
| @RestControllerAdvice @RequestMapping("/user") public class UserController { @RequestMapping(value = "/exposure/test",method = RequestMethod.POST) public Integer dele() throws ExceptionVo { throw new ExceptionVo(RespBeanEnum.DELETE_ERROR); } }
|
这个时候,之前这部分的代码就发挥作用了
1 2 3
| if (body instanceof ResponseResult){ return body; }
|
没有这部分代码的话,结果会是这样
1 2 3 4 5 6 7 8
| { "code": 200, "msg": "SUCCESS", "data": { "code": 3314, "msg": "删除出错" } }
|
加上这部分,才会是正确的结果,具体为什么很简单。在异常处理器里你已经返回了一个ResponseResult对象,不加这句话,会在你定义的ResponseAdvice里再封装一次,因此判断一下body是不是已经是ResponseResult,是就直接返回。
正确结果
1 2 3 4
| { "code": 3314, "msg": "删除出错" }
|
到这里,项目上必要的优化配置已经结束了,接下来就是在合适的地方写就好了。如果有任何错误的地方欢迎联系我指出。