引言
編寫接口時,常用的參數校驗使用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);
//接口處理業務代碼
...
}
知識點
涉及到的知識點
- 建造者模式
- 鏈式調用
- 枚舉
- 異常
- 繼承與多態
拓展所需知識點
- 委托模式
使用前准備
自定義異常
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);
}
}
}
