电子说
一个VO对象在新增的时候某些字段为必填,在更新的时候又非必填。如上面的ValidVO
中 id`` 和 appId
属性在新增操作时都是 非必填 ,而在编辑操作时都为 必填 ,name在新增操作时为 必填 ,面对这种场景你会怎么处理呢? 在实际开发中我见到很多同学都是建立两个VO对象,ValidCreateVO
,ValidEditVO
来处理这种场景,这样确实也能实现效果,但是会造成类膨胀。
其实
Validator
校验框架已经考虑到了这种场景并且提供了解决方案,就是 分组校验 ,只不过很多同学不知道而已。
要使用分组校验,只需要三个步骤。
public interface ValidGroup extends Default {
interface Crud extends ValidGroup{
interface Create extends Crud{
}
interface Update extends Crud{
}
interface Query extends Crud{
}
interface Delete extends Crud{
}
}
}
这里我们定义一个分组接口ValidGroup让其继承javax.validation.groups.Default
,再在分组接口中定义出多个不同的操作类型,Create
,Update
,Query
,Delete
。
@Data
public class ValidVO {
@Null(groups = ValidGroup.Crud.Create.class)
@NotNull(groups = ValidGroup.Crud.Update.class, message = "应用ID不能为空")
private String id;
@Length(min = 6,max = 12,message = "appId长度必须位于6到12之间")
@Null(groups = ValidGroup.Crud.Create.class)
@NotNull(groups = ValidGroup.Crud.Update.class, message = "应用ID不能为空")
private String appId;
@NotBlank(message = "名字为必填项")
@NotBlank(groups = ValidGroup.Crud.Create.class,message = "名字为必填项")
private String name;
@Email(message = "请填写正确的邮箱地址")
private String email;
@EnumString(value = {"F","M"}, message="性别只允许为F或M")
private String sex;
@NotEmpty(message = "级别不能为空")
private String level;
}
给参数指定分组,对于未指定分组的则使用的是默认分组。
@PostMapping(value = "/valid/add")
public String add(@Validated(value = ValidGroup.Crud.Create.class) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test3 valid success";
}
@PostMapping(value = "/valid/update")
public String update(@Validated(value = ValidGroup.Crud.Update.class) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test4 valid success";
}
这里我们通过value属性给add()
和update()
方法分别指定Create
和Update
分组
POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded
name=javadaily&level=12&email=476938977@qq.com&sex=F
在Create时我们 没有传递id和appId参数 ,校验通过。
{
"status": 100,
"message": "操作成功",
"data": "test3 valid success",
"timestamp": 1652186105359
}
使用同样的参数调用update方法时则提示参数校验错误
{
"status": 400,
"message": "ID不能为空; 应用ID不能为空",
"data": null,
"timestamp": 1652186962377
}
复制代码
由于email属于默认分组,而我们的分组接口ValidGroup
已经继承了Default
分组,所以也是可以对email字段作参数校验的。故意写错email格式
POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded
/valid/update?name=javadaily&level=12&email=476938977&sex=F
{
"status": 400,
"message": "请填写正确的邮箱地址; ID不能为空; 应用ID不能为空",
"data": null,
"timestamp": 1652187273865
}
业务规则校验指接口需要满足某些特定的业务规则,举个例子:业务系统的用户需要保证其唯一性,用户属性不能与其他用户产生冲突,不允许与数据库中任何已有用户的用户名称、手机号码、邮箱产生重复。 这就要求在 创建用户时需要校验用户名称、手机号码、邮箱是否被注册 ; 编辑用户时不能将信息修改成已有用户的属性 。最优雅的实现方法应该是参考 **Bean Validation**
的标准方式,借助自定义校验注解完成业务规则校验。
首先我们需要创建两个自定义注解,用于业务规则校验:
UniqueUser
:表示一个用户是唯一的,唯一性包含:用户名,手机号码、邮箱@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.UniqueUserValidator.class)
public @interface UniqueUser {
String message() default "用户名、手机号码、邮箱不允许与现存用户重复";
Class?[] groups() default {};
Class? extends Payload[] payload() default {};
}
NotConflictUser
:表示一个用户的信息是无冲突的,无冲突是指该用户的敏感信息与其他用户不重合@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.NotConflictUserValidator.class)
public @interface NotConflictUser {
String message() default "用户名称、邮箱、手机号码与现存用户产生重复";
Class?[] groups() default {};
Class? extends Payload[] payload() default {};
}
想让自定义验证注解生效,需要实现 ConstraintValidator
接口。接口的第一个参数是 自定义注解类型 ,第二个参数是 被注解字段的类 ,因为需要校验多个参数,我们直接传入用户对象。 需要提到的一点是 ConstraintValidator
接口的实现类无需添加 @Component
它在启动的时候就已经被加载到容器中了。
@Slf4j
public class UserValidation<T extends Annotation> implements ConstraintValidator<T, User> {
protected Predicate
这里使用Predicate函数式接口对业务规则进行判断。
@RestController
@RequestMapping("/senior/user")
@Slf4j
@Validated
public class UserController {
@Autowired
private UserRepository userRepository;
@PostMapping
public User createUser(@UniqueUser @Valid User user){
User savedUser = userRepository.save(user);
log.info("save user id is {}",savedUser.getId());
return savedUser;
}
@SneakyThrows
@PutMapping
public User updateUser(@NotConflictUser @Valid @RequestBody User user){
User editUser = userRepository.save(user);
log.info("update user is {}",editUser);
return editUser;
}
}
使用很简单,只需要在方法上加入自定义注解即可,业务逻辑中不需要添加任何业务规则的代码。
POST http://localhost:8080/valid/add
Content-Type: application/json
/senior/user
{
"userName" : "100001"
}
{
"status": 400,
"message": "用户名、手机号码、邮箱不允许与现存用户重复",
"data": null,
"timestamp": 1652196524725
}
通过上面几步操作,业务校验便和业务逻辑就完全分离开来,在需要校验时用@Validated
注解自动触发,或者通过代码手动触发执行,可根据你们项目的要求,将这些注解应用于控制器、服务层、持久层等任何层次的代码之中。 这种方式比任何业务规则校验的方法都优雅,推荐大家在项目中使用。在开发时可以将不带业务含义的格式校验注解放到 Bean 的类定义之上,将带业务逻辑的校验放到 Bean 的类定义的外面。这两者的区别是放在类定义中的注解能够自动运行,而放到类外面则需要像前面代码那样,明确标出注解时才会运行。
全部0条评论
快来发表一下你的评论吧 !