springboot 后端统一返回数据格式,异常统一处理

模板

场景:

  • 后端给前端的数据类型可能会是基本数据类型、String字符串、对象、数组、或者异常提示等。前端拿到你返回的数据去展示或者给出错误提示,但他不可能说每个接口都把这些异常提示处理一遍,比如说返回没有登录、或者一些业务异常等。

分析:

  • 基于上面场景,那么我们要做的就是在后端返回结果前做一层统一处理。返回一个统一的对象,如ResponseVO,有code、msg、data;前端根据返回的code做统一处理
  • code=0,返回成功,返回数据在data上
  • code=1或其他,后端异常返回,可能是业务异常,也可能是程序异常,错误信息放在msg上
  • 如果未登入,后端返回403,这时候前端在调用后端接口返回那里根据错误码去做统一的处理,统一提示或其他。成功的话就把返回的数据data给对应调用方法那里。

实现:

初级版,我们返回一个map,然后通过map把code、msg、data 放进去

@RequestMapping("/test")
    public Map<String,Object> test(){
        Map<String,Object> map = new HashMap<>();
        map.put("code","0");
        map.put("msg","成功");
        map.put("data","测试");
        return map;
    }
  • 返回结果:
  • 上面图片我们可以看到,满足了我们的需求,返回了code、msg、还有我们的数据data。
  • 但是问题来了,我们每个方法都要写一遍map,把这些数据放进去是不是很麻烦呢,在上面花这么多时间去写这个还怎么摸鱼呢,因此我们小小的优化一下就有了我们的进阶版

进阶版,统一封装:定义一个统一的返回对象ResponseVO ,在ResponseVO 里写成功和失败的方法

@Data
public class ResponseVO implements Serializable {
/**
    * 响应状态码,0-成功,非0-失败
    */
   private Integer code = 0;
   /**
    * 返回结果说明
    */
   private String msg = "成功";
   /**
    * JSON格式响应数据:实体类数据
    */
   private Object data;
   /**
    * 返回成功
    * @param data
    * @return
    */
   public static ResponseVO success(Object data){
   	ResponseVO response = new ResponseVO();
   	response.setCode(0);
   	response.setMsg("成功");
   	response.setData(data);
   	return response;
   }
}
  • 这时候在controller调用就变成了下面这样,是不是简洁多了呢
@RequestMapping("/test1")
    public ResponseVO test1(){
        return ResponseVO.success("测试1");
    }
  • 现在虽然简洁多了,但是还是在每个方法上都要写ResponseVO.success()或者ResponseVO.fail(),而且每个方法的返回值都变成了ResponseVO,我们都不知道他们的意义了,那有没有统一处理的呢,就是我该返回啥就返回啥,controller层不用关心这些?答案当然是有的,因此就有了下面的最终版方案。

最终版,ResponseBodyAdvice

  • 接下来就要用到ResponseBodyAdvice,从字面意思理解它的意思就是返回体切面,就是对Controller返回的数据进行统一处理,因此我们只要实现这个接口,在上面做统一处理即可,他有两个接口,我们只需在beforeBodyWrite方法处理就可以了,唯一要注意的就是当返回String类型时要特殊处理,不然会报转换错误。统一封装后就不用去关心返回类型了。
@RestControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice<Object> {
	private Log log = LogFactory.getLog(ResponseHandler.class);
	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		return true;
	}
	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
								  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
								  ServerHttpResponse response) {
		ResponseVO respVo = null;
		if (body instanceof ResponseVO) {
			respVo = (ResponseVO) body;
		}else {
			respVo = new ResponseVO();
			respVo.setData(body);
		}
		//如果返回的字符串类型,会先判断HttpMessageConverter能否支持对应的返回类型再使用ResponseBodyAdvice进行封装
		//那么此时在进来就不是String类型,所以会报无法转换成ResponseVO对象,那么这里有两种方法,一种是直接返回json字符串,另一种是
		//一种是自己的WebConfig进行额外的配置
		if (body instanceof String){
			return JSONUtil.toJsonStr(respVo);
		}
		return respVo;
	}
}

