Bean validation 最佳实践

文章目录

    • 1. Java Bean validation
      • 1.1. 对象为空校验
      • 1.2. 字符串为空校验
      • 1.3. 集合为空校验
      • 1.4. 数字整数位、小数位校验
      • 1.5. 整数大小校验
      • 1.6. 正整数校验
      • 1.7. 正整数和0校验
      • 1.8. 负数校验
      • 1.9. 负数和0校验
      • 1.10. BigDecimal 大小校验
      • 1.11. 字符串长度校验
      • 1.12. 过去日期校验
      • 1.13. 过期日期与当前日期校验
      • 1.14. 未来日期校验
      • 1.15. 未来日期与当前日期校验
      • 1.16. 集合大小校验
      • 1.17. 集合泛型为基本类型的包装类型与 String 类型校验
      • 1.18. 集合泛型为自定义对象校验
      • 1.19. 字符串正则匹配校验
      • 1.20. 邮箱校验
      • 1.21. 自定义 Bean Validation 注解
      • 1.22. 自定义 Bean Validation 注解与 Java Bean Validation 的结合
      • 1.23. 方法参数对象多属性合法校验
    • 2. Spring Bean Validation
      • 2.1. 路径传参
      • 2.2. body 传参
    • 3. Spring MVC 统一异常对参数校验的处理
    • 4. 参考文档

1. Java Bean validation

Bean Validation 2.0 (JSR 380) 是 Java EE 和 Java SE 中用于 JavaBean 验证的 Java API 规范。

1.1. 对象为空校验

public class SaleOrderAfterSaleInitiateCommand {

    @NotNull(message = "销售订单ID不能为空")
    private Long saleOrderId;
}

1.2. 字符串为空校验

public static class Product {

        @NotBlank(message = "产品名称不能为空")
        private final String productName;
}

1.3. 集合为空校验

public static class Product {

        @NotEmpty(message = "产品图片不能为空")
        private final List<String> productImage;
}

1.4. 数字整数位、小数位校验

public class SaleOrderAfterSaleInitiateCommand {

    @Digits(integer = 9, fraction = 2, message = "退款金额整数位最多9位,小数位最多2位")
    private BigDecimal refundAmount;
}

1.5. 整数大小校验

public class Product {

        @Max(value = 100, message = "产品箱规不能大于 100")
        @Min(value = 1, message = "产品箱规不能小于 1")
        private Integer boxGauge;
}

1.6. 正整数校验

public class Product {

        @Positive(message = "产品箱规必须是正整数")
        private Integer boxGauge;
}

1.7. 正整数和0校验

public class Product {

        @PositiveOrZero(message = "产品箱规必须是正整数或0")
        private Integer boxGauge;
}

1.8. 负数校验

public class Product {

        @Negative(message = "产品箱规必须是负数")
        private Integer boxGauge;
}

1.9. 负数和0校验

public class Product {

        @NegativeOrZero(message = "产品箱规必须是负数或0")
        private Integer boxGauge;
}

1.10. BigDecimal 大小校验

public class SaleOrderAfterSaleInitiateCommand {

    @DecimalMax(value = "10000000.00", message = "退款金额需小于 10000000.00")
    @DecimalMin(value = "0.01", message = "退款金额需大于 0.00")
    private BigDecimal refundAmount;
}

1.11. 字符串长度校验

public class Manufacturer {
  
        @Length(max = 100, message = "产品生产商名称长度不能大于100")
        private String name;

        @Length(max = 500, message = "产品生产商地址长度不能大于500")
        private String address;
}

1.12. 过去日期校验

public static class User {

        @Past(message = "用户生日必须是过去的一个日期")
        private final Date birthday;
}

1.13. 过期日期与当前日期校验

public static class User {

        @PastOrPresent(message = "用户生日必须是过去的一个日期与当前日期")
        private final Date birthday;
}

1.14. 未来日期校验

public static class User {

        @Future(message = "用户生日必须是未来的一个日期")
        private final Date birthday;
}

1.15. 未来日期与当前日期校验

public static class User {

        @FutureOrPresent(message = "用户生日必须是未来的一个日期与当前日期")
        private final Date birthday;
}

1.16. 集合大小校验

public static class Product {

        @Size(max = 5, message = "产品图片数量不能大于5")
        private final List<String> productImage;
}

1.17. 集合泛型为基本类型的包装类型与 String 类型校验

public static class Product {

        private final List<@Length(max = 5, message = "产品图片长度不能大于5个字符") String> productImage;
}

1.18. 集合泛型为自定义对象校验

public static class Product {
        private final List<@Valid Manufacturer> manufacturerList;
}

public class Manufacturer {

        @NotBlank(message = "产品生产商名称不能为空!")
        @Length(max = 100, message = "产品生产商名称长度不能大于100")
        private final String name;

        @NotBlank(message = "产品生产商地址不能为空!")
        @Length(max = 500, message = "产品生产商地址长度不能大于500")
        private final String address;
}

1.19. 字符串正则匹配校验

public class User {

        @Pattern(regexp = "^1[3-9]\\d{9}$", message = "用户手机号不正确")
        private String phoneNumber;
}

1.20. 邮箱校验

public static class User {

        @Email(message = "用户邮箱格式不正确")
        private String email;
}

1.21. 自定义 Bean Validation 注解

