11、整合JDBC

11.1、SpringData简介

​ 对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。

​ Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。

​ Sping Data 官网:https://spring.io/projects/spring-data

数据库相关的启动器 :可以参考官方文档:

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

11.2、整合JDBC

11.2.1、创建测试项目测试数据源

11.2.1.1、我去新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块

11.2.1.2、项目建好之后,发现自动帮我们导入了如下的启动器:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>
11.2.1.3、编写yaml配置文件连接数据库;
spring:datasource:username: rootpassword: 888888url: jdbc:mysql://localhost:825/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driver
11.2.1.4、配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {//查看默认数据源System.out.println(dataSource.getClass());//获取数据库连接Connection connection = dataSource.getConnection();System.out.println(connection);connection.close();
}

结果:我们可以看到他默认给我们配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置

我们来全局搜索一下,找到数据源的所有自动配置都在 :DataSourceAutoConfiguration文件:

@Import({Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class}
)
protected static class PooledDataSourceConfiguration {protected PooledDataSourceConfiguration() {}
}

这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.2.5 默认使用HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;

HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;

可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。

关于数据源我们并不做介绍,有了数据库连接,显然就可以 CRUD 操作数据库了。但是我们需要先了解一个对象 JdbcTemplate

11.2.2、JDBCTemplate

​ (1)有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库;

​ (2)即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。

​ (3)数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。

​ (4)Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用

​ (5)JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类

JdbcTemplate主要提供以下几类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
  • query方法及queryForXXX方法:用于执行查询相关语句;
  • call方法:用于执行存储过程、函数相关语句。
测试:

​ 编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试;

@RestController
public class JdbcController {@AutowiredJdbcTemplate jdbcTemplate;//查询数据库的所有信息//此时没有实体类,数据库的东西应该如何获取?@GetMapping("/userList")public List<Map<String,Object>> userList(){String sql = "select * from user";List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);return list_maps;}@GetMapping("/addUser")public String addUser(){String sql = "insert into user(id,name,pwd) values (8,'阿三','436476')";jdbcTemplate.update(sql);return "OK";}@GetMapping("/delUser/{id}")public String delUser(@PathVariable("id") int id){String sql = "delete from user where id=?";jdbcTemplate.update(sql,id);return "ok";}@GetMapping("/updateUser/{id}")public String updateUser(@PathVariable("id") int id){String sql = "update user set name=?,pwd=? where id="+id;//数据Object[] data = new Object[2];data[0] = "哈哈";data[1] = "dffbdght";jdbcTemplate.update(sql,data);return "ok";}
}

测试请求,结果正常;

到此,CURD的基本操作,使用 JDBC 就搞定了。

12、整合Druid

12.1、Druid简介

Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。

Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。

Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。

Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。

Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。

Github地址:https://github.com/alibaba/druid/

com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:

12.2、配置数据源

12.2.1、添加上 Druid 数据源依赖。

<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.23</version>
</dependency>

12.2.2、切换数据源;

之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。

spring:datasource:type: com.alibaba.druid.pool.DruidDataSource

12.2.3、数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;

12.2.4、切换成功!

既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码

spring:datasource:username: rootpassword: ******url: jdbc:mysql://localhost:825/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSource#Spring Boot 默认是不注入这些属性值的,需要自己绑定#druid 数据源专有配置initialSize: 5minIdle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: true#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入#如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4jfilters: stat,wall,log4jmaxPoolPreparedStatementPerConnectionSize: 20useGlobalDataSourceStat: trueconnectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

12.2.5、导入Log4j 的依赖

<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>

12.2.6、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性

@Configuration
public class DruidConfig {/*将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中*/@ConfigurationProperties(prefix = "spring.datasource")@Beanpublic DataSource druidDataSource(){return new DruidDataSource();}
}

12.2.7、去测试类中测试一下;看是否成功!

输出结果 :可见配置参数已经生效!

12.3、配置Druid数据源监控

Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,人家也提供了一个默认的 web 页面。

12.3.1、设置后台监控

所以第一步需要设置 Druid 的后台管理页面,比如 登录账号、密码 等;配置后台管理;

在DruidConfig里配置:

//后台监控: web.xml,ServletRegistrationBean
//因为springBoot内置了servlet容器,所以没有web.xml ,替代方法: ServletRegistrationBean
@Bean
public ServletRegistrationBean StatViewServlet(){ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet// 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到//后台需要有人登陆,账号密码配置HashMap<String, String> initParameters = new HashMap<>();initParameters.put("loginUsername", "admin"); //后台管理界面的登录账号//登录的key是固定的,即loginUsername、loginPassword是固定的,不能变动initParameters.put("loginPassword", "123456"); //后台管理界面的登录密码//允许谁可以访问//initParameters.put("allow", "localhost"):表示只有本机可以访问initParameters.put("allow","");//为空或者为null时,表示允许所有访问//deny:Druid 后台拒绝谁访问//initParameters.put("kuangshen", "192.168.1.20");表示禁止此ip访问bean.setInitParameters(initParameters);//设置初始化参数return bean;
}

配置完毕后,我们可以选择访问 :http://localhost:8080/druid,会直接跳转到http://localhost:8080/druid/login.html,即登录页面。

登录成功后:

12.3.2、配置 Druid web 监控 filter 过滤器

//配置Druid监控之web监控的filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {FilterRegistrationBean bean = new FilterRegistrationBean();bean.setFilter(new WebStatFilter());//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计Map<String, String> initParams = new HashMap<>();initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");bean.setInitParameters(initParams);//"/*" 表示过滤所有请求bean.setUrlPatterns(Arrays.asList("/*"));return bean;
}

平时在工作中,按需求进行配置即可,主要用作监控!

13、整合MyBatis

官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

13.1、导入 MyBatis 所需要的依赖

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version>
</dependency>

13.2、配置数据库连接信息

spring:datasource:username: rootpassword: ******url: jdbc:mysql://localhost:825/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driver# 整合mybatis
mybatis:#mybatis前不能加/,否则会直接去springboot-05-mybatis目录找     classpath:就是指resources目录mapper-locations: classpath:mybatis/mapper/*.xmltype-aliases-package: com.kuang.pojo

13.3、测试数据库是否连接成功!

13.4、创建实体类,导入 Lombok!

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private int id;private String name;private String pwd;
}

13.5、创建mapper目录以及对应的 Mapper 接口

@Mapper//或者这里不加@Mapper,在启动项里加@MapperScan("com.kuang.mapper")
@Repository//表示是dao层
public interface UserMapper {List<User> queryUserList();User queryUserById(int id);int addUser(User user);int updateUser(User user);int deleteUser(int id);}

13.6、在resources\mybatis\mapper下创建UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.UserMapper"><select id="queryUserList" resultType="user">select * from user;</select><select id="queryUserById" resultType="user">select * from user where id = #{id}</select><insert id="addUser" parameterType="user">insert into user(id,name,pwd) values (#{id},#{name},#{pwd})</insert><update id="updateUser" parameterType="user">update user set name=#{name},pwd=#{pwd} where id=#{id}</update><delete id="deleteUser" parameterType="int">delete from user where id=#{id}</delete>
</mapper>

