小编典典

带有额外信息的Bean验证

spring-boot

我正在尝试创建一个UniqueName注释,作为针对create project api的定制bean验证注释:

@PostMapping("/users/{userId}/projects")
public ResponseEntity createNewProject(@PathVariable("userId") String userId,
                                       @RequestBody @Valid ProjectParam projectParam) {
    User projectOwner = userRepository.ofId(userId).orElseThrow(ResourceNotFoundException::new);

    Project project = new Project(
        IdGenerator.nextId(),
        userId,
        projectParam.getName(),
        projectParam.getDescription()
    );
    ...
  }

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
class ProjectParam {

  @NotBlank
  @NameConstraint
  private String name;
  private String description;
}

@Constraint(validatedBy = UniqueProjectNameValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface UniqueName {

    public String message() default "already existed";

    public Class<?>[] groups() default {};

    public Class<? extends Payload>[] payload() default{};
}

public class UniqueProjectNameValidator implements ConstraintValidator<UniqueName, String> {
   @Autowired
   private ProjectQueryMapper mapper;

   public void initialize(UniqueName constraint) {
   }

   public boolean isValid(String value, ConstraintValidatorContext context) {
      // how can I get the userId info??
      return mapper.findByName(userId, value) == null;
   }
}

问题在于name字段只需要用户级别的唯一性。因此,我需要{userId}从URL字段获取进行验证。但是如何将其添加到中UniqueProjectNameValidator?还是有一些更好的方法来处理此验证?这只是大对象的一小部分,真实对象在请求处理程序中还有许多其他复杂的验证,这会使代码变得很脏。


阅读 292

收藏
2020-05-30

共1个答案

小编典典

如@Abhijeet所述,将userId属性动态传递给约束验证器是不可能的。至于如何更好地处理此验证案例,有干净的解决方案和肮脏的解决方案。

干净的解决方案
是将所有业务逻辑提取到服务方法中,并ProjectParam在服务级别进行验证。这样,您可以在上添加userId属性ProjectParam,然后在调用服务之前将其从映射@PathVariable@RequestBody。然后UniqueProjectNameValidator,您调整以验证ProjectParam而不是String

肮脏的解决方案 是使用Hibernate
Validator的交叉参数约束(另请参见此链接以获取示例)。本质上 ,您两个控制器方法参数都 视为自定义验证器的输入。

2020-05-30