天天看點

傳回值封裝,異常統一處理優雅解決接口所有問題

作者:技術閑聊DD

在項目整體架構設計的時候,我們經常需要做以下工作:

  1. 傳回值的統一封裝處理,因為隻有規範好統一的傳回值格式,才能不會給接口使用者帶來疑惑和友善前端對接口的統一處理。
  2. 對異常碼進行嚴格規定,什麼錯誤傳回什麼碼制,這樣前端就可以根據不同的碼制進行錯誤提示,同時也友善三方調用者對異常進行處理。要注意,微服務架構,不同的微服務之間的碼制字首要做區分,這樣我們就可以根據碼制很快定位到是哪個微服務出現問題。
  3. 對異常的統一攔截,這一點非常重要,因為一旦對異常沒有做統一處理,嚴重就會導緻堆棧乃至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);
    }           

運作傳回顯示

傳回值封裝,異常統一處理優雅解決接口所有問題

異常統一處理和傳回值封裝就到此結束了,上面的稍微修改下,完全可以做生産用,希望大家多多點贊關注支援,後續給大家分享更多有用的知識點!!!

繼續閱讀