13.7、编写UserController进行测试

@RestController
public class UserController {@Autowiredprivate UserMapper userMapper;@GetMapping("/queryUserList")public List<User> queryUserList() {List<User> users = userMapper.queryUserList();for (User user : users) {System.out.println(user);}return users;}//添加一个用户@GetMapping("/addUser")public String addUser() {userMapper.addUser(new User(8, "阿毛", "456789"));return "ok";}//修改一个用户@GetMapping("/updateUser")public String updateUser() {userMapper.updateUser(new User(8, "阿毛", "421319"));return "ok";}//根据id删除用户@GetMapping("/deleteUser")public String deleteUser() {userMapper.deleteUser(8);return "ok";}
}

启动项目访问进行测试!

14、SpringSecurity

在web开发中,安全第一位! 过滤器,拦截器~

功能性需求:否

做网站:安全应该在什么时候考虑?设计之初!

  • 漏洞,隐私泄露~
  • 架构一旦确定~

shiro、Springsecurity :很像~除了类不一样,名字不一样;

认证,授权(vip1,vip2,vip3)

  • 功能权限
  • 访问权限
  • 菜单权限
  • …拦截器,过滤器:大量的原生代码~冗余

MVC—SPRING–SPRINGBOOT—框架思想

14.1、简介

在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。

市面上存在比较有名的:Shiro,Spring Security !

这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢?

首先我们看下它的官网介绍:Spring Security官网地址

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。

Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求

从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。

怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种。

Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

实验环境搭建

(1)新建一个初始的springboot项目web模块,thymeleaf模块

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--thymeleaf-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

(2)导入静态资源

(3)controller跳转!

@Controller
public class RouterController {@RequestMapping({"/","/index"})public String index(){return "index";}@RequestMapping("/toLogin")public String toLogin(){return "views/login";}@RequestMapping("/level1/{id}")public String level1(@PathVariable("id") int id){return "views/level1/"+id;}@RequestMapping("/level2/{id}")public String level2(@PathVariable("id") int id){return "views/level2/"+id;}@RequestMapping("/level3/{id}")public String level3(@PathVariable("id") int id){return "views/level3/"+id;}}

(4)关闭thymeleaf缓存

#关闭thymeleaf缓存
spring.thymeleaf.cache=false

(5)运行测试

14.2、认识SpringSecurity

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式 @Enablexxxx开启某个功能

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

这个概念是通用的,而不是只在Spring Security 中存在。

14.2.1、认证和授权

目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能

14.2.1.1、引入 Spring Security 模块
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
14.2.1.2、编写 Spring Security 配置类

参考官网:https://spring.io/projects/spring-security

查看我们自己项目中的版本,找到对应的帮助文档:

https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {//链式编程//授权@Overrideprotected void configure(HttpSecurity http) throws Exception {}
}
14.2.1.3、定制请求的授权规则
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {///首页所有人可以访问,功能页只有对应有权限的人才能访问//请求授权的规则http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/level1/**").hasRole("vip1").antMatchers("/level2/**").hasRole("vip2").antMatchers("/level3/**").hasRole("vip3");
}
14.2.1.4、测试一下:发现除了首页都进不去了!

因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!

14.2.1.5、在configure()方法中加入以下配置,开启自动配置的登录功能!
//设置没有权限会跳转登录页面
http.formLogin();
14.2.1.6、测试一下:发现,没有权限的时候,会跳转到登录的页面!
14.2.1.7、查看刚才登录页的注释信息;

我们可以定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法

//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {//在内存中定义,也可以在jdbc中去拿....auth.inMemoryAuthentication().withUser("kuangshen").password("123456").roles("vip2","vip3").and().withUser("root").password("123456").roles("vip1","vip2","vip3").and().withUser("guest").password("123456").roles("vip1","vip2");
}
14.2.1.8、测试,我们可以使用这些账号登录进行测试!发现会报错!

There is no PasswordEncoder mapped for the id “null”

14.2.1.9、原因,我们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码
//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {//在内存中定义,也可以在jdbc中去拿....//Spring security 5.0+ 中新增了多种加密方式,也改变了密码的格式。//要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密//spring security 官方推荐的是使用bcrypt加密方式auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3").and().withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3").and().withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2");
}
14.2.1.10、测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!搞定

14.2.2、权限控制和注销

14.2.2.1、开启自动配置的注销的功能
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {//....//开启自动配置的注销的功能// /logout 注销请求http.logout();
}
14.2.2.2、我们在前端,增加一个注销的按钮,index.html 导航栏中
<a class="item" th:href="@{/logout}"><i class="address card icon"></i> 注销
</a>
14.2.2.3、我们可以去测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面!
14.2.2.4、但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?
// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");
14.2.2.5、测试,注销完毕后,发现跳转到首页OK
14.2.2.6、我们现在又来一个需求:

用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如kuangshen这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?

我们需要结合thymeleaf中的一些功能

sec:authorize=“isAuthenticated()”:是否认证登录!来显示不同的页面

Maven依赖:

<!--security-thymeleaf整合包-->
<dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId><version>3.0.4.RELEASE</version>
</dependency>
14.2.2.7、修改我们的前端页面
  1. 导入命名空间:两个任选一,下面的有提示

  2. xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
    xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
    

​ 修改导航栏,增加认证判断

<!--登录注销-->
<div class="right menu"><!--如果未登录--><div sec:authorize="!isAuthenticated()"><a class="item" th:href="@{/login}"><i class="address card icon"></i> 登录</a></div><!--如果已登录--><div sec:authorize="isAuthenticated()"><a class="item"><i class="address card icon"></i>用户名:<span sec:authentication="principal.username"></span>角色:<span sec:authentication="principal.authorities"></span></a></div><div sec:authorize="isAuthenticated()"><a class="item" th:href="@{/logout}"><i class="address card icon"></i> 注销</a></div>
</div>
14.2.2.8、重启测试,我们可以登录试试看,登录成功后确实,显示了我们想要的页面;
14.2.2.9、如果注销404

就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试:在 配置中增加 http.csrf().disable();

http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.logout().logoutSuccessUrl("/");

14.2.2.10、我们继续将下面的角色功能块认证完成!

<!--菜单根据用户的角色动态的实现:  sec:authorize="hasRole('vip1')"-->
<div class="column" sec:authorize="hasRole('vip1')">
<div class="column" sec:authorize="hasRole('vip2')">
<div class="column" sec:authorize="hasRole('vip3')">

14.2.2.11、测试一下!

14.2.2.12、权限控制和注销搞定!

14.2.3、记住我

现在的情况,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单

14.2.3.1、开启记住我功能
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {//。。。。。。。。。。。//记住我http.rememberMe();
}
14.2.3.2、我们再次启动项目测试一下

发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!

