Alibaba P8 explained in person: SpringBoot - form verification - unified exception handling - Custom verification information source

Posted by vMan on Mon, 03 Jan 2022 08:22:21 +0100

1. Introduction

We all know that the verification of the front desk is only to meet the friendliness of the interface, customer experience and so on. However, it is far from enough to rely on the front end for data legitimacy verification. Because illegal users may directly obtain the request address from the client for illegal requests, background verification is necessary; Especially if the application does not allow null values to be entered and requires the legitimacy of the data.

2. Open

2.1 project structure

Structure description:

├── java
│   └── com
│       └── ldx
│           └── valid
│               ├── ValidApplication.java # Startup class
│               ├── annotation
│               │   └── Phone.java # Custom validation annotation
│               ├── config
│               │   └── ValidatorConfig.java # Form validation configuration class
│               ├── controller
│               │   └── SysUserController.java # User management controller
│               ├── exception
│               │   ├── BusinessException.java # Business exception class
│               │   └── GlobalExceptionHandler.java # Unified exception handling class
│               ├── model
│               │   ├── SysUser.java # User information entity
│               │   └── ValidationInterface.java # General grouping interface for form validation
│               ├── util
│               │   └── CommonResult.java # Interface return encapsulation class
│               └── validation
│                   └── PhoneValidator.java #Custom validation implementation class
└── resources
    ├── application.yaml # configuration file
    └── messages
        └── validation
            └── messages.properties # Custom validation information source

2.1 quick start

2.1. 1 import dependency

Create a springboot project and import the following dependencies

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ldx</groupId>
    <artifactId>valid</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>valid</name>
    <description>Form Validation  demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- web support -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Form Validation  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2.1. 2 add configuration class

Create a form verification configuration class to configure quick verification. You don't have to wait for all parameters to be verified. As long as there is an error, it will be thrown immediately.

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

/**
 * Configure Hibernate parameter verification
 * @author ludangxin
 * @date 2021/8/5
 */
@Configuration
public class ValidatorConfig {
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
        //Quick verification, return as soon as there is an error
        postProcessor.setValidator(validator());
        return postProcessor;
    }

    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
            .configure()
            .addProperty("hibernate.validator.fail_fast", "true")
            .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}

2.1. 3 add entity class

import lombok.*;
import javax.validation.constraints.*;
import java.io.Serializable;

/**
 * User information management
 * @author ludangxin
 * @date 2021/8/5
 */
@Data
public class SysUser  implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * Primary key
     */
    private Long id;

    /**
     * user name
     */
    @NotEmpty(message = "User name cannot be empty")
    private String username;

    /**
     * password
     */
    @Size(min = 6, max = 16, message = "Password length must be{min}-{max}between")
    private String password = "123456";

    /**
     * e-mail address
     */
    @Email(message = "Illegal email address")
    @NotEmpty(message = "Mailbox cannot be empty")
    private String email;

    /**
     * Telephone
     */
    @Size(min = 11, max = 11, message = "Illegal mobile phone number")
    @NotEmpty(message = "Mobile phone number cannot be empty")
    private String phone;
}

2.1. 4 interface return encapsulation class

import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * Operation message reminder
 * @author ludangxin
 * @date 2021/8/5
 */
@Data
@NoArgsConstructor
public class CommonResult {
    /** Status code */
    private int code;

    /** Return content */
    private String msg;

    /** data object */
    private Object data;

    /**
     * Initialize a newly created CommonResult object
     * @param type Status type
     * @param msg Return content
     */
    public CommonResult(Type type, String msg) {
        this.code = type.value;
        this.msg = msg;
    }

    /**
     * Initialize a newly created CommonResult object
     * @param type Status type
     * @param msg Return content
     * @param data data object
     */
    public CommonResult(Type type, String msg, Object data) {
        this.code = type.value;
        this.msg = msg;
        if (data != null) {
            this.data = data;
        }
    }

    /**
     * Return success message
     * @return Success message
     */
    public static CommonResult success() {
        return CommonResult.success("Operation succeeded");
    }

    /**
     * Return success data
     * @return Success message
     */
    public static CommonResult success(Object data) {
        return CommonResult.success("Operation succeeded", data);
    }

    /**
     * Return success message
     * @param msg Return content
     * @return Success message
     */
    public static CommonResult success(String msg) {
        return CommonResult.success(msg, null);
    }

