在項目中,某些情景下咱們須要驗證編碼是否重複,帳號是否重複,身份證號是否重複等... 而像驗證這類代碼以下: 那麼有沒有辦法能夠解決這相似的重複代碼量呢?前端
咱們能夠經過自定義註解校驗的方式去實現,以下 在實體類上面加上自定義的註解 @FieldRepeatValidator(field = "resources", message = "菜單編碼重複!")
便可 下面就先來上代碼吧~java
在SpringBoot環境中已經自動包含在spring-boot-starter-web
中了,若是由於版本致使沒有,可去maven倉庫搜索手動引入到項目中使用git
小編的springboot版本爲: 2.1.7web
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
MyBatis-Plus
的架構下實現的,其餘架構略不一樣,本文實現方式可作參考]@FieldRepeatValidator
// 元註解: 給其餘普通的標籤進行解釋說明 【@Retention、@Documented、@Target、@Inherited、@Repeatable】 @Documented /** * 指明生命週期: * RetentionPolicy.SOURCE 註解只在源碼階段保留,在編譯器進行編譯時它將被丟棄忽視。 * RetentionPolicy.CLASS 註解只被保留到編譯進行的時候,它並不會被加載到 JVM 中。 * RetentionPolicy.RUNTIME 註解能夠保留到程序運行的時候,它會被加載進入到 JVM 中,因此在程序運行時能夠獲取到它們。 */ @Retention(RetentionPolicy.RUNTIME) /** * 指定註解運用的地方: * ElementType.ANNOTATION_TYPE 能夠給一個註解進行註解 * ElementType.CONSTRUCTOR 能夠給構造方法進行註解 * ElementType.FIELD 能夠給屬性進行註解 * ElementType.LOCAL_VARIABLE 能夠給局部變量進行註解 * ElementType.METHOD 能夠給方法進行註解 * ElementType.PACKAGE 能夠給一個包進行註解 * ElementType.PARAMETER 能夠給一個方法內的參數進行註解 * ElementType.TYPE 能夠給一個類型進行註解,好比類、接口、枚舉 */ @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE}) @Constraint(validatedBy = FieldRepeatValidatorClass.class) //@Repeatable(LinkVals.class)(可重複註解同一字段,或者類,java1.8後支持) public @interface FieldRepeatValidator { /** * 實體類id字段 - 默認爲id (該值可無) * @return */ String id() default "id";; /** * 註解屬性 - 對應校驗字段 * @return */ String field(); /** * 默認錯誤提示信息 * @return */ String message() default "字段內容重複!"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
@FieldRepeatValidator
註解接口實現類/** * <p> FieldRepeatValidator註解接口實現類 </p> * * @description : * 技巧01:必須實現ConstraintValidator接口 * 技巧02:實現了ConstraintValidator接口後即便不進行Bean配置,spring也會將這個類進行Bean管理 * 技巧03:能夠在實現了ConstraintValidator接口的類中依賴注入其它Bean * 技巧04:實現了ConstraintValidator接口後必須重寫 initialize 和 isValid 這兩個方法; * initialize 方法主要來進行初始化,一般用來獲取自定義註解的屬性值; * isValid 方法主要進行校驗邏輯,返回true表示校驗經過,返回false表示校驗失敗,一般根據註解屬性值和實體類屬性值進行校驗判斷 [Object:校驗字段的屬性值] * @author : zhengqing * @date : 2019/9/10 9:22 */ public class FieldRepeatValidatorClass implements ConstraintValidator<FieldRepeatValidator, Object> { private String id; private String field; private String message; @Override public void initialize(FieldRepeatValidator fieldRepeatValidator) { this.id = fieldRepeatValidator.id(); this.field = fieldRepeatValidator.field(); this.message = fieldRepeatValidator.message(); } @Override public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) { return FieldRepeatValidatorUtils.fieldRepeat(id, field, o, message); } }
public class FieldRepeatValidatorUtils { /** * 實體類id字段 */ private static String id; /** * 實體類id字段值 */ private static Integer idValue; /** * 校驗字段 */ private static String field; /** * 校驗字段值 - 字符串、數字、對象... */ private static Object fieldValue; /** * 校驗字段 - 對應數據庫字段 */ private static String db_field; /** * 實體類對象值 */ private static Object object; /** * 校驗數據 TODO 後期若是須要校驗同個字段是否重複的話,將 `field` 作 , 或 - 分割... ; 若是id不惟一考慮傳值過來判斷 或 取fields第二個字段值拿id * * @param field:校驗字段 * @param object:對象數據 * @param message:回調到前端提示消息 * @return: boolean */ public static boolean fieldRepeat(String id, String field, Object object, String message) { // 使用Class類的中靜態forName()方法得到與字符串對應的Class對象 ; className: 必須是接口或者類的名字 // 靜態方法forName()調用 啓動類加載器 -> 加載某個類xx -> 實例化 ----> 從而達到降耦 更靈活 // Object object = Class.forName(className).newInstance(); FieldRepeatValidatorUtils.id = id; FieldRepeatValidatorUtils.field = field; FieldRepeatValidatorUtils.object = object; getFieldValue(); // ⑦ 校驗字段內容是否重複 // 工廠模式 + ar動態語法 BaseEntity entity = (BaseEntity) object; // List list = entity.selectPage( new Page<>( 1,1 ), new EntityWrapper().eq( field, fieldValue ) ).getRecords(); List list = entity.selectList( new EntityWrapper().eq( db_field, fieldValue ) ); // 若是數據重複返回false -> 再返回自定義錯誤消息到前端 if ( idValue == null ){ if ( !CollectionUtils.isEmpty( list ) ){ throw new MyException( message ); } } else { if ( !CollectionUtils.isEmpty( list ) ){ // fieldValueNew:前端輸入字段值 Object fieldValueNew = fieldValue; FieldRepeatValidatorUtils.object = entity.selectById( idValue ); // 獲取該id所在對象的校驗字段值 - 舊數據 getFieldValue(); if ( !fieldValueNew.equals( fieldValue ) || list.size() > 1 ){ throw new MyException( message ); } } } return true; } /** * 獲取id、校驗字段值 */ public static void getFieldValue(){ // ① 獲取全部的字段 Field[] fields = object.getClass().getDeclaredFields(); for (Field f : fields) { // ② 設置對象中成員 屬性private爲可讀 f.setAccessible(true); // ③ 判斷字段註解是否存在 if ( f.isAnnotationPresent(ApiModelProperty.class) ) { // ④ 若是存在則獲取該註解對應的字段,並判斷是否與咱們要校驗的字段一致 if ( f.getName().equals( field ) ){ try { // ⑤ 若是一致則獲取其屬性值 fieldValue = f.get(object); // ⑥ 獲取該校驗字段對應的數據庫字段屬性 目的: 給 mybatis-plus 作ar查詢使用 TableField annotation = f.getAnnotation(TableField.class); db_field = annotation.value(); } catch (IllegalAccessException e) { e.printStackTrace(); } } // ⑦ 獲取id值 -> 做用:判斷是插入仍是更新操做 if ( id.equals( f.getName() ) ){ try { idValue = (Integer) f.get(object); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } }
@Slf4j @RestControllerAdvice public class MyGlobalExceptionHandler { private static final Logger LOG = LoggerFactory.getLogger(MyGlobalExceptionHandler.class); /** * 自定義異常處理 */ @ExceptionHandler(value = MyException.class) public ApiResult myException(MyException be) { log.error("自定義異常:", be); if(be.getCode() != null){ return ApiResult.fail(be.getCode(), be.getMessage()); } return ApiResult.fail( be.getMessage() ); } // 參數校驗異常處理 =========================================================================== // MethodArgumentNotValidException是springBoot中進行綁定參數校驗時的異常,須要在springBoot中處理,其餘須要處理ConstraintViolationException異常進行處理. /** * 方法參數校驗 */ @ExceptionHandler(MethodArgumentNotValidException.class) public ApiResult handleMethodArgumentNotValidException( MethodArgumentNotValidException e ) { log.error( "方法參數校驗:" + e.getMessage(), e ); return ApiResult.fail( e.getBindingResult().getFieldError().getDefaultMessage() ); } /** * ValidationException */ @ExceptionHandler(ValidationException.class) public ApiResult handleValidationException(ValidationException e) { log.error( "ValidationException:", e ); return ApiResult.fail( e.getCause().getMessage() ); } /** * ConstraintViolationException */ @ExceptionHandler(ConstraintViolationException.class) public ApiResult handleConstraintViolationException(ConstraintViolationException e) { log.error( "ValidationException:" + e.getMessage(), e ); return ApiResult.fail( e.getMessage() ); } }
其中自定義異常處理代碼以下:正則表達式
public class MyException extends RuntimeException { /** * 異常狀態碼 */ private Integer code; public MyException(Throwable cause) { super(cause); } public MyException(String message) { super(message); } public MyException(Integer code, String message) { super(message); this.code = code; } public MyException(String message, Throwable cause) { super(message, cause); } public Integer getCode() { return code; } }
@FieldRepeatValidator
註解使用舉例@FieldRepeatValidator(field = "resources", message = "菜單編碼重複!") public class Menu extends BaseEntity { ... }
@Validated
註解便可!@PostMapping(value = "/save", produces = "application/json;charset=utf-8") @ApiOperation(value = "保存菜單 ", httpMethod = "POST", response = ApiResult.class) public ApiResult save(@RequestBody @Validated Menu input) { Integer id = menuService.save(input); // 更新權限 shiroService.updatePermission(shiroFilterFactoryBean, null, false); return ApiResult.ok("保存菜單成功", id); }
下面的這些原生註解 百度一下,就會發現發現有不少,很簡單就很少說了spring
@Null 必須爲null @NotNull 必須不爲 null @AssertTrue 必須爲 true ,支持boolean、Boolean @AssertFalse 必須爲 false ,支持boolean、Boolean @Min(value) 值必須小於value,支持BigDecimal、BigInteger,byte、shot、int、long及其包裝類 @Max(value) 值必須大於value,支持BigDecimal、BigInteger,byte、shot、int、long及其包裝類 @DecimalMin(value) 值必須小於value,支持BigDecimal、BigInteger、CharSequence,byte、shot、int、long及其包裝類 @DecimalMax(value) 值必須大於value,支持BigDecimal、BigInteger、CharSequence,byte、shot、int、long及其包裝類 @Size(max=, min=) 支持CharSequence、Collection、Map、Array @Digits (integer, fraction) 必須是一個數字 @Negative 必須是一個負數 @NegativeOrZero 必須是一個負數或0 @Positive 必須是一個正數 @PositiveOrZero 必須是個正數或0 @Past 必須是一個過去的日期 @PastOrPresent 必須是一個過去的或當前的日期 @Future 必須是一個未來的日期 @FutureOrPresent 必須是一個將來的或當前的日期 @Pattern(regex=,flag=) 必須符合指定的正則表達式 @NotBlank(message =) 必須是一個非空字符串 @Email 必須是電子郵箱地址 @NotEmpty 被註釋的字符串的必須非空 ... ... ...
這裏簡單說下小編的實現思路吧 首先咱們自定義一個註解,放在字段
或者類
上,目的:經過反射獲取其值,而後拿到值咱們就能夠進行一系列本身的業務操做了,好比更具字段屬性和屬性值查詢到相應的數據庫數據,而後進行校驗,若是不符合本身的邏輯,咱們就拋出一個異常交給全局統一異常類處理錯誤信息,最後返回給前端作處理,大致思路就是這樣,實現起來很簡單,代碼中該有的註釋都有,相信不會太難理解數據庫
最後再給出小編的源碼讓你們做參考吧json