1

我正在使用Spring Boot,Spring Data REST,Spring HATEOAS,Hibernate,Spring Validation創建應用程序。在Spring數據REST中帶有驗證錯誤的空郵件

我創建了我自己的驗證,以支持以下this guide SpEL。

所以我我的驗證:

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(); 

} 

驗證的配置:

@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; 
} 

..和對REST庫定義的驗證:

@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); 
    } 

} 

使用我的倉庫:

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

    public LicensePlate findByLicensePlate(String licencePlate); 

使用揚鞭我做了此json的POST:

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

因爲我是檢查小巴少於17席的驗證規則,我應該看到驗證錯誤,instad我看到:

{ 
    "errors": [] 
} 

與HTTP 400錯誤(該返回碼是正確的)。

我已經指出,我創建JUnit測試案例,我看到正確的訊息:

@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()); 
} 

所以我想這個問題是關於REST/MVC的一部分。我調試了這個請求,並且檢查了類org.springframework.data.rest.core.RepositoryConstraintViolationException;在構造函數中,我看到我的錯誤是正確的,我可以看到錯誤消息和正確的結構:

org.springframework.data.rest.core.ValidationErrors: 1 errors 
Error in object 'LicensePlate': codes [ValidateClassExpression.LicensePlate,ValidateClassExpression]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [LicensePlate.,]; arguments []; default message [],org.springframework.valid[email protected]520b6a25]; default message [I veicoli di tipo minibus possono avere al massimo 17 posti (16 passeggeri più il conducente).] 

我不能看到我做的錯誤。與其他(也)自定義驗證器,我看到正確的消息。我也有人把我放在正確的方向來解決問題?

+0

嗨! )你[註冊](https://docs.spring。io/spring-data/rest/docs/current/reference/html /#validation)你的驗證器?你不想看到我的SDR驗證[示例](https://github.com/Cepr0/sdr-validation-demo)嗎? – Cepr0

+0

@ Cepr0是的。我更新了我的問題。我檢查了你的例子,似乎我正在做同樣的事情。 – drenda

+0

正如我所見,你只註冊了標準的驗證器('@Autowired private Validator validator;'),但不是自定義的:'validatingListener.addValidator(「beforeSave」,新的SpELClassValidator());'。或者我錯了? – Cepr0

回答

0

我認爲Spring MVC不知道在哪裏顯示錯誤消息,因爲違反類級別約束的約束條件不會指示任何特定的屬性。

HV的@ScriptAssert提供reportOn()屬性用於指定屬性以報告錯誤。

對於您的自定義約束,您可以使用通過ConstraintValidatorContext公開的API創建自定義約束違規和屬性路徑。

+0

你是對的。我猜也沒有一個特定的屬性春天mvc能夠顯示CoinstraintValidation錯誤。順便說一下,我不得不將Hibernate Validator更新到版本> = 5.4,因爲reportOn()僅適用於該版本。 – drenda