思考:如何实现的呢?其实非常简单

我们可以查看浏览器的cookie

14.2.3.3、我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie

4、结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie,具体的原理我们在JavaWeb阶段都讲过了,这里就不在多说了!

14.2.4、定制登录页

现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?

14.2.4.1、在刚才的登录页配置后面指定 loginpage
/设置没有权限会跳转登录页面
//http.formLogin();
http.formLogin().loginPage("/toLogin");//跳转到我们自己的登录页面
14.2.4.2、然后前端也需要指向我们自己定义的 login请求

index.html

<a class="item" th:href="@{/toLogin}"><i class="address card icon"></i> 登录
</a>

login.html

<div class="ui form"><form th:action="@{/toLogin}" method="post"><div class="field">

此时再去访问http://localhost:8080/login就会报错。

如果login.html路径不改仍为@{/login},需要在配置里设置:

14.2.4.3、我们登录,需要将这些信息发送到哪里,我们也需要配置,login.html 配置提交请求及方式,方式必须为post:
<form th:action="@{/login}" method="post"><div class="field"><label>Username</label><div class="ui left icon input"><!--<input type="text" placeholder="Username" name="username">--><input type="text" placeholder="Username" name="user"><i class="user icon"></i></div></div><div class="field"><label>Password</label><div class="ui left icon input"><!--<input type="password" name="password">--><input type="password" name="pwd"><i class="lock icon"></i></div></div>
14.2.4.4、这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码!我们配置接收登录的用户名和密码的参数!
http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
14.2.4.5、在登录页增加记住我的多选框
<div class="field"><input type="checkbox" name="remember2">记住我
</div>
14.2.4.6、后端验证处理!
//开启记住我功能
http.rememberMe().rememberMeParameter("remember2");
14.2.4.7、测试,OK

15、Shiro

15.1、Shiro简介

15.1.1、什么是Shiro?

  • Apache Shiro是一个Java的安全(权限)框架。
  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
  • Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。
  • 下载地址: http://shiro.apache.org

15.1.2、功能

  • Authentication :认证、验证
  • Authorization :授权
  • Session Management :会话管理
  • Crytography :加密机制

15.1.3、shiro架构(外部)

从外部来看 Shiro,即从应用程序角度来观察如何使用 Shiro 完成工作:

  • Subject

    • 应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject
    • Subject 代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如:网络爬虫,机器人等
    • 与 Subject 的所有交互都会委托给 SecurityManager
    • Subject 其实是一个门面, SecurityManageer 才是实际的执行者
  • SecurityManager
    • 安全管理器
    • 所有与安全有关的操作都会与 SercurityManager 交互,并且它管理着所有的 Subject
    • 它是 Shiro 的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 的DispatcherServlet 的角色
  • Realm
    • Shiro 从 Realm 获取安全数据 (用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较,来确定用户的身份是否合法
    • 需要从Realm 得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把 Realm 看DataSource

15.1.4、Shiro 架构(内部)

  • Subject:任何可以与应用交互的用户
  • Security Manager:相当于 SpringMVC 中的 DispatcherSerlet,是 Shiro 的心脏,所有具体的交互都通过 Security Manager 进行控制,它管理者所有的 Subject,且负责进行认证、授权、会话及缓存的管理
  • Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现,可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了
  • Authorizer:授权器,即访问控制器,用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的那些功能
  • Realm:可以有一个或者多个的 realm,可以认为是安全实体数据源,即用于获取安全实体的,可以用 JDBC 实现,也可以是内存实现等等,由用户提供,所以,一般在应用中都需要实现自己的 realm
  • SessionManager:管理 Session 生命周期的组件,而 Shiro 并不仅仅可以用在 Web 环境,也可以用在普通的 JavaSE 环境中
  • CacheManager:缓存控制器,来管理,如:用户,角色,权限等缓存的,因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于密码加密、解密…

15.2、HelloWorld

15.2.1、快速实践

查看官网文档: http://shiro.apache.org/tutorial.html

官方的quickstart: https://github.com/apache/shiro/tree/master/samples/quickstart/

(1)创建一个maven父工程,用于学习Shiro,删掉不必要的东西
(2)创建一个普通的Maven子工程: shiro-01-helloworld
(3)根据官方文档,我们来导入Shiro的依赖
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.4.1</version>
</dependency><!-- configure logging -->
<dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId><version>1.7.21</version>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.21</version>
</dependency>
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>
(4)配置log4j.properties
log4j.rootLogger=INFO,stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n#GeneraL Apache libraries
1og4j.logger.org.apache=WARN
#Spring
log4j.logger.org.springframework=WARN
#Default Shiro logging
log4j.logger.org.apache.shiro=INFO
#Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
(5)配置shiro.ini
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
(6)创建Quickstart.java
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** Simple Quickstart application showing how to use Shiro's API.** @since 0.9 RC2*/
public class Quickstart {private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);public static void main(String[] args) {Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);// Now that a simple Shiro environment is set up, let's see what you can do://获取当前的用户对象SubjectSubject currentUser = SecurityUtils.getSubject();//通过当前当用户拿SessionSession session = currentUser.getSession();session.setAttribute("someKey", "aValue");String value = (String) session.getAttribute("someKey");if (value.equals("aValue")) {log.info("Subject=>session [" + value + "]");}//判断当前的用广是否被认证~if (!currentUser.isAuthenticated()) {//Token :令牌    会根据username和password生成一个令牌UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");token.setRememberMe(true);//设置记住我try {currentUser.login(token);//执行登录操作} catch (UnknownAccountException uae) {log.info("There is no user with username of " + token.getPrincipal());} catch (IncorrectCredentialsException ice) {log.info("Password for account " + token.getPrincipal() + " was incorrect!");} catch (LockedAccountException lae) {log.info("The account for username " + token.getPrincipal() + " is locked.  " +"Please contact your administrator to unlock it.");}// ... catch more exceptions here (maybe custom ones specific to your application?catch (AuthenticationException ae) {//unexpected condition?  error?}}//say who they are://print their identifying principal (in this case, a username):log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");//test a role:if (currentUser.hasRole("schwartz")) {log.info("May the Schwartz be with you!");} else {log.info("Hello, mere mortal.");}//粗粒度//test a typed permission (not instance-level)if (currentUser.isPermitted("lightsaber:wield")) {log.info("You may use a lightsaber ring.  Use it wisely.");} else {log.info("Sorry, lightsaber rings are for schwartz masters only.");}//细粒度//a (very powerful) Instance Level permission:if (currentUser.isPermitted("winnebago:drive:eagle5")) {log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +"Here are the keys - have fun!");} else {log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");}//注销currentUser.logout();//结束System.exit(0);}
}
主要内容为:
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
currentUser.isAuthenticated();
currentUser.getPrincipal();
currentUser.hasRole("schwartz");
currentUser.isPermitted("lightsaber:wield");
currentUser.logout();

16、springboot整合shiro

16.1、测试环境搭建

16.1.1、导入依赖

