数据校验-java

103 阅读1分钟

快速入门

快速入门 - 非SpringBoot环境

1.引入依赖

    <!--  Bean Validation 的实现 -->
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>
    <!-- el表达式语言支持 -->
    <dependency>
        <groupId>org.glassfish.expressly</groupId>
        <artifactId>expressly</artifactId>
        <version>5.0.0</version>
    </dependency>

img.png

2.测试

/**a
 * 用户添加DTO
 */
@Data
public class UserAddDTO {
    /**
     * 账号
     */
    @NotBlank(message="账号不能为空")
    @Length(min = 5, max = 16, message = "账号长度为 5-16 位")
    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
    private String username;

![img.png](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/c7a214bb47db41fe9434fe55260cd83a~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pWF5Y-K:q75.awebp?rk3s=f64ab15b&x-expires=1775023770&x-signature=19ufWuQKNIgbMabMKa6HJjU9WaI%3D)
![img.png](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/71c8b16e60644269b46c6cfa78b3a23d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5pWF5Y-K:q75.awebp?rk3s=f64ab15b&x-expires=1775023770&x-signature=eBh1Jfq7bB7oSCrvW4lceQNduTY%3D)
    /**
     * 密码
     */
    @NotEmpty(message = "密码不能为空")
    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
    private String password;
}
public class DTOTest {
    public static void main(String[] args) {
        // 定义校验对象
        UserAddDTO userAddDTO = new UserAddDTO();
        userAddDTO.setUsername("123");
        userAddDTO.setPassword("123");
        // 获取校验工厂
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        // 校验
        Set<ConstraintViolation<UserAddDTO>> validate = validator.validate(userAddDTO);
        validate.forEach(x->
                System.out.println(x.getMessage())
        );
    }
}

快速入门-SpringBoot 环境

1.引入依赖

    <!-- web环境 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 单元测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!-- Bean Validation starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

2.测试

@RestController
@RequestMapping("/users")
@Validated
public class UserController {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @GetMapping("/get")
    public void get(@RequestParam("id") @Min(value = 1L, message = "编号必须大于 0") Integer id) {
        logger.info("[get][id: {}]", id);
    }
}

实战功能

处理校验异常

在实际开发中,我们需要对校验结果统一返回给前端。

实现思路:在校验失败后,validation会抛出异常,所以通过SpringBoot的全局异常捕获机制来统一捕获校验异常返回给前端

1.定义统一返回结果类

/**
 * 通用返回结果
 *
 * @param <T> 结果泛型
 */
public class CommonResult<T> implements Serializable {

    public static Integer CODE_SUCCESS = 0;

    /**
     * 错误码
     */
    private Integer code;
    /**
     * 错误提示
     */
    private String message;
    /**
     * 返回数据
     */
    private T data;

    /**
     * 将传入的 result 对象,转换成另外一个泛型结果的对象
     *
     * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
     *
     * @param result 传入的 result 对象
     * @param <T> 返回的泛型
     * @return 新的 CommonResult 对象
     */
    public static <T> CommonResult<T> error(CommonResult<?> result) {
        return error(result.getCode(), result.getMessage());
    }

    public static <T> CommonResult<T> error(Integer code, String message) {
        Assert.isTrue(!CODE_SUCCESS.equals(code), "code 必须是错误的!");
        CommonResult<T> result = new CommonResult<>();
        result.code = code;
        result.message = message;
        return result;
    }

    public static <T> CommonResult<T> success(T data) {
        CommonResult<T> result = new CommonResult<>();
        result.code = CODE_SUCCESS;
        result.data = data;
        result.message = "";
        return result;
    }
}

2.定义异常枚举

/**
 * 业务异常枚举
 */
public enum ServiceExceptionEnum {
    // ========== 系统级别 ==========
    SUCCESS(0, "成功"),
    SYS_ERROR(2001001000, "服务端发生异常"),
    MISSING_REQUEST_PARAM_ERROR(2001001001, "参数缺失"),
    INVALID_REQUEST_PARAM_ERROR(2001001002, "请求参数不合法"),

    // ========== 用户模块 ==========
    USER_NOT_FOUND(1001002000, "用户不存在"),
    ;
    /**
     * 错误码
     */
    private final int code;
    /**
     * 错误提示
     */
    private final String message;

    ServiceExceptionEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

3.定义全局异常捕获类

@ControllerAdvice(basePackages = "com.zwf.module.validation")
public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 处理Exception 异常
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
        // 记录异常日志
        logger.error("[exceptionHandler]", e);
        // 返回 ERROR CommonResult
        return CommonResult.error(ServiceExceptionEnum.SYS_ERROR.getCode(),
                ServiceExceptionEnum.SYS_ERROR.getMessage());
    }

    /**
     * 处理 ConstraintViolationException 数据校验异常
     */
    @ResponseBody
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
        logger.debug("[constraintViolationExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(constraintViolation.getMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage);
    }
}

4.测试结果

{
    "code": 2001001002,
    "message": "请求参数不合法:编号必须大于 0",
    "data": null
}

自定义校验规则

1.创建自定义注解

定义一个新的注解、它用于标记需要进行自定义校验的字段、方法或者参数

@Documented
@Constraint(validatedBy = MobileNoValidator.class) // 指定校验器
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER}) // 指定校验对象
@Retention(RetentionPolicy.RUNTIME) // 指定校验时机
public @interface MobileNo {
    String message() default "手机号码格式不正确"; // 校验失败提示信息
    Class<?>[] groups() default {}; // 校验分组
    Class<? extends Payload>[] payload() default {}; // 负载信息,用于传递额外的信息
}

2.创建校验器

/**
 * 约束校验器
 */
public class MobileNoValidator implements ConstraintValidator<MobileNo, String> {
    // 以1开头、后面跟着10位数组
    private static final String PHONE_REGEX = "^1\\d{10}$";
    @Override
    public void initialize(MobileNo constraintAnnotation) {
        // 初始化
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(value == null || value.isEmpty()) {
            return false;
        }
        return value.matches(PHONE_REGEX);
    }
}

3.使用自定义注解

@Data
public class UserDTO {
    @NotNull
    private Long id;
    @NotNull
    @MobileNo
    private String mobileNo;
}
@RestController
@RequestMapping("/users")
@Validated
public class UserController {
    @PostMapping("/test")
    public String test(@Valid @RequestBody UserDTO userVo) {
        System.out.println(userVo);
        return "success";
    }
}

4.拦截自定义注解异常

@ControllerAdvice(basePackages = "com.zwf.module.validation")
public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public CommonResult hasndleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        logger.debug("[handleMethodArgumentNotValidException]", ex);
        StringBuilder detailMessage = new StringBuilder();
        for (FieldError error : ex.getBindingResult().getFieldErrors()) {
            if (!detailMessage.isEmpty()) {
                detailMessage.append(";");
            }
            detailMessage.append("【").append(error.getField()).append(":").append(error.getDefaultMessage()).append("】");
        }
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage);
    }
}

5.测试结果

{
    "code": 2001001002,
    "message": "请求参数不合法:【mobileNo:手机号码格式不正确】",
    "data": null
}

Bean Validation 定义的约束注解

百度 推荐 juejin.cn/post/739913…