How can I get rid of if-else parameter checking?

Keywords: Java Mobile Spring Hibernate github

background

In development, it is often necessary to write some field validation code, such as non-empty, length limitations, mailbox format validation, etc., which results in code full of if-else, which is not only quite lengthy, but also crazy.

hibernate validator(Official Documents ) provides a set of relatively complete and convenient verification implementation methods.It defines a number of commonly used check annotations that we can add directly to our JavaBean properties to check when we need them.Now that Spring Boot is hot, the tool is included in the spring-boot-starter-web without the need to introduce additional packages.

I. Quick Start

1.1 Declare parameters to check in UserDTO

Check instructions see comments in the code

@Data
public class UserDTO {

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

    /** 
     User name (check: cannot be empty, cannot exceed 20 strings)
     */
    @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 = "Mobile number cannot be empty")
    @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "Bad format of mobile number")
    private String mobile;

    /** 
     Mailbox (Check: Don't be afraid and check mailbox format)
     */
    @NotBlank(message = "Contact mailbox cannot be empty")
    @Email(message = "The mailbox is not in the right format")
    private String email;
}

1.2 Declare the parameters to be checked at the interface

The @Validated annotation declaration is required at the entry location of the Controller layer

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

    /**
     * Note Parameter Check Cases
     * @param userDTO
     * @return
     */
    @PostMapping("/test")
    public HttpResult test(@Validated UserDTO userDTO) {
        return HttpResult.success(userDTO);
    }
}

Here, HttpResult is a result set encapsulated by Van himself, as detailed in the source code for the Github address at the end of the article.

1.3 Web Global Exception Capture

@Valid throws an exception during binding parameter checking in Spring Boot and needs to be handled in Spring Boot.

@RestControllerAdvice
@Slf4j
public class WebExceptionHandler {


    /**
     * Method Parameter Check
     * @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");
    }
}

1.4 Test

Posman used by the test tool

  • Request method: POST
  • Request address: localhost:8080/demo/test
  • Request parameters:
userName:Van
mobile:17098705205
email:123
  • Return results:
{
    "success": false,
    "code": 400,
    "data": null,
    "message": "The mailbox is not in the right format"
}
  • Explain
  1. More notes, please try your own;
  2. The test results show that the parameter check is valid and returns exception information according to the result set we set.

1.5 Common Check Notes

  1. @Null: The commented element must be null
  2. @NotNull: The commented element must not be null
  3. @AssertTrue: The commented element must be true
  4. @AssertFalse: The commented element must be false
  5. @Min(value): The commented element must be a number whose value must be greater than or equal to the specified minimum value
  6. @Max(value): The commented element must be a number whose value must be less than or equal to the specified maximum
  7. @DecimalMin(value): The commented element must be a number whose value must be greater than or equal to the specified minimum value
  8. @DecimalMax(value): The commented element must be a number whose value must be less than or equal to the specified maximum
  9. @Size(max=, min=): The size of the commented element must be within the specified range
  10. @Digits (integer, fraction): The commented element must be a number whose value must be within an acceptable range
  11. @Past: The commented element must be a past date
    @Future: The commented element must be a future date
  12. @Pattern(regex=,flag=): The commented element must conform to the specified regular expression
  13. @NotBlank(message =): Verification string is not null and must be greater than 0 in length
  14. @Email: The commented element must be an e-mail address
  15. @Length(min=,max=): The size of the commented string must be within the specified range
  16. @NotEmpty: The commented string must be non-empty
  17. @Range(min=,max=,message=): The commented element must be in the appropriate range

2. Custom Annotation Check

hibernate validator comes with annotations that allow simple parameter checking, plus regular writing, to solve most parameter checking cases.However, in some cases, such as checking whether you are logged in, we need to customize annotation checks.For the convenience of testing, I'm going to finish the process of customizing the verification using the ID card verification as an example.

2.1 Identity Card Verification Tool Class

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"}, {"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", "Ningxia"},
            {"65", "Xinjiang"}, {"71", "Taiwan"}, {"81", "Hong Kong"}, {"82", "Macao"},
            {"91", "abroad"}};

    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"};


    // Per-Bit Weighting Factor
    private static int power[] = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};

    // 18th Bit Calibration Code
    private String verifyCode[] = {"1", "0", "X", "9", "8", "7", "6", "5",
            "4", "3", "2"};

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

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

        if (isDigital(idcard)) {
            // Get the 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 Character Array to Integer Array
                bit = converCharToInt(c);
                int sum17 = 0;
                sum17 = getPowerSum(bit);

                // Get Sum Value and 11 Modular Remainder to Check Code
                checkCode = getCheckCodeBySum(sum17);
                // Cannot get check bits
                if (null == checkCode) {
                    return null;
                }

                // Splicing the first 17 bits to the 18th bit check code
                idcard17 += checkCode;
            }
        } else { // ID Card Contains Numbers
            return null;
        }
        return idcard17;
    }

    /**
     * @param idCard
     * @return
     */
    public static boolean isValidate18Idcard(String idCard) {
        // Non18 digits are false
        if (idCard.length() != 18) {
            return false;
        }
        // Get Top 17 Bits
        String idcard17 = idCard.substring(0, 17);
        // Get the 18th bit
        String idcard18Code = idCard.substring(17, 18);
        char c[] = null;
        String checkCode = "";
        // Are they 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);

            // Check the sum and 11 modulus to get the remainder
            checkCode = getCheckCodeBySum(sum17);
            if (null == checkCode) {
                return false;
            }
            // Match the 18th digit of your ID card with the calculated proof code, it's false if it's not equal
            if (!idcard18Code.equalsIgnoreCase(checkCode)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 18 Basic Number and Digit Check for Bit ID 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 weighting factors of each and corresponding bit of the ID card to get 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;
    }

    /**
     * Check the sum and 11 modulus to get the remainder
     *
     * @param sum17
     * @return Check bits
     */
    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 = "7";
                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 Character Array to 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("Identity card is verified correctly");
        } else {
            System.out.println("ID card verification error!");
        }
    }
}

