Pinda general permission system - medium

6. pd-tools-dozer

The PD tools dozer module is positioned as object conversion. Its essence is a Spring Boot starter. Other modules can directly import this module to complete object conversion.

6.1 dozer introduction

dozer is a java bean to Java Bean mapper that recursively copies data from one object to another. dozer is a tool for attribute conversion between two objects. With this tool, we don't need to write repeated calls to set and get methods when we transfer all attribute values of one object to another object. dozer is actually an encapsulation of what we know as bean utils.

maven coordinates of dozer:

<dependency>
    <groupId>com.github.dozermapper</groupId>
    <artifactId>dozer-core</artifactId>
    <version>6.5.0</version>
</dependency>

In order to simplify the usage, dozer also provides a starter whose maven coordinates are:

<dependency>
    <groupId>com.github.dozermapper</groupId>
    <artifactId>dozer-spring-boot-starter</artifactId>
    <version>6.5.0</version>
</dependency>

6.2 dozer introduction case

Step 1: create a maven project dozer_demo and configure pom.xml file

<?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 http://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.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>cn.itcast</groupId>
    <artifactId>dozer_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>com.github.dozermapper</groupId>
            <artifactId>dozer-spring-boot-starter</artifactId>
            <version>6.5.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

Step 2: create UserDTO and UserEntity

package com.itheima.dto;
import lombok.Data;
@Data
public class UserDTO {
    private String userId;
    private String userName;
    private int userAge;
    private String address;
    private String birthday;
}
package com.itheima.entity;
import lombok.Data;
import java.util.Date;
@Data
public class UserEntity {
    private String id;
    private String name;
    private int age;
    private String address;
    private Date birthday;
}

Step 3: create the global configuration file global.dozer.xml of dozer in the resources/dozer / directory

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns="http://dozermapper.github.io/schema/bean-mapping"
          xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping 
                              http://dozermapper.github.io/schema/bean-mapping.xsd">
    <!--
    Global configuration:
    <date-format>Represents the date format
     -->
    <configuration>
        <date-format>yyyy-MM-dd</date-format>
    </configuration>
</mappings>

Note: the global profile name can be arbitrary

Step 4: create the dozer mapping file biz.dozer.xml in the resources/dozer / directory

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns="http://dozermapper.github.io/schema/bean-mapping"
          xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping
                             http://dozermapper.github.io/schema/bean-mapping.xsd">
    <!--Describes the correspondence between attributes in two classes. Attributes with the same name in two classes may not be mapped-->
    <mapping date-format="yyyy-MM-dd">
        <class-a>com.itheima.entity.UserEntity</class-a>
        <class-b>com.itheima.dto.UserDTO</class-b>
        <field>
            <a>id</a>
            <b>userId</b>
        </field>
        <field>
            <a>name</a>
            <b>userName</b>
        </field>
        <field>
            <a>age</a>
            <b>userAge</b>
        </field>
    </mapping>
	<!--
	have access to map-id Specifies the ID of the mapping, which is used to determine the current mapping relationship in the program
	-->
    <mapping date-format="yyyy-MM-dd" map-id="user">
        <class-a>com.itheima.entity.UserEntity</class-a>
        <class-b>com.itheima.dto.UserDTO</class-b>
        <field>
            <a>id</a>
            <b>userId</b>
        </field>
        <field>
            <a>name</a>
            <b>userName</b>
        </field>
        <field>
            <a>age</a>
            <b>userAge</b>
        </field>
    </mapping>
</mappings>

Note: the name of the mapping file can be arbitrary

Step 5: write the application.yml file

dozer:
  mappingFiles:
    - classpath:dozer/global.dozer.xml
    - classpath:dozer/biz.dozer.xml

Step 6: write the startup class DozerApp

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DozerApp {
    public static void main(String[] args) {
        SpringApplication.run(DozerApp.class,args);
    }
}

Step 7: write unit test DozerTest

package cn.itcast.test;

import com.github.dozermapper.core.DozerBeanMapper;
import com.github.dozermapper.core.DozerBeanMapperBuilder;
import com.github.dozermapper.core.Mapper;
import com.github.dozermapper.core.metadata.MappingMetadata;
import com.itheima.DozerApp;
import com.itheima.dto.UserDTO;
import com.itheima.entity.UserEntity;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DozerApp.class)
public class DozerTest {
    @Autowired
    private Mapper mapper;
    @Test
    public void testDozer1(){
        UserDTO userDTO = new UserDTO();
        userDTO.setUserId("100");
        userDTO.setUserName("itcast");
        userDTO.setUserAge(20);
        userDTO.setAddress("bj");
        userDTO.setBirthday("2010-11-20");

        UserEntity user = mapper.map(userDTO, UserEntity.class);
        System.out.println(user);
    }

    @Test
    public void testDozer2(){
        UserDTO userDTO = new UserDTO();
        userDTO.setUserId("100");
        userDTO.setUserName("itcast");
        userDTO.setUserAge(20);
        userDTO.setAddress("bj");
        userDTO.setBirthday("2010-11-20");

        UserEntity user = new UserEntity();
        user.setId("200");
        System.out.println(user);
        mapper.map(userDTO,user);
        System.out.println(user);
    }

    @Test
    public void testDozer3(){
        UserDTO userDTO = new UserDTO();
        userDTO.setUserId("100");
        userDTO.setUserName("itcast");
        userDTO.setUserAge(20);
        userDTO.setAddress("bj");

        UserEntity user = new UserEntity();
        System.out.println(user);
        mapper.map(userDTO,user,"user");
        System.out.println(user);
    }
}

