background

In development, you often need to write code for field validation, such as non-null, length restriction, mailbox format validation, etc., resulting in if-else code, which is not only tedious, but also frustrating.

Hibernate Validator (official documentation) provides a complete and convenient way to implement validation. It defines a number of common validation annotations that can be directly added to the properties of our Javabeans so that validation can be performed whenever needed. Now that Spring Boot is hot, the tool is included in spring-boot-starter-Web without the need to introduce additional packages.

A quick start

1.1 inUserDTODeclare the parameters to check in

Check instructions in the code comments

@Data
public class UserDTO {

    /** Gender (not verified) */
    private String sex;

    /** User name (verification: cannot be empty, cannot exceed 20 characters) */
    @NotBlank(message = "User name cannot be empty")
    @Length(max = 20, message = "User name cannot exceed 20 characters.")
    private String userName;

    /** * Mobile phone number (check: cannot be empty and in regular check format) */
    @NotBlank(message = "Cell phone number cannot be empty.")
    @Pattern(regexp = "^ [1] [3,4,5,6,7,8,9] [0-9] {9} $", message = "The phone number format is wrong.")
    private String mobile;

    /** mailbox (verify: can not verify mailbox format) */
    @NotBlank(message = "Contact email cannot be empty.")
    @Email(message = "Wrong email format")
    private String email;
}
Copy the code

1.2 Interface Specifies parameters to be checked

You need to declare the input position of the Controller layer with an @Validated annotation

@RestController
@RequestMapping("/demo")
public class ValidatorDemoController {

