1. 程式人生 > 其它 >springboot 後端統一返回資料格式,異常統一處理

springboot 後端統一返回資料格式,異常統一處理

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";
    }

統一處理返回的業務異常

  • 對於異常我們想統一處理,就要用到@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("測試異常");
    }