6.3 use of PD tools dozer

In the PD tools dozer module, in order to further simplify the operation, a tool class DozerUtils is encapsulated, which internally uses the operation of Mapper object. The / resources/META-INF/spring.factories file is prepared according to the specification of Spring Boot starter. The contents are as follows:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.itheima.pinda.dozer.DozerAutoConfiguration

Complete the creation of DozerUtils object in the configuration class dozeutoconfiguration, so that if other programs need to use dozer for object conversion, they only need to introduce the maven coordinate of this module and provide the corresponding mapping file to directly inject DozerUtils object into the program for operation.

Specific use process:

Step 1: create maven project myDozerApp and configure pom.xml

<?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 
                             http://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.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>myDozerApp</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!--Introduce our own definition dozer Basic module-->
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>pd-tools-dozer</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

Step 2: create UserEntity and UserDTO

package com.itheima.entity;

import lombok.Data;

@Data
public class UserEntity {
    private Integer id;
    private String name;
    private int age;
}
package com.itheima.dto;

import lombok.Data;

@Data
public class UserDTO {
    private Integer id;
    private String name;
    private int age;
}

Step 3: create UserController

package com.itheima.controller;

import com.itheima.dto.UserDTO;
import com.itheima.entity.UserEntity;
import com.itheima.pinda.dozer.DozerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private DozerUtils dozerUtils; //Automatic configuration has been completed in PD tools dozer and can be injected directly

    @GetMapping("/mapper")
    public UserEntity mapper(){
        UserDTO userDTO = new UserDTO();
        userDTO.setId(10);
        userDTO.setName("itcast");
        userDTO.setAge(20);

        UserEntity userEntity = dozerUtils.map(userDTO, UserEntity.class);
        return userEntity;
    }
}

Step 4: create application.yml

server:
  port: 8080

Step 5: create a startup class

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyDozerApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyDozerApplication.class,args);
    }
}

Start the project at: http://localhost:8080/user/mapper

Note: since the attributes in UserEntity and UserDTO created by us are completely consistent, no mapping file is provided. If the attributes in these two classes are inconsistent, a mapping file needs to be created for mapping, and the location of the mapping file needs to be configured in application.yml, for example:

dozer:
  mappingFiles:
    - classpath:dozer/biz.dozer.xml  #Specifies the location of the dozer mapping file

7. pd-tools-validator

The PD tools validator module is positioned as the back-end form data verification. Other modules can directly introduce the maven coordinates of PD tools validator to use the form verification function provided by PD tools validator. The underlying layer of PD tools validator is implemented based on Hibernate validator.

7.1 introduction to hibernate validator

In early websites, users entered an email address and needed to send the email address to the server. The server verified it. After the verification was successful, a response was sent to the front end.

With JavaScript, verification can be performed on the front end. So why do you need server-side verification? Because the data from the front end is not trusted. The front end can easily obtain the back-end interface. If someone calls the interface directly, illegal data may appear, so the server also needs data verification.

in general:

  • [] front end verification: mainly to improve the user experience
  • [] back end verification: mainly to ensure data security and reliability

Checking parameters is basically a manual work, and there are many redundant codes, which also affects the readability of the code. We need a more elegant way to solve this problem. Hibernate Validator framework just solves this problem. It can implement parameter verification in a very elegant way, separate business code from verification logic, and no longer write duplicate verification logic.

Hibernate validator advantages:

  • [] the verification logic is separated from the business logic to reduce the program coupling
  • [] unified and standardized verification method, without you writing repeated verification code again
  • [] you will focus more on your business and put all these cumbersome things aside

maven coordinates of Hibernate validator:

<dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.18.Final</version>
</dependency>

7.2 common notes of Hibernate validator

The verification method provided by hibernate validator is to add corresponding annotations to the class properties to achieve the purpose of verification. The comments provided by hibernate validator for verification are as follows:

annotation explain
@AssertTrue Used for boolean field, which can only be true
@AssertFalse Used for boolean field, which can only be false
@CreditCardNumber Make a general verification of the credit card number
@DecimalMax Can only be less than or equal to this value
@DecimalMin Can only be greater than or equal to this value
@Email Check whether it is a valid email address
@Future Check whether the date in this field belongs to the future
@Length(min=,max=) Check whether the length of the field is between min and max, which can only be used for string
@Max The value of this field can only be less than or equal to this value
@Min The value of this field can only be greater than or equal to this value
@NotNull Cannot be null
@NotBlank Cannot be empty. Spaces will be ignored during inspection
@NotEmpty Cannot be null. Null here refers to an empty string
@Pattern(regex=) The annotated element must conform to the specified regular expression
@URL(protocol=,host,port) Check whether it is a valid URL. If protocol, host, etc. are provided, the URL also needs to meet the provided conditions

7.3 introduction to hibernate validator

Step 1: create maven project hibernate validator_ Demo and configure pom.xml file

<?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 
                             http://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.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>cn.itcast</groupId>
    <artifactId>hibernate-validator_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!--
		spring-boot-starter-web Has been dependent on hibernate-validator
		-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--
		<dependency>
              <groupId>org.hibernate</groupId>
              <artifactId>hibernate-validator</artifactId>
              <version>6.0.18.Final</version>
        </dependency>
		-->
    </dependencies>
</project>

Note: spring boot starter web already relies on Hibernate validator, so you don't need to import it again.

Step 2: create User class

package cn.itcast.entity;

import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;

@Data
public class User {
    @NotNull(message = "user id Cannot be empty")
    private Integer id;

    @NotEmpty(message = "User name cannot be empty")
    @Length(max = 50, message = "User name length cannot exceed 50")
    private String username;

