preface

In daily development, parameter verification is a very important link, strict parameter verification will reduce the probability of many bugs, increase the security of the interface. Before this, I wrote a unified parameter verification of SpringBoot mainly introduced some simple verification methods. And this article is to introduce some advanced verification methods. For example, in the process of writing A certain interface will encounter, when xxType value is A, paramA value must be passed. The xxType value is B, and the paramB value must be transmitted. The usual thing to do for this is to add all sorts of if judgments to the controller. Obviously such code is not elegant, and grouping verification and custom parameter verification are used to solve this problem.

PathVariable parameter verification

Restful interface, in today’s speaking should be more common, commonly used address bar parameters, we are this check.

/** * get phone number information */
@GetMapping("/phoneInfo/{phone}")
public ResultVo phoneInfo(@PathVariable("phone") String phone){
    // Verify the phone number is valid
    String pattern = "^ [1] [3,4,5,7,8] [0-9] {9} $";
    boolean isValid =  Pattern.matches(pattern, phone);
    if(isValid){
        // Execute the corresponding logic
        return ResultVoUtil.success(phone);
    } else {
        // Return an error message
        return ResultVoUtil.error("Cell phone number invalid"); }}Copy the code

Obviously the above code is not elegant enough, so we can verify this by adding the corresponding regular expression phone: after the parameter. This saves writing validation code in the Controller.

/** * get phone number information */
@ GetMapping ("/phoneInfo / {phone: ^ [1] [3,4,5,7,8] [0-9] {9} $} ")
public ResultVo phoneInfo(@PathVariable("phone") String phone){
    return ResultVoUtil.success(phone);
}
Copy the code

Although the code is much more streamlined this way. However, if the incoming phone number does not match the rule, 404 will be returned. Not the wrong number. The error message is as follows:

Custom validation annotations

Validation uses a mobile phone number as an example, although validation provides the @pattern annotation to use a regular expression for validation. If the regular expression is used in multiple places, it needs to be modified one by one as soon as the regular expression changes. Obviously to avoid doing this, custom validation annotations are a good way to help.

@Data
public class PhoneForm {

    /** * Phone number */
    ^ @ the Pattern (regexp = "[1] [3,4,5,7,8] [0-9] {9} $", message =" wrong number ")
    private String phone;

}
Copy the code

There are two main steps to implementing a custom validation annotation. One is the annotation itself, and the other is the validation logic implementation class.

PhoneVerify verifies annotations

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
 
    String message(a) default"Wrong phone number format"; Class<? >[] groups()default {};

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

}
Copy the code

PhoneValidator validates the implementation class

public class PhoneValidator implements ConstraintValidator<Phone.Object> {

    @Override
    public boolean isValid(Object telephone, ConstraintValidatorContext constraintValidatorContext) {
        String pattern = "^1[3|4|5|7|8]\\d{9}$";
        returnPattern.matches(pattern, telephone.toString()); }}Copy the code

CustomForm form data

@Data
public class CustomForm {

    /** * Phone number */
    @Phone
    private String phone;

}
Copy the code

The test interface

@PostMapping("/customTest")
public ResultVo customTest(@RequestBody @Validated CustomForm form){
	return ResultVoUtil.success(form.getPhone());
}
Copy the code

Meaning of annotations

@Target({ElementType.FIELD})

Annotations specify where the current custom annotations can be used, but only on properties. But it can be used in many more places, such as methods, constructors, and so on.

  • TYPE – Class, interface (including annotation TYPE) or enumeration
  • FIELD – Fields (including enumeration constants)
  • METHOD – the METHOD
  • Parameters of the PARAMETER –
  • Constructor-constructor
  • LOCAL_VARIABLE – Local variable
  • ANNOTATION_TYPE – Annotation type
  • PACKAGE – PACKAGE
  • TYPE_PARAMETER – Type parameter
  • TYPE_USE – Usage type
@Retention(RetentionPolicy.RUNTIME)

Specifies that the current annotation is retained at runtime. There are three retention policies:

  • Source-annotations remain in the SOURCE file only, and are discarded when a Java file is compiled into a class file.
  • CLASS – Annotations are reserved to the CLASS file, but are discarded when the JVM loads the CLASS file, which is the default lifecycle.
  • Run-time annotations are not only saved to the class file, but still exist after the JVM loads the class file.
@Constraint(validatedBy = PhoneValidator.class)

Specifies which validation class the current annotation uses for validation.

Packet check

UserForm

@Data
public class UserForm {

    /** * id */
    @null (message = "new id must be Null ", groups = {insert.class})
    @notnull (message = "Update id cannot be empty ", groups = {update.class})
    private String id;

    /** * type */
    @notempty (message = "name cannot be empty", groups = {insert.class})
    private String name;

    /** * age */
    @notempty (message = "age cannot be empty", groups = {insert.class})
    private String age;
    
}
Copy the code

Insert the grouping

public interface Insert {}Copy the code

The Update packet

public interface Update {}Copy the code

The test interface

