数据校验在软件开发和信息系统中扮演着至关重要的角色,它确保了进入系统或业务流程的数据是准确、完整且符合预期的,通过实施有效的数据校验措施,可以为软件应用和信息系统提供多方面的保障和支持。本文介绍Springboot的另一个核心:参数校验。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
DTO参数校验
package cn.wideth.validation;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;
@Data
public class UserDTO {
@Min(value = 1, message = "最小值为1")
@NotNull(message = "缺少用户id")
private Long userId;
@NotBlank(message = "姓名不能为空")
@Length(min = 2, max = 10,message = "姓名长度为2-10个字符")
private String userName;
@NotNull(message = "账号不能为空")
@Size(min = 8, max = 20, message = "账号长度必须在8到20个字符之间")
private String account;
@NotNull(message = "密码不能为空")
@Length(min = 6, max = 10, message = "密码长度范围在6~10个字符之间")
@Password
private String password;
@NotEmpty(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotEmpty(message = "缺少部门信息")
private List<Depart> depart;
}
校验的使用
// 路径变量
@GetMapping("{userId}")
public String Userdetail(@PathVariable("userId") @NotNull Long userId) {
// 校验通过,才会执行业务逻辑处理
return "ok";
}
// 查询参数
@GetMapping("getByAccount")
public String getByAccount(@Length(min = 6, max = 20) @NotNull String account) {
// 校验通过,才会执行业务逻辑处理
return "ok";
}
//DTO校验
@PostMapping("/addUser")
public String addUser(@RequestBody @Validated UserDTO userDTO){
// 校验通过,才会执行业务逻辑处理
return "ok";
}
public class User {
@NotNull(groups = Create.class, message = "姓名不能为空")
private String name;
@NotEmpty(groups = Create.class, message = "邮箱不能为空")
@Email(groups = Create.class, message = "邮箱格式不正确")
private String email;
@NotNull(groups = Update.class, message = "用户ID不能为空")
private Long userId;
……
}
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(@Validated(Create.class) @RequestBody User user) {
// 创建用户逻辑
return ResponseEntity.ok("创建成功");
}
@PutMapping("/{id}")
public ResponseEntity<String> updateUser(@PathVariable Long id, @Validated(Update.class) @RequestBody User user) {
// 更新用户逻辑
return ResponseEntity.ok("更新成功");
}
}
如果请求直接传递集合并希望对每一项参数校验,可以直接使用java.util.Collection下的list或者set来接收数据,参数校验并不会生效!通过使用自定义list集合来接收参数:
public class ValidationList<E> implements List<E> {
@Delegate // @Delegate是lombok注解
@Valid // 一定要加@Valid注解
public List<E> list = new ArrayList<>();
// 一定要记得重写toString方法
@Override
public String toString() {
return list.toString();
}
}
例如,一次性保存多个用户使用方法:
@PostMapping("/saveUserList")
public Result saveUserList(@RequestBody @Validated(UserDTO.Save.class) ValidationList<UserDTO> userList) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
如果实际业务场景中,内置的校验器不符合业务需求,可以使用自定义校验器。自定义校验器需要实现ConstraintValidator接口,并在需要校验的字段上使用@Constraint注解进行标记。
例:密码安全校验
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
public @interface Password {
//默认错误消息
String message() default "密码不符合安全规范";
//分组
Class<?>[] groups() default {};
//负载
Class<? extends Payload>[] payload() default {};
}
注释:
@Constraint(validatedBy = {PasswordValidator.class}):用于指定验证器类;
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}):指定@Password 注解可以作用在方法、字段、构造函数、参数以及类型上
实现ConstraintValidator编写校验规则:
public class PasswordValidator implements ConstraintValidator<Password, String> {
private static final Pattern PATTERN = Pattern.compile("^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,}$");
@Override
public void initialize(Password constraintAnnotation) {
// 初始化操作
}
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
// 实际的校验逻辑,不为null进行校验
if (password != null) {
Matcher matcher = PATTERN.matcher(password);
return matcher.find();
}
return true;
}
}
使用@Password进行参数校验即可。
如果校验失败,会抛出MethodArgumentNotValidException或者ConstraintViolationException异常。在实际项目开发中,通常使用统一异常处理来返回一个更友好的提示。
使用@RestControllerAdvice注解可以创建全局异常处理器,在控制器没有处理的异常都会在这里被处理。
@RestControllerAdvice
@Slf4j
public class CommonExceptionHandler {
@ExceptionHandler({MethodArgumentNotValidException.class})
public Result handleMethodNotValidException(MethodArgumentNotValidException ex) {
log.error("MethodArgumentNotValidException:{}", e);
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("校验失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
return Result.fail(BusinessCode.ERROR, msg);
}
@ExceptionHandler({ConstraintViolationException.class})
public Result handleConstraintViolationException(ConstraintViolationException ex) {
return Result.fail(BusinessCode.ERROR, ex.getMessage());
}
}
@NotNull: 验证注解的元素值不是 null。
@Null: 验证注解的元素值是 null。
@AssertTrue: 验证注解的元素值是 true。
@AssertFalse: 验证注解的元素值是 false。
@Min(value): 验证注解的元素值大于等于指定的值。
@Max(value): 验证注解的元素值小于等于指定的值。
@Size(max, min): 验证注解的元素值的大小在指定的范围内。
@Digits(integer, fraction): 验证注解的元素值的整数部分和小数部分的数字不超过指定的数值。
@NotBlank: 验证注解的元素值不为空(不为 null、去掉前后空格后长度大于0)。
@NotEmpty: 验证注解的元素值不为 null 且不为空。
@Email: 验证注解的元素值是一个有效的电子邮件地址。
@Positive: 验证注解的元素值是正数。
@PositiveOrZero: 验证注解的元素值是非负数。
@Negative: 验证注解的元素值是负数。
@NegativeOrZero: 验证注解的元素值是非正数。
@Past: 验证注解的元素值是过去的日期或时间。
@PastOrPresent: 验证注解的元素值是过去或当前的日期或时间。
@Future: 验证注解的元素值是将来的日期或时间。
@FutureOrPresent: 验证注解的元素值是将来或当前的日期或时间。
@Pattern:用于校验字符串是否符合指定的正则表达式。
@DecimalMax:验证数字是否小于等于指定的最大值。
@DecimalMin:验证数字是否大于等于指定的最小值。
@Digits(integer,fraction):被注解元素必须是一个数字,其值必须在指定范围内
@URL:被注解元素必须是合法的 URL
@NotBlank和@NotEmpty细节和差异:
@NotBlank注解用于验证字符串非null,且去除首尾空白字符后长度必须大于0
@NotEmpty注解用于验证集合、数组、Map或者字符串非null,且长度必须大于0
@Valid 和 @Validated 的比较:
@Valid:不直接支持分组验证的概念。
@Validated: 提供了分组校验功能,允许指定一个或多个验证分组,可以根据不同的场景应用不同的验证规则。
参考:文章 FC464782123