    @Max(value = 80,message = "The maximum age is 80")
    @Min(value = 18,message = "The minimum age is 18")
    private int age;

    @Pattern(regexp = "[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$",
             message = "The mailbox format is incorrect")
    private String email;
}

Step 3: create UserController

package cn.itcast.controller;

import cn.itcast.entity.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotBlank;

@RestController
@RequestMapping("/user")
@Validated //Turn on the verification function
public class UserController {
    //Simple data type verification
    @RequestMapping("/delete")
    public String delete(@NotBlank(message = "id Cannot be empty") String id){
        System.out.println("delete..." + id);
        return "OK";
    }

    //Object attribute verification
    @RequestMapping("/save")
    public String save(@Validated User user){
        System.out.println("save..." + user);
        return "OK";
    }
}

Step 4: create application.yml

server:
  port: 9100

Step 5: create a startup class

package cn.itcast;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HibernateValidatorApp {
    public static void main(String[] args) {
        SpringApplication.run(HibernateValidatorApp.class,args);
    }
}

Start the project at: http://localhost:9100/user/save , it can be seen from the console output that the data can be verified as follows:

Field error in object 'user' on field 'age': rejected value [3]; codes [Min.user.age,Min.age,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age],18]; default message [The minimum age is 18]]
2020-03-02 16:44:35.504  WARN 14896 --- [nio-9100-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors
Field error in object 'user' on field 'id': rejected value [null]; codes [NotNull.user.id,NotNull.id,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.id,id]; arguments []; default message [id]]; default message [user id Cannot be empty]
Field error in object 'user' on field 'age': rejected value [0]; codes [Min.user.age,Min.age,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age],18]; default message [The minimum age is 18]
Field error in object 'user' on field 'username': rejected value [null]; codes [NotEmpty.user.username,NotEmpty.username,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.username,username]; arguments []; default message [username]]; default message [User name cannot be empty]]

The browser page directly reports an error:

Step 6: in order to display the data verification results friendly on the page, you can solve it through global exception handling and create a global exception handling class

package cn.itcast.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;

/**
 * Global exception handling
 */
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
public class ExceptionConfiguration {
    @ExceptionHandler({ConstraintViolationException.class,BindException.class})
    public String validateException(Exception ex, HttpServletRequest request) {
        ex.printStackTrace();
        String msg = null;
        if(ex instanceof ConstraintViolationException){
            ConstraintViolationException constraintViolationException = 
                (ConstraintViolationException)ex;
            Set<ConstraintViolation<?>> violations = 
                constraintViolationException.getConstraintViolations();
            ConstraintViolation<?> next = violations.iterator().next();
            msg = next.getMessage();
        }else if(ex instanceof BindException){
            BindException bindException = (BindException)ex;
            msg = bindException.getBindingResult().getFieldError().getDefaultMessage();
        }
        return msg;
    }
}

Restart the program and you can find that the verification information can be displayed on the page friendly:

From the output of the console, we can see that the verification framework performs data verification on multiple attributes (the default behavior). If we want to directly return a prompt message as long as one attribute fails to be verified, how can we implement it?

Step 7: create the ValidatorConfiguration class and specify the fast failure return mode to be used during verification

package cn.itcast.config;

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

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

    /**
     * Turn on quick return
     * If there is an exception in the parameter verification, throw the exception directly and will not enter the controller. Use global exception interception to intercept
     */
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor postProcessor = 
            new MethodValidationPostProcessor();
        /**Set the validator mode to fast failure return*/
        postProcessor.setValidator(validator());
        return postProcessor;
    }
}

Note: the class created above is not a configuration class, so the quick failure return mode will not take effect so far. In order to make it take effect, you need to create an annotation to control the opening of this mode

Step 8: create the annotation EnableFormValidator to control the startup of the quick failure return mode

package cn.itcast.config;

import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Add this annotation on the startup class to start the form verification function - fast failure return mode
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ValidatorConfiguration.class)
public @interface EnableFormValidator {
}

Step 9: add the EnableFormValidator annotation on the startup class to enable the quick failure return mode

package cn.itcast;

import cn.itcast.config.EnableFormValidator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableFormValidator
public class HibernateValidatorApp {
    public static void main(String[] args) {
        SpringApplication.run(HibernateValidatorApp.class,args);
    }
}

It can be seen from the console output that although many of the data we entered do not comply with the verification rules, there is only one verification failure exception message, which indicates that the fast failure return mode has been enabled.

7.4 use of PD tools validator

The implementation of PD tools validator is consistent with the above entry case, but does not provide global exception handling. This is because different systems may handle verification results differently, so each system needs to handle them individually, while PD tools validator only provides data verification function.

Specific use process:

Step 1: create maven project and configure pom.xml file

<?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 
                             http://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.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>myHibernateValidatorApp</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>pd-tools-validator</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

Step 2: create User class

package cn.itcast.entity;

import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;

@Data
public class User {
    @NotNull(message = "user id Cannot be empty")
    private Integer id;

    @NotEmpty(message = "User name cannot be empty")
    @Length(max = 50, message = "User name length cannot exceed 50")
    private String username;

    @Max(value = 80,message = "The maximum age is 80")
    @Min(value = 18,message = "The minimum age is 18")
    private int age;

    @Pattern(regexp = "[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$",
             message = "The mailbox format is incorrect")
    private String email;
}

Step 3: create UserController

package cn.itcast.controller;

import cn.itcast.entity.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotBlank;

