在做前后端分離的項目時,后端業務通常會使用多個微服務,我們希望在每一個微服務的調用接口返回給前端的結果都是統一的數據結構,如:
{
"successful": true,
"code": "200",
"message": "success",
"data": "this is data"
}
在上面的結構中,有請求是否成功標識-successful,其值為boolean類型;有服務處理結果編碼-code,其值為String,可以封裝自定義編碼,也可以使用HttpStatus;有服務處理結果文本信息message;還有業務返回數據-data,其值類型為Object,即可以返回String、List、對象信息等等。
定義統一的返回結構體:
/**
* 返回結構體
* @author Yoko
*/
@Data
@Builder
public class ResponseDto implements Serializable {
private static final long serialVersionUID = 2684402708467978694L;
private Boolean successful;
private String code;
private String message;
private Object data;
public static ResponseDto success() {
return ResponseDto.builder().successful(true).code(String.valueOf(HttpStatus.OK.value())).message(HttpStatus.OK.getReasonPhrase()).build();
}
public static ResponseDto success(Object data) {
ResponseDto responseDto = success();
responseDto.setData(data);
return responseDto;
}
public static ResponseDto fail() {
return ResponseDto.builder().successful(false).code(ExceptionEnum.SERVER_ERROR.getCode()).message(ExceptionEnum.SERVER_ERROR.getMessage()).build();
}
public static ResponseDto fail(String message) {
ResponseDto responseDto = fail();
responseDto.setMessage(message);
return responseDto;
}
public static ResponseDto fail(String code,String message) {
ResponseDto responseDto = fail();
responseDto.setCode(code);
responseDto.setMessage(message);
return responseDto;
}
public static ResponseDto fail(String code,String message, Object data) {
ResponseDto responseDto = fail();
responseDto.setCode(code);
responseDto.setMessage(message);
responseDto.setData(data);
return responseDto;
}
}
對於正常的返回場景,我們可以直接進行封裝數據,而對於異常場景,也希望使用上面的統一數據格式,而如果在controller層使用try...catch來處理異常,那么會產生大量的try...catch,代碼會變的很亂。這種場景可以使用Spring Boot的@RestControllerAdvice注解來實現封裝異常信息返回給前端。
定義異常枚舉:
/**
* @author Yoko
*/
public enum ExceptionEnum {
/**
* code: 異常編碼
* message:異常信息
*/
BAD_REQUEST("400", "請求數據格式非法。"),
FORBIDDEN("403", "沒有訪問權限!"),
NO_RESOURCE("404", "請求的資源找不到!"),
SERVER_ERROR("500", "服務器內部錯誤!"),
SERVICE_BUSY("503", "服務器正忙,請稍后再試!"),
UNKNOWN("10000", "未知異常!"),
IS_NOT_NULL("10001","%s不能為空");
private String code;
private String message;
ExceptionEnum(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
自定義異常類:
/**
* @author Yoko
*/
public class BusinessException extends RuntimeException{
private static final long serialVersionUID = -330737451734079004L;
private ExceptionEnum exceptionEnum;
private String code;
private String message;
public BusinessException() {
super();
}
public BusinessException(ExceptionEnum exceptionEnum) {
this.exceptionEnum = exceptionEnum;
this.code = exceptionEnum.getCode();
this.message = exceptionEnum.getMessage();
}
public BusinessException(String code, String message) {
this.code = code;
this.message = message;
}
public BusinessException(String code, String message, Object... args) {
this.code = code;
this.message = String.format(message, args);
}
public ExceptionEnum getExceptionEnum() {
return exceptionEnum;
}
public void setExceptionEnum(ExceptionEnum exceptionEnum) {
this.exceptionEnum = exceptionEnum;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
定義RestControllerAdvice捕獲全局異常
/**
* @author Yoko
* @date 2021/11/19
*/
@Slf4j
@RestControllerAdvice(annotations = RestController.class)
public class ExceptionHandlerConfig {
/**
* 業務異常處理
*/
@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public ResponseDto exceptionHandler(BusinessException e) {
return ResponseDto.fail(e.getCode(), e.getMessage());
}
/**
* 未知異常處理
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseDto exceptionHandler(Exception e) {
return ResponseDto.fail(ExceptionEnum.UNKNOWN.getCode(),
ExceptionEnum.UNKNOWN.getMessage());
}
/**
* 空指針異常
*/
@ExceptionHandler(value = NullPointerException.class)
@ResponseBody
public ResponseDto exceptionHandler(NullPointerException e) {
return ResponseDto.fail(ExceptionEnum.SERVER_ERROR.getCode(),
ExceptionEnum.SERVER_ERROR.getMessage());
}
}
定義service:
/**
* @author Yoko
*/
@Service
public class TestServiceImpl {
public Object test1() {
return "hello world";
}
public Object test3() {
throw new BusinessException(ExceptionEnum.NO_RESOURCE.getCode(), ExceptionEnum.NO_RESOURCE.getMessage());
}
}
定義controller:
/**
* @author Yoko
*/
@Slf4j
@RestController
public class RestControllerAdviceTest {
@Resource
private TestServiceImpl testService;
@RequestMapping(name = "正常場景", value = "/test1", method = RequestMethod.PUT)
public ResponseDto test1() {
return ResponseDto.success(this.testService.test1());
}
@RequestMapping(name = "空指針場景", value = "/test2", method = RequestMethod.PUT)
public ResponseDto test2() {
String str = null;
str.equals("abc");
return ResponseDto.success();
}
@RequestMapping(name = "指定錯誤異常", value = "/test3", method = RequestMethod.PUT)
public ResponseDto test3() {
this.testService.test3();
return ResponseDto.success(this.testService.test1());
}
}
測試結果:
注:ControllerAdvice和RestControllerAdvice的區別
兩者都是全局捕獲異常,但是RestControllerAdvice更加強大,其作用相當於ControllerAdvice+ResponseBody