品达通用权限系统(Day 3~Day 4)

2023-10-21 12:40
文章标签 系统 通用 权限 day 品达

本文主要是介绍品达通用权限系统(Day 3~Day 4),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

7. pd-tools-validator

pd-tools-validator模块定位为后端表单数据校验,其他模块可以直接引入pd-tools-validator的maven坐标就可以使用其提供的表单校验功能。pd-tools-validator底层基于hibernate-validator实现。

7.1 hibernate-validator介绍

早期的网站,用户输入一个邮箱地址,需要将邮箱地址发送到服务端,服务端进行校验,校验成功后,给前端一个响应。

有了JavaScript后,校验工作可以放在前端去执行。那么为什么还需要服务端校验呢? 因为前端传来的数据不可信。前端很容易获取到后端的接口,如果有人直接调用接口,就可能会出现非法数据,所以服务端也要数据校验。

总的来说:

  • 前端校验:主要是提高用户体验
  • 后端校验:主要是保证数据安全可靠

校验参数基本上是一个体力活,而且冗余代码繁多,也影响代码的可读性,我们需要一个比较优雅的方式来解决这个问题。Hibernate Validator 框架刚好解决了这个问题,可以以很优雅的方式实现参数的校验,让业务代码和校验逻辑分开,不再编写重复的校验逻辑。

hibernate-validator优势:

  • 验证逻辑与业务逻辑之间进行了分离,降低了程序耦合度
  • 统一且规范的验证方式,无需你再次编写重复的验证代码
  • 你将更专注于你的业务,将这些繁琐的事情统统丢在一边

hibernate-validator的maven坐标:

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

7.2 hibernate-validator常用注解

hibernate-validator提供的校验方式为在类的属性上加入相应的注解来达到校验的目的。hibernate-validator提供的用于校验的注解如下:

注解说明
@AssertTrue用于boolean字段,该字段只能为true
@AssertFalse用于boolean字段,该字段只能为false
@CreditCardNumber对信用卡号进行一个大致的验证
@DecimalMax只能小于或等于该值
@DecimalMin只能大于或等于该值
@Email检查是否是一个有效的email地址
@Future检查该字段的日期是否是属于将来的日期
@Length(min=,max=)检查所属的字段的长度是否在min和max之间,只能用于字符串
@Max该字段的值只能小于或等于该值
@Min该字段的值只能大于或等于该值
@NotNull不能为null
@NotBlank不能为空,检查时会将空格忽略
@NotEmpty不能为空,这里的空是指空字符串
@Pattern(regex=)被注释的元素必须符合指定的正则表达式
@URL(protocol=,host,port)检查是否是一个有效的URL,如果提供了protocol,host等,则该URL还需满足提供的条件

7.3 hibernate-validator入门案例

第一步:创建maven工程hibernate-validator_demo并配置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>cn.itcast</groupId><artifactId>hibernate-validator_demo</artifactId><version>1.0-SNAPSHOT</version><dependencies><!--spring-boot-starter-web中已经依赖了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>

注意:spring-boot-starter-web中已经依赖了hibernate-validator,所以无需再次导入。

第二步:创建User类

package cn.itcast.entity;import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;@Data
public class User {@NotNull(message = "用户id不能为空")private Integer id;@NotEmpty(message = "用户名不能为空")@Length(max = 50, message = "用户名长度不能超过50")private String username;@Max(value = 80,message = "年龄最大为80")@Min(value = 18,message = "年龄最小为18")private int age;@Pattern(regexp = "[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$",message = "邮箱格式不正确")private String email;
}

第三步:创建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 //开启校验功能
public class UserController {//简单数据类型校验@RequestMapping("/delete")public String delete(@NotBlank(message = "id不能为空") String id){System.out.println("delete..." + id);return "OK";}//对象属性校验@RequestMapping("/save")public String save(@Validated User user){System.out.println("save..." + user);return "OK";}
}

第四步:创建application.yml

server:port: 9100

第五步:创建启动类

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