    /**
     * Return success message
     * @param msg Return content
     * @param data data object
     * @return Success message
     */
    public static CommonResult success(String msg, Object data) {
        return new CommonResult(Type.SUCCESS, msg, data);
    }

    /**
     * Return warning message
     * @param msg Return content
     * @return Warning message
     */
    public static CommonResult warn(String msg) {
        return CommonResult.warn(msg, null);
    }

    /**
     * Return warning message
     * @param msg Return content
     * @param data data object
     * @return Warning message
     */
    public static CommonResult warn(String msg, Object data) {
        return new CommonResult(Type.WARN, msg, data);
    }

    /**
     * Return error message
     * @return error message
     */
    public static CommonResult error() {
        return CommonResult.error("operation failed");
    }

    /**
     * Return error message
     * @param msg Return content
     * @return Error message
     */
    public static CommonResult error(String msg) {
        return CommonResult.error(msg, null);
    }

    /**
     * Return error message
     * @param msg Return content
     * @param data data object
     * @return Error message
     */
    public static CommonResult error(String msg, Object data) {
        return new CommonResult(Type.ERROR, msg, data);
    }

    /**
     * Status type
     */
    public enum Type {
        /** success */
        SUCCESS(200),
        /** warning */
        WARN(301),
        /** error */
        ERROR(500);
        private final int value;

        Type(int value){
            this.value = value;
        }

        public int value() {
            return this.value;
        }
    }
}

2.1. 5 controller

Use the @ Validated annotation to identify the class to be Validated, and use the BindingResult class to receive error messages

import com.ldx.valid.exception.BusinessException;
import com.ldx.valid.model.SysUser;
import com.ldx.valid.model.ValidationInterface;
import com.ldx.valid.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * User management controller
 *
 * @author ludangxin
 * @date 2021/8/5
 */
@Slf4j
@RestController
@RequestMapping("sys/user")
public class SysUserController {
   private static final List<SysUser> USERS = new ArrayList<>();

   // Data initialization
   static {
      SysUser user = new SysUser();
      user.setId(1L);
      user.setUsername("zhangsan");
      user.setPhone("13566666666");
      user.setEmail("example@qq.com");
      USERS.add(user);
      SysUser user1 = new SysUser();
      user1.setId(2L);
      user1.setUsername("lisi");
      user1.setPhone("13588888888");
      user1.setEmail("example1@qq.com");
      USERS.add(user1);
   }

   /**
    * Add user information
    * @param sysUser User information
    * @return Success identification
    */
   @PostMapping
   public CommonResult add(@Validated @RequestBody SysUser sysUser, BindingResult result) {
      FieldError fieldError = result.getFieldError();

      if(Objects.nonNull(fieldError)) {
         String field = fieldError.getField();
         Object rejectedValue = fieldError.getRejectedValue();
         String msg = "[" + fieldError.getDefaultMessage() + "]";
         log.error("{}: field=={}\t value=={}", msg, field, rejectedValue);
         return CommonResult.error(msg);
      }

      USERS.add(sysUser);
      return CommonResult.success("Successfully added");
   }
}

2.1. 5 start up test

When adding, the email information is intentionally filled in incorrectly, and the test results meet the expectations.

log:

[nio-8080-exec-9] c.l.valid.controller.SysUserController   : [Illegal email address]: field==email	value==123

3. Group verification

What are groups used for?

Because an entity cannot do only one operation,An entity must have operations of adding, deleting, modifying and querying,Then the problem comes
 If I want to id Update operation,that id It must not be empty
 At this time, I have to add,because id It is generated by adding a database,I certainly didn't accept the data id of
 So there is a contradiction
 that groups This parameter works,It can indicate which group my annotation belongs to,This will solve the embarrassing problem.

When verifying the form data in the controller, if groups is used, the properties without this group will not be verified

3.1 add grouping interface

/**
 * General grouping interface for form validation
 * @author ludangxin
 * @date 2021/8/5
 */
public interface ValidationInterface {
    /**
     * New grouping
     */
    interface add{}

    /**
     * Delete group
     */
    interface delete{}

    /**
     * Query grouping
     */
    interface select{}

    /**
     * Update group
     */
    interface update{}
}

If there are other special grouping requirements, you can directly create an interface in DO
Example: if there is another select operation that needs to verify username and password (only these two parameters)
Directly create the interface of UsernamePasswordValidView in SysUser

