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

场景:

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

分析:

基于上面场景,那么我们要做的就是在后端返回结果前做一层统一处理。返回一个统一的对象,如ResponseVO,有code、msg、data;前端根据返回的code做统一处理

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

实现:

好那么接下来就是基本的实现。

  1. 初级版,我们返回一个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,把这些数据放进去是不是很麻烦呢,在上面花这么多时间去写这个还怎么摸鱼呢,因此我们小小的优化一下就有了我们的进阶版

  1. 进阶版,统一封装
    定义一个统一的返回对象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层不用关心这些?答案当然是有的,因此就有了下面的最终版方案。
3. 最终版,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、假如有个接口特殊,不需要这个返回这个格式怎么办呢?
2、返回的业务异常是否也可以统一处理呢?

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

我们可以用到ResponseBodyAdvice接口的另一个方法,让你的方法返回值不走这个统一返回格式处理,最好的方式就是定一个注解,在需要忽略的方法上加上这个注解,实现方式如下
1、定义注解IgnoreResponseHandler

@Documented
@Inherited
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseHandler {

}

2、在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";
    }

返回结果,这样就忽略掉了
在这里插入图片描述

2、返回的业务异常是否也可以统一处理呢?

对于异常我们想统一处理,就要用到@ExceptionHandler(value = Exception.class)这个注解了,加上这个注解,当抛出异常时都会进这个方法

	@ExceptionHandler(value = Exception.class)
	public ResponseVO onException(HttpServletRequest request, Exception ex) {
		ResponseVO resp = null;
		if (ex instanceof AppException) {
			resp = new ResponseVO((AppException) ex);
		} else {
			AppException exception = new AppException(9999,"未知异常");
			resp = new ResponseVO(exception);
			log.error("未知异常:", ex);
		}

		return resp;
	}

使用

@RequestMapping("/test3")
    public String test3(){
        throw new AppException("测试异常");
    }

在这里插入图片描述

好了这样就可以了,当我们在业务实现是抛出业务异常,返回的异常就会统一处理了。

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值