启动项目,访问地址:http://localhost:9100/user/save,通过控制台输出可以看到已经可以进行数据校验了,如下:

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 [年龄最小为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 [用户id不能为空]
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 [年龄最小为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 [用户名不能为空]]

浏览器页面直接报错:

在这里插入图片描述

第六步:为了能够在页面友好的显示数据校验结果,可以通过全局异常处理来解决,创建全局异常处理类

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;/*** 全局异常处理*/
@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;}
}

重启程序可以发现此时页面可以友好的显示校验信息了:

在这里插入图片描述

通过控制台的输出可以看到,校验框架将我们的多个属性都进行了数据校验(默认行为),如果我们希望只要有一个属性校验失败就直接返回提示信息,后面的属性不再进行校验了该如何实现呢?

第七步:创建ValidatorConfiguration类,指定校验时使用快速失败返回模式

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 {@Beanpublic Validator validator() {ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure()//快速失败返回模式.addProperty("hibernate.validator.fail_fast", "true").buildValidatorFactory();return validatorFactory.getValidator();}/*** 开启快速返回* 如果参数校验有异常,直接抛异常,不会进入到 controller,使用全局异常拦截进行拦截*/@Beanpublic MethodValidationPostProcessor methodValidationPostProcessor() {MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();/**设置validator模式为快速失败返回*/postProcessor.setValidator(validator());return postProcessor;}
}

注意:上面创建的类并不是配置类,所以到目前为止快速失败返回模式并不会生效,为了使其生效需要创建一个注解用于控制此模式的开启

第八步:创建注解EnableFormValidator用于控制快速失败返回模式的开启

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;/*** 在启动类上添加该注解来启动表单验证功能---快速失败返回模式*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ValidatorConfiguration.class)
public @interface EnableFormValidator {
}

第九步:在启动类上加入EnableFormValidator注解,开启快速失败返回模式

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

通过控制台的输出可以看到,虽然我们输入的数据有多个都不符合校验规则,但是只有一个校验失败异常信息,这说明已经开启了快速失败返回模式。

7.4 pd-tools-validator使用

pd-tools-validator的实现和我们上面的入门案例是一致的,只是没有提供全局异常处理,这是因为不同的系统对于校验结果的处理方式可能不一样,所以需要各个系统自己进行个性化的处理,而pd-tools-validator只是提供数据校验功能。

具体使用过程:

第一步:创建maven工程并配置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>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>

第二步:创建User类

package cn.itcast.entity;import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;@Data
public class User {@NotNull(message = "用户id不能为空")private Integer id;@NotEmpty(message = "用户名不能为空")@Length(max = 50, message = "用户名长度不能超过50")private String username;@Max(value = 80,message = "年龄最大为80")@Min(value = 18,message = "年龄最小为18")private int age;@Pattern(regexp = "[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$",message = "邮箱格式不正确")private String email;
}

第三步:创建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 //开启校验功能
public class UserController {//简单数据类型校验@RequestMapping("/delete")public String delete(@NotBlank(message = "id不能为空") String id){System.out.println("delete..." + id);return "OK";}//对象属性校验@RequestMapping("/save")public String save(@Validated User user){System.out.println("save..." + user);return "OK";}
}

第四步:创建application.yml

server:port: 9000

第五步:创建启动类

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

启动项目,访问地址:http://localhost:9000/user/save,可以看到控制台输出:

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 [年龄最小为18]]

页面效果:

在这里插入图片描述

这说明已经开始进行输入校验了,而且根据控制台输出可以看出已经开启快速失败返回模式。

为了能够给页面一个友好的提示,也可以加入全局异常处理。

8. pd-tools-xss

pd-tools-xss模块定位为防跨站脚本攻击(XSS),通过对用户在页面输入的 HTML / CSS / JavaScript 等内容进行检验和清理,确保输入内容符合应用规范,保障系统的安全。

8.1 XSS介绍

XSS:跨站脚本攻击(Cross Site Scripting),为不和 CSS混淆,故将跨站脚本攻击缩写为XSS。XSS是指恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。有点类似于sql注入。

XSS攻击原理:

HTML是一种超文本标记语言,通过将一些字符特殊地对待来区别文本和标记,例如,小于符号(<)被看作是HTML标签的开始,<title>与</title>之间的字符是页面的标题等等。当动态页面中插入的内容含有这些特殊字符时,用户浏览器会将其误认为是插入了HTML标签,当这些HTML标签引入了一段JavaScript脚本时,这些脚本程序就将会在用户浏览器中执行。所以,当这些特殊字符不能被动态页面检查或检查出现失误时,就将会产生XSS漏洞。
在这里插入图片描述
在这里插入图片描述

8.2 AntiSamy介绍

AntiSamy是OWASP的一个开源项目,通过对用户输入的 HTML / CSS / JavaScript 等内容进行检验和清理,确保输入符合应用规范。AntiSamy被广泛应用于Web服务对存储型和反射型XSS的防御中。

AntiSamy的maven坐标:

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

8.3 AntiSamy入门案例

第一步:创建maven工程antiSamy_demo并配置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>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>

第二步:创建application.yml

server:port: 9000

第三步:创建策略文件/resources/antisamy-test.xml,文件内容可以从antisamy的jar包中获取

注:AntiSamy对“恶意代码”的过滤依赖于策略文件。策略文件规定了AntiSamy对各个标签、属性的处理方法,策略文件定义的严格与否,决定了AntiSamy对XSS漏洞的防御效果。在AntiSamy的jar包中,包含了几个常用的策略文件

在这里插入图片描述

第四步:创建User实体类

package cn.itcast.entity;import lombok.Data;@Data
public class User {private int id;private String name;private int age;
}

第五步:创建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();}
}

第六步:创建/resources/static/index.html页面

<!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>

第七步:创建启动类

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

此时我们可以启动项目进行访问,但是还没有进行参数的过滤,所以如果我们输入任意参数都可以正常传递到Controller中,这在实际项目中是非常不安全的。为了对我们输入的数据进行过滤清理,需要通过过滤器来实现。

第八步:创建过滤器,用于过滤所有提交到服务器的请求参数

package cn.itcast.filter;import cn.itcast.wrapper.XssRequestWrapper;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;/*
*过滤所有提交到服务器的请求参数
*/
public class XssFilter implements Filter{@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)servletRequest;//传入重写后的RequestfilterChain.doFilter(new XssRequestWrapper(request),servletResponse);}
}