3.2 modify entity class

Group attributes

import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

/**
 * User information management
 * @author ludangxin
 * @date 2021/8/5
 */
@Data
public class SysUser implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * Primary key
     */
    @NotNull(message = "id Cannot be empty", groups = {ValidationInterface.update.class})
    private Long id;

    /**
     * user name
     */
    @NotEmpty(message = "User name cannot be empty", groups = {
              ValidationInterface.update.class, 
              ValidationInterface.add.class})
    private String username;

    /**
     * password
     */
    @Size(min = 6, max = 16, message = "Password length must be{min}-{max}between", groups = {
          ValidationInterface.update.class, 
          ValidationInterface.add.class})
    private String password = "123456";

    /**
     * e-mail address
     */
    @Email(message = "Illegal email address", groups = {
           ValidationInterface.update.class, 
           ValidationInterface.add.class, 
           ValidationInterface.select.class})
    @NotEmpty(message = "Mailbox cannot be empty", groups = ValidationInterface.add.class)
    private String email;

    /**
     * Telephone
     */
    @Size(min = 11, max = 11, message = "Illegal mobile phone number", groups = {
          ValidationInterface.update.class,
          ValidationInterface.add.class,
          ValidationInterface.select.class})
    @NotEmpty(message = "Mobile phone number cannot be empty",groups = {ValidationInterface.add.class})
    private String phone;
}

3.3 modifying the controller

Add an action method, and specify the grouping of validation on the method parameter

import com.ldx.valid.exception.BusinessException;
import com.ldx.valid.model.SysUser;
import com.ldx.valid.model.ValidationInterface;
import com.ldx.valid.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * User management controller
 * @author ludangxin
 * @date 2021/8/5
 */
@Slf4j
@RestController
@RequestMapping("sys/user")
public class SysUserController {
   private static final List<SysUser> USERS = new ArrayList<>();

   // Data initialization
   static {
      SysUser user = new SysUser();
      user.setId(1L);
      user.setUsername("zhangsan");
      user.setPhone("13566666666");
      user.setEmail("example@qq.com");
      USERS.add(user);
      SysUser user1 = new SysUser();
      user1.setId(2L);
      user1.setUsername("lisi");
      user1.setPhone("13588888888");
      user1.setEmail("example1@qq.com");
      USERS.add(user1);
   }

   /**
    * Query user information according to mobile phone number or email
    * @param sysUser query criteria
    * @return User list
    */
   @GetMapping
   public CommonResult queryList(@Validated(value = ValidationInterface.select.class) SysUser sysUser,
                                 BindingResult result)
   {
      FieldError fieldError = result.getFieldError();

      if(Objects.nonNull(fieldError)) {
         return CommonResult.error(getErrorMsg(fieldError));
      }

      String phone = sysUser.getPhone();
      String email = sysUser.getEmail();

      if(phone == null && email == null) {
         return CommonResult.success(USERS);
      }

      List<SysUser> queryResult = USERS.stream()
            .filter(obj -> obj.getPhone().equals(phone) || obj.getEmail().equals(email))
            .collect(Collectors.toList());
      return CommonResult.success(queryResult);
   }

   /**
    * Add user information
    * @param sysUser User information
    * @return Success identification
    */
   @PostMapping
   public CommonResult add(@Validated(value = ValidationInterface.add.class)
                           @RequestBody SysUser sysUser,
                           BindingResult result)
   {
      FieldError fieldError = result.getFieldError();

      if(Objects.nonNull(fieldError)) {
         return CommonResult.error(getErrorMsg(fieldError));
      }
     
      Long id = (long) (USERS.size() + 1);
      sysUser.setId(id);
      USERS.add(sysUser);
      return CommonResult.success("Successfully added");
   }

   /**
    * Update user information according to Id
    * @param sysUser User information
    * @return Success identification
    */
   @PutMapping("{id}")
   public CommonResult updateById(@PathVariable("id") Long id,
                                  @Validated(value = ValidationInterface.update.class)
                                  @RequestBody SysUser sysUser,
                                  BindingResult result)
   {
      FieldError fieldError = result.getFieldError();

      if(Objects.nonNull(fieldError)) {
         return CommonResult.error(getErrorMsg(fieldError));
      }

      for(int i = 0; i < USERS.size(); i++) {
         if(USERS.get(i).getId().equals(id)) {
            USERS.set(i,sysUser);
         }
      }
      return CommonResult.success("Update succeeded");
   }