<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.7.0</version>
</dependency>

16.1.2、编写首页

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
</body>
</html>

16.1.3、编写MyController

@Controller
public class MyController {@RequestMapping({"/","/index"})public String toIndex (Model model){model.addAttribute("msg","hello,Shiro");return "index";}
}

测试,OK!

16.1.4、自定义Realm配置类

//自定义UserRealm
public class UserRealm extends AuthorizingRealm {//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("执行了=>授权doGetAuthorizationInfo");return null;}//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("执行了=>认证doGetAuthorizationInfo");return null;}
}

16.1.5、编写配置类

注:
  • shiroFilterFactoryBean方法名必须固定,或者@Bean(name = “shiroFilterFactoryBean”),否则会报错找不到shiroFilterFactoryBean
@Configuration
public class ShiroConfig {//第三步:ShiroFilterFactoryBean@Bean//(name = "shiroFilterFactoryBean")public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){//因为下方getDefaultWebSecurityManager方法设置了bean的名称@Bean(name = "securityManager")//因此用@Qualifier("securityManager")绑定ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();//设置安全管理器bean.setSecurityManager(defaultWebSecurityManager);return bean;}//第二步:DefaultWebSecurityManager@Bean(name = "securityManager")public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){//@Qualifier("userRealm")中userRealm指的是自定义的realm对象名DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//关联UserRealmsecurityManager.setRealm(userRealm);return securityManager;}//第一步:创建realm 对象,需要自定义@Beanpublic UserRealm userRealm(){return new UserRealm();}}

16.1.6、在templates/user下编写一个简单的add.html、update.html页面

<body>
<h1>add</h1>
</body>
<body>
<h1>update</h1>
</body>

16.1.7、编写MyController

@RequestMapping("/user/add")
public String add(){return "user/add";
}@RequestMapping("/user/update")
public String update(){return "/user/update";
}

16.1.8、首页添加add、update操作

<a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a>

16.1.9、测试!

16.2、shiro实现登录拦截

16.2.1、添加shiro内置过滤器

//添加shiro内置过滤器
/*anon:无需认证就可以访问authc:必颈认证了才能访问user:必须拥有记往我功能才能用perms :拥有对某个资源的权限才能访问;role:拥有某个角色权限才能访问
*/
//拦截
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add","authc");
filterMap.put("/user/update","authc");bean.setFilterChainDefinitionMap(filterMap);

此时再点击add或者update会直接报错,正常情况下如果没有权限应该会跳转到登录页面,因此需要编写登录代码

16.2.2、编写login.html

<body>
<h1>登录</h1>
<hr><form><p>用户名:<input type="text" name="username"></p><p>密码:<input type="text" name="password"></p><p><input type="submit"></p>
</form>
</body>

16.2.3、编写controller

@RequestMapping("/toLogin")
public String toLogin(){return "login";
}

16.2.4、在shiroFilterFactoryBean设置登录的请求

//如果没有权限   设置登录的请求
bean.setLoginUrl("/toLogin");

16.3、shiro实现用户认证

在shiro中,认证应该放到Realm中

1、编写UserRealm的认证方法,获取用户名和密码,但是在controller里编写更方便一点

MyController.java

@RequestMapping("/login")
public String login(String username,String password,Model model){//获取当前的用户Subject subject = SecurityUtils.getSubject();//封装用户的登录信息UsernamePasswordToken token = new UsernamePasswordToken(username, password);try {subject.login(token);//执行登录方法,如果没有异常就说明OKreturn "index";//登录成功返回首页}catch (UnknownAccountException e){//用户名不存在model.addAttribute("msg","用户名错误");return "login";}catch (IncorrectCredentialsException e){//密码不存在model.addAttribute("msg","密码错误");return "login";}}

2、在登录页接收异常信息

<p th:text="${msg}" style="color: red"></p>

3、在UserRealm做用户认证

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("执行了=>认证doGetAuthorizationInfo");//用户名,密码  正常是在数据库取,这里伪造一下String username = "root";String password = "123456";//将这里的token转换成封装的UsernamePasswordToken类型UsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken;//if(!userToken.getUsername().equals(username)){//当封装的用户名与我们设定的用户名不一致时return null;//抛出异常 UnknownAccountException}//密码认证,不用我们手动去验证,shiro会帮我们做return new SimpleAuthenticationInfo("",password,"");
}

16.4、shiro整合mybatis

16.4.1、导入依赖

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.23</version>
</dependency>
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version>
</dependency>

16.4.2、配置

application.yml

spring:datasource:username: rootpassword: ******url: jdbc:mysql://localhost:825/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSource#Spring Boot 默认是不注入这些属性值的,需要自己绑定#druid 数据源专有配置initialSize: 5minIdle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: true#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入#如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4jfilters: stat,wall,log4jmaxPoolPreparedStatementPerConnectionSize: 20useGlobalDataSourceStat: trueconnectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

application.properties

mybatis.type-aliases-package=com.kuang.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
#关闭thymeleaf缓存
spring.thymeleaf.cache=false

16.4.3、创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private int id;private String name;private String pwd;
}

16.4.4、编写mapper

@Repository
@Mapper
public interface UserMapper {public User queryUserByName(String name);
}

16.4.5、在resources\mybatis\mapper下创建UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.UserMapper"><select id="queryUserByName" resultType="user">select * from user where name = #{name}</select>
</mapper>

16.4.6、创建service层

public interface UserService {public User queryUserByName(String name);
}
@Service
public class UserServiceImpl implements UserService{@Autowiredprivate UserMapper userMapper;@Overridepublic User queryUserByName(String name) {return userMapper.queryUserByName(name);}
}

16.4.7、测试

@Autowired
private UserService userService;
@Test
void contextLoads() {System.out.println(userService.queryUserByName("狂神"));
}

成功!

16.4.8、改造UserRealm,从数据库获取用户名密码

@Autowired
private UserService userService;//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("执行了=>认证doGetAuthorizationInfo");//将这里的token转换成封装的UsernamePasswordToken类型UsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken;//连接真实的数据库User user = userService.queryUserByName(userToken.getUsername());if(user==null){//没有这个人时return null;//抛出异常 UnknownAccountException}//可以加密  MD5  MD5盐值加密//密码认证,不用我们手动去验证,shiro会帮我们做return new SimpleAuthenticationInfo("",user.getPwd(),"");}

测试,只有数据库存在的username和密码才能登录

扩展:MD5盐值加密怎么做

16.5、shiro请求授权实现

16.5.1、在ShiroConfig的shiroFilterFactoryBean中添加授权

Map<String, String> filterMap = new LinkedHashMap<>();
//授权 正常的情况下,没有授权会跳转到未授权页面
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
//拦截
filterMap.put("/user/*","authc");//所有user路径下的都会被拦截
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
bean.setLoginUrl("/toLogin");
//设置未授权页面
bean.setUnauthorizedUrl("/noauth");

16.5.2、编写未授权controller