@Documented
@Constraint(validatedBy = PasswordValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {

    String message() default "密码长度为8~32个字符,必须包含大写英文字母、小写英文字母、数字、特殊符号每种至少各一个";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}

public class PasswordValidator implements ConstraintValidator<Password, String> {

    @Override
    public boolean isValid(String password, ConstraintValidatorContext constraintValidatorContext) {

        if (StringUtils.isBlank(password)) {
            return false;
        }
        // 密码长度为8~32个字符,必须包含大写英文字母、小写英文字母、数字、特殊符号每种至少各一个
        return password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[$@!%*?&^+.~])[A-Za-z0-9$@!%*?&^+.~]{8,32}$");
    }
}

1.22. 自定义 Bean Validation 注解与 Java Bean Validation 的结合

@Documented
@NotBlank(message = "密码不能为空")
@Constraint(validatedBy = PasswordValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {

    String message() default "密码长度为8~32个字符,必须包含大写英文字母、小写英文字母、数字、特殊符号每种至少各一个";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}

public class PasswordValidator implements ConstraintValidator<Password, String> {

    @Override
    public boolean isValid(String password, ConstraintValidatorContext constraintValidatorContext) {
        // 密码长度为8~32个字符,必须包含大写英文字母、小写英文字母、数字、特殊符号每种至少各一个
        return password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[$@!%*?&^+.~])[A-Za-z0-9$@!%*?&^+.~]{8,32}$");
    }
}

1.23. 方法参数对象多属性合法校验

public class Validator<T> {
    public void validate(T t) {
        Set<ConstraintViolation<T>> validate =
                Validation.buildDefaultValidatorFactory().getValidator().validate(t);
      // 所有参数包含在内
        String validateString = validate.stream().map(v -> String.format("%s:%s", v.getPropertyPath(), v.getMessage())).collect(Collectors.joining(";"));
        if (StringUtils.isNotBlank(validateString)) {
            throw new IllegalArgumentException(validateString);
        }
    }
}

public void addProduct(Product product) {
    Validator<Product> validator = new Validator<>();
    validator.validate(product);
}

public class Product {

        @NotBlank(message = "不能为空")
        private String productName;

        @NotBlank(message = "不能为空")
        private String barCode;
}

2. Spring Bean Validation

Spring Bean Validation 是 Spring 提供了对 Java Bean Validation API 的支持。

<dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-validationartifactId>
        dependency>
 dependencies>

2.1. 路径传参

@GetMapping("/v1/info/{productId}")
public BaseResponseDTO<Product> queryProductInfo(@PathVariable("productId") @NotNull(message = "产品ID不能为空") Long productId) {
    return success(name);
}

@GetMapping("/v1/info")
public BaseResponseDTO<Product> queryProductInfo(@RequestParam("productId") @NotNull(message = "产品ID不能为空") Long productId) {
    return success(name);
}

2.2. body 传参

@PostMapping("/add")
public BaseResponseDTO<ReplenishmentPlanAddDTO> addReplenishmentPlan(@RequestBody @Validated ReplenishmentPlanAddCommand replenishmentPlanAddCommand){

    ReplenishmentPlanAddDTO replenishmentPlanAddDTO = replenishmentPlanCommandApplicationService.addReplenishmentPlan(replenishmentPlanAddCommand);
    return successWithData(replenishmentPlanAddDTO);
}

3. Spring MVC 统一异常对参数校验的处理

@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final LogUtil LOG_UTIL = LogUtil.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public BaseResponse<Object> handleError(MissingServletRequestParameterException e) {
        LOG_UTIL.warn("Missing Request Parameter", e);
        String message = String.format("Missing Request Parameter: %s", e.getParameterName());
        return new BaseResponse<>(ResultCode.PARAM_MISS.getCode(),message,null);

    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public BaseResponse<Object> handleError(MethodArgumentTypeMismatchException e) {
        LOG_UTIL.warn("Method Argument Type Mismatch", e);
        String message = String.format("Method Argument Type Mismatch: %s", e.getName());
        return new BaseResponse<>(ResultCode.PARAM_TYPE_ERROR.getCode(),message,null);

    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public BaseResponse<Object> handleError(MethodArgumentNotValidException e) {
        LOG_UTIL.warn("Method Argument Not Valid", e);
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        assert error != null;
        String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
        return new BaseResponse<>(ResultCode.PARAM_VALID_ERROR.getCode(),message,null);
    }

    @ExceptionHandler(BindException.class)
    public BaseResponse<Object> handleError(BindException e) {
        LOG_UTIL.warn("Bind Exception", e);
        FieldError error = e.getFieldError();
        assert error != null;
        String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
        return new BaseResponse<>(ResultCode.PARAM_BIND_ERROR.getCode(),message,null);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public BaseResponse<Object> handleError(ConstraintViolationException e) {
        LOG_UTIL.warn("Constraint Violation", e);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        ConstraintViolation<?> violation = violations.iterator().next();
        String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
        String message = String.format("%s:%s", path, violation.getMessage());
        return new BaseResponse<>(ResultCode.PARAM_VALID_ERROR.getCode(),message,null);
    }
}

4. 参考文档

  • [1] Bean Validation 2.0 (JSR 380)
  • [2] Jakarta Bean Validation API 2.0.2

你可能感兴趣的:(Java基础,SpringBoot,java,后端,spring,spring,boot)