   /**
    * Delete user information according to Id
    * @param id Primary key
    * @return Success identification
    */
   @DeleteMapping("{id}")
   public CommonResult deleteById(@PathVariable Long id) {
      USERS.removeIf(obj -> obj.getId().equals(id));
      return CommonResult.success("Delete succeeded");
   }

   /**
    * Get form validation error msg
    * @param fieldError Error reporting field
    * @return msg
    */
   public String getErrorMsg(FieldError fieldError) {
      String field = fieldError.getField();
      Object rejectedValue = fieldError.getRejectedValue();
      String msg = "[" + fieldError.getDefaultMessage() + "]";
      log.error("{}: field=={}\t value=={}", msg, field, rejectedValue);
      return msg;
   }
}

3.4 start up test

Query:

Enter illegal mobile phone number

newly added:

Normal condition

Remove mailbox

Modification:

Remove id

Delete:

4. Custom validation

Many times, the functions provided by the framework can not meet our business scenarios. At this time, we need to customize some validation rules to complete the validation.

4.1 adding notes

import com.ldx.valid.validation.PhoneValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Verify whether the mobile phone number is legal
 * @author ludangxin
 * @date 2021/8/7
 */
@Documented
@Constraint(validatedBy = {PhoneValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface Phone {
   //Default error message
   String message() default "Not a legal cell phone number";

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

   //Payload a method that associates certain metadata information with a given annotation declaration
   Class<? extends Payload>[] payload() default {};

   //Use when specifying multiple
   @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
   @Retention(RUNTIME)
   @Documented
   @interface List {
      Phone[] value();
   }
}

4.2 writing verification logic

import javax.validation.ConstraintValidator;
import com.ldx.valid.annotation.Phone;
import javax.validation.ConstraintValidatorContext;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * Mobile phone number verifier
 * @author ludangxin
 * @date 2021/8/7
 */
public class PhoneValidator implements ConstraintValidator<Phone, String> {

   /**
    * Mobile number regular expression
    */
   private static final String REGEXP_PHONE = "^1[3456789]\\d{9}$";

   @Override
   public boolean isValid(String value, ConstraintValidatorContext context) {
      if(Objects.isNull(value)) {
         return true;
      }

      return Pattern.matches(REGEXP_PHONE, value);
   }
}

4.3 modifying entities

To sysuser Annotate @ Phone} attribute

@Phone(groups = {
                ValidationInterface.update.class,
                ValidationInterface.add.class,
                ValidationInterface.select.class})
@NotEmpty(message = "Mobile phone number cannot be empty", groups = {ValidationInterface.add.class})
private String phone;

4.4 start up test

Enter the wrong phone number to test

4.5 @Pattern

Of course, validation also provides the annotation @ Pattern based on regular matching

@Pattern(message = "Illegal mobile phone number", regexp = "^1[3456789]\\d{9}$", groups = {ValidationInterface.add.class})
@NotEmpty(message = "Mobile phone number cannot be empty", groups = {ValidationInterface.add.class})
private String phone;

Note that javax validation. Under constraints package

test

5. Call process validation

Sometimes we need to verify the parameters of the incoming object during parameter transmission, but the above description is to verify the parameters when binding. Can we use validation for verification?

The answer must be yes.

5.1 using spring bean s

5.1. 1 inject validator

Bean validator is the bean defined in the config file. If the default configuration ValidationAutoConfiguration::defaultValidator() of springboot is used, just inject bean name defaultValidator directly

@Resource(name = "validator")
javax.validation.Validator validator;

5.1. 2 define verification method

public void validateParams(SysUser user) {
   validator.validate(user, ValidationInterface.add.class).stream().findFirst().ifPresent(obj -> {
      String objName = obj.getRootBean().getClass().getSimpleName();
      String fieldName = obj.getPropertyPath().toString();
      Object val = obj.getInvalidValue();
      String msg = obj.getMessage();
      String errMsg = MessageFormat.format(msg + ": Object:{0},Field:{1},Value:{2}", objName, fieldName, val);
      throw new RuntimeException(errMsg);
   });

5.1. 2 start verification

Call the new method, and call the validateParams method through the new method

The error log is as follows

java.lang.RuntimeException: Illegal mobile phone number: object: SysUser,Field: phone,Value: 135999

5.2 non spring environment verification

5.2. 1. Define verification method

Get the default factory class directly, and then get the validation object for validation

public static void main(String[] args) {
   ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
   Validator validator = vf.getValidator();
   SysUser user = new SysUser();
   user.setId(1L);
   user.setUsername("zhangsan");
   user.setPhone("1356666");
   user.setEmail("example@qq.com");
   validator.validate(user, ValidationInterface.add.class).stream().findFirst().ifPresent(obj -> {
      String objName = obj.getRootBean().getClass().getSimpleName();
      String fieldName = obj.getPropertyPath().toString();
      Object val = obj.getInvalidValue();
      String msg = obj.getMessage();
      String errMsg = MessageFormat.format(msg + ": Object:{0},Field:{1},Value:{2}", objName, fieldName, val);
      throw new RuntimeException(errMsg);
   });
}

5.2. 2 start verification

The error information is as follows, which is in line with expectations

Exception in thread "main" java.lang.RuntimeException: Illegal mobile phone number: object: SysUser,Field: phone,Value: 1356666
	at com.ldx.valid.controller.SysUserController.lambda$main$4(SysUserController.java:215)
	at java.util.Optional.ifPresent(Optional.java:159)
	at com.ldx.valid.controller.SysUserController.main(SysUserController.java:209)

6. Method parameter verification

Sometimes we want to directly verify the parameters on the method. The steps are as follows

6.1 modifying the controller

Add the annotation @ Validated directly on the class and verify it directly on the method

@Slf4j
@Validated
@RestController
@RequestMapping("sys/user")
public class SysUserController {
  ... Omit code
    /**
     * Query user information according to mobile phone number and email address
     * @param phone cell-phone number
     * @return User list
     */
    @GetMapping("selectByPhone")
    public CommonResult queryByPhone(@NotEmpty(message = "Mobile phone number cannot be empty") String phone) {
       List<SysUser> queryResult = USERS.stream()
             .filter(obj -> obj.getPhone().equals(phone))
             .collect(Collectors.toList());
       return CommonResult.success(queryResult);
    }
}

6.2 start up verification

The phone field is not assigned a value, and the operation result is as expected

Error log:

javax.validation.ConstraintViolationException: queryByPhone.phone: Mobile phone number cannot be empty

7. Unified exception handling

In the above parameter verification, the error information is received through the BindingResult result parameter. Exception handling is the same in each method, which is particularly troublesome. Even in step 5 and 6, the stack information of the exception is directly returned to the front end, which is very unfriendly to the user. In addition, in some cases, we need to actively throw business exceptions. For example, users cannot directly delete the roles of bound users.

So, drive.

7.1 create business exception class

/**
 * Business exception
 * @author ludangxin
 * @date 2021/8/5
 */
public class BusinessException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    protected final String message;

    public BusinessException(String message) {
        this.message = message;
    }

    public BusinessException(String message, Throwable e) {
        super(message, e);
        this.message = message;
    }

    @Override
    public String getMessage() {
        return message;
    }
}

7.2 creating a global exception handler

import com.ldx.valid.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.Set;

/**
 * Global exception handler
 *
 * @author ludangxin
 * @date 2021/8/5
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * The parameter binding exception class is used to handle exceptions thrown during form validation
     */
    @ExceptionHandler(BindException.class)
    public CommonResult validatedBindException(BindException e){
        log.error(e.getMessage(), e);
        BindingResult bindingResult = e.getBindingResult();
        FieldError fieldError = e.getFieldError();
        String message = "[" + e.getAllErrors().get(0).getDefaultMessage() + "]";
        return CommonResult.error(message);
    }

    /**
     * Used for exception handling thrown during parameter verification in method parameters
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public CommonResult handle(ValidationException e) {
        log.error(e.getMessage(), e);
        String errorInfo = "";
        if(e instanceof ConstraintViolationException){
            ConstraintViolationException exs = (ConstraintViolationException) e;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                errorInfo = errorInfo + "[" + item.getMessage() + "]";
            }
        }
        return CommonResult.error(errorInfo);
    }

    /**
     * The request method is not supported
     */
    @ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
    public CommonResult handleException(HttpRequestMethodNotSupportedException e){
        log.error(e.getMessage(), e);
        return CommonResult.error("I won't support it' " + e.getMethod() + "'request");
    }

    /**
     * Intercept unknown runtime exceptions
     */
    @ExceptionHandler(RuntimeException.class)
    public CommonResult notFount(RuntimeException e) {
        log.error("Runtime exception:", e);
        return CommonResult.error("Runtime exception:" + e.getMessage());
    }

    /**
     * System exception
     */
    @ExceptionHandler(Exception.class)
    public CommonResult handleException(Exception e) {
        log.error(e.getMessage(), e);
        return CommonResult.error("Server error, please contact the administrator");
    }

    /**
     * Business exception
     */
    @ExceptionHandler(BusinessException.class)
    public CommonResult businessException(HttpServletRequest request, BusinessException e) {
        log.error(e.getMessage());
        return CommonResult.error(e.getMessage());
    }
}