问题1、假如有个接口特殊,不需要这个返回这个格式怎么办呢?

  • 我们可以用到ResponseBodyAdvice接口的另一个方法,让你的方法返回值不走这个统一返回格式处理,最好的方式就是定一个注解,在需要忽略的方法上加上这个注解,实现方式如下
  1. 定义注解IgnoreResponseHandler
@Documented
@Inherited
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseHandler {

}
  1. 在ResponseBodyAdvice的supports方法忽略
@RestControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice<Object> {
	private Log log = LogFactory.getLog(ResponseHandler.class);
	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		return !returnType.hasMethodAnnotation(IgnoreResponseHandler.class);
	}
}
  • 使用,返回结果,这样就忽略掉了
@RequestMapping("/test5")
    @IgnoreResponseHandler
    public String test5(){
        return "测试1";
    }

spring boot 后端返回 springboot对返回结果处理_spring boot 后端返回

全局异常处理器:统一处理返回的业务异常

  • Spring3.2帮我们写了一个通知新注解,其原理是Aop,注解为@ControllerAdvice,它能够默认拦截所有带@Controller注解的类,拦截之后可以写增强功能,可对controller中被 @RequestMapping注解的方法加一些逻辑处理。
  • @RestControllerAdvice@ControllerAdvice的区别就和@RestController与@Controller的区别类似,@RestControllerAdvice注解包含了@ControllerAdvice注解和@ResponseBody注解。
  • 当自定义类加@ControllerAdvice注解时,方法需要返回json数据时,每个方法还需要添加@ResponseBody注解:
  • 当自定义类加@RestControllerAdvice注解时,方法自动返回json数据,每个方法无需再添加@ResponseBody注解:
  • 最常用的就是异常处理:在类的方法上加上注解@ExceptionHandler(XxxException.class),就能捕获所有指定的异常。
  • 需要配合@ExceptionHandler使用。当将异常抛到controller时,可以对异常进行统一处理,规定返回的json格式或是跳转到一个错误页面
  • 对于全部的异常我们想统一处理,就要用到@ExceptionHandler(value = Exception.class)这个注解了,加上这个注解,当抛出异常时都会进这个方法。
//全局异常捕捉处理
	@ControllerAdvice
	public class CustomExceptionHandler {
        @ResponseBody
        @ExceptionHandler(value = Exception.class)
        public Map errorHandler(Exception ex) {
        Map map = new HashMap();
        map.put("code", 400);
        //判断异常的类型,返回不一样的返回值
        if(ex instanceof MissingServletRequestParameterException){
            map.put("msg","缺少必需参数:"+((MissingServletRequestParameterException) ex).getParameterName());
        }
        else if(ex instanceof MyException){
            map.put("msg","这是自定义异常");
        }
        return map;
    }
}
  • 自定义异常类
//自定义异常类
@Data
public class MyException extends RuntimeException {
    private long code;
    private String msg;
    public MyException(Long code, String msg){
        super(msg);
        this.code = code;
        this.msg = msg;
    }
    public MyException(String msg){
        super(msg);
        this.msg = msg;
    }
}
@RestController
public class TestController {
    @RequestMapping("testException")
    public String testException() throws Exception{
        throw new MissingServletRequestParameterException("name","String");
    }
    @RequestMapping("testMyException")
    public String testMyException() throws MyException{
        throw new MyException("i am a myException");
    }
}
  • 分别访问testException和testMyException接口,可得到以下结果
  • 如果不需要返回json数据,而要渲染某个页面模板返回给浏览器,那么可以这么实现:
@ExceptionHandler(value = MyException.class)
public ModelAndView myErrorHandler(MyException ex) {
    ModelAndView modelAndView = new ModelAndView();
    //指定错误页面的模板页
    modelAndView.setViewName("error");
    modelAndView.addObject("code", ex.getCode());
    modelAndView.addObject("msg", ex.getMsg());
    return modelAndView;
}