接口參數校驗(不使用hibernate-validator,規避大量if else)


引言

編寫接口時,常用的參數校驗使用hibernate-validator注解+@Validated注解進行參數校驗。當遇到一些特殊場景或需求,需要自己對參數進行手動校驗時,會出現以下問題:
不可避免的需要對接受的參數進行判斷,此時便會出現大量if…else…影響代碼可讀性,且校驗不夠優雅。
本文給出一個參數校驗方案,給大家一個思路。如果只是想使用,基本校驗的已足夠。

使用舉例

具體項目中是如何使用的,可以參考博客中我正在開發的U-Learning后端開發日志,其中有GitHub項目地址

Before1

@PostMapping("/save")
public ResponseEntity<JsonResult> save(TeacherDto teacher) {
    if(StringUtil.isEmpty(teacher.getTeaName())){
        return ...;
    }
    if(StringUtil.isEmpty(teacher.getTeaNumber())){
        return ...;
    }
    if(StringUtil.isEmpty(teacher.getTeaEmail())){
        return ...;
    }

    //接口處理業務代碼
    ...
}

After1

@PostMapping("/save")
public ResponseEntity<JsonResult> save(TeacherDto teacher) {
    ValidatorBuilder.build()
        .on(StringUtil.isEmpty(teacher.getTeaName()), SystemErrorCodeEnum.NAME_CANNOT_BE_NULL)
        .on(StringUtil.isEmpty(teacher.getTeaNumber()), SystemErrorCodeEnum.TEA_NUMBER_CANNOT_BE_NULL)
        .on(StringUtil.isEmpty(teacher.getTeaEmail()), SystemErrorCodeEnum.EMAIL_CANNOT_BE_NULL)
        .doValidate().checkResult();

    //接口處理業務代碼
    ...
}

Beafore2

當你只有一個需要校驗的參數時,可能會覺得這有些啰嗦

@GetMapping("/delete")
public ResponseEntity<JsonResult> delete(Long id) {
    if(StringUtil.isEmpty(id)){
        return ...;
    }
    //接口處理業務代碼
    ...
}

After2

@GetMapping("/delete")
public ResponseEntity<JsonResult> delete(Long id) {
    ValidateHandler.checkParameter(StringUtil.isEmpty(id), SystemErrorCodeEnum.ID_CANNOT_BE_NULL);
    //接口處理業務代碼
    ...
}

知識點

涉及到的知識點

  1. 建造者模式
  2. 鏈式調用
  3. 枚舉
  4. 異常
  5. 繼承與多態

拓展所需知識點

  1. 委托模式

使用前准備

自定義異常

public class BaseException extends RuntimeException {
    private Integer status = HttpStatus.BAD_REQUEST.value();

    public BaseException(Integer status) {
        this.status = status;
    }

    public BaseException(BaseEnum baseEnum) {
        super(baseEnum.getMessage());
        this.status = baseEnum.getCode();
    }

    public BaseException(String message) {
        super(message);
    }

    public BaseException(String message, Throwable cause) {
        super(message, cause);
    }

    public BaseException(Throwable cause) {
        super(cause);
    }

    public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public Integer getStatus() {
        return status;
    }
}

舉個栗子

@Getter
public class BadRequestException extends BaseException {

    public BadRequestException(String msg) {
        super(msg);
    }

    public BadRequestException(BaseEnum baseEnum) {
        super(baseEnum);
    }
}

統一異常管理

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 處理系統自定義異常
     */
    @ExceptionHandler(BaseException.class)
    public ResponseEntity handleBaseException(BaseException e) {
        String message = StringUtil.isContainChinese(e.getMessage()) ? e.getMessage() : null;
        return ResponseEntityUtil.badRequest(JsonResult.buildErrorMsg(e.getStatus(),
                Optional.ofNullable(message).orElse(MicroErrorCodeEnum.OPERATE_ERROR.getMessage())));
    }

    /**
     * Throwable
     * 接收非系統預測內的異常
     */
    @ExceptionHandler(Throwable.class)
    public ResponseEntity handleThrowable(Throwable e) {
        log.error("系統捕捉Throwable異常並處理 ==> " + e.getMessage(), e);
        String message = StringUtil.isContainChinese(e.getMessage()) ? e.getMessage() : null;
        return ResponseEntityUtil.internalServerError(JsonResult.buildErrorMsg(HttpStatus.INTERNAL_SERVER_ERROR.value(),
                Optional.ofNullable(message).orElse(MicroErrorCodeEnum.SYSTEM_ERROR.getMessage())));
    }
}