注意:通过上面的过滤器可以发现我们并没有在过滤器中直接进行请求参数的过滤清理,而是直接放行了,那么我们还怎么进行请求参数的过滤清理呢?其实过滤清理的工作是在另外一个类XssRequestWrapper中进行的,当上面的过滤器放行时需要调用filterChain.doFilter()方法,此方法需要传入请求Request对象,此时我们可以将当前的request对象进行包装,而XssRequestWrapper就是Request对象的包装类,在过滤器放行时会自动调用包装类的getParameterValues方法,我们可以在包装类的getParameterValues方法中进行统一的请求参数过滤清理。

第九步:创建XssRequestWrapper类

package cn.itcast.wrapper;import org.owasp.validator.html.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;public class XssRequestWrapper extends HttpServletRequestWrapper {/** 策略文件 需要将要使用的策略文件放到项目资源文件路径下*/private static String antiSamyPath = XssRequestWrapper.class.getClassLoader().getResource( "antisamy-test.xml").getFile();public static  Policy policy = null;static {// 指定策略文件try {policy = Policy.getInstance(antiSamyPath);} catch (PolicyException e) {e.printStackTrace();}}/*** AntiSamy过滤数据* @param taintedHTML 需要进行过滤的数据* @return 返回过滤后的数据* */private String xssClean( String taintedHTML){try{// 使用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);}@Overridepublic 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过滤清理,清理之前的参数值:" + values[j]);// 过滤清理newArray[j] = xssClean(values[j]);System.out.println("Antisamy过滤清理,清理之后的参数值:" + newArray[j]);}return newArray;}
}

第十步:为了使上面定义的过滤器生效,需要创建配置类,用于初始化过滤器对象

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 {/*** 配置跨站攻击过滤器*/@Beanpublic FilterRegistrationBean filterRegistrationBean() {FilterRegistrationBean filterRegistration = new FilterRegistrationBean(new XssFilter());filterRegistration.addUrlPatterns("/*");filterRegistration.setOrder(1);return filterRegistration;}
}

启动项目,页面输入非法数据,可以看到非法数据被清理掉了。

注意:当前我们在进行请求参数过滤时只是在包装类的getParameterValues方法中进行了处理,真实项目中可能用户提交的数据在请求头中,也可能用户提交的是json数据,所以如果考虑所有情况,我们可以在包装类中的多个方法中都进行清理处理即可,如下:

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 {/*** 策略文件 需要将要使用的策略文件放到项目资源文件路径下* */private static String antiSamyPath = XssRequestWrapper.class.getClassLoader().getResource( "antisamy-ebay.xml").getFile();public static  Policy policy = null;static {// 指定策略文件try {policy = Policy.getInstance(antiSamyPath);} catch (PolicyException e) {e.printStackTrace();}}/*** AntiSamy过滤数据* @param taintedHTML 需要进行过滤的数据* @return 返回过滤后的数据* */private String xssClean( String taintedHTML){try{// 使用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);}@Overridepublic 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++){// 过滤清理newArray[j] = xssClean(values[j]);}return newArray;}@Overridepublic String getParameter(String paramString) {String str = super.getParameter(paramString);if (str == null) {return null;}return xssClean(str);}@Overridepublic String getHeader(String paramString) {String str = super.getHeader(paramString);if (str == null) {return null;}return xssClean(str);}@Overridepublic 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 pd-tools-xss使用

pd-tools-xss的实现和我们上面的入门案例是一致的,底层也是基于AntiSamy对输入参数进行检验和清理,确保输入符合应用规范。

为了方便使用,pd-tools-xss已经定义为了starter,其他应用只需要导入其maven坐标,不需要额外进行任何配置就可以使用。

具体使用过程:

第一步:创建maven工程并配置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>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>

第二步:创建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 "处理之后的文本内容为:" + text;}
}

第三步:创建启动类

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

启动项目,访问如下地址:

在这里插入图片描述

在这里插入图片描述

可以看到,如果输入的是普通文本则不做任何处理。如果输入的是特殊标签则被清理掉了。

9. pd-tools-log

pd-tools-log模块定位为日志模块,本质也是一个starter。提供的日志功能主要有两个方面:

1、通过logback框架可以在控制台或者日志文件记录日志信息

2、拦截用户请求,将操作日志保存到数据库

pd-tools-log涉及到的技术点:
1、切面Aspect、切点PointCut、通知Advice
2、Spring Event 异步监听事件
3、logback日志组件
4、函数式接口
5、ThreadLocal

9.1 logback

9.1.1 logback介绍

Logback继承自log4j。Logback的架构非常的通用,适用于不同的使用场景。

在这里插入图片描述

通过上图可以看到logback和Log4j都是slf4j规范的具体实现,我们在程序中直接调用的API其实都是slf4j的api,底层则是真正的日志实现组件—logback或者log4j。

Logback 构建在三个主要的类上:Logger,Appender 和 Layout。这三个不同类型的组件一起作用能够让开发者根据消息的类型以及日志的级别来打印日志。

  • Logger作为日志的记录器,把它关联到应用的对应的context后,主要用于存放日志对象,也可以定义日志类型、级别。各个logger都被关联到一个 LoggerContext,LoggerContext负责制造logger,也负责以树结构排列各 logger。
  • Appender主要用于指定日志输出的目的地,目的地可以是控制台、文件、 数据库等。
  • Layout 负责把事件转换成字符串,输出格式化的日志信息。

logback的maven坐标:

<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层级

在 logback中每一个 logger 都依附在 LoggerContext 上,它负责产生 logger,并且通过一个树状的层级结构来进行管理。

一个 Logger 被当作为一个实体,它们的命名是大小写敏感的,并且遵循以下规则:

如果一个logger的名字加上一个.作为另一个logger名字的前缀,那么该logger就是另一个logger的祖先。
如果一个logger与另一个logger之间没有其它的logger,则该logger就是另一个logger的父级。举例:
名为cn.itcast的logger是名为cn.itcast.service的logger的父级
名为cn的logger是名为cn.itcast的logger的父级,是名为cn.itcast.service的logger的祖先

在logback中有一个root logger,它是logger层次结构的最高层,它是一个特殊的logger,因为它是每一个层次结构的一部分。

9.1.3 logback日志输出等级

logback的日志输出等级分为:TRACE, DEBUG, INFO, WARN, ERROR。

如果一个给定的logger没有指定一个日志输出等级,那么它就会继承离它最近的一个祖先的层级。

为了确保所有的logger都有一个日志输出等级,root logger会有一个默认输出等级 — DEBUG。

9.1.4 logback初始化步骤
  1. logback会在类路径下寻找名为logback-test.xml的文件
  2. 如果没有找到,logback会继续寻找名为logback.groovy的文件
  3. 如果没有找到,logback会继续寻找名为logback.xml的文件
  4. 如果没有找到,将会在类路径下寻找文件META-INFO/services/ch.qos.logback.classic.spi.Configurator,该文件的内容为实现了Configurator接口的实现类的全限定类名
  5. 如果以上都没有成功,logback会通过BasicConfigurator为自己进行配置,并且日志将会全部在控制台打印出来

最后一步的目的是为了保证在所有的配置文件都没有被找到的情况下,提供一个默认的配置。

9.1.5 logback入门案例
9.1.5.1 案例一

本案例是一个logback简单应用,并且不提供配置文件而使用其提供的默认配置。

第一步:创建maven工程logback_demo并配置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><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>

第二步:编写单元测试

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使用方法
*/
public class LogbackTest {//简单使用@Testpublic void test1(){Logger logger = LoggerFactory.getLogger("cn.itcast.logback.HelloWorld");logger.debug("debug ...");}//打印日志内部状态@Testpublic void test2(){Logger logger = LoggerFactory.getLogger("cn.itcast.logback.HelloWorld");logger.debug("debug ...");// 打印内部的状态LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();StatusPrinter.print(lc);}/** 日志输出级别:ERROR > WARN > INFO > DEBUG > TRACE* *///测试默认的日志输出级别@Testpublic void test3(){Logger logger = LoggerFactory.getLogger("cn.itcast.logback.HelloWorld");logger.error("error ...");logger.warn("warn ...");logger.info("info ...");logger.debug("debug ...");//因为默认的输出级别为debug,所以这一条日志不会输出logger.trace("trace ...");}//设置日志输出级别@Testpublic 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 ...");}//测试Logger的继承@Testpublic 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" 会继承 "cn.itcast" 的有效级别Logger barLogger = LoggerFactory.getLogger("cn.itcast.logback");// 这条日志会打印,因为 INFO >= INFObarLogger.info("子级信息");// 这条日志不会打印,因为 DEBUG < INFObarLogger.debug("子级调试信息");}//Logger获取,根据同一个名称获得的logger都是同一个实例@Testpublic void test6(){Logger logger1 = LoggerFactory.getLogger("cn.itcast");Logger logger2 = LoggerFactory.getLogger("cn.itcast");System.out.println(logger1 == logger2);}//参数化日志@Testpublic void test7(){Logger logger = LoggerFactory.getLogger("cn.itcast");logger.debug("hello {}","world");}
}
9.1.5.2 案例二

本案例是logback中Spring Boot项目中的应用。

第一步:创建maven工程springboot_logback_demo并配置pom文件

<?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>

第二步:在resources下编写logback配置文件logback-base.xml和logback-spring.xml

logback-base.xml

<?xml version="1.0" encoding="UTF-8"?>
<included><contextName>logback</contextName><!-- name的值是变量的名称,value的值时变量定义的值定义变量后,可以使“${}”来使用变量--><property name="log.path" value="d:\\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" /><!-- 彩色日志格式 --><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}}"/><!--输出到控制台--><appender name="LOG_CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><Pattern>${CONSOLE_LOG_PATTERN}</Pattern><!-- 设置字符集 --><charset>UTF-8</charset></encoder></appender><!--输出到文件--><appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/logback.log</file><!--日志文件输出格式--><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 每天日志归档路径以及格式 --><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><!--日志文件保留天数--><maxHistory>15</maxHistory></rollingPolicy></appender>
</included>

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration><!--引入其他配置文件--><include resource="logback-base.xml" /><!--<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。name:用来指定受此logger约束的某一个包或者具体的某一个类。level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,如果未设置此属性,那么当前logger将会继承上级的级别。addtivity:是否向上级logger传递打印信息。默认是true。--><!--开发环境--><springProfile name="dev"><logger name="cn.itcast.controller" additivity="false" level="debug"><appender-ref ref="LOG_CONSOLE"/></logger></springProfile><!--生产环境--><springProfile name="pro"><logger name="cn.itcast.controller" additivity="false" level="info"><appender-ref ref="LOG_FILE"/></logger></springProfile><!--root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性level:设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF 默认是DEBUG可以包含零个或多个元素,标识这个appender将会添加到这个logger。--><root level="info"><appender-ref ref="LOG_CONSOLE" /><appender-ref ref="LOG_FILE" /></root>
</configuration>