7.3 modifying the controller

Delete the bindingresult parameter in the method and throw the error directly to the unified exception handling class for resolution.

import com.ldx.valid.exception.BusinessException;
import com.ldx.valid.model.SysUser;
import com.ldx.valid.model.ValidationInterface;
import com.ldx.valid.service.UserService;
import com.ldx.valid.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.*;
import javax.validation.constraints.NotEmpty;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * User management controller
 * @author ludangxin
 * @date 2021/8/5
 */
@Slf4j
@Validated
@RestController
@RequestMapping("sys/user")
public class SysUserController {
   private static final List<SysUser> USERS = new ArrayList<>();

   // Data initialization
   static {
      SysUser user = new SysUser();
      user.setId(1L);
      user.setUsername("zhangsan");
      user.setPhone("13566666666");
      user.setEmail("example@qq.com");
      USERS.add(user);
      SysUser user1 = new SysUser();
      user1.setId(2L);
      user1.setUsername("lisi");
      user1.setPhone("13588888888");
      user1.setEmail("example1@qq.com");
      USERS.add(user1);
   }

   /**
    * Query user information according to mobile phone number or email
    * @param sysUser query criteria
    * @return User list
    */
   @GetMapping
   public CommonResult queryList(@Validated(value = ValidationInterface.select.class) SysUser sysUser) {
      String phone = sysUser.getPhone();
      String email = sysUser.getEmail();

      if(phone == null && email == null) {
         return CommonResult.success(USERS);
      }

      List<SysUser> queryResult = USERS.stream()
            .filter(obj -> obj.getPhone().equals(phone) || obj.getEmail().equals(email))
            .collect(Collectors.toList());
      return CommonResult.success(queryResult);
   }

