在springboot工程开发中,会对入参进行校验,以保证接口的安全稳定。参数校验通常引入validation校验框架,在实体类中的属性上加上校验注解(@Null、@Blank等),具体使用方式可看大佬相关文章。
在对入参校验之后,通常会有业务规则的校验需求,通常通过 if 批判,然后 throw 异常的方式处理。
典型场景,新增用户时,需要校验其唯一性,用户的唯一性标识不能存在;编辑用户时,需要校验其冲突性,用户的唯一性标识不能与存在的用户重复,只能与自己的重复。
通常在service层处理,代码逻辑如下:
public void add(User user) {
User result = ***query***;
if (result != null) {
throw new ***Exception("用户已存在,请重新输入");
}
}
那有没有更优雅的方式呢?减少校验对业务代码的入侵?
借鉴参数校验的方式,通过自定义注解完成业务校验。
实例
1、pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2、自定义注解
新增时:表示一个用户是唯一的,唯一性包含:用户名,手机号码、邮箱
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
@Constraint(validatedBy = UserValidator.UniqueUserValidator.class)
public @interface Unique {
String message() default "用户名、邮箱、手机号码不允许与现存用户重复";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
修改时:表示一个用户的信息是无冲突的,无冲突是指该用户的敏感信息与其他用户不重合
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
@Constraint(validatedBy = UserValidator.NotConflictUserValidator.class)
public @interface NotConflict {
String message() default "用户名、邮箱、手机号码与现存用户产生重复";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
3、业务规则校验
想让自定义验证注解生效,需要实现 ConstraintValidator 接口。ConstraintValidator 接口的实现类无需添加 @Component 它在启动的时候就已经被加载到容器中了。
public class UserValidator<T extends Annotation> implements ConstraintValidator<T, User> {
protected Predicate<User> predicate = c -> true;
@Override
public boolean isValid(User user, ConstraintValidatorContext constraintValidatorContext) {
return predicate.test(user);
}
/**
* 校验用户是否唯一
* 即判断数据库是否存在当前新用户的信息,如用户名,手机,邮箱
*/
public static class UniqueUserValidator extends UserValidator<Unique> {
@Override
public void initialize(Unique unique) {
predicate = c -> {
// 持久层查询是否存在
return false;
};
}
}
/**
* 校验是否与其他用户冲突
* 将用户名、邮件、电话改成与现有完全不重复的,或者只与自己重复的,就不算冲突
*/
public static class NotConflictUserValidator extends UserValidator<NotConflict> {
@Override
public void initialize(NotConflict notConflict) {
predicate = c -> {
Collection<User> collection = null;
// 持久层查询是否冲突
return false;
};
}
}
}
4、测试
@RestController
@Validated
public class UserController {
@PostMapping("add")
public User addUser(@Unique @Valid User user){
System.out.printf("save user id is %s", user.getId());
return user;
}
@PostMapping("update")
public User updateUser(@NotConflict @Valid User user){
System.out.printf("update user is %s", user.getId());
return user;
}
}
结果
{
"status": false,
"code": 400,
"message": "addUser.user: 用户名、邮箱、手机号码不允许与现存用户重复",
"data": null
}
使用方式和入参校验一样,只需要在方法上加上自定义注解即可,service层不需要添加业务校验的代码了。
可见,业务校验和业务逻辑解耦了,在需要校验时用@Validated注解自动触发即可。