    /** * annotation parameter verification case *@param userDTO
     * @return* /
    @PostMapping("/test")
    public HttpResult test(@Validated UserDTO userDTO) {
        returnHttpResult.success(userDTO); }}Copy the code

HttpResult is an HttpResult result set that Van encapsulates. See the Github address source code at the end of this article.

1.3 Web Global Exception Capture

@Valid An exception is thrown when binding parameter verification is performed in the Spring Boot. This exception needs to be handled in the Spring Boot.

@RestControllerAdvice
@Slf4j
public class WebExceptionHandler {


    /** * method parameter verification *@param e
     * @return* /
    @ExceptionHandler(BindException.class)
    public HttpResult handleMethodArgumentNotValidException(BindException e) {
        log.error(e.getMessage(), e);
        return HttpResult.failure(400,e.getBindingResult().getFieldError().getDefaultMessage());
    }

    @ExceptionHandler(Exception.class)
    public HttpResult handleException(Exception e) {
        log.error(e.getMessage(), e);
        return HttpResult.failure(400."System busy, please try again later."); }}Copy the code

1.4 test

The test tool used postman

  • Request mode: POST
  • Request address: localhost:8080/demo/test
  • Request parameters:
userName:Van
mobile:17098705205
email:123
Copy the code
  • Return result:
{
    "success": false."code": 400,
    "data": null,
    "message": "Wrong email format"
}
Copy the code
  • instructions
  1. More notes, please try your own;
  2. The test results show that the parameter verification is effective, and the abnormal information is returned according to the result set we set.

1.5 Common verification annotations

  1. @Null: The commented element must benull
  2. @NotNull: The annotated element must not benull
  3. @AssertTrue: The commented element must betrue
  4. @AssertFalse: The commented element must befalse
  5. @Min(value): The annotated element must be a number whose value must be greater than or equal to the specified minimum
  6. @Max(value): The annotated element must be a number whose value must be less than or equal to the specified maximum
  7. @DecimalMin(value): The annotated element must be a number whose value must be greater than or equal to the specified minimum
  8. @DecimalMax(value): The annotated element must be a number whose value must be less than or equal to the specified maximum
  9. @Size(max=, min=): The annotated element must be within the specified size range
  10. @Digits (integer, fraction): The annotated element must be a number and its value must be within an acceptable range
  11. @Past: The annotated element must be a past date

    @FutureThe annotated element must be a future date
  12. @Pattern(regex=,flag=): The annotated element must conform to the specified regular expression
  13. @NotBlank(message =): Verifies that the string is notnullAnd the length must be greater than 0
  14. @Email: The commented element must be an E-mail address
  15. @Length(min=,max=): The size of the annotated string must be within the specified range
  16. @NotEmpty: The value of the annotated string must be non-empty
  17. @Range(min=,max=,message=): The annotated element must be in the appropriate range

Second, custom annotation verification

Hibernate Validator comes with annotations for simple parameter validation, and with regular writing, it can handle most parameter validation situations. However, some cases, such as whether to verify login, require custom annotation verification. To facilitate the test, I take the ID verification as an example to complete the process of custom verification.

2.1 Id card Verification Tools

public class IdCardValidatorUtils {

    protected String codeAndCity[][] = {{"11"."Beijing"}, {"12"."Tianjin"},
            {"13"."Hebei"}, {"14"."Shanxi"}, {"15".Inner Mongolia}, {"21"."Liaoning"},
            {"22"."Jilin"}, {"23"."Heilongjiang"}, {"31"."Shanghai"}, {"32"."Jiangsu"},
            {"33"."Zhejiang"}, {"34"."Anhui province"}, {"35"."Fujian"}, {"36"."Jiangxi"},
            {"37"."Shandong"}, {"41"."Henan"}, {"42"."Hubei"}, {"43"."Hunan"},
            {"44"."Guangdong"}, {"45"."Guangxi"}, {"46"."Hainan"}, {"50"."Chongqing"},
            {"51"."Sichuan"}, {"52"."Guizhou"}, {"53"."Yunnan"}, {"54"."Tibet"},
            {"61"."Shaanxi"}, {"62"."Gansu"}, {"63"."Qinghai"}, {"64"."The ningxia"},
            {"65"."Xinjiang"}, {"71"."Taiwan"}, {"81"."Hong Kong"}, {"82"."Macau"},
            {"91"."Foreign"}};

    private String cityCode[] = {"11"."12"."13"."14"."15"."21"."22"."23"."31"."32"."33"."34"."35"."36"."37"."41"."42"."43"."44"."45"."46"."50"."51"."52"."53"."54"."61"."62"."63"."64"."65"."71"."81"."82"."91"};


    // each weighting factor
    private static int power[] = {7.9.10.5.8.4.2.1.6.3.7.9.10.5.8.4.2};

    // select * from ()
    private String verifyCode[] = {"1"."0"."X"."9"."8"."Seven"."6"."5"."4"."3"."2"};

    /** * Verify the validity of all id cards **@param idcard
     * @return* /
    public static boolean isValidatedAllIdcard(String idcard) {
        if (idcard.length() == 15) {
            idcard = convertIdcarBy15bit(idcard);
        }
        return isValidate18Idcard(idcard);
    }

    /** * Convert a 15-bit ID card to an 18-bit ID card **@param idcard
     * @return* /
    public static String convertIdcarBy15bit(String idcard) {
        String idcard17 = null;
        // Non-15-digit id card
        if(idcard.length() ! =15) {
            return null;
        }

        if (isDigital(idcard)) {
            // Get date of birth
            String birthday = idcard.substring(6.12);
            Date birthdate = null;
            try {
                birthdate = new SimpleDateFormat("yyMMdd").parse(birthday);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            Calendar cday = Calendar.getInstance();
            cday.setTime(birthdate);
            String year = String.valueOf(cday.get(Calendar.YEAR));

            idcard17 = idcard.substring(0.6) + year + idcard.substring(8);

            char c[] = idcard17.toCharArray();
            String checkCode = "";

            if (null! = c) {int bit[] = new int[idcard17.length()];

                // Convert a character array to an integer array
                bit = converCharToInt(c);
                int sum17 = 0;
                sum17 = getPowerSum(bit);

                // get the sum value modulo with 11 to get the remainder of the check code
                checkCode = getCheckCodeBySum(sum17);
                // Failed to obtain the parity bit
                if (null == checkCode) {
                    return null;
                }

                // Concatenate the first 17 bits with the 18th bitidcard17 += checkCode; }}else { // The id card contains numbers
            return null;
        }
        return idcard17;
    }

    / * * *@param idCard
     * @return* /
    public static boolean isValidate18Idcard(String idCard) {
        // Non-18 bits are false
        if(idCard.length() ! =18) {
            return false;
        }
        // Get the first 17 bits
        String idcard17 = idCard.substring(0.17);
        // get the 18th position
        String idcard18Code = idCard.substring(17.18);
        char c[] = null;
        String checkCode = "";
        // Whether they are all numbers
        if (isDigital(idcard17)) {
            c = idcard17.toCharArray();
        } else {
            return false;
        }

        if (null! = c) {int bit[] = new int[idcard17.length()];
            bit = converCharToInt(c);
            int sum17 = 0;
            sum17 = getPowerSum(bit);

            // Modulo the sum value with 11 to obtain the remainder for checking code judgment
            checkCode = getCheckCodeBySum(sum17);
            if (null == checkCode) {
                return false;
            }
            // Match the 18th bit of the id card with the calculated calibration code. If it is not equal, it is false
            if(! idcard18Code.equalsIgnoreCase(checkCode)) {return false; }}return true;
    }

    /** * basic digits and digits of the 18-digit ID card number **@param idCard
     * @return* /
    public boolean is18Idcard(String idCard) {
        return Pattern.matches("^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([\\d|x|X]{1})$", idCard);
    }

    /** * Digital validation **@param str
     * @return* /
    public static boolean isDigital(String str) {
        return str == null || "".equals(str) ? false : str.matches("^ [0-9] * $");
    }

    /** * Multiply the weighted factors of each and the corresponding bits of the ID card to obtain the sum value **@param bit
     * @return* /
    public static int getPowerSum(int[] bit) {

        int sum = 0;

        if(power.length ! = bit.length) {return sum;
        }

        for (int i = 0; i < bit.length; i++) {
            for (int j = 0; j < power.length; j++) {
                if(i == j) { sum = sum + bit[i] * power[j]; }}}return sum;
    }

    /** * modulo the sum value and 11 to obtain the remainder for verification code judgment **@param sum17
     * @returnCheck digit * /
    public static String getCheckCodeBySum(int sum17) {
        String checkCode = null;
        switch (sum17 % 11) {
            case 10:
                checkCode = "2";
                break;
            case 9:
                checkCode = "3";
                break;
            case 8:
                checkCode = "4";
                break;
            case 7:
                checkCode = "5";
                break;
            case 6:
                checkCode = "6";
                break;
            case 5:
                checkCode = "Seven";
                break;
            case 4:
                checkCode = "8";
                break;
            case 3:
                checkCode = "9";
                break;
            case 2:
                checkCode = "x";
                break;
            case 1:
                checkCode = "0";
                break;
            case 0:
                checkCode = "1";
                break;
        }
        return checkCode;
    }

    /** * convert a character array to an integer array **@param c
     * @return
     * @throws NumberFormatException
     */
    public static int[] converCharToInt(char[] c) throws NumberFormatException {
        int[] a = new int[c.length];
        int k = 0;
        for (char temp : c) {
            a[k++] = Integer.parseInt(String.valueOf(temp));
        }
        return a;
    }