   /**
    * Query user information according to mobile phone number and email address
    * @param phone cell-phone number
    * @return User list
    */
   @GetMapping("selectByPhone")
   public CommonResult queryByPhone(@NotEmpty(message = "Mobile phone number cannot be empty") String phone) {
      List<SysUser> queryResult = USERS.stream()
            .filter(obj -> obj.getPhone().equals(phone))
            .collect(Collectors.toList());
      return CommonResult.success(queryResult);
   }

   /**
    * Add user information
    * @param sysUser User information
    * @return Success identification
    */
   @PostMapping
   public CommonResult add(@Validated(value = ValidationInterface.add.class) @RequestBody SysUser sysUser) {
      Long id = (long) (USERS.size() + 1);
      sysUser.setId(id);
      USERS.add(sysUser);
      return CommonResult.success("Successfully added");
   }

   /**
    * Update user information according to Id
    * @param sysUser User information
    * @return Success identification
    */
   @PutMapping("{id}")
   public CommonResult updateById(@PathVariable("id") Long id,
                                  @Validated(value = ValidationInterface.update.class)
                                  @RequestBody SysUser sysUser)
   {
      for(int i = 0; i < USERS.size(); i++) {
         if(USERS.get(i).getId().equals(id)) {
            USERS.set(i,sysUser);
         }
      }
      return CommonResult.success("Update succeeded");
   }

   /**
    * Delete user information according to Id
    * @param id Primary key
    * @return Success identification
    */
   @DeleteMapping("{id}")
   public CommonResult deleteById(@PathVariable Long id) {
      USERS.removeIf(obj -> obj.getId().equals(id));
      return CommonResult.success("Delete succeeded");
   }

   /**
    * Test service exception
    */
   @GetMapping("testException")
   public CommonResult testException(String name) {
      if(!"Zhang San".equals(name)){
         throw new BusinessException("Only Zhang San can access it");
      }
      return CommonResult.success();
   }
}

7.4 start up test

Query:

Output wrong mailbox

