七、spring boot 添加注解校验@Valid

常用的验证注解注解

  1. 引入maven坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  1. 实体添加注解
import lombok.Data;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

@Data
public class UserDTO implements Serializable {
    @NotNull
    @NotBlank
    @Size(min = 4, max = 50, message = "用户名长度必须在4到50个字符之间")
    private String username;

    @NotNull
    @NotBlank
    @Size(min = 6, max = 20, message = "密码长度必须在6到20个字符之间")
    private String password;

    @NotNull
    @NotBlank
    @Size(min = 6, max = 20, message = "密码长度必须在6到20个字符之间")
    private String matchingPassword;

    @NotNull
    @Email // 此处的@email注解只是做了简单的规则校验,实际应用还是需要换为正则表达式比较合适
    private String email;
    
}
  1. 请求接口参数添加@Valid注解,开启验证

    @PostMapping("/register")
    @ApiOperation("注册")
    public UserDTO register(@RequestBody @Valid UserDTO userDTO) {
        return userDTO;
    }
  1. 当请求参数不符合规范时,返回错误消息

模拟参数:

{
  "email"           : "",
  "matchingPassword": "123456",
  "password"        : "123456",
  "username"        : "aep"
}

响应结果:

{
  "timestamp": "2023-04-01T14:22:28.167+00:00",
  "status": 400,
  "error": "Bad Request",
  "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.uaa.dto.UserDTO com.example.uaa.rest.AuthorizeController.register(com.example.uaa.dto.UserDTO): [Field error in object 'userDTO' on field 'username': rejected value [aep]; codes [Size.userDTO.username,Size.username,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDTO.username,username]; arguments []; default message [username],50,4]; default message [用户名长度必须在4到50个字符之间]] ...",
  "message": "Validation failed for object='userDTO'. Error count: 1",
  "errors": [
    {
      "codes": [
        "Size.userDTO.username",
        "Size.username",
        "Size.java.lang.String",
        "Size"
      ],
      "arguments": [
        {
          "codes": [
            "userDTO.username",
            "username"
          ],
          "arguments": null,
          "defaultMessage": "username",
          "code": "username"
        },
        50,
        4
      ],
      "defaultMessage": "用户名长度必须在4到50个字符之间",
      "objectName": "userDTO",
      "field": "username",
      "rejectedValue": "aep",
      "bindingFailure": false,
      "code": "Size"
    }
  ],
  "path": "/authorize/register"
}

自定义校验注解

创建一个自定义注解,名称为@ValidEmail

import com.example.uaa.myvalidation.EmailValidation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EmailValidation.class) // 指定具体的类去处理校验
@Documented
public @interface ValidEmail {
    String message() default "无效的email格式"; // 可使用国际化key名称, 例如:"{javax.validation.constraints.NotBlank.message}";

    Class<?>[] groups() default {};

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

创建一个注解处理类EmailValidation.java

package com.example.uaa.myvalidation;

import com.example.uaa.myvalidation.annotation.ValidEmail;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;

/**
 * @ValidEmail 注解校验逻辑
 */
public class EmailValidation implements ConstraintValidator<ValidEmail, String> {
    private final static String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";

    @Override
    public void initialize(ValidEmail constraintAnnotation) { }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        var compile = Pattern.compile(EMAIL_REGEX);
        return compile.matcher(value).matches();
    }
}

使用注解

@Data
public class UserDTO implements Serializable {
    ... 

    @NotNull
    @ValidEmail // 使用自定义的email校验注解
    private String email;

    ...
}

使用passay密码工具,自定义复杂的密码验证规则

创建一个自定义注解,名称为@ValidPassword

import com.example.uaa.myvalidation.PasswordConstraintValidation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordConstraintValidation.class) // 指定具体的类去处理校验
@Documented
public @interface ValidPassword {
    String message() default "无效的密码格式"; // 可使用国际化key名称, 例如:"{javax.validation.constraints.NotBlank.message}";

    Class<?>[] groups() default {};

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

创建一个注解处理类PasswordConstraintValidation.java
需要用到的工具如下:

   <!--密码验证工具 https://www.passay.org/-->
   <dependency>
      <groupId>org.passay</groupId>
      <artifactId>passay</artifactId>
      <version>1.6.3</version>
   </dependency>
package com.example.uaa.myvalidation;

import com.example.uaa.myvalidation.annotation.ValidPassword;
import org.passay.*;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * @author: aesop
 * @description: 校验密码具体规则
 * @date: 2023/4/3 19:14
 */
public class PasswordConstraintValidation implements ConstraintValidator<ValidPassword, String> {
    @Override
    public void initialize(ValidPassword constraintAnnotation) { }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        //passay 官方文档 使用教程 :https://www.passay.org/reference/
        PasswordValidator validator = new PasswordValidator(
                // 长度在8-16个字符之间
                new LengthRule(8, 16),
                // 至少有一个大写字母
                new CharacterRule(EnglishCharacterData.UpperCase, 1),
                // 至少有一个小写字母
                new CharacterRule(EnglishCharacterData.LowerCase, 1),
                // 至少有一个数字
                new CharacterRule(EnglishCharacterData.Digit, 1),
                // 至少有一个特殊字符
                new CharacterRule(EnglishCharacterData.Special, 1),
                // 非法:字母按序列连续出现超过5个 (>=5)
                new IllegalSequenceRule(EnglishSequenceData.Alphabetical, 5, false),
                // 非法:数字按序列连续超过5个 (>=5)
                new IllegalSequenceRule(EnglishSequenceData.Numerical, 5, false),
                // 非法:按键盘布局连续相邻出现5个 (>=5)
                new IllegalSequenceRule(EnglishSequenceData.USQwerty, 5, false),
                // 不准有空白字符
                new WhitespaceRule());
        var validate = validator.validate(new PasswordData(value));
        return validate.isValid();
    }
}

实体类字段加上注解

    @ValidPassword
    private String password;

校验两次密码是否匹配

匹配两次密码是否相同
创建一个自定义注解,名称为@ValidPasswordMatching

import com.example.uaa.myvalidation.PasswordConstraintMatchingValidation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordConstraintMatchingValidation.class) // 指定具体的类去处理校验
@Documented
public @interface ValidPasswordMatching {
    String message() default "两次密码不匹配"; // 可使用国际化key名称, 例如:"{javax.validation.constraints.NotBlank.message}";

    Class<?>[] groups() default {};

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

创建一个注解处理类PasswordConstraintMatchingValidation.java

import com.example.uaa.dto.UserDTO;
import com.example.uaa.myvalidation.annotation.ValidPasswordMatching;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * @author: aesop
 * @description: 校验两次密码是否匹配
 * @date: 2023/4/3 19:13
 */
public class PasswordConstraintMatchingValidation implements ConstraintValidator<ValidPasswordMatching, UserDTO> {
    @Override
    public void initialize(ValidPasswordMatching constraintAnnotation) { }

    @Override
    public boolean isValid(UserDTO userDTO, ConstraintValidatorContext constraintValidatorContext) {
        return userDTO.getPassword().equals(userDTO.getMatchingPassword());
    }
}

将注解@ValidPasswordMatching放到类上面

import com.example.uaa.myvalidation.annotation.ValidEmail;
import com.example.uaa.myvalidation.annotation.ValidPassword;
import com.example.uaa.myvalidation.annotation.ValidPasswordMatching;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

@Data
@ValidPasswordMatching
public class UserDTO implements Serializable {
    @NotNull
    @ValidPassword
    private String password;

    @NotNull
    private String matchingPassword;

}

posted @ 2023-04-01 22:52  aesopcmc  阅读(413)  评论(0)    收藏  举报