@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){return "未经授权无法访问此页面";
}

此时访问首页http://localhost:8080/再点击add会直接返回上述controller中设置的"未经授权无法访问此页面",而点击update则会直接进入到update页面

16.5.2、在UserRealm中对用户进行授权

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("执行了=>授权doGetAuthorizationInfo");SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addStringPermission("user:add");//表示所有的用户访问add都可以return info;
}

此时访问首页http://localhost:8080/再点击add会直接进入到add页面授权成功,但同时会有一个问题,也就是数据库中的所有的账户都可以访问页面,但实际上每个账户根据权限不同能够访问到的东西也不会相同。因此需要再数据库中添加权限字段。

优化:

数据库增加perms字段,并赋值,如图:

实体类中增加属性:

private String perms;

用户登录是在UserRealm中认证中查出来的,但认证查出来的东西怎么能在授权里面做?

//自定义UserRealm
public class UserRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("执行了=>授权doGetAuthorizationInfo");SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addStringPermission("user:add");//表示所有的用户访问add都可以//拿到当前登录的这个对象Subject subject = SecurityUtils.getSubject();//将下方认证中返回的 new SimpleAuthenticationInfo(user,user.getPwd(),"")的第一个Principal参数的值改为userUser currentUser = (User) subject.getPrincipal();//拿到user对象//设置当前用户的权限info.addStringPermission(currentUser.getPerms());return info;}//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("执行了=>认证doGetAuthorizationInfo");//将这里的token转换成封装的UsernamePasswordToken类型UsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken;//连接真实的数据库User user = userService.queryUserByName(userToken.getUsername());if(user==null){//没有这个人时return null;//抛出异常 UnknownAccountException}//可以加密  MD5  MD5盐值加密//密码认证,不用我们手动去验证,shiro会帮我们做return new SimpleAuthenticationInfo(user,user.getPwd(),"");}
}

此时虽然有授权,账户会被拦截,但是所有的账户仍然能够看到add和update项,正确的应该是什么样的权限可以看到什么样的界面,因此需要整合thymeleaf

16.6、shiro整合thymeleaf

16.6.1、导入依赖

<!--shiro-thymeleaf整合包-->
<dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version>
</dependency>

16.6.2、编写ShiroConfig

//整合ShiroDialect:用来整合shiro thymeLeaf
//由于没有spring配置文件,因此需要设置一个bean
@Bean
public ShiroDialect getShiroDialect(){return new ShiroDialect();
}

16.6.3、修改index.html

加入约束:

xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"

给add和update增加约束:

<div shiro:hasPermission="user:add"><a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update"><a th:href="@{/user/update}">update</a>
</div>

此时启动项目再访问首页会隐藏add和update项

16.6.4、在index.html中增加登录按钮,并且从session判断是否登录

<!--从session中判断:如果没有登录,就显示登录按钮,如果登录成功就不再显示登录按钮-->
<div th:if="${session.loginUser==null}"><a th:href="@{/toLogin}">登录</a>
</div>

16.6.5、编写UserRealm的认证方法:doGetAuthenticationInfo

Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("loginUser",user);

此时启动项目会发现首页只有登录按钮,点击登录以后会根据权限生成不同的页面。

16.7、shiro-springboot项目中的全部代码

ShiroConfig

@Configuration
public class ShiroConfig {//第三步:ShiroFilterFactoryBean@Bean//(name = "shiroFilterFactoryBean")public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){//因为下方getDefaultWebSecurityManager方法设置了bean的名称@Bean(name = "securityManager")//因此用@Qualifier("securityManager")绑定ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();//设置安全管理器bean.setSecurityManager(defaultWebSecurityManager);//添加shiro内置过滤器/*anon:无需认证就可以访问authc:必颈认证了才能访问user:必须拥有记往我功能才能用perms :拥有对某个资源的权限才能访问;role:拥有某个角色权限才能访问*/Map<String, String> filterMap = new LinkedHashMap<>();//授权 正常的情况下,没有授权会跳转到未授权页面filterMap.put("/user/add","perms[user:add]");filterMap.put("/user/update","perms[user:update]");//拦截filterMap.put("/user/*","authc");//所有user路径下的都会被拦截bean.setFilterChainDefinitionMap(filterMap);//设置登录的请求bean.setLoginUrl("/toLogin");//设置未授权页面bean.setUnauthorizedUrl("/noauth");return bean;}//第二步:DefaultWebSecurityManager@Bean(name = "securityManager")public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){//@Qualifier("userRealm")中userRealm指的是自定义的realm对象名DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//关联UserRealmsecurityManager.setRealm(userRealm);return securityManager;}//第一步:创建realm 对象,需要自定义@Beanpublic UserRealm userRealm(){return new UserRealm();}//整合ShiroDialect:用来整合shiro thymeLeaf//由于没有spring配置文件,因此需要设置一个bean@Beanpublic ShiroDialect getShiroDialect(){return new ShiroDialect();}
}

UserRealm

//自定义UserRealm
public class UserRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("执行了=>授权doGetAuthorizationInfo");SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addStringPermission("user:add");//表示所有的用户访问add都可以//拿到当前登录的这个对象Subject subject = SecurityUtils.getSubject();//将下方认证中返回的 new SimpleAuthenticationInfo(user,user.getPwd(),"")的第一个Principal参数的值改为userUser currentUser = (User) subject.getPrincipal();//拿到user对象//设置当前用户的权限info.addStringPermission(currentUser.getPerms());return info;}/*//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("执行了=>认证doGetAuthorizationInfo");//用户名,密码  正常是在数据库取,这里伪造一下String username = "root";String password = "123456";//将这里的token转换成封装的UsernamePasswordToken类型UsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken;//if(!userToken.getUsername().equals(username)){//当封装的用户名与我们设定的用户名不一致时return null;//抛出异常 UnknownAccountException}//密码认证,不用我们手动去验证,shiro会帮我们做return new SimpleAuthenticationInfo("",password,"");}*///认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("执行了=>认证doGetAuthorizationInfo");//将这里的token转换成封装的UsernamePasswordToken类型UsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken;//连接真实的数据库User user = userService.queryUserByName(userToken.getUsername());if(user==null){//没有这个人时return null;//抛出异常 UnknownAccountException}Subject subject = SecurityUtils.getSubject();Session session = subject.getSession();session.setAttribute("loginUser",user);//可以加密  MD5  MD5盐值加密//密码认证,不用我们手动去验证,shiro会帮我们做return new SimpleAuthenticationInfo(user,user.getPwd(),"");}
}

MyController