Query according to mobile phone number:

Enter a null phone number

newly added:

Enter the wrong phone number

The test actively throws business exceptions:

8. User defined authentication information source

8.1 modifying configuration files

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validator;
import java.util.Properties;

/**
 * Configure Hibernate parameter verification
 * @author ludangxin
 * @date 2021/8/5
 */
@Configuration
public class ValidatorConfig {
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
        MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
        postProcessor.setValidator(validator);
        return postProcessor;
    }

    /**
     * Import entity class field verification internationalization
     */
    @Bean
    public Validator validator() {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        // Set messages resource information
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        // Multiple are separated by commas
        messageSource.setBasenames("classpath:/messages/validation/messages");
        // Set character set encoding
        messageSource.setDefaultEncoding("UTF-8");
        validator.setValidationMessageSource(messageSource);
        // Set validation related parameters
        Properties properties = new Properties();
        // Quick failure, return as soon as there is an error
        properties.setProperty("hibernate.validator.fail_fast", "true");
        validator.setValidationProperties(properties);
        return validator;
    }
}

8.2 adding information source files

├───resources
    └── messages
        └── validation
            └── messages.properties
# messages.properties
name.not.empty=User name cannot be empty
email.not.valid=${validatedValue}Is it an email address?
email.not.empty=Mailbox cannot be empty
phone.not.valid=${validatedValue}Is it a cell phone number?
phone.not.empty=Mobile phone number cannot be empty
password.size.valid=Password length must be{min}-{max}between
id.not.empty=Primary key cannot be empty

8.3 modifying entity classes

import com.ldx.valid.annotation.Phone;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.*;
import java.io.Serializable;

/**
 * User information management
 * @author ludangxin
 * @date 2021/8/5
 */
@Data
public class SysUser implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * Primary key
     */
    @NotNull(message = "{id.not.empty}", groups = {ValidationInterface.update.class})
    private Long id;

    /**
     * user name
     */
    @NotEmpty(message = "{name.not.empty}", groups = {
              ValidationInterface.update.class,
              ValidationInterface.add.class})
    private String username;

    /**
     * password
     */
    @Size(min = 6, max = 16, message = "{password.size.valid}", groups = {
          ValidationInterface.update.class,
          ValidationInterface.add.class})
    private String password = "123456";

    /**
     * e-mail address
     */
    @Email(message = "{email.not.valid}",
           groups = {
           ValidationInterface.update.class,
           ValidationInterface.add.class,
           ValidationInterface.select.class})
    @NotEmpty(message = "{email.not.empty}", groups = ValidationInterface.add.class)
    private String email;

    /**
     * Telephone
     */
    @Pattern(message = "{phone.not.valid}", regexp = "^1[3456789]\\d{9}$", 
             groups = {ValidationInterface.add.class})
    @NotEmpty(message = "{phone.not.empty}", groups = {ValidationInterface.add.class})
    private String phone;
}

8.4 start up test

Enter wrong email address test:

9. Preset annotation list

annotation

explain

@Null

Restriction can only be null

@NotNull

Restriction must not be null

@AssertFalse

Restriction must be false

@AssertTrue

Restriction must be true

@DecimalMax(value)

The limit must be a number no greater than the specified value

@DecimalMin(value)

The limit must be a number not less than the specified value

@Digits(integer,fraction)

The limit must be one decimal, and the number of digits in the integer part cannot exceed integer, and the number of digits in the decimal part cannot exceed fraction

@Future

Limit must be a future date

@Max(value)

The limit must be a number no greater than the specified value

@Min(value)

The limit must be a number not less than the specified value

@Past

Limit must be a past date

@Pattern(value)

The restriction must conform to the specified regular expression

@Size(max,min)

The limit character length must be between min and max

@Past

Verify that the element value (date type) of the annotation is earlier than the current time

@NotEmpty

The element value of the validation annotation is not null and empty (string length is not 0, collection size is not 0)

@NotBlank

The element value of the validation annotation is not empty (not null, and the length is 0 after removing the first space), which is different from @ NotEmpty and @ NotBlank

@Email

Verify that the element value of the annotation is email. You can also specify a custom email format through regular expression and flag

Original link:
https://www.cnblogs.com/ludangxin/p/15113954.html

Author: Zhang Tieniu

If you think this article is helpful to you, you can forward it for attention and support

Topics: Java Spring Spring Boot