@RestController
@RequestMapping("/user")
@Validated //Turn on the verification function
public class UserController {
    //Simple data type verification
    @RequestMapping("/delete")
    public String delete(@NotBlank(message = "id Cannot be empty") String id){
        System.out.println("delete..." + id);
        return "OK";
    }

    //Object attribute verification
    @RequestMapping("/save")
    public String save(@Validated User user){
        System.out.println("save..." + user);
        return "OK";
    }
}

Step 4: create application.yml

server:
  port: 9000

Step 5: create a startup class

package cn.itcast;

import com.itheima.pinda.validator.config.EnableFormValidator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableFormValidator
public class MyHibernateValidatorApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyHibernateValidatorApplication.class,args);
    }
}

Start the project at: http://localhost:9000/user/save , you can see the console output:

2020-03-11 16:40:30.288  WARN 17428 --- [nio-9000-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'user' on field 'age': rejected value [0]; codes [Min.user.age,Min.age,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age],18]; default message [The minimum age is 18]]

Page effect:

This indicates that the input verification has started, and according to the console output, it can be seen that the fast failure return mode has been turned on.

In order to give a friendly prompt to the page, you can also add global exception handling.

8. pd-tools-xss

PD tools XSS module is positioned to prevent cross site scripting attack (XSS). By checking and cleaning the HTML / CSS / JavaScript and other contents input by users on the page, it ensures that the input contents comply with the application specifications and ensures the security of the system.

8.1 introduction to XSS

XSS: cross site scripting attack. In order not to confuse CSS, cross site scripting attack is abbreviated as XSS. XSS means that a malicious attacker inserts malicious Script code into a Web page. When a user browses the page, the Script code embedded in the Web will be executed, so as to achieve the purpose of malicious attack on the user. It is similar to sql injection.

XSS attack principle:

Html is a hypertext markup language that distinguishes text and markup by treating some characters specially. For example, the less than symbol (<) is regarded as the beginning of HTML tags,

The characters between and are the title of the page, etc. When the content inserted in the dynamic page contains these special characters, the user browser will mistakenly think that it is inserted with HTML tags. When these HTML tags introduce a JavaScript script, these script programs will be executed in the user browser. Therefore, when these special characters cannot be checked by dynamic pages or errors occur in the check, XSS vulnerabilities will be generated.

8.2 AntiSamy introduction

AntiSamy is an open source project of OWASP, which ensures that the input meets the application specifications by checking and cleaning the HTML / CSS / JavaScript and other contents input by users. AntiSamy is widely used in the defense of Web services against storage and reflective XSS.

maven coordinates of AntiSamy:

<dependency>
  <groupId>org.owasp.antisamy</groupId>
  <artifactId>antisamy</artifactId>
  <version>1.5.7</version>
</dependency>

8.3 introduction to antisamy

Step 1: create maven project antiSamy_demo and configure pom.xml file

<?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 
                             http://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.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>cn.itcast</groupId>
    <artifactId>antiSamy_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.owasp.antisamy</groupId>
            <artifactId>antisamy</artifactId>
            <version>1.5.7</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

Step 2: create application.yml

server:
  port: 9000

Step 3: create the policy file / resources/antisamy-test.xml. The contents of the file can be obtained from the jar package of antisamy

Note: AntiSamy's filtering of "malicious code" depends on the policy file. The policy file specifies the processing method of AntiSamy for each tag and attribute. The strict definition of the policy file determines the defense effect of AntiSamy against XSS vulnerabilities. The jar package of AntiSamy contains several common policy files

Step 4: create User entity class

package cn.itcast.entity;

import lombok.Data;

@Data
public class User {
    private int id;
    private String name;
    private int age;
}

Step 5: create UserController

package cn.itcast.controller;

import cn.itcast.entity.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/save")
    public String save(User user){
        System.out.println("UserController save.... " + user);
        return user.getName();
    }
}

Step 6: create / resources/static/index.html page

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form method="post" action="/user/save">
            id:<input type="text" name="id"><br>
            name:<input type="text" name="name"><br>
            age:<input type="text" name="age"><br>
            <input type="submit" value="submit">
        </form>
    </body>
</html>

Step 7: create a startup class

package cn.itcast;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AntiSamyApp {
    public static void main(String[] args) {
        SpringApplication.run(AntiSamyApp.class,args);
    }
}

At this time, we can start the project for access, but we have not filtered the parameters, so if we enter any parameters, they can be passed to the Controller normally, which is very unsafe in the actual project. In order to filter and clean the data we input, we need to implement it through filters.

Step 8: create a filter to filter all request parameters submitted to the server

package cn.itcast.filter;

import cn.itcast.wrapper.XssRequestWrapper;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/*
*Filter all request parameters submitted to the server
*/
public class XssFilter implements Filter{
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 
                         FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        //Incoming rewritten Request
        filterChain.doFilter(new XssRequestWrapper(request),servletResponse);
    }
}

Note: through the above filter, we can find that we have not directly filtered and cleaned the request parameters in the filter, but directly released them. How can we filter and clean the request parameters? In fact, the filtering and cleaning work is carried out in another class, XssRequestWrapper. When the above filter is released, you need to call the filterChain.doFilter() method, which needs to pass in the request object. At this time, we can wrap the current request object, and XssRequestWrapper is the wrapper class of the request object, When the filter is released, the getParameterValues method of the wrapper class will be called automatically. We can filter and clean the request parameters uniformly in the getParameterValues method of the wrapper class.

Step 9: create the XssRequestWrapper class

package cn.itcast.wrapper;