2.2 Custom Notes

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

    String message() default "The ID number is not in the correct format";

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

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

Carefully, you will find that compared to the general custom comment, the comment:
@Constraint(validatedBy = IdentityCardNumberValidator.class), the purpose of this annotation is to invoke the tool for identity verification.

2.3 Add declarations in fields that UserDTO needs to check

/**
 * ID Number (Check: Custom Note Check)
 */
@IdentityCardNumber
private String idNumber;

2.4 Control Layer Interface

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

    /**
     * Custom Annotation Parameter Check Case
     * @param userDTO
     * @return
     */
    @PostMapping("/test")
    public HttpResult test(@Validated UserDTO userDTO) {
        return HttpResult.success(userDTO);
    }

}

2.5 Test of Custom Annotations

  • Request method: POST
  • Request address: localhost:8080/private/test
  • Request parameters:
userName:Van
mobile:17098705205
email:110@qq.com
idNumber:350583199108290106
  • Return results:
{
    "success": false,
    "code": 400,
    "data": null,
    "message": "The ID number is not in the correct format"
}

3. Grouping Check

In addition to the above checks, there may be other requirements:

UserId does not need to be checked when creating user information; however, when updating user information, userId needs to be checked in both cases, such as user name and mailbox.In this case, the group check can be used to solve the problem.

3.1 Define Grouping Interface

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

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

public interface Update extends Default {
}

3.2 Add declarations in fields that UserDTO needs to check

/**
     * User ID (check non-empty only in groups with Update)
     */
    @NotNull(message = "id Cannot be empty", groups = Update.class)
    private Long userId;

3.3 Statement of control layer entry position

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

    /**
     * Update data, need to 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) {
        return HttpResult.success(userDTO);
    }
}

3.4 Tests for Grouped Checks - New Tests

  • Request method: POST
  • Request address: localhost:8080/groups/create
  • Request parameters:
userName:Van
mobile:17098705205
email:110@qq.com
idNumber:350583197106150219
userId:
  • Return results:
{
    "success": true,
    "code": 200,
    "data": {
        "userId": null,
        "sex": null,
        "userName": "Van",
        "mobile": "17098705205",
        "email": "110@qq.com",
        "idNumber": "350583197106150219",
        "passWord": null
    },
    "message": null
}

The request succeeds, indicating a new request, and does not verify the userId, which means the userId can be null.

Test-Update Test for 3.5 Grouped Checks

  • Request method: POST
  • Request address: localhost:8080/groups/update
  • Request parameters: Same as above (3.4)
  • Return results:
{
    "success": false,
    "code": 400,
    "data": null,
    "message": "id Cannot be empty"
}

The request failed, indicating an update request, verifying userId, that is, userId cannot be empty.

Combining the test results of 3.4 and 3.5, the group check is successful.

4. Summary

Hopefully, every line of code you write is a business need, not a boring and endless set of parameter checks.

Github sample code

Posted by jim_de_bo on Wed, 11 Sep 2019 09:43:13 -0700