在項目整體架構設計的時候,我們經常需要做以下工作:
- 傳回值的統一封裝處理,因為隻有規範好統一的傳回值格式,才能不會給接口使用者帶來疑惑和友善前端對接口的統一處理。
- 對異常碼進行嚴格規定,什麼錯誤傳回什麼碼制,這樣前端就可以根據不同的碼制進行錯誤提示,同時也友善三方調用者對異常進行處理。要注意,微服務架構,不同的微服務之間的碼制字首要做區分,這樣我們就可以根據碼制很快定位到是哪個微服務出現問題。
- 對異常的統一攔截,這一點非常重要,因為一旦對異常沒有做統一處理,嚴重就會導緻堆棧乃至sql資訊暴露給前端,這是非常嚴重的事故。
是以今天我會給大家展示如何對傳回值進行統一封裝,如何對異常進行統一攔截,異常碼定義,根據自己的業務自行定義即可。希望大家多多點贊支援
1. 傳回值的封裝
分為兩步,首先定義傳回值類型和字段,然後定義BaseController。
1.1 響應資訊主體
我定義的這個涵蓋了我們要傳回的各種情況,大家可以參考
/**
* 響應資訊主體
*/
@Data
@NoArgsConstructor
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成功
*/
public static final int SUCCESS = 200;
/**
* 失敗
*/
public static final int FAIL = 500;
private int code;
private String msg;
private T data;
public static <T> R<T> ok() {
return restResult(null, SUCCESS, "操作成功");
}
public static <T> R<T> ok(T data) {
return restResult(data, SUCCESS, "操作成功");
}
public static <T> R<T> ok(String msg) {
return restResult(null, SUCCESS, msg);
}
public static <T> R<T> ok(String msg, T data) {
return restResult(data, SUCCESS, msg);
}
public static <T> R<T> fail() {
return restResult(null, FAIL, "操作失敗");
}
public static <T> R<T> fail(String msg) {
return restResult(null, FAIL, msg);
}
public static <T> R<T> fail(T data) {
return restResult(data, FAIL, "操作失敗");
}
public static <T> R<T> fail(String msg, T data) {
return restResult(data, FAIL, msg);
}
public static <T> R<T> fail(int code, String msg) {
return restResult(null, code, msg);
}
/**
* 傳回警告消息
*
* @param msg 傳回内容
* @return 警告消息
*/
public static <T> R<T> warn(String msg) {
return restResult(null, HttpStatus.WARN, msg);
}
/**
* 傳回警告消息
*
* @param msg 傳回内容
* @param data 資料對象
* @return 警告消息
*/
public static <T> R<T> warn(String msg, T data) {
return restResult(data, HttpStatus.WARN, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) {
R<T> r = new R<>();
r.setCode(code);
r.setData(data);
r.setMsg(msg);
return r;
}
public static <T> Boolean isError(R<T> ret) {
return !isSuccess(ret);
}
public static <T> Boolean isSuccess(R<T> ret) {
return R.SUCCESS == ret.getCode();
}
}
1.2 BaseController 的寫法
/**
* web層通用資料處理
*/
public class BaseController {
/**
* 響應傳回結果
*
* @param rows 影響行數
* @return 操作結果
*/
protected R<Void> toAjax(int rows) {
return rows > 0 ? R.ok() : R.fail();
}
/**
* 響應傳回結果
*
* @param result 結果
* @return 操作結果
*/
protected R<Void> toAjax(boolean result) {
return result ? R.ok() : R.fail();
}
//同時也可以将一些常用的方法放在這裡,比如擷取登入使用者資訊
}
1.3 示例
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/category")
public class WfCategoryController extends BaseController {
@GetMapping("/eventTest")
public R<Void> eventTest(){
applicationEventPublisher.publishEvent(new UserChangePasswordEvent("11111"));
return R.ok();
}
}
傳回值:
2. 全局異常攔截
全局異常攔截的本質實際上采用的是spring的AOP攔截實作的,然後根據異常類型映射到不同的ExceptionHandler處理方法上。
2.1 引入AOP依賴
必須引入AOP依賴,我剛開始忘了依賴,怎麼試都不對,是以一定要檢查好,不要忘了。
<!-- SpringBoot 攔截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 寫異常攔截器
在寫異常攔截器的時候,我們得要分為這麼幾個部分,首先是自定義的業務異常攔截,驗證類異常,比如參數校驗類異常攔截處理,還有未知的運作時異常攔截,如果使用到Mybatis,還得要注意Mybatis系統異常的通用攔截處理,還有就是請求不支援異常,最後再是統一的大異常攔截。
可以看我下面寫的案例:
/**
* 全局異常處理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 請求方式不支援
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("請求位址'{}',不支援'{}'請求", requestURI, e.getMethod());
return R.fail(e.getMessage());
}
/**
* Mybatis系統異常 通用處理
*/
@ExceptionHandler(MyBatisSystemException.class)
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
String message = e.getMessage();
if (message.contains("CannotFindDataSourceException")) {
log.error("請求位址'{}', 未找到資料源", requestURI);
return R.fail("未找到資料源,請聯系管理者确認");
}
log.error("請求位址'{}', Mybatis系統異常", requestURI, e);
return R.fail(message);
}
/**
* 業務異常
*/
@ExceptionHandler(ServiceException.class)
public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) {
log.error(e.getMessage(), e);
Integer code = e.getCode();
return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage());
}
/**
* 攔截未知的運作時異常
*/
@ExceptionHandler(RuntimeException.class)
public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("請求位址'{}',發生未知異常.", requestURI, e);
return R.fail(e.getMessage());
}
/**
* 系統異常
*/
@ExceptionHandler(Exception.class)
public R<Void> handleException(Exception e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("請求位址'{}',發生系統異常.", requestURI, e);
return R.fail(e.getMessage());
}
/**
* 自定義驗證異常
*/
@ExceptionHandler(BindException.class)
public R<Void> handleBindException(BindException e) {
log.error(e.getMessage(), e);
String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
return R.fail(message);
}
/**
* 自定義驗證異常
*/
@ExceptionHandler(ConstraintViolationException.class)
public R<Void> constraintViolationException(ConstraintViolationException e) {
log.error(e.getMessage(), e);
String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
return R.fail(message);
}
/**
* 自定義驗證異常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return R.fail(message);
}
運作傳回顯示
異常統一處理和傳回值封裝就到此結束了,上面的稍微修改下,完全可以做生産用,希望大家多多點贊關注支援,後續給大家分享更多有用的知識點!!!