import org.owasp.validator.html.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class XssRequestWrapper extends HttpServletRequestWrapper {
    /*
     * The policy file needs to put the policy file to be used under the project resource file path
    */
    private static String antiSamyPath = XssRequestWrapper.class.getClassLoader()
            .getResource( "antisamy-test.xml").getFile();

    public static  Policy policy = null;
    static {
        // Specify policy file
        try {
            policy = Policy.getInstance(antiSamyPath);
        } catch (PolicyException e) {
            e.printStackTrace();
        }
    }

    /**
     * AntiSamy Filter data
     * @param taintedHTML Data to be filtered
     * @return Return filtered data
     * */
    private String xssClean( String taintedHTML){
        try{
            // Filtering with AntiSamy
            AntiSamy antiSamy = new AntiSamy();
            CleanResults cr = antiSamy.scan( taintedHTML, policy);
            taintedHTML = cr.getCleanHTML();
        }catch( ScanException e) {
            e.printStackTrace();
        }catch( PolicyException e) {
            e.printStackTrace();
        }
        return taintedHTML;
    }

    public XssRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String[] getParameterValues(String name){
        String[] values = super.getParameterValues(name);
        if ( values == null){
            return null;
        }
        int len = values.length;
        String[] newArray = new String[len];
        for (int j = 0; j < len; j++){
            System.out.println("Antisamy Filter cleaning, parameter values before cleaning:" + values[j]);
            // Filter cleaning
            newArray[j] = xssClean(values[j]);
            System.out.println("Antisamy Filter cleaning. Parameter values after cleaning:" + newArray[j]);
        }
        return newArray;
    }
}

Step 10: to make the filter defined above effective, you need to create a configuration class to initialize the filter object

package cn.itcast.config;

import cn.itcast.filter.XssFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AntiSamyConfiguration {
    /**
     * Configure cross site attack filters
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistration = 
            new FilterRegistrationBean(new XssFilter());
        filterRegistration.addUrlPatterns("/*");
        filterRegistration.setOrder(1);

        return filterRegistration;
    }
}

Start the project and enter illegal data on the page. You can see that the illegal data has been cleared.

Note: at present, we only process the request parameter filtering in the getParameterValues method of the wrapper class. In the real project, the data submitted by the user may be in the request header or json data submitted by the user. Therefore, if all conditions are considered, we can clean up in multiple methods in the wrapper class, as follows:

package cn.itcast.wrapper;

import org.owasp.validator.html.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Map;

public class XssRequestWrapper extends HttpServletRequestWrapper {
    /**
     * The policy file needs to put the policy file to be used under the project resource file path
     * */
    private static String antiSamyPath = XssRequestWrapper.class.getClassLoader()
            .getResource( "antisamy-ebay.xml").getFile();

    public static  Policy policy = null;
    static {
        // Specify policy file
        try {
            policy = Policy.getInstance(antiSamyPath);
        } catch (PolicyException e) {
            e.printStackTrace();
        }
    }

    /**
     * AntiSamy Filter data
     * @param taintedHTML Data to be filtered
     * @return Return filtered data
     * */
    private String xssClean( String taintedHTML){
        try{
            // Filtering with AntiSamy
            AntiSamy antiSamy = new AntiSamy();
            CleanResults cr = antiSamy.scan( taintedHTML, policy);
            taintedHTML = cr.getCleanHTML();
        }catch( ScanException e) {
            e.printStackTrace();
        }catch( PolicyException e) {
            e.printStackTrace();
        }
        return taintedHTML;
    }

    public XssRequestWrapper(HttpServletRequest request) {
        super(request);
    }
    @Override
    public String[] getParameterValues(String name){
        String[] values = super.getParameterValues(name);
        if ( values == null){
            return null;
        }
        int len = values.length;
        String[] newArray = new String[len];
        for (int j = 0; j < len; j++){
            // Filter cleaning
            newArray[j] = xssClean(values[j]);
        }
        return newArray;
    }

    @Override
    public String getParameter(String paramString) {
        String str = super.getParameter(paramString);
        if (str == null) {
            return null;
        }
        return xssClean(str);
    }


    @Override
    public String getHeader(String paramString) {
        String str = super.getHeader(paramString);
        if (str == null) {
            return null;
        }
        return xssClean(str);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> requestMap = super.getParameterMap();
        for (Map.Entry<String, String[]> me : requestMap.entrySet()) {
            String[] values = me.getValue();
            for (int i = 0; i < values.length; i++) {
                values[i] = xssClean(values[i]);
            }
        }
        return requestMap;
    }
}

8.4 use of PD tools XSS

The implementation of PD tools XSS is consistent with the above entry case. The bottom layer also checks and cleans the input parameters based on AntiSamy to ensure that the input meets the application specifications.

For ease of use, PD tools XSS has been defined as a starter. Other applications only need to import their maven coordinates and can be used without any additional configuration.

Specific use process:

Step 1: create maven project and configure pom.xml file

<?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 
                             http://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.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>myXssApp</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>pd-tools-xss</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

Step 2: create XSSController

package com.itheima.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/xss")
public class XSSController {
    @GetMapping("/get")
    public String get(String text){
        return "The text content after processing is:" + text;
    }
}

Step 3: create a startup class

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class XssApplication {
    public static void main(String[] args) {
        SpringApplication.run(XssApplication.class,args);
    }
}

Start the project and visit the following address:

As you can see, if you enter plain text, no processing will be done. If a special label is entered, it is cleared.

9. pd-tools-log

PD tools log module is positioned as a log module and is also a starter in essence. The log function provided mainly includes two aspects:

1. Log information can be recorded in the console or log file through the logback framework

2. Intercept user requests and save operation logs to the database

Technical points involved in PD tools log:
1. Facet Aspect, PointCut, Advice
2. Spring Event asynchronously listens for events
3. logback log component
4. Functional interface
5,ThreadLocal

9.1 logback

9.1.1 logback introduction

Logback inherits from log4j. The architecture of logback is very general and suitable for different use scenarios.

As can be seen from the figure above, logback and log4j are the specific implementations of slf4j specification. The APIs we call directly in the program are actually slf4j APIs, and the bottom layer is the real log implementation component - logback or log4j.

Logback is built on three main classes: Logger, Appender and Layout. These three different types of components work together to enable developers to print logs according to the type of messages and the level of logs.

As a log recorder, logger is mainly used to store log objects after it is associated with the corresponding context of the application. It can also define log type and level. Each logger is associated to a LoggerContext. LoggerContext is responsible for manufacturing loggers and arranging loggers in a tree structure.

Appender is mainly used to specify the destination of log output, which can be console, file, database, etc.

Layout is responsible for converting events into strings and outputting formatted log information.

maven coordinates of logback:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>

9.1.2 logback level

In logback, each logger is attached to the LoggerContext. It is responsible for generating loggers and managing them through a tree hierarchy.

A Logger is regarded as an entity. Its naming is case sensitive and follows the following rules:

If one logger Add one to your name.As another logger The prefix of the name, then logger It's another one logger Our ancestors. If one logger With another logger There is nothing else between us logger,Then logger It's another one logger The parent of the.

give an example:
be known as cn.itcast of logger It's called cn.itcast.service of logger Parent of
 be known as cn of logger It's called cn.itcast of logger The parent of is named cn.itcast.service of logger Ancestors of

There is a root logger in the logback, which is the highest level of the logger hierarchy. It is a special logger because it is a part of each hierarchy.

9.1.3 logback log output level

The log output levels of logback are: trace, debug, info, warn and error.

If a given logger does not specify a log output level, it will inherit the level of its nearest ancestor.

In order to ensure that all loggers have a log output level, root logger will have a default output level - DEBUG.

9.1.4 logback initialization steps

  1. Logback looks for a file named logback-test.xml in the classpath
  2. If not, logback continues to look for a file called logback.groovy
  3. If not, logback continues to look for a file called logback.xml
  4. If it is not found, the file META-INFO/services/ch.qos.logback.classic.spi.Configurator will be found in the class path. The content of this file is the fully qualified class name of the implementation class that implements the Configurator interface
  5. If none of the above is successful, logback will configure itself through the basic configurator, and all logs will be printed on the console

The purpose of the last step is to ensure that a default configuration is provided when all configuration files are not found.

9.1.5 introduction to logback

9.1.5.1 case I

This case is a simple application of logback, and the default configuration provided is used instead of the configuration file.

Step 1: create a maven project logback_demo and configure pom.xml file

<?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 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.itcast</groupId>
    <artifactId>logback_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>

Step 2: write unit tests

package cn.itcast.logback;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.util.StatusPrinter;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* logback usage method
*/
public class LogbackTest {
    //Simple use
    @Test
    public void test1(){
        Logger logger = LoggerFactory.getLogger("cn.itcast.logback.HelloWorld");
        logger.debug("debug ...");
    }

    //Print log internal status
    @Test
    public void test2(){
        Logger logger = LoggerFactory.getLogger("cn.itcast.logback.HelloWorld");
        logger.debug("debug ...");
        // Print internal status
        LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
        StatusPrinter.print(lc);
    }

    /*
    * Log output level: error > warn > info > debug > trace
    * */

    //Test the default log output level
    @Test
    public void test3(){
        Logger logger = LoggerFactory.getLogger("cn.itcast.logback.HelloWorld");
        logger.error("error ...");
        logger.warn("warn ...");
        logger.info("info ...");
        logger.debug("debug ...");
        //Because the default output level is debug, this log will not be output
        logger.trace("trace ...");
    }

    //Set log output level
    @Test
    public void test4(){
        ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("cn.itcast.logback.HelloWorld");
        logger.setLevel(Level.WARN);
        logger.error("error ...");
        logger.warn("warn ...");
        logger.info("info ...");
        logger.debug("debug ...");
        logger.trace("trace ...");
    }

    //Test Logger inheritance
    @Test
    public void test5(){
        ch.qos.logback.classic.Logger logger = 
            (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("cn.itcast");
        logger.setLevel(Level.INFO);
        logger.error("error ...");
        logger.warn("warn ...");
        logger.info("info ...");
        logger.debug("debug ...");
        logger.trace("trace ...");

        // "cn.itcast.logback" will inherit the valid level of "cn.itcast"
        Logger barLogger = LoggerFactory.getLogger("cn.itcast.logback");
        // This log will be printed because info > = info
        barLogger.info("Child information");
        // This log will not be printed because debug < info
        barLogger.debug("Sub level debugging information");
    }

    //Logger acquisition: all loggers obtained by the same name are the same instance
    @Test
    public void test6(){
        Logger logger1 = LoggerFactory.getLogger("cn.itcast");
        Logger logger2 = LoggerFactory.getLogger("cn.itcast");
        System.out.println(logger1 == logger2);
    }

    //Parameterized log
    @Test
    public void test7(){
        Logger logger = LoggerFactory.getLogger("cn.itcast");
        logger.debug("hello {}","world");
    }
}

9.1.5.2 case II

This case is an application in the Spring Boot project of logback.

Step 1: create maven project springboot_logback_demo and configure pom file

<?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 
                             http://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.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>cn.itcast</groupId>
    <artifactId>springboot_logback_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

Step 2: write logback configuration files logback-base.xml and logback-spring.xml under resources

logback-base.xml

<?xml version="1.0" encoding="UTF-8"?>
<included>
    <contextName>logback</contextName>
    <!-- 
		name The value of is the name of the variable, value The value defined by the variable
		After defining variables, you can make“ ${}"To use variables
	-->
    <property name="log.path" value="d:\\logs" />

    <!-- Color log -->
    <!-- Rendering classes dependent on color logs -->
    <conversionRule 
              conversionWord="clr" 
              converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule 
              conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <!-- Color log format -->
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    <!--Output to console-->
    <appender name="LOG_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- Set character set -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--output to a file-->
    <appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/logback.log</file>
        <!--Log file output format-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- Daily log archive path and format -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
    </appender>
</included>

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--Import other profiles-->
    <include resource="logback-base.xml" />
    <!--
    <logger>It is used to set the log printing level of a package or a specific class
    And specify<appender>. <logger>Only one name Properties,
    An optional level And an optional addtivity Properties.
    name:Used to specify that this logger A package or a specific class of constraints.
    level:Used to set the print level, regardless of case: TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF,
          If this property is not set, the current logger Will inherit the level of the superior.
    addtivity:Report to superior logger Transfer printing information. The default is true. 
     -->

    <!--development environment -->
    <springProfile name="dev">
        <logger name="cn.itcast.controller" additivity="false" level="debug">
            <appender-ref ref="LOG_CONSOLE"/>
        </logger>
    </springProfile>
    <!--production environment -->
    <springProfile name="pro">
        <logger name="cn.itcast.controller" additivity="false" level="info">
            <appender-ref ref="LOG_FILE"/>
        </logger>
    </springProfile>

    <!--
    root Node is a required node. It is used to specify the most basic log output level. There is only one node level attribute
    level:Set the print level regardless of case: TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF The default is DEBUG
    Can contain zero or more elements that identify this appender Will be added to this logger. 
    -->
    <root level="info">
        <appender-ref ref="LOG_CONSOLE" />
        <appender-ref ref="LOG_FILE" />
    </root>
</configuration>

Step 3: write application.yml

server:
  port: 9000
logging:
  #In the Spring Boot project, the logback-spring.xml file under the classpath is loaded by default
  config: classpath:logback-spring.xml
spring:
  profiles:
    active: dev

Step 4: create UserController

package cn.itcast.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    Logger logger = LoggerFactory.getLogger(UserController.class);

    @GetMapping("/get")
    public String get(){
        logger.trace("trace...");
        logger.debug("debug...");
        logger.info("info...");
        logger.warn("warn...");
        logger.error("error...");
        return "OK";
    }
}

Step 5: create a startup class

package cn.itcast;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LogbackApp {
    public static void main(String[] args) {
        SpringApplication.run(LogbackApp.class,args);
    }
}

Start the project at: http://localhost:9000/user/get

You can see that the console has started outputting log information.

Change the development mode in the application.yml file to pro, restart the project, and the log is output to the file.

9.2 Spring Event

9.2.1 introduction to spring event

Spring Event is the event notification mechanism of spring, which can decouple the coupled code, so as to facilitate the modification and addition of functions. Spring Event is a concrete implementation of listener pattern.

The Listener mode includes Listener listener, Event event and Event publisher EventPublish. The process is that EventPublish publishes an Event, which is captured by the Listener, and then executes the corresponding methods of the Event.

The related API s of Spring Event are in the spring context package.

9.2.2 introduction to spring event

Step 1: create maven project springevent_demo and configure pom.xml file

<?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 
                             http://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.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>cn.itcast</groupId>
    <artifactId>springevent_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

Step 2: create an OptLogDTO class to encapsulate operation log information

package cn.itcast.dto;

import lombok.Data;

@Data
public class OptLogDTO {
    private String requestIp; //Operation IP
    private String type; //Log type LogType{OPT: operation type; EX: exception type}
    private String userName; //Operator
    private String description; //pedagogical operation
}

Step 3: create the event class SysLogEvent

package cn.itcast.event;

import cn.itcast.dto.OptLogDTO;
import org.springframework.context.ApplicationEvent;
/**
* Define system log events
*/
public class SysLogEvent extends ApplicationEvent {
    public SysLogEvent(OptLogDTO optLogDTO) {
        super(optLogDTO);
    }
}

Step 4: create listener class SysLogListener

package cn.itcast.listener;

import cn.itcast.dto.OptLogDTO;
import cn.itcast.event.SysLogEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
* Asynchronous listening log events
*/
@Component
public class SysLogListener {
    @Async//Asynchronous processing
    @EventListener(SysLogEvent.class)
    public void saveSysLog(SysLogEvent event) {
        OptLogDTO sysLog = (OptLogDTO) event.getSource();
        long id = Thread.currentThread().getId();
        System.out.println("Listening to log operation events:" + sysLog + " thread  id: " + id);
        //Save log information to database
    }
}

Step 5: create a Controller to publish events

package cn.itcast.controller;

import cn.itcast.dto.OptLogDTO;
import cn.itcast.event.SysLogEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private ApplicationContext applicationContext;
    @GetMapping("/getUser")
    public String getUser(){
        //Construct operation log information
        OptLogDTO logInfo = new OptLogDTO();
        logInfo.setRequestIp("127.0.0.1");
        logInfo.setUserName("admin");
        logInfo.setType("OPT");
        logInfo.setDescription("Query user information");
		
        //Construct event object
        ApplicationEvent event = new SysLogEvent(logInfo);
        
        //Publish event
        applicationContext.publishEvent(event);
        
        long id = Thread.currentThread().getId();
        System.out.println("Publish event,thread  id: " + id);
        return "OK";
    }
}