第三步:编写application.yml

server:port: 9000
logging:#在Spring Boot项目中默认加载类路径下的logback-spring.xml文件config: classpath:logback-spring.xml
spring:profiles:active: dev

第四步:创建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";}
}

第五步:创建启动类

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

启动项目,访问地址:http://localhost:9000/user/get

可以看到控制台已经开始输出日志信息。

修改application.yml文件中的开发模式为pro,重启项目这日志输出到了文件中。

9.2 Spring Event

9.2.1 Spring Event介绍

Spring Event是Spring的事件通知机制,可以将相互耦合的代码解耦,从而方便功能的修改与添加。Spring Event是监听者模式的一个具体实现。

监听者模式包含了监听者Listener、事件Event、事件发布者EventPublish,过程就是EventPublish发布一个事件,被监听者捕获到,然后执行事件相应的方法。

Spring Event的相关API在spring-context包中。

9.2.2 Spring Event入门案例

第一步:创建maven工程springevent_demo并配置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>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>

第二步:创建OptLogDTO类,用于封装操作日志信息

package cn.itcast.dto;import lombok.Data;@Data
public class OptLogDTO {private String requestIp; //操作IPprivate String type; //日志类型 LogType{OPT:操作类型;EX:异常类型}private String userName; //操作人private String description; //操作描述
}