自定義枚舉

基礎枚舉接口

public interface BaseEnum {

    Integer getCode();

    String getMessage();
}

舉個栗子(根據自己需求創建)

public enum SystemErrorCodeEnum implements BaseEnum {

    /**
     * 后台管理系統錯誤狀態碼
     */
    PARAMETER_EMPTY(HttpStatus.BAD_REQUEST, "參數不可為空!"),
    TEACHER_NOT_EXISTS(HttpStatus.BAD_REQUEST, "教師不存在!"),
    ID_CANNOT_BE_NULL(HttpStatus.BAD_REQUEST, "ID不可為空!"),
    EMAIL_CANNOT_BE_NULL(HttpStatus.BAD_REQUEST, "郵箱不可為空!"),
    ;

    private Integer code;
    private String message;

    SystemErrorCodeEnum(HttpStatus httpStatus, String message) {
        this.code = httpStatus.value();
        this.message = message;
    }

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}

Validate核心代碼

校驗類context

@Data
@AllArgsConstructor
public class ValidatorContext {

    /** 校驗結果 */
    private Boolean checkResult;

    /** 錯誤信息枚舉 */
    private BaseEnum baseEnum;

}

校驗類上下文

public class ValidatorHolder {

    /** context集合 */
    private List<ValidatorContext> validatorContexts;

    /** 校驗結果 */
    private Boolean result;

    /** 若result = true,保存錯誤信息 */
    private BaseEnum baseEnum;

    /** 初始化 */
    public ValidatorHolder() {
        this.validatorContexts = new ArrayList<>();
        this.result = true;
        this.baseEnum = null;
    }

    /** 校驗鏈 */
    public ValidatorHolder on(Boolean checkResult, BaseEnum baseEnum){
        if(checkResult != null && baseEnum != null){
            addContext(checkResult, baseEnum);
        }
        return this;
    }

    /** 添加待校驗屬性和錯誤信息 */
    private void addContext(Boolean checkResult, BaseEnum baseEnum){
        validatorContexts.add(new ValidatorContext(checkResult, baseEnum));
    }

    /** 校驗 */
    public ValidatorHolder doValidate(){
        for (ValidatorContext validatorContext : validatorContexts) {
            if(validatorContext.getCheckResult()){
                result = false;
                baseEnum = validatorContext.getBaseEnum();
                break;
            }
        }
        return this;
    }

    public void checkResult(){
        ValidateHandler.checkValidator(this);
    }

    public Boolean getResult() {
        return result;
    }

    public BaseEnum getBaseEnum() {
        return baseEnum;
    }
}

校驗builder類

public class ValidatorBuilder {

    public static ValidatorHolder build(){
        return new ValidatorHolder();
    }
}

校驗處理器

參考Guava類庫中提供的一個作參數檢查的工具類--Preconditions類

public class ValidateHandler {

    /**
     * 判斷校驗是否成功,若存在錯誤,拋出異常
     * 針對ValidatorHolder
     */
    public static void checkValidator(ValidatorHolder validator){
        if(! validator.getResult()){
            throw new BadRequestException(validator.getBaseEnum());
        }
    }

    /**
     * 針對單參數校驗
     *
     * @param checkResult true:參數錯誤; false:參數正確
     * @param baseEnum 錯誤碼
     */
    public static void checkParameter(Boolean checkResult, BaseEnum baseEnum){
        if(checkResult){
            throw new BadRequestException(baseEnum);
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM