在Spring Data REST中清空带有验证错误的消息

时间:2017-10-10 15:12:38

标签: spring spring-mvc spring-data-rest hibernate-validator spring-validator

我正在使用Spring Boot,Spring Data REST,Spring HATEOAS,Hibernate,Spring Validation创建一个应用程序。

我创建了自己的验证,以便在this guide之后支持SpEL。

所以我有我的验证员:

componentWillMount() {
    this.setState({
        userPrize: 1
    })
}

nextPrize() {
    this.setState(userPrize: this.state.userPrize + 1);
}

previousPrize() {
    this.setState(userPrize: this.state.userPrize - 1);
}


render() {
        switch(this.state.userPrize) {
            case 1:
                return <Prizes userPrize={this.state.userPrize} nextPrize={ this.nextPrize() } previousPrize={ this.previousPrize() } />
            case 2:
                return <Prizes userPrize={this.state.userPrize} nextPrize={ this.nextPrize() } previousPrize={ this.previousPrize() } />
            case 3:
                return <Prizes userPrize={this.state.userPrize} nextPrize={ this.nextPrize() } previousPrize={ this.previousPrize() } />
            case 4:
                return <Prizes userPrize={this.state.userPrize} nextPrize={ this.nextPrize() } previousPrize={ this.previousPrize() } />
            case 5:
                return <Prizes userPrize={this.state.userPrize} nextPrize={ this.nextPrize() } previousPrize={ this.previousPrize() } />
            default:
                throw new Error("Unknown prize state");
        }
  }

和我的注释:

  public class SpELClassValidator implements ConstraintValidator<ValidateClassExpression, Object> {
    private Logger log = LogManager.getLogger();

    private ValidateClassExpression annotation;
    private ExpressionParser parser = new SpelExpressionParser();

    public void initialize(ValidateClassExpression constraintAnnotation) {
        annotation = constraintAnnotation;
        parser.parseExpression(constraintAnnotation.value());
    }

    public boolean isValid(Object value, ConstraintValidatorContext context) {
        try {           
            StandardEvaluationContext spelContext = new StandardEvaluationContext(value);
            return (Boolean) parser.parseExpression(annotation.value()).getValue(spelContext);
        } catch (Exception e) {
            log.error("", e);
            return false;
        }

    }
}

验证器的配置:

@Target({ java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { SpELClassValidator.class })
@Documented
@Repeatable(ValidateClassExpressions.class)
public @interface ValidateClassExpression {

    String message() default "{expression.validation.message}";

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

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

    String value();

}

..并为REST存储库定义了验证器:

@Bean
public MessageSource messageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasenames("classpath:/i18n/messages");
    // messageSource.setDefaultEncoding("UTF-8");
    // set to true only for debugging
    messageSource.setUseCodeAsDefaultMessage(false);
    messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
    messageSource.setFallbackToSystemLocale(false);
    return messageSource;
}

/**
 * Enable Spring bean validation
 * https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation
 * 
 * @return
 */
@Bean   
public LocalValidatorFactoryBean validator() {
    LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
    factoryBean.setValidationMessageSource(messageSource());
    return factoryBean;
}

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
    MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
    methodValidationPostProcessor.setValidator(validator());
    return methodValidationPostProcessor;
}

这是我的豆子:

    @Configuration
public class RestConfig extends RepositoryRestConfigurerAdapter {
    @Autowired
    private Validator validator;

    public static final DateTimeFormatter ISO_FIXED_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
            .withZone(ZoneId.of("Z"));

    @Bean
    public RootResourceProcessor rootResourceProcessor() {
        return new RootResourceProcessor();
    }

    @Override
    public void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) {

    }

    @Override
    public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
        validatingListener.addValidator("beforeCreate", validator);
        validatingListener.addValidator("beforeSave", validator);
        super.configureValidatingRepositoryEventListener(validatingListener);
    }
}

在配置上我也是这个类:

    @Entity
// Validate the number of seats if the bus is a minibus
@ValidateClassExpression(value = "#this.isMiniBus() == true ? #this.getSeats()<=17 : true", message = "{Expression.licenseplate.validminibus}")
public class LicensePlate extends AbstractEntity {
    private static final long serialVersionUID = -6871697166535810224L;

    @NotEmpty
    @ColumnTransformer(read = "UPPER(licensePlate)", write = "UPPER(?)")
    @Column(nullable = false, unique = true)
    private String licensePlate;

    // The engine euro level (3,4,5,6)
    @Range(min = 0, max = 6)
    @NotNull
    @Column(nullable = false, columnDefinition = "INTEGER default 0")
    private int engineEuroLevel = 0;

    @NotNull(message = "{NotNull.licenseplate.enginetype}")
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private EngineType engineType = EngineType.DIESEL;

    // If the bus has the particulate filter
    @NotNull(message = "{NotNull.licenseplate.particulatefilter}")
    @Column(nullable = false, columnDefinition = "BOOLEAN default false")
    private boolean particulateFilter = false;

    // Number of seats
    @NotNull
    @Range(min = 1, max = 99)
    @Column(nullable = false, columnDefinition = "INTEGER default 50")
    private int seats = 50;

    // If the vehicle is a minibus
    @NotNull
    @Column(nullable = false, columnDefinition = "BOOLEAN default false")
    private boolean miniBus = false;

    @NotNull(message = "{NotNull.licenseplate.country}")
    // The country of the vehicle
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Country country;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Note> notes = new ArrayList<>();

    public LicensePlate() {
    }

    public String getLicensePlate() {
        return licensePlate;
    }

    public void setLicensePlate(String licensePlate) {
        this.licensePlate = licensePlate;
    }

    public int getEngineEuroLevel() {
        return engineEuroLevel;
    }

    public void setEngineEuroLevel(int engineEuroLevel) {
        this.engineEuroLevel = engineEuroLevel;
    }

    public int getSeats() {
        return seats;
    }

    public void setSeats(int seats) {
        this.seats = seats;
    }

    public boolean isMiniBus() {
        return miniBus;
    }

    public void setMiniBus(boolean miniBus) {
        this.miniBus = miniBus;
    }

    public EngineType getEngineType() {
        return engineType;
    }

    public void setEngineType(EngineType engineType) {
        this.engineType = engineType;
    }

    public boolean isParticulateFilter() {
        return particulateFilter;
    }

    public void setParticulateFilter(boolean particulateFilter) {
        this.particulateFilter = particulateFilter;
    }

    public Country getCountry() {
        return country;
    }

    public void setCountry(Country country) {
        this.country = country;
    }

    @Override
    public String toString() {
        return "LicensePlate [licensePlate=" + licensePlate + ", engineEuroLevel=" + engineEuroLevel + ", engineType="
                + engineType + ", particulateFilter=" + particulateFilter + ", seats=" + seats + ", miniBus=" + miniBus
                + "]";
    }

    public List<Note> getNotes() {
        return notes;
    }

    public void setNotes(List<Note> notes) {
        this.notes = notes;
    }

}

使用我的存储库:

@RestControllerAdvice
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
            HttpHeaders headers, HttpStatus status, WebRequest request) {
        throw new RuntimeException(ex);
    }

    @Override
    protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
            HttpHeaders headers, HttpStatus status, WebRequest request) {
        throw new RuntimeException(ex);
    }

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
            HttpHeaders headers, HttpStatus status, WebRequest request) {
        throw new RuntimeException(ex);
    }

}

使用Swagger我正在对这个json进行POST:

@Transactional
@RepositoryRestResource(excerptProjection = LicensePlateProjection.class)
@PreAuthorize("isAuthenticated()")
public interface LicensePlateRepository
        extends PagingAndSortingRepository<LicensePlate, Long>, RevisionRepository<LicensePlate, Long, Integer> {

    public LicensePlate findByLicensePlate(String licencePlate);

因为我有检查小巴的座位少于17个的验证规则,我应该看到验证错误,我看到了这个:

{"licensePlate":"asdfg","engineEuroLevel":"4","particulateFilter":true,"seats":18,"miniBus":true,"country":"http://localhost:8080/api/v1/countries/1"}

出现HTTP 400错误(此返回代码正确)。

我要指出我创建了Junit测试用例,并且看到了正确的消息:

 {
  "errors": []
}

所以我猜问题出现在REST / MVC部分。我调试了请求,并检查了班级@Test @WithMockUser(roles = "ADMIN") public void validateMinibusWithMoreThan17SeatsFails() { assertEquals(1, countryRepository.count()); LicensePlate plate = new LicensePlate(); plate.setLicensePlate("AA123BB"); plate.setEngineEuroLevel(3); plate.setMiniBus(true); plate.setSeats(18); plate.setCountry(countryRepository.findFirstByOrderByIdAsc()); Set<ConstraintViolation<LicensePlate>> constraintViolations = validator.validate(plate); assertEquals(1, constraintViolations.size()); ConstraintViolation<LicensePlate> constraintViolation = constraintViolations.iterator().next(); assertEquals("I veicoli di tipo minibus possono avere al massimo 17 posti (16 passeggeri più il conducente).", constraintViolation.getMessage()); } ;在构造函数中,我看到我的错误是正确的,我可以看到错误消息和正确的结构:

org.springframework.data.rest.core.RepositoryConstraintViolationException

我无法看到我犯错误的地方。使用其他(也)自定义验证器,我看到了正确的消息。我还有人让我朝着正确的方向解决问题吗?

1 个答案:

答案 0 :(得分:0)

我认为Spring MVC不知道在哪里显示错误消息,因为类级别约束的约束违反并不表示任何特定属性。

HV的@ScriptAssert提供了reportOn()属性,用于指定报告错误的属性。

对于自定义约束,您可以使用通过ConstraintValidatorContext公开的API创建自定义约束违规和属性路径来执行相同操作。