    public static void main(String[] args) {
        String idCardForFalse = "350583199108290106";
        String idCardForTrue = "350583197106150219";
        if (IdCardValidatorUtils.isValidatedAllIdcard(idCardForTrue)) {
            System.out.println("Correct verification of id card");
        } else {
            System.out.println("Id check error!"); }}}Copy the code

2.2 Custom annotations

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

    String message(a) default"Incorrect format of ID card number"; Class<? >[] groups()default {};

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

Carefully you will find that, compared with the common custom annotations, the annotations: @ the Constraint (validatedBy = IdentityCardNumberValidator. Class), the role of the annotation is the call id verification tools.

2.3 inUserDTOAdd a declaration for the field to be verified

/** * ID number (validation: custom annotation validation) */
@IdentityCardNumber
private String idNumber;
Copy the code

2.4 Interfaces on the control layer

@RestController
@RequestMapping("/custom")
public class ValidatorCustomController {

    /** * Custom annotation parameter verification case *@param userDTO
     * @return* /
    @PostMapping("/test")
    public HttpResult test(@Validated UserDTO userDTO) {
        returnHttpResult.success(userDTO); }}Copy the code

2.5 Test of custom annotations

  • Request mode: POST
  • Request address: localhost: 8080 / private/test
  • Request parameters:
userName:Van
mobile:17098705205
email:[email protected]
idNumber:350583199108290106
Copy the code
  • Return result:
{
    "success": false."code": 400."data": null."message": "Incorrect format of ID card number"
}
Copy the code

Third, grouping check

In addition to the above validation, there may be requirements for:

When creating user information, you do not need to verify the userId. However, when updating the user information, you need to verify the userId, and the user name, mailbox, and so on. In this case, group check can be used to solve the problem.

3.1 Defining a Packet Interface

  • Create.java
import javax.validation.groups.Default;

public interface Create extends Default {}Copy the code
  • Update.java
import javax.validation.groups.Default;

public interface Update extends Default {}Copy the code

3.2 inUserDTOAdd a declaration for the field to be verified

/** * user ID (non-null only if there are Update groups) */ @notnull (message ="Id cannot be empty", groups = Update.class)
    private Long userId;
Copy the code

3.3 Control layer entry parameter position declaration

@RestController
@RequestMapping("/groups")
public class ValidatorGroupsController {

    /** * to update data, pass in userID *@param userDTO
     * @return* /
    @PostMapping("/update")
    public HttpResult updateData(@Validated(Update.class)UserDTO userDTO) {
        return HttpResult.success(userDTO);
    }
    /** * add data without passing in userID *@param userDTO
     * @return* /
    @PostMapping("/create")
    public HttpResult createData(@Validated(Create.class)UserDTO userDTO) {
        returnHttpResult.success(userDTO); }}Copy the code

3.4 Group Verification Tests – Added tests

  • Request mode: POST
  • Request address: localhost: 8080 / groups/create
  • Request parameters:
userName:Van
mobile:17098705205
email:[email protected]
idNumber:350583197106150219
userId:
Copy the code
  • Return result:
{
    "success": true."code": 200."data": {
        "userId": null."sex": null."userName": "Van"."mobile": "17098705205"."email": "[email protected]"."idNumber": "350583197106150219"."passWord": null
    },
    "message": null
}
Copy the code

If the request succeeds, a new request is created and the userId is not verified, that is, the userId can be empty.

3.5 Group Verification Test – Update test

  • Request mode: POST
  • Request address: localhost: 8080 / groups/update
  • Request parameters: same as above (3.4)
  • Return result:
{
    "success": false."code": 400."data": null."message": "Id cannot be empty"
}
Copy the code

If the request fails, the userId cannot be null.

Combined with the test results of 3.4 and 3.5, it indicates that the grouping verification is successful.

Four,

Hopefully, every line of code you write is a business necessity, not a boring and endless validation of parameters.

4.1 Technical Exchange

  1. Over the blog
  2. Travelogue Blog – Blog Garden
  3. The Dust Blog -CSDN

Follow our official account to learn more:

4.2 Source Code address

Github sample code