/** * Add user */
@PostMapping("/addUser")
public ResultVo addUser(@RequestBody @Validated({Insert.class}) UserForm form){
  	// Select the corresponding group for verification
    return ResultVoUtil.success(form);
}

/** * Update user */
@PostMapping("/updateUser")
public ResultVo updateUser(@RequestBody @Validated({Update.class}) UserForm form){
    // Select the corresponding group for verification
    return ResultVoUtil.success(form);
}
Copy the code

The test results

Add test

Update the test

Sequential calibration@GroupSequence

In @GroupSequence, you can specify the group verification sequence. For example, @groupSequence ({insert. class, update. class, userform. class}) performs the Insert check first and then the Update check. If the Insert group fails validation, the Update group will not be validated.

@Data
@GroupSequence({Insert.class, Update.class, UserForm.class})
public class UserForm {

    /** * id */
    @null (message = "new id must be Null ", groups = {insert.class})
    @notnull (message = "Update id cannot be empty ", groups = {update.class})
    private String id;

    /** * type */
    @notempty (message = "name cannot be empty", groups = {insert.class})
    private String name;

    /** * age */
    @notempty (message = "age cannot be empty", groups = {insert.class})
    private String age;

}
Copy the code
The test interface
/** * edit user */
@PostMapping("/editUser")
public ResultVo editUser(@RequestBody @Validated UserForm form){
	return ResultVoUtil.success(form);
}
Copy the code
The test results

Ha ha ha, test results are actually a loop, no matter how you input will return an error, friends can try oh. The above example is just a demonstration, so don’t do this in real life.

User-defined packet verification

As mentioned earlier, when xxType is A, paramA must be passed. The xxType value is B, and the paramB value must be passed to the scenario. Using packet checksums and packet sequences alone is not possible. You need to use @GroupSequenceProvider.

Custom grouped forms

@Data
@GroupSequenceProvider(value = CustomSequenceProvider.class)
public class CustomGroupForm {

    /** * type */
    @ the Pattern (regexp = "(A | B)," the message = "type does not have to A | B)"
    private String type;

    /** * parameter A */
    @notempty (message = "parameter A cannot be empty", groups = {whentypeisa.class})
    private String paramA;

    /** * parameter B */
    @notempty (message = "parameter B cannot be null ", groups = {whentypeisb.class})
    private String paramB;

    /** * group A */
    public interface WhenTypeIsA {}/** * group B */
    public interface WhenTypeIsB {}}Copy the code

CustomSequenceProvider

public class CustomSequenceProvider implements DefaultGroupSequenceProvider<CustomGroupForm> {

    @Override
    publicList<Class<? >> getValidationGroups(CustomGroupForm form) { List<Class<? >> defaultGroupSequence =new ArrayList<>();

        defaultGroupSequence.add(CustomGroupForm.class);

        if(form ! =null && "A".equals(form.getType())) {
            defaultGroupSequence.add(CustomGroupForm.WhenTypeIsA.class);
        }

        if(form ! =null && "B".equals(form.getType())) {
            defaultGroupSequence.add(CustomGroupForm.WhenTypeIsB.class);
        }

        returndefaultGroupSequence; }}Copy the code

The test interface

/** * User-defined group */
@PostMapping("/customGroup")
public ResultVo customGroup(@RequestBody @Validated CustomGroupForm form){
    return ResultVoUtil.success(form);
}
Copy the code

The test results

Type The value is A

Type The value is B

The subtotal

The GroupSequence annotation is a standard Bean authentication annotation. As before, it allows you to statically redefine the default checkgroup order of a class. However, the GroupSequenceProvider allows you to dynamically define the order of a checksum group.

One thing to notice

SpringBoot 2.3.x removes validation dependencies requiring manual import of dependencies.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Copy the code

conclusion

Some personal experience, parameter non-null judgment, this should be the first step of the verification, in addition to non-null verification, we also need to do the following points:

  • Normal parameters – You need to limit the length of the field. If the data is stored in the database, the length depends on the database. Otherwise, the length depends on the service.
  • Type parameters – It is best to use the re to strictly check for possible types. Such astypeA value of 0 | 1 | 2 】 【 like this.
  • List parameters – Not only do you need to check whether the parameters in the list are qualified, but you also need to limit the size of the list. Let’s say 100.
  • Parameters such as date, mail, amount, and URL all need to be verified with the correct re.
  • Parameter authenticity – This applies to various typesIdFor instanceuserId,merchantId, such parameters need to be verified to determine whether the system contains them and whether the corresponding status is normal.

The stricter the parameter verification, the better. Strict verification rules not only reduce the probability of interface errors, but also prevent dirty data, ensuring system security and stability.

Wrong reminder information needs to be a little bit more friendly oh, so as to prevent the front brother ridicule oh.

Review of the previous period

  • SpringBoot unified parameter verification

At the end

If you feel helpful to you, you can comment more, more like oh, you can also go to my home page to have a look, maybe there is an article you like, you can also click a concern oh, thank you.

I am a different kind of tech nerd, making progress every day and experiencing a different life. See you next time!