第三步:创建事件类SysLogEvent

package cn.itcast.event;import cn.itcast.dto.OptLogDTO;
import org.springframework.context.ApplicationEvent;
/**
* 定义系统日志事件
*/
public class SysLogEvent extends ApplicationEvent {public SysLogEvent(OptLogDTO optLogDTO) {super(optLogDTO);}
}

第四步:创建监听器类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;/**
* 异步监听日志事件
*/
@Component
public class SysLogListener {@Async//异步处理@EventListener(SysLogEvent.class)public void saveSysLog(SysLogEvent event) {OptLogDTO sysLog = (OptLogDTO) event.getSource();long id = Thread.currentThread().getId();System.out.println("监听到日志操作事件:" + sysLog + " 线程id:" + id);//将日志信息保存到数据库...}
}

第五步:创建Controller,用于发布事件

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 {@Autowiredprivate ApplicationContext applicationContext;@GetMapping("/getUser")public String getUser(){//构造操作日志信息OptLogDTO logInfo = new OptLogDTO();logInfo.setRequestIp("127.0.0.1");logInfo.setUserName("admin");logInfo.setType("OPT");logInfo.setDescription("查询用户信息");//构造事件对象ApplicationEvent event = new SysLogEvent(logInfo);//发布事件applicationContext.publishEvent(event);long id = Thread.currentThread().getId();System.out.println("发布事件,线程id:" + id);return "OK";}
}

第六步:创建启动类

package cn.itcast;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;@SpringBootApplication
@EnableAsync//启用异步处理
public class SpringEventApp {public static void main(String[] args) {SpringApplication.run(SpringEventApp.class,args);}
}

启动项目并访问Controller可以发现监听器触发了。

9.3 pd-tools-log使用

pd-tools-log的开发步骤为:

1、定义日志操作事件类SysLogEvent

2、定义@SysLog注解,用于在Controller的方法上标注当前方法需要进行操作日志的保存处理

3、定义切面类SysLogAspect

4、在切面类SysLogAspect中定义切点,拦截Controller中添加@SysLog注解的方法

5、在切面类SysLogAspect中定义前置通知,在前置通知方法recordLog中收集操作日志相关信息封装为OptLogDTO对象并保存到ThreadLocal中

6、在切面类SysLogAspect中定义后置通知,在后置通知方法doAfterReturning中通过ThreadLocal 获取OptLogDTO并继续设置其他的操作信息到OptLogDTO

7、在切面类SysLogAspect的后置通知方法doAfterReturning中发布事件SysLogEvent

8、定义监听器SysLogListener,监听日志发布事件SysLogEvent

9、定义配置类LogAutoConfiguration,用于自动配置切面SysLogAspect对象

10、定义starter所需的META-INF/spring.factories文件,并配置自动配置类LogAutoConfiguration

问题思考:

通过分析pd-tools-log的代码可以发现,它只是将日志信息进行收集然后发布日志事件,最后通过监听器来监听日志事件,并没有真正将日志信息保存到数据库,这是为什么呢?

这是因为不同的系统对于操作日志的处理方式可能不同,比如有的系统可能将日志保存到数据库,有的系统可能将日志保存到文件。pd-tools-log作为通用的日志组件不可能将具体的日志处理逻辑限制死,所以他只是将日志信息进行收集,具体的处理方式需要交给我们的应用系统来完成。

具体使用过程:

第一步:创建maven工程myLogApp并配置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>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>

第二步:创建logback配置文件logback-base.xml和logback-spring.xml

logback-base.xml

<?xml version="1.0" encoding="UTF-8"?>
<included><contextName>logback</contextName><!--name的值是变量的名称,value的值时变量定义的值定义变量后,可以使“${}”来使用变量--><property name="log.path" value="d:\\logs" /><!-- 彩色日志 --><!-- 彩色日志依赖的渲染类 --><conversionRuleconversionWord="clr"converterClass="org.springframework.boot.logging.logback.ColorConverter" /><conversionRuleconversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" /><conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /><!-- 彩色日志格式 --><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}}"/><!--输出到控制台--><appender name="LOG_CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><Pattern>${CONSOLE_LOG_PATTERN}</Pattern><!-- 设置字符集 --><charset>UTF-8</charset></encoder></appender><!--输出到文件--><appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/logback.log</file><!--日志文件输出格式--><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 每天日志归档路径以及格式 --><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><!--日志文件保留天数--><maxHistory>15</maxHistory></rollingPolicy></appender>
</included>

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration><!--引入其他配置文件--><include resource="logback-base.xml" /><!--开发环境--><springProfile name="dev"><logger name="com.itheima" additivity="false" level="debug"><appender-ref ref="LOG_CONSOLE"/></logger></springProfile><!--生产环境--><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>

第三步:编写application.yml

server:port: 8080
pinda:log:enabled: true
logging:#在Spring Boot项目中默认加载类路径下的logback-spring.xml文件config: classpath:logback-spring.xml
spring:profiles:active: dev

第四步:创建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 = "用户控制器")
public class UserController {@SysLog("分页查询用户")//记录操作日志@ApiImplicitParams({@ApiImplicitParam(name = "pageNum", value = "页码",required = true, type = "Integer"),@ApiImplicitParam(name = "pageSize", value = "每页条数",required = true, type = "Integer"),})@ApiOperation(value = "分页查询用户信息")@GetMapping(value = "page/{pageNum}/{pageSize}")public String findByPage(@PathVariable Integer pageNum,@PathVariable Integer pageSize) {return "OK";}
}

第五步:创建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 {//将日志信息保存到数据库public void saveLog(OptLogDTO optLogDTO){//此处只是将日志信息进行输出,实际项目中可以将日志信息保存到数据库log.debug("保存日志信息:" + optLogDTO);}
}

第六步:创建配置类

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;/*
*日志配置类
*/@Configuration
public class LogAutoConfiguration {//自动配置日志监听器组件@Bean@ConditionalOnMissingBeanpublic SysLogListener sysLogListener(LogService logService){return new SysLogListener(optLogDTO -> logService.saveLog(optLogDTO));}
}

第七步:编写启动类

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

启动项目,访问地址:http://localhost:8080/user/page/1/10

可以看到LogService的saveLog方法已经被调用了。

这篇关于品达通用权限系统(Day 3~Day 4)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/254456

相关文章

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识