Step 6: create a startup class

package cn.itcast;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync//Enable asynchronous processing
public class SpringEventApp {
    public static void main(String[] args) {
        SpringApplication.run(SpringEventApp.class,args);
    }
}

Start the project and access the Controller to find that the listener is triggered.

9.3 PD tools log usage

The development steps of PD tools log are as follows:

1. Define log operation event class SysLogEvent

2. Define @ SysLog annotation, which is used to mark the current method on the Controller method and save the operation log

3. Define aspect class SysLogAspect

4. Define the pointcut in the facet class SysLogAspect to intercept the method of adding @ SysLog annotation in the Controller

5. Define the pre notification in the aspect class SysLogAspect, collect the operation log related information in the pre notification method recordLog, package it as an OptLogDTO object and save it to ThreadLocal

6. Define the post notification in the aspect class SysLogAspect, obtain the OptLogDTO through ThreadLocal in the post notification method doAfterReturning, and continue to set other operation information to the OptLogDTO

7. Publish the event SysLogEvent in the post notification method doAfterReturning of the aspect class SysLogAspect

8. Define the listener SysLogListener to listen to the log publishing event SysLogEvent

9. Defines the configuration class LogAutoConfiguration, which is used to automatically configure the aspect SysLogAspect object

10. Define the META-INF/spring.factories file required by the starter, and configure the automatic configuration class LogAutoConfiguration

Problem thinking:

By analyzing the PD tools log code, we can find that it only collects the log information and then publishes the log events. Finally, it listens to the log events through the listener, and does not really save the log information to the database. Why?

This is because different systems may have different processing methods for operation logs. For example, some systems may save logs to the database, and some systems may save logs to files. As a general log component, PD tools log cannot limit the specific log processing logic, so it only collects the log information, and the specific processing method needs to be handed over to our application system.

Specific use process:

Step 1: create the maven project myLogApp and configure the pom.xml file

<?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 
                             http://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.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>myLogApp</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>pd-tools-log</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

Step 2: create logback configuration files logback-base.xml and logback-spring.xml

logback-base.xml

<?xml version="1.0" encoding="UTF-8"?>
<included>
    <contextName>logback</contextName>
    <!--
		name The value of is the name of the variable, value The value defined by the variable
		After defining variables, you can make“ ${}"To use variables
	-->
    <property name="log.path" value="d:\\logs" />

    <!-- Color log -->
    <!-- Rendering classes dependent on color logs -->
    <conversionRule
            conversionWord="clr"
            converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule
            conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <!-- Color log format -->
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    <!--Output to console-->
    <appender name="LOG_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- Set character set -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--output to a file-->
    <appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/logback.log</file>
        <!--Log file output format-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- Daily log archive path and format -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
    </appender>
</included>

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--Import other profiles-->
    <include resource="logback-base.xml" />

    <!--development environment -->
    <springProfile name="dev">
        <logger name="com.itheima" additivity="false" level="debug">
            <appender-ref ref="LOG_CONSOLE"/>
        </logger>
    </springProfile>
    <!--production environment -->
    <springProfile name="pro">
        <logger name="com.itheima" additivity="false" level="info">
            <appender-ref ref="LOG_FILE"/>
        </logger>
    </springProfile>

    <root level="info">
        <appender-ref ref="LOG_CONSOLE" />
        <appender-ref ref="LOG_FILE" />
    </root>
</configuration>

Step 3: write application.yml

server:
  port: 8080
pinda:
  log:
    enabled: true
logging:
  #In the Spring Boot project, the logback-spring.xml file under the classpath is loaded by default
  config: classpath:logback-spring.xml
spring:
  profiles:
    active: dev

Step 4: create UserController

package com.itheima.controller;

import com.itheima.pinda.log.annotation.SysLog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
@Api(tags = "User controller")
public class UserController {
    @SysLog("Paging query user")//Record operation log
    @ApiImplicitParams({
            @ApiImplicitParam(name = "pageNum", value = "Page number",
                    required = true, type = "Integer"),
            @ApiImplicitParam(name = "pageSize", value = "Number of entries per page",
                    required = true, type = "Integer"),
    })
    @ApiOperation(value = "Paging query user information")
    @GetMapping(value = "page/{pageNum}/{pageSize}")
    public String findByPage(@PathVariable Integer pageNum,
                             @PathVariable Integer pageSize) {
        return "OK";
    }
}

Step 5: create LogService

package com.itheima.service;

import com.itheima.pinda.log.entity.OptLogDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class LogService {
    //Save log information to database
    public void saveLog(OptLogDTO optLogDTO){
        //The log information is only output here. The log information can be saved to the database in the actual project
        log.debug("Save log information:" + optLogDTO);
    }
}

Step 6: create a configuration class

package com.itheima.config;

import com.itheima.pinda.log.event.SysLogListener;
import com.itheima.service.LogService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/*
*Log configuration class
*/

@Configuration
public class LogAutoConfiguration {
    //Auto configure log listener component
    @Bean
    @ConditionalOnMissingBean
    public SysLogListener sysLogListener(LogService logService){
        return new SysLogListener(optLogDTO -> logService.saveLog(optLogDTO));
    }
}

Step 7: write the startup class

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyLogApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyLogApplication.class,args);
    }
}

Start the project at: http://localhost:8080/user/page/1/10

You can see that the saveLog method of LogService has been called.

10. pd-tools-jwt

Posted by twmcmahan on Mon, 08 Nov 2021 00:27:23 -0800