@Controller
public class MyController {@RequestMapping({"/","/index"})public String toIndex (Model model){model.addAttribute("msg","hello,Shiro");return "index";}@RequestMapping("/user/add")public String add(){return "user/add";}@RequestMapping("/user/update")public String update(){return "/user/update";}@RequestMapping("/toLogin")public String toLogin(){return "login";}@RequestMapping("/login")public String login(String username,String password,Model model){//获取当前的用户Subject subject = SecurityUtils.getSubject();//封装用户的登录信息UsernamePasswordToken token = new UsernamePasswordToken(username, password);try {subject.login(token);//执行登录方法,如果没有异常就说明OKreturn "index";//登录成功返回首页}catch (UnknownAccountException e){//用户名不存在model.addAttribute("msg","用户名错误");return "login";}catch (IncorrectCredentialsException e){//密码不存在model.addAttribute("msg","密码错误");return "login";}}@RequestMapping("/noauth")@ResponseBodypublic String unauthorized(){return "未经授权无法访问此页面";}
}

UserMapper、UserMapper.xml、User、UserService、UserServiceImpl、add.html、update.html、application.properties、application.yml一经写出就没有变化

login.html

<body>
<h1>登录</h1>
<hr><p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}"><p>用户名:<input type="text" name="username"></p><p>密码:<input type="text" name="password"></p><p><input type="submit"></p>
</form>
</body>

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>首页</h1><!--从session中判断:如果没有登录,就显示登录按钮,如果登录成功就不再显示登录按钮-->
<div th:if="${session.loginUser==null}"><a th:href="@{/toLogin}">登录</a>
</div>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add"><a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update"><a th:href="@{/user/update}">update</a>
</div>
</body>
</html>

17、开源项目

地址:https://github.com/WinterChenS/my-site

18、Swagger

学习目标:

  • 了解Swagger的作用和概念
  • 了解前后端分离
  • 在SpringBoot中集成Swagger

18.1、Swagger简介

前后端分离

  • 前端 -> 前端控制层、视图层
  • 后端 -> 后端控制层、服务层、数据访问层
  • 前后端通过API进行交互
  • 前后端相对独立且松耦合

产生的问题

  • 前后端集成,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发

解决方案

  • 首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险
  • 早些年:指定word计划文档;
  • 前后端分离:
    • 前端测试后端接口:postman
    • 后端提供接口,需要实时更新最新的消息及改动!

Swagger

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
  • 直接运行,在线测试API
  • 支持多种语言 (如:Java,PHP等)
  • 官网:https://swagger.io/

18.2、SpringBoot集成Swagger

SpringBoot集成Swagger => springfox,两个jar包

  • Springfox-swagger2
  • swagger-springmvc

使用Swagger

要求:jdk 1.8 + 否则swagger2无法运行

步骤:

18.2.1、新建一个SpringBoot-web项目

18.2.2、添加Maven依赖

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version>
</dependency>

18.2.3、编写HelloController,测试确保运行成功!

18.2.4、要使用Swagger,我们需要编写一个配置类-SwaggerConfig来配置 Swagger

@Configuration
@EnableSwagger2//开启Swagger
public class SwaggerConfig {}

18.2.5、访问测试 :http://localhost:8080/swagger-ui.html ,可以看到swagger的界面;

18.3、配置Swagger

18.3.1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger。

//配置docket以配置Swagger具体参数
@Bean
public Docket docket(){return new Docket(DocumentationType.SWAGGER_2);
}

18.3.2、可以通过apiInfo()属性配置文档信息

//配置swagger信息=apiInfo
public ApiInfo apiInfo(){// contact:联系人信息Contact contact = new Contact("联系人名字", "http://xxx.xxx.com/联系人访问链接", "联系人邮箱");return new ApiInfo("Swagger学习", // 标题"学习演示如何配置Swagger", // 描述"v1.0", // 版本"http://terms.service.url/组织链接", // 组织链接contact, // 联系人信息"Apach 2.0 许可", // 许可"许可链接", // 许可链接new ArrayList<>());// 扩展
}

18.3.3、Docket 实例关联上 apiInfo()

@Bean
public Docket docket(){return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}

4、重启项目,访问测试 http://localhost:8080/swagger-ui.html 看下效果;

18.4、配置扫描接口

1、构建Docket时通过select()方法配置怎么扫描接口。

@Bean
public Docket docket() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")).build();
}

2、重启项目测试,由于我们配置根据包的路径扫描接口,所以我们只能看到一个类

3、除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式:

@Bean
public Docket docket() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口//.any() // 扫描所有,项目中的所有接口都会被扫描到//.none() // 不扫描接口//.withMethodAnnotation(GetMapping.class)// 通过方法上的注解扫描,如只扫描get请求//.withClassAnnotation(Controller.class)// 通过类上的注解扫描,如只扫描有controller注解的类中的接口.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))// 根据包路径扫描接口.paths(PathSelectors.ant("/kuang/**"))// 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口.build();
}

PathSelectors这里的可选值还有:

​ PathSelectors
​ .any() // 任何请求都扫描
​ .none() // 任何请求都不扫描
​ .regex(final String pathRegex) // 通过正则表达式控制
​ .ant(final String antPattern) // 通过ant()控制

18.5、配置Swagger开关

1、通过enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了

@Bean
public Docket docket() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(false) //配置是否启用Swagger,如果是false,在浏览器将无法访问.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))// 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口.build();
}

2、如何动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示?

//配置docket以配置Swagger具体参数
@Bean
public Docket docket(Environment environment) {//设置要显示的Swagger环境Profiles profiles = Profiles.of("dev","test");//获取项目的环境//通过environment.acceptsProfiles判断是否处在自己设定的环境当中boolean flag = environment.acceptsProfiles(profiles);return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(flag) //enable是否启动Swagger,如果为False,则swagger不能在浏览器中访问
}

3、可以在项目中增加一个dev的配置文件查看效果!

application-dev.properties

server.port=8081

application-prod.properties

server.port=8082

启动项目,http://localhost:8081/swagger-ui.html可以访问,http://localhost:8082/swagger-ui.html则不能。

18.6、配置API分组

1、如果没有配置分组,默认是default。通过groupName()方法即可配置分组:

@Bean
public Docket docket(Environment environment) {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).groupName("hello") // 配置分组// 省略配置....
}

2、重启项目查看分组

3、如何配置多个分组?配置多个分组只需要配置多个docket即可:

@Bean
public Docket docket1(){return new Docket(DocumentationType.SWAGGER_2).groupName("group1");
}
@Bean
public Docket docket2(){return new Docket(DocumentationType.SWAGGER_2).groupName("group2");
}
@Bean
public Docket docket3(){return new Docket(DocumentationType.SWAGGER_2).groupName("group3");
}

4、重启项目查看即可

18.7、实体配置

1、新建一个实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("用户实体类")
public class User {@ApiModelProperty("用户名")public String username;@ApiModelProperty("密码")public String password;
}

2、只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中:

//只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger叶
@PostMapping("/user")
public User user(){return new User();
}

3、重启查看测试

注:并不是因为@ApiModel这个注解让实体显示在这里了,而是只要出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。

  • @ApiModel为类添加注释

  • @ApiModelProperty为类属性添加注释

18.8、常用注解

Swagger的所有注解定义在io.swagger.annotations包下

下面列一些经常用到的,未列举出来的可以另行查阅说明:

Swagger注解 简单说明
@Api(tags = “xxx模块说明”) 作用在模块类上
@ApiOperation(“xxx接口说明”) 作用在接口方法上
@ApiModel(“xxxPOJO说明”) 作用在模型类上:如VO、BO
@ApiModelProperty(value = “xxx属性说明”,hidden = true) 作用在类方法和属性上,hidden设置为true可以隐藏该属性
@ApiParam(“xxx参数说明”) 作用在参数、方法和字段上,类似@ApiModelProperty

我们也可以给请求的接口配置一些注释

@ApiOperation("狂神的接口")//不是放在类上,是放在方法上
@GetMapping("/kuang") //get请求在swagger上,必须@RequestParam才能输入参数
public String kuang(@RequestParam @ApiParam("这个名字会被返回")String username){return username;
}@ApiOperation("我的接口")//不是放在类上,是放在方法上
@PostMapping("/postt")
public User postt(@ApiParam("用户")User user){return user;
}

这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!

相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。

Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。

总结:

​ 1.我们可以通过Swagger给一些比较难理解的属性或者接口,增加注释信息

​ 2.接口文档实时更新

​ 3.可以在线测试

19、异步、定时、邮件任务

19.1、异步任务

19.1.1、创建一个service包

19.1.2、创建一个类AsyncService

异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。

编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;

@Service
public class AsyncService {public void hello(){try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("业务进行中....");}
}

19.1.3、编写controller包

19.1.4、编写AsyncController类

我们去写一个Controller测试一下

@RestController
public class AsyncController {@AutowiredAsyncService asyncService;@GetMapping("/hello")public String hello(){asyncService.hello();return "success";}
}

19.1.5、访问http://localhost:8080/hello进行测试,3秒后出现success,这是同步等待的情况。

问题:我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:

19.1.6、给hello方法添加@Async注解;

//告诉Spring这是一个异步方法
@Async
public void hello(){try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("业务进行中....");
}

SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;

@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootTaskApplication {public static void main(String[] args) {SpringApplication.run(SpringbootTaskApplication.class, args);}}

19.1.7、重启测试,网页瞬间响应,后台代码依旧执行!

19.2、邮件任务

邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持

  • 邮件发送需要引入spring-boot-start-mail
  • SpringBoot 自动配置MailSenderAutoConfiguration
  • 定义MailProperties内容,配置在application.yml中
  • 自动装配JavaMailSender
  • 测试邮件发送

测试:

19.2.1、引入pom依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId>
</dependency>

看它引入的依赖,可以看到 jakarta.mail

<dependency><groupId>com.sun.mail</groupId><artifactId>jakarta.mail</artifactId><version>1.6.4</version><scope>compile</scope>
</dependency>

19.2.2、查看自动配置类:MailSenderAutoConfiguration

这个类中存在bean,JavaMailSenderImpl

然后我们去看下配置文件

@ConfigurationProperties(prefix = "spring.mail"
)
public class MailProperties {private static final Charset DEFAULT_CHARSET;private String host;private Integer port;private String username;private String password;private String protocol = "smtp";private Charset defaultEncoding;private Map<String, String> properties;private String jndiName;
}

19.2.3、配置文件:

spring.mail.username=24736743@qq.com
spring.mail.password=你的qq授权码
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true

获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务

19.2.4、Spring单元测试

@Autowired
JavaMailSenderImpl mailSender;@Test
public void contextLoads() {//邮件设置1:一个简单的邮件SimpleMailMessage message = new SimpleMailMessage();message.setSubject("通知-明天来狂神这听课");message.setText("今晚7:30开会");message.setTo("24736743@qq.com");message.setFrom("24736743@qq.com");mailSender.send(message);
}@Test
public void contextLoads2() throws MessagingException {//邮件设置2:一个复杂的邮件MimeMessage mimeMessage = mailSender.createMimeMessage();MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);helper.setSubject("通知-明天来狂神这听课");helper.setText("<b style='color:red'>今天 7:30来开会</b>",true);//发送附件helper.addAttachment("1.jpg",new File(""));helper.addAttachment("2.jpg",new File(""));helper.setTo("24736743@qq.com");helper.setFrom("24736743@qq.com");mailSender.send(mimeMessage);
}

查看邮箱,邮件接收成功!

我们只需要使用Thymeleaf进行前后端结合即可开发自己网站邮件收发功能了!

19.3、定时任务

项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。

  • TaskExecutor接口
  • TaskScheduler接口

两个注解:

  • @EnableScheduling
  • @Scheduled

19.3.1、cron表达式:

19.3.1.1、域取值:

下表为Cron表达式中六个域能够取的值以及支持的特殊字符。

是否必需 取值范围 特殊字符
[0, 59] * , - /
分钟 [0, 59] * , - /
小时 [0, 23] * , - /
日期 [1, 31] * , - /? L W
月份 [1, 12]或[JAN, DEC] * , - /
星期 [1, 7]或[MON, SUN]。若您使用[1, 7]表达方式,1代表星期一,7代表星期日。 * , - / ? L #
[当前年份,2099] * , - /
19.3.1.2、特殊字符

Cron表达式中的每个域都支持一定数量的特殊字符,每个特殊字符有其特殊含义。

特殊字符 含义 示例
* 所有可能的值。 在月域中,*表示每个月;在星期域中,*表示星期的每一天。
, 列出枚举值。 在分钟域中,5,20表示分别在5分钟和20分钟触发一次。
- 范围。 在分钟域中,5-20表示从5分钟到20分钟之间每隔一分钟触发一次。
/ 指定数值的增量。 在分钟域中,0/15表示从第0分钟开始,每15分钟。在分钟域中3/20表示从第3分钟开始,每20分钟。
? 不指定值,仅日期和星期域支持该字符。 当日期或星期域其中之一被指定了值以后,为了避免冲突,需要将另一个域的值设为?
L 单词Last的首字母,表示最后一天,仅日期和星期域支持该字符。 在日期域中,L表示某个月的最后一天。在星期域中,L表示一个星期的最后一天,也就是星期日(SUN)。 如果在L前有具体的内容,例如,在星期域中的6L表示这个月的最后一个星期六。
W 除周末以外的有效工作日,在离指定日期的最近的有效工作日触发事件。W字符寻找最近有效工作日时不会跨过当前月份,连用字符LW时表示为指定月份的最后一个工作日。 在日期域中5W,如果5日是星期六,则将在最近的工作日星期五,即4日触发。如果5日是星期天,则将在最近的工作日星期一,即6日触发;如果5日在星期一到星期五中的一天,则就在5日触发。
# 确定每个月第几个星期几,仅星期域支持该字符。 在星期域中,4#2表示某月的第二个星期四。
19.3.1.3、取值示例

以下为Cron表达式的取值示例.

