有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java Spring boot mvc按值验证枚举

在我的spring boot应用程序中,我想通过自定义值验证enum: 我的数据如下:

@Data
public class PayOrderDTO {
    @NotNull
    @EnumValidator(enumClass = TransactionMethod.class)
    private TransactionMethod method;
}

我的枚举验证器注释定义如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Constraint(validatedBy = EnumValidatorImpl.class)
public @interface EnumValidator {
    String message() default "is invalid";

    /**
     * @return Specify group
     */
    Class<?>[] groups() default {};

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

    /**
     * @return Specifies the enumeration type. The parameter value must be a value in this enumeration type
     */
    Class<? extends EnumBase> enumClass();

    /**
     * @return Can it be null
     */
    boolean nullable() default false;

    /**
     * @return Values to exclude
     */
    int[] exclusion() default {};

}

这是我的枚举验证器注释的实现

public class EnumValidatorImpl implements ConstraintValidator<EnumValidator, EnumBase> {
    private boolean nullable;

    private Set<String> values;

    @Override
    public void initialize(EnumValidator constraintAnnotation) {
        this.nullable = constraintAnnotation.nullable();
        Class<? extends EnumBase> enumClass = constraintAnnotation.enumClass();
        int[] exclusion = constraintAnnotation.exclusion();

        values = new HashSet<>();
        EnumBase[] enumConstants = enumClass.getEnumConstants();
        for (EnumBase iEnum : enumConstants) {
            values.add(iEnum.getValue());
        }
        if (exclusion.length > 0)
            for (int i : exclusion) {
                values.remove(i);
            }
    }

    @Override
    public boolean isValid(EnumBase param, ConstraintValidatorContext constraintValidatorContext) {
        if (nullable && param == null) {
            return true;
        }
        else if(param == null)
            return false;
        return values.contains(param.getValue());
    }
}

这是我的枚举:

public enum TransactionMethod implements EnumBase {
    CREDIT_CARD("creditcard"),
    DEBIT_CARD("debitcard");
    public String label;

    TransactionMethod(String label) {
        this.label = label;
    }

    @Override
    public String getValue() {
        return this.label;
    }

    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static TransactionMethod fromString(String value) {
        return TransactionMethod.valueOf(value);
//        return Arrays.stream(TransactionMethod.values())
//                .filter(el -> el.getValue().equals(value))
//                .findFirst()
//                .orElseThrow(() -> {
//                    throw new IllegalArgumentException("Not valid method");
//                });
    }
}

当我向rest控制器发送http请求时:

@RequiredArgsConstructor
@RestController
@RequestMapping("/orders")
@Validated
public class PaymentRestController {
    
    public ResponseEntity<?> createPayment(
            @Valid @RequestBody PayOrderDTO payOrderDTO
    ) {
        return ResponseEntity.ok("Worked");
    }
}

请求示例:

POST /orders/ HTTP/1.1
Host: store.test
Content-Type: application/json
Content-Length: 152

{
    "method":"creditcard",
}

我希望在我的枚举验证器中得到定义的无效异常或错误消息,而在控制台中得到包含以下内容的异常:

JSON parse error: Cannot construct instance of `x.TransactionMethod`, problem: No enum constant x.TransactionMethod.creditcard

但如果我发出这个请求:

POST /orders/ HTTP/1.1
Host: store.test
Content-Type: application/json
Content-Length: 152

{
    "method":"CREDIT_CARD",
}

应用程序工作正常

我想使用标签而不是枚举的常量值来验证枚举,如果它不存在,将抛出一个验证错误,如下所示:

HTTP 422 : field 'method' is not valid, expected values are ['creditcard','debitcard']

我尝试了一些解决方案,比如转换器

public class TransactionMethodStringEnumConverter implements Converter<String, TransactionMethod> {
    @Override
    public TransactionMethod convert(String source) {
        Optional<TransactionMethod> first = Arrays.stream(TransactionMethod.values()).filter(e -> e.label.equals(source)).findFirst();
        return first.orElseThrow(() -> {
            throw new IllegalArgumentException();
        });
    }

}

但我好像什么都没做。 如果有人能想出一个很好的解决方案,我将不胜感激,谢谢🙏


共 (1) 个答案

  1. # 1 楼答案

    要按label值反序列化枚举,可以在getter上使用@JsonValue注释:

    public enum TransactionMethod implements EnumBase {
    
        CREDIT_CARD("creditcard"),
        DEBIT_CARD("debitcard");
    
        public String label;
    
        TransactionMethod(String label) {
            this.label = label;
        }
    
        @Override
        @JsonValue
        public String getValue() {
            return this.label;
        }
    }
    

    How To Serialize and Deserialize Enums with Jackson

    此外,请注意以下事实:

    1. EnumValidatorImplinitialize()方法中有values.remove(i),尽管Set的元素没有索引,并且Set<String> values具有泛型String
    2. EnumValidator中,可以使用boolean nullable()设置nullable=true,但在PayOrderDTO中,仍然使用@NotNull检查method字段是否为空,这可能会导致不希望的结果

    编辑:

    可以使用MessageSource定义特定于语言环境的消息,请参见this article

    在你的留言里。属性:invalid.transaction.method=is not valid, expected values are ['creditcard','debitcard']

    然后将消息添加到注释:

    @EnumValidator(enumClass = TransactionMethod.class, message = "{invalid.transaction.method}")
    

    通过在@RestController@RestControllerAdvice中的@ExceptionHandler方法中捕获MethodArgumentNotValidException并根据需要格式化消息,例如:

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
    public void handleException(MethodArgumentNotValidException e) {
    
        String errorMessage = e.getBindingResult().getFieldErrors().stream()
                .map(fieldError -> fieldError.getField() + " " + fieldError.getDefaultMessage()) 
                .collect(Collectors.joining(";"));
    
    
        LOG.error(e.getMessage()); // method is not valid, expected values are ['creditcard','debitcard']
    }