在项目中,某些情景下咱们须要验证编码是否重复,帐号是否重复,身份证号是否重复等... 而像验证这类代码以下: 那么有没有办法能够解决这相似的重复代码量呢?前端
咱们能够经过自定义注解校验的方式去实现,以下 在实体类上面加上自定义的注解 @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