示例 说明
0 15 10 ? * * 每天上午10:15执行任务
0 15 10 * * ? 每天上午10:15执行任务
0 0 12 * * ? 每天中午12:00执行任务
0 0 10,14,16 * * ? 每天上午10:00点、下午14:00以及下午16:00执行任务
0 0/30 9-17 * * ? 每天上午09:00到下午17:00时间段内每隔半小时执行任务
0 * 14 * * ? 每天下午14:00到下午14:59时间段内每隔1分钟执行任务
0 0-5 14 * * ? 每天下午14:00到下午14:05时间段内每隔1分钟执行任务
0 0/5 14 * * ? 每天下午14:00到下午14:55时间段内每隔5分钟执行任务
0 0/5 14,18 * * ? 每天下午14:00到下午14:55、下午18:00到下午18:55时间段内每隔5分钟执行任务
0 0 12 ? * WED 每个星期三中午12:00执行任务
0 15 10 15 * ? 每月15日上午10:15执行任务
0 15 10 L * ? 每月最后一个星期六上午10:15执行任务
0 15 10 ? * 6#3 每月第三个星期六上午10:15执行任务
0 10,44 14 ? 3 WED 每年3月的每个星期三下午14:10和14:44执行任务
0 15 10 ? * * 2022 2022年每天上午10:15执行任务
0 15 10 ? * * * 每年每天上午10:15执行任务
0 0/5 14,18 * * ? 2022 2022年每天下午14:00到下午14:55、下午18:00到下午18:55时间段内每隔5分钟执行任务
0 15 10 ? * 6#3 2022,2023 2022年至2023年每月第三个星期六上午10:15执行任务
0 0/30 9-17 * * ? 2022-2025 2022年至2025年每天上午09:00到下午17:30时间段内每隔半小时执行任务
0 10,44 14 ? 3 WED 2022/2 从2022年开始,每隔两年3月的每个星期三下午14:10和14:44执行任务

测试步骤:

19.3.2、创建一个ScheduledService

我们里面存在一个hello方法,他需要定时执行,怎么处理呢?

@Service
public class ScheduledService {//秒   分   时     日   月   周几//0 * * * * MON-FRI//注意cron表达式的用法;@Scheduled(cron = "0 * * * * 0-7")public void hello(){System.out.println("hello.....");}
}

19.3.3、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能

@EnableAsync //开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {public static void main(String[] args) {SpringApplication.run(SpringbootTaskApplication.class, args);}
}

启动测试即可!

springboot学习(下)相关推荐

  1. SpringBoot的全局异常处理的优雅吃法!要进来学习下吗

    SpringBoot的全局异常处理的优雅吃法!要进来学习下吗 SpringBoot全局异常准备 开发准备 环境要求 JDK :1.8 SpringBoot :1.5.17.RELEASE 首先还是Ma ...

  2. spring-boot学习资料

    spring-boot: http://www.ityouknow.com/spring-boot 这里的内容都可以学习下: https://zhuanlan.zhihu.com/dreawer?to ...

  3. SpringBoot学习笔记(3):静态资源处理

    SpringBoot学习笔记(3):静态资源处理 在web开发中,静态资源的访问是必不可少的,如:Html.图片.js.css 等资源的访问. Spring Boot 对静态资源访问提供了很好的支持, ...

  4. SpringBoot学习笔记(4)----SpringBoot中freemarker、thymeleaf的使用

    1. freemarker引擎的使用 如果你使用的是idea或者eclipse中安装了sts插件,那么在新建项目时就可以直接指定试图模板 如图: 勾选freeMarker,此时springboot项目 ...

  5. SpringBoot学习笔记(16):单元测试

    SpringBoot学习笔记(16):单元测试 单元测试 单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作.程序单元是应用的最小 ...

  6. SpringBoot学习笔记(9)----SpringBoot中使用关系型数据库以及事务处理

    在实际的运用开发中,跟数据库之间的交互是必不可少的,SpringBoot也提供了两种跟数据库交互的方式. 1. 使用JdbcTemplate 在SpringBoot中提供了JdbcTemplate模板 ...

  7. SpringBoot学习笔记(16)----SpringBoot整合Swagger2

    Swagger 是一个规范和完整的框架,用于生成,描述,调用和可视化RESTful风格的web服务 http://swagger.io Springfox的前身是swagger-springmvc,是 ...

  8. SpringBoot学习笔记(8):事物处理

    SpringBoot学习笔记(8):事物处理 快速入门 在传统的JDBC事务代码开发过程中,业务代码只有一部分,大部分都是与JDBC有关的功能代码,比如数据库的获取与关闭以及事务的提交与回滚.大量的t ...

  9. SpringBoot 学习二:操作数据库

    2019独角兽企业重金招聘Python工程师标准>>> 本文将从以下几个方面介绍: 前言 配置数据源 SpringBoot 整合 Mybatis SpringBoot 整合 Jdbc ...

  10. SpringBoot学习历程(十一):SpringBoot2.X集成mail发送邮件

    SpringBoot学习历程(十一):SpringBoot2.X集成mail发送邮件 前言 1. 引入依赖 2. 设置邮件配置信息 3. 发送邮件 3.1 发送普通文本邮件 3.2 发送HTML格式内 ...

最新文章

  1. 法总统:英国若“无协议脱欧” 将成最大输家
  2. 【C 语言】文件操作 ( 文件加密解密 | 加密解密原理 | 对称加密 | 非对称加密 | 散列函数 )
  3. 整理Java相关的工具类Utils,持续更新中,建议收藏【目前更新至24】
  4. SAP S/4HANA product search implementation debug in backend
  5. sql 嵌套select与关联select
  6. 一家踏实做产品,在疫情下销售增速仍达35%的公司,年报长啥样?
  7. 6-2-1:STL之string——string的快速入门、常用接口
  8. You Only Watch Once:实时人体动作定位网络
  9. PTA c语言 数组元素循环右移问题
  10. 半个月可以写出一个电脑操作系统吗?
  11. 老外的一个用的SQLite的例子,看起来比较初级
  12. PJAX,站点加速之翼
  13. 数据驱动的物流网络体系
  14. 2022年8月22日 暑假第六周总结
  15. windows对图片进行标记处理等操作
  16. Unity平行光源方向_WorldSpaceLightPos0自定义
  17. l33t-hoster .htaccess \x00注释putenv绕过disable_function计算c代码
  18. canvas——绘制文字
  19. ubuntu lotus testnet-staging
  20. Cocos2d-x开发实例:使用Lambda 表达式

热门文章

  1. iOS弱引用表 SideTable weak_table_t weak_entry_t
  2. 豆瓣上的一条关于泰坦尼克的影评1
  3. v全球与中国男士西装市场经营规模分析与投资前景研究报告2022年版
  4. HC-SR501红外人体传感模块封锁时间调整
  5. win7自动登录(win7自动登录不用输入密码)
  6. Android Things在 #io17
  7. 网站设计65条原则 作者:小柯
  8. Approaching (Almost) Any Machine Learning Problem
  9. Win8.1屏幕亮度自动调节关闭方法
  10. docker安装dejavu