為什么要統(tǒng)一返回值
兩點:
1 為了制定規(guī)范提高工作效率(效率)
2 統(tǒng)一異常處理給用戶一個正確的提示(易用)
實現(xiàn)原理
使用@ControllerAdvice注解,該注解是Springmvc controller增強器。
統(tǒng)一接口的返回值實現(xiàn)

返回錢處理流程
使用@ControllerAdvice注解,實現(xiàn)ResponseBodyAdvice的beforeBodyWrite方法,用于對@ResponseBody返回值增加處理。
統(tǒng)一異常的返回值實現(xiàn)
使用@ControllerAdvice注解,自定義異常處理類,當被@ExceptionHandler注解的方法發(fā)生異常會處理定義的異常,然后分發(fā)到具體的處理方法。
實現(xiàn)方法
統(tǒng)一接口的返回值代碼實現(xiàn)
統(tǒng)一返回數(shù)據(jù)定義(RestReturn.java)
package com.springboot.action.saas.common.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import java.time.Instant;
/**
* 接口返回統(tǒng)一數(shù)據(jù)結構
*/
@Data
public class RestReturn {
//是否成功標志
private boolean success;
//code錯誤碼
private Integer code;
//外帶數(shù)據(jù)信息
private Object data;
//前端進行頁面展示的信息
private Object message;
//返回時間戳
private Long currentTime;
/**
*構造函數(shù)(無參數(shù))
*/
public RestReturn() {
//毫秒
this.currentTime = Instant.now().toEpochMilli();
}
/**
*構造函數(shù)(有參數(shù))
*/
public RestReturn(boolean success, Integer code, Object data, Object message) {
this.success = success;
this.code = code;
this.data = data;
this.message = message;
//毫秒
this.currentTime = Instant.now().toEpochMilli();
}
@Override
public String toString() {
return "RestReturn{" +
"success=" + success +
",code='" + code + '\'' +
",data=" + data +
",message=" + message +
",currentTime=" + currentTime +
'}';
}
public RestReturn success(Object data, Object message) {
this.success = true;
this.code = 0;
this.data = data;
this.message = message;
return this;
}
public RestReturn error(Integer code, Object data, Object message) {
this.success = false;
this.code = code;
this.data = data;
this.message = message;
return this;
}
public boolean isRestReturnJson(String data) {
//臨時實現(xiàn)先判定下字符串的格式和字段
try {
/**
* ObjectMapper支持從byte[]、File、InputStream、字符串等數(shù)據(jù)的JSON反序列化。
*/
ObjectMapper mapper = new ObjectMapper();
RestReturn dataRestReturn = mapper.readValue(data, RestReturn.class);
//比較兩個類的字段,如果一致返回為真,不一致返回為假
return true;
} catch (Exception e) {
return false;
}
}
}
實現(xiàn)ResponseBodyAdvice接口,重寫beforeBodyWrite,對返回的body做規(guī)范化返回值(RestReturnWrapper.java)
package com.springboot.action.saas.common.controller;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class RestReturnWrapper implements ResponseBodyAdvice<Object> {
/**
* 判定哪些請求要執(zhí)行beforeBodyWrite,返回true執(zhí)行,返回false不執(zhí)行
* */
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) {
//獲取當前處理請求的controller的方法
//String methodName = methodParameter.getMethod().getName();
// 攔/不攔截處理返回值的方法,如登錄
//String method = "login";
//這里可以加入很多判定,如果在白名單的List里面,是否攔截
return true;
}
/**
* 返回前對body,request,response等請求做處理
*
* @param body
* @param methodParameter
* @param mediaType
* @param httpMessageConverter
* @param serverHttpRequest
* @param serverHttpResponse
*
* @return
* */
@Override
public Object beforeBodyWrite(Object body,
MethodParameter methodParameter,
MediaType mediaType,
Class<? extends HttpMessageConverter<?>> httpMessageConverter,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
//具體返回值處理
//情況1 如果返回的body為null
if(body == null){
if (mediaType == MediaType.APPLICATION_JSON) {
//返回是json個格式類型,無body內(nèi)容
RestReturn restReturn = new RestReturn();
return restReturn.success("", "");
} else {
return null;
}
} else {
//情況2 文件上傳下載,不需要改動,直接返回
if (body instanceof Resource) {
return body;
} else if (body instanceof String) {
// 返回的是 String,
RestReturn restReturn = new RestReturn();
try {
if (restReturn.isRestReturnJson((String) body)) {
// 情況3 已經(jīng)是RestReturn格式的json 字符串不做統(tǒng)一格式封裝
return body;
} else {
//情況4 普通的返回,需要統(tǒng)一格式,把數(shù)據(jù)賦值回去即可。
return restReturn.success(body, "");
}
} catch (Exception e) {
// 因為 API返回值為String,理論上不會走到這個分支。
return restReturn.error(10000, body, e.getMessage());
}
} else {
//返回的是非字符串格式,實際上很多時候用都是是在應用程返回的對象居多
if(body instanceof RestReturn){
//情況5 如果已經(jīng)封裝成RestReturn,直接return
return body;
}else{
//情況6 非字符串非統(tǒng)一格式的返回,需要統(tǒng)一格式
//需要判定是否是拋出的異常返回(統(tǒng)一到錯誤輸出)
RestReturn restReturn = new RestReturn();
return restReturn.success(body, "");
}
}
}
}
}
統(tǒng)一異常的返回值實現(xiàn)
返回值統(tǒng)一處理后,發(fā)生異常的時候,異常的信息會全出現(xiàn)在body中,這個不是最終想要的結果。
定義統(tǒng)一的異常返回值和提示(RestReturnEnum.java)
package com.springboot.action.saas.common.controller;
/**
* 接口返回狀態(tài)和錯誤提示
*/
public enum RestReturnEnum {
SUCCESS(200, "成功"),
FAIL(-1, "失敗"),
BAD_REQUEST(400, "錯誤請求"),
FORBIDDEN(403, "禁止訪問"),
NOT_FOUND(404, "未找到"),
EXCEPTION(500, "系統(tǒng)異常");
//code錯誤碼
private Integer code;
//前端進行頁面展示的信息
private String message;
private RestReturnEnum(Integer code, String message){
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
}
定義自定義異常處理類(EntityNotFoundException.java)
樣例定義個獲取數(shù)據(jù)庫對應的數(shù)據(jù)為空的異常
package com.springboot.action.saas.common.exception;
import org.springframework.util.StringUtils;
public class EntityNotFoundException extends RuntimeException {
public EntityNotFoundException(Class clazz,
String key,
String value) {
super(EntityNotFoundException.generateMessage(
clazz.getSimpleName(),
key,
value));
}
//轉化為字符串,類的名字和k-v結構可變參數(shù)
private static String generateMessage(String entity,
String key,
String value) {
return StringUtils.capitalize(entity) +
" 未找到 " +
key +
" : " +
value;
}
}
定義全局異常處理Advice(GlobalExceptionAdvice.java)
指定發(fā)生的異常由那些異常處理類處理。
package com.springboot.action.saas.common.exception;
import com.springboot.action.saas.common.controller.RestReturn;
import com.springboot.action.saas.common.controller.RestReturnEnum;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
public class GlobalExceptionAdvice {
/**
* 處理所有不可知的異常
* @param e
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public RestReturn defaultException(HttpServletRequest request, Exception e){
//輸出堆棧信息到控制臺,以后記錄到日志
e.printStackTrace();
return new RestReturn(
false,
RestReturnEnum.EXCEPTION.getCode(),
"",
RestReturnEnum.EXCEPTION.getMessage()
);
}
/**
* 處理 接口無權訪問異常AccessDeniedException FORBIDDEN(403, "Forbidden"),
* @param e
* @return
*/
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
public RestReturn accessDeniedException(AccessDeniedException e){
//輸出堆棧信息到控制臺,以后記錄到日志
e.printStackTrace();
return new RestReturn(
false,
RestReturnEnum.FORBIDDEN.getCode(),
"",
RestReturnEnum.FORBIDDEN.getMessage()
);
}
/**
* 處理bad請求異常
* @param e
* @return
*/
@ExceptionHandler(value = RuntimeException.class)
@ResponseBody
public RestReturn badRequestException(RuntimeException e) {
e.printStackTrace();
return new RestReturn(
false,
RestReturnEnum.BAD_REQUEST.getCode(),
"",
RestReturnEnum.BAD_REQUEST.getMessage()
);
}
/**
* 處理 EntityNotFound 數(shù)據(jù)庫數(shù)據(jù)未找到
* @param e
* @return
*/
@ExceptionHandler(value = EntityNotFoundException.class)
@ResponseBody
public RestReturn entityNotFoundException(EntityNotFoundException e) {
// 打印堆棧信息
e.printStackTrace();
return new RestReturn(
false,
RestReturnEnum.NOT_FOUND.getCode(),
"",
RestReturnEnum.NOT_FOUND.getMessage()
);
}
}