【SpringBoot】的学习

目录

简介:

01.Spring Boot 入门程序

​​02.常用注解【spring的java配置】

@Controller   --控制器

传参方式

03. Spring Boot热部署

04.Spring Boot的自动配置原理以及启动分析(难点)

 点进去spring-boot-starter-parent以后这依赖管理里面写和很从版本

 05.Spring Boot的两种配置文件语法【重中之重】

 06.@Value读取配置文件以及验证

07.Profile配置文件详解

7.1为什么要使用profiles

 08.配置文件加载优先级和外部配置文件

09.自动配置原理以及 @Conditional派生注解

 10. 整合logback

11.AOP开发 

12.WEB静态资源访问规则

 13.Thymeleaf模板的使用

14. 内嵌tomcat加载原理分析

15.启动内嵌jetty服务器【了解】

16.注册Web三大组件【重点】

17.数据源配置和自动管理【重中之中】

18.集成JdbcTemplate【熟悉】

19.集成mybatis【重点】

 20.集成mybatisplus【重点】

21.集成swagger3.0【熟悉】

22.集成shiro非前后端分离【重点】

23.集成shiro前后端分离

24.普通缓存(非redis)

25.Spring Boot定时任务 

 26.Spring Boot邮件发送


简介:

 Spring SpringMVC和SpringBoot 区别

1、Spring

Spring是一个开源容器框架,可以接管web层,业务层,dao层,持久层的组件,并且可以配置各种bean,和维护bean与bean之间的关系。其核心就是控制反转(IOC),和面向切面(AOP),简单的说就是一个分层的轻量级开源框架。

2、SpringMVC

Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。SpringMVC是一种web层mvc框架,用于替代servlet(处理|响应请求,获取表单参数,表单校验等。SpringMVC是一个MVC的开源框架,SpringMVC=struts2+spring,springMVC就相当于是Struts2加上Spring的整合。

3、SpringBoot

Springboot是一个微服务框架,延续了spring框架的核心思想IOC和AOP,简化了应用的开发和部署。Spring Boot是为了简化Spring应用的创建、运行、调试、部署等而出现的,使用它可以做到专注于Spring应用的开发,而无需过多关注XML的配置。提供了一堆依赖打包,并已经按照使用习惯解决了依赖问题—>习惯大于约定

区别与总结
1.简单理解为:Spring包含了SpringMVC,而SpringBoot又包含了Spring或者说是在Spring的基础上做得一个扩展。

spring mvc < spring < springboot

2、Spring Boot 对比Spring的一些优点包括:

提供嵌入式容器支持

使用命令java -jar独立运行jar

在外部容器中部署时,可以选择排除依赖关系以避免潜在的jar冲突

部署时灵活指定配置文件的选项

用于集成测试的随机端口生成

3、结论

Spring Boot只是Spring本身的扩展,使开发,测试和部署更加方便。

01.Spring Boot 入门程序

创建第一个springboot项目

 

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

这个spring-boot-starter-web 是web的启动器,封装了springmv所需要的jar包


 创建一个controller

启动测试

 启动项目访问http://localhost:8080/index/hello

 

 ​​​​​jar包启动测试,先打包

​ 

 测试访问

 

 项目结构

 彩蛋

spring Boot启动的时候会有一个默认的启动图案。

在src/main/resources路径下新建一个banner.txt文件,并输入想要的内容即可

02.常用注解【spring的java配置】

类上面的注解:

@Controller   --控制器

@RestController   --返回json的控制器 =Controller  +ResponseBody

@Service --标记服务接口

@Respority --标记Dao接口

@Component --标记组件

@RequestMapping --请求映射 (也可在方法上)

方法上的注解:

|--GetMapping

|--PostMapping

 |--DeleteMapping

|--PutMapping

|--PatchMapping

@ResponseBody --返回JSON对象

参数上的注解:

@RequestBody --入参是JSON对象

@PathVariable(value=””) --将路径上面的参数映射到入参里面

http://127.0.0.1:8080/car/user/1

@RequestParam   如果Controller里面使用map接收参数

|--如果controller不知道页面要传多少参数,那么可以使用Map<String,String>去接收。 必须加@RequestParam的注解

属性上面的注解

@Autowried --自动注入(首选按照类型)  byName byType

@Resource --自动注入(首选按照名字)

传参方式

http://127.0.0.1:8080/car/user?id=1
http://127.0.0.1:8080/car/user/1    加@pathvarible注解

 @Configuration

作用于类上,相当于一个xml配置文件;|--application-dao.xml

@Bean  作用于方法上,相当于xml配置中的<bean>

​ 

@Qualifier注解 

qualifier的意思是合格者,通过这个标示,表明了哪个实现类才是我们所需要的,我们修改调用代码,添加@Qualifier注解,需要注意的是@Qualifier的参数名称必须为我们之前定义@Bean注解的名称之一

 @Primary 主候选的

当IOC容器里面有多个同类型的对象时,就会发生冲突,标注了该注解的就作为主候选对象

​ 

03. Spring Boot热部署

 什么是热部署

spring为开发者提供了一个名为spring-boot-devtools的模块来使springboot应用支持热部署,提高开发的效率,修改代码后无需重启应用

<!--添加热部署的依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <!--运行时生效-->
    <scope>runtime</scope>
    <!--依赖是否会传递,为true则不会传递-->
    <optional>true</optional>
</dependency>

配置idea的启动面板

如果不配置面板,那么可直接使用ctrl+F9去刷新

  

04.Spring Boot的自动配置原理以及启动分析(难点)

4.​​​​​​1.pom的依赖分析

<!-- 继承springboot的父项目 -->
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

 点进去spring-boot-starter-parent以后这依赖管理里面写和很从版本

 <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.6.4</version>
  </parent>

 这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了

4.2.启动器 spring-boot-starter

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

springboot-boot-starter-xxx:就是spring-boot的场景启动器

<!-- 引入了spring-boot-starter-web以后,springboot就帮我们导入了web模块运行的所有组件 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ,也可以自己自定义 starter;

4.3原理分析

  1. 为什么启动类所在的所及其子包能被默认扫描
  2. 为什么引入spring-boot-starter-web之后能直接集成springmvc

注解功能划分

如何自动扫描(默认扫描启动类所在的包以下面的及子包)

@Import(AutoConfigurationPackages.Registrar.class)

AutoConfigurationPackages中registerBeanDefinitions

 如图:

 疑问:这是springmvc中的写法,我们要这么用,必须配置相关xml

为什么引入spring-boot-starter-web之后能直接集成springmvc

回顾SSM的配置:

  1. 包扫描
  2. 注解
  3. 配置适配器
    1. Contoller的实方式
      • 实现Controller接口
      • 实现HttpRequestHandler
      • 使用注解
        1. Controller
        2. RestController
    2. 总结,就是为了适配Controller的不同实现方式
  4. 配置映射器
    1. 处理用户请求地址和Controller里方法的映射关系的
  5. 配置视图解释器
    1. org.springframework.web.servlet.view.InternalResourceViewResolver
  6. 配置拦截器
    1. 拦截请求 Inteceptor
  7. 配置文件上传管理器
    1. <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
  8. 配置前端控制器

springmvc.xml ​

 web.xml

是因为上面配置是springboot帮我们做了

如何加载自动配置类

找到@Import(AutoConfigurationImportSelector.class)

在这个类里面的getAutoConfigurationEntry()方法

springboot在启动的时候默认加载了所有的配置 

进入getCandidateConfigurations()方法查看究竟

这个方法是spring的加载工厂去筛选所有引入(link)EnableAutoConfiguration的配置类

EnableAutoConfiguration是Key 返回的List<String>是vlaue

接着进入loadFactoryNames()方法

 最后回到getAutoConfigurationEntry()方法里面往下执行

剔除不需要的(pom里面没有引入starter的自动配置类)

至此自动加载配置类的初探就结束了

(从加载到的配置类中选讲)如何加载前端控制器,在ssm里面,我们需要手动去创建DispatcherServlet对象,然后注入到Servlet容器中,在springboot里面,已经帮我们自动配置了

 查看DispatcherServletAutoConfiguration这个自动配置类

 创建DispatcherServlet对象

 设置servlet的名称,loadonstart 启动时加载  路径

Springboot的run方法到底执行了什么

Springboot默认提供了哪些候选的starter

https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter

 05.Spring Boot的两种配置文件语法【重中之重】

引入依赖

<!--添加配置文件前缀的依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

创建Hero 类

properties方式:

hero.id=2
hero.name=LJH
hero.age=18
hero.birth=2012/12/31
hero.hobby=LOL,DNF,CF
hero.list=C#,Python,PS
hero.set=football,basketball,swim
hero.map.k1=v1
hero.map.k2=v2

 yml方式:

hero:
  id: 1
  age: 18
  name: 超人
  birth: 2012/12/31
  hobby:
    - LOL
    - DNF
    - CF
  list:
    - JAVA
    - JS
    - C++
  set:
    - 足球
    - 篮球
    - 排球
  map:
    k1: v1
    k2: v2

 测试

 配置文件取值

hero.name=${hero.age}

两种配置文件的用法说明

  1. 如果properties里面配置了就不会去yml里面去取值,如果没有配置就会去yml里面去取
  2. 两种配置文件是互补的存在

注入对象 

 

 06.@Value读取配置文件以及验证

总结说明 

1,@Value只能注入普通的属性[也就是基本数据类型和String] 其它的复杂类型是不能取到值的[如果yaml配置是array:LOL,DNF]这样的配置是可以取 或者在perpeties里面配置

2,如果属性是使用驼峰命名法则不能使用属性名注入,要使用@Value("${hero.class-name}")来取值

不能使用@Value("${hero.className}")来取值

 ​​​​​​​@Value和@ConfigurationProperties取值比较

注解验证 

引入依赖

<!--引入注解验证的依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

 

 ​​​​​​​常用的验证注解

@NotNull --不能为null,但可以空着不写 (name:)

@NotEmpty --不能为空,也不能为null,但可以是空格 “  ”

@NotBlank --不能为空格,也不能为null,也不能为空格

@Min     最大值

@Max    最小值

@Size(min = 1, max = 6)   长度限制

@Range(min = 1,max = 2)   范围限制

@Pattern(regexp"[0,1]{1}")   正则限制

全局异常

package com.bjsxt.exception;

import com.bjsxt.vo.ResultObj;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = {BindException.class})
    public ResultObj bindException(BindException e){
        BindingResult result = e.getBindingResult();
        return getResultObj(result);
    }

    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    public ResultObj methodArgumentNotValidException(MethodArgumentNotValidException e){
        BindingResult result = e.getBindingResult();
        return getResultObj(result);
    }

    private ResultObj getResultObj(BindingResult result) {
        List<Map<String,Object>> resList=new ArrayList<>();
        //取出errors
        List<ObjectError> allErrors = result.getAllErrors();
        for (ObjectError allError : allErrors) {
            //取出对象名
            String objectName = allError.getObjectName();
            String defaultMessage = allError.getDefaultMessage();
            FieldError fieldError= (FieldError) allError;
            String field = fieldError.getField();
            Map<String,Object> map=new HashMap<>();
            map.put("objectName",objectName);
            map.put("field",field);
            map.put("defaultMessage",defaultMessage);
            resList.add(map);
        }
        return new ResultObj(400,"数据验证不通过",resList);
    }
}

07.Profile配置文件详解​​​​​​​

7.1为什么要使用profiles

 在开发中,一般有两种环境

        1,生产环境 [项目上线,客户在使用中,就是生产环境]

        2,开发环境 [就是开发环境,自己开发测试的环境]

有时候开发环境和生产环境的配置方法是不一样的,那么如何快速的切换呢,这里就要使用profiles文件

​​​​​​​7.2使用方法

​​​​​​​创建application.yml

创建application-dev.yml

创建application-pro.yml

​​​​​​​运行测试

​​​​​​​总结

在application.yml的主配置文件中,激活哪个配置文件,就会使用该配置文件进行运行

 08.配置文件加载优先级和外部配置文件

​​​​​​​8.1项目内部配置文件

spring boot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件

其中同一目录下的properties文件的优先级大于yml文件

​​​​​​​配置文件可以放的位置和优先级

classpath:/           --优先级4        默认放的位置

classpath:/config/    --优先级3

file:./                 --优先级2            跟jar包同级别的目录

file:./config/          --优先级1

 测试前两个

 测试前三个

先打包

启动jar包运行

 在jar包同级路面建config包,里面application.yml端口为8004

启动测试:

Win10 下关闭已运行jar包的方法,本文直接查找端口杀之

查找端口命令    netstat -aon|findstr “8081”

使用命令杀掉:taskkill /pid 8080 -f 杀掉端口 

​​​​​​​查看ConfigFileApplicationListener

8.2外部的配置文件 

在D盘放一个application.yml文件 端口指定为8009

java -jar 01springboot-hello-0.0.1-SNAPSHOT.jar --spring.config.location=D:/application.yml

09.自动配置原理以及 @Conditional派生注解

9.1​​​​​​​自动配置

我们就了解到了Spring Boot在启动的时候:

  • 去扫描加载了所有引入依赖的jar包下面的MATE-INF/spring.factories文件,
  • 然后通过EnableAutoConfiguration为key,下面所有自动配置类的全限定类名作为value,(List<String>)
  • 经过筛选排除掉不符合要求的(pom中没有添加依赖的配置类)
  • 最后把(List<String>:符合条件的自配配置类的全限定类名)添加到IOC容器去管理,从而实现了自动配置原理

@Conditional派生注解

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置类里面的所有内容才生效;(条件之间是并且的关系)

 10. 整合logback

10.1​​​​​​​​​​​​​​概述

在搭建新的系统时候必不可少的是需要日志的,日志的作用就不用多说了吧,可以用来调试程序,记录程序运行的状态,最重要的是可以用来排查线上的问题。

那我们该如何在项目中使用日志呢?

SpringBoot内部集成了LogBack日志依赖,SpringBoot默认使用LogBack记录日志信息,默认根据base.xml配置内容来输出到控制台和文件之中,这是默认的配置不能达到企业级项目的要求

 创建logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration  scan="true" scanPeriod="10 seconds">

    <!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->

    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="D:/mylogs/" />

    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <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="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!--输出到文件-->

    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_debug.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}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录debug级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_info.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>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_warn.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}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_error.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}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
        以及指定<appender>。<logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
              如果未设置此属性,那么当前logger将会继承上级的级别。
        addtivity:是否向上级logger传递打印信息。默认是true。
    -->
    <!--<logger name="org.springframework.web" level="info"/>-->
    <!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
     -->


    <!--
        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        不能设置为INHERITED或者同义词NULL。默认是DEBUG
        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
    -->

    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <logger name="com.nmys.view" level="debug"/>
    </springProfile>

    <root level="info">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DEBUG_FILE" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="WARN_FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>

    <!--生产环境:输出到文件-->
    <!--<springProfile name="pro">-->
    <!--<root level="info">-->
    <!--<appender-ref ref="CONSOLE" />-->
    <!--<appender-ref ref="DEBUG_FILE" />-->
    <!--<appender-ref ref="INFO_FILE" />-->
    <!--<appender-ref ref="ERROR_FILE" />-->
    <!--<appender-ref ref="WARN_FILE" />-->
    <!--</root>-->
    <!--</springProfile>-->

</configuration>

创建启动类测试

 ​​​​​​​查看本地日志

11.AOP开发 

​​​​​​​11.1概述

aop是spring的两大功能模块之一,功能非常强大,为解耦提供了非常优秀的解决方案。SpringBoot集成aop是非常方便的,下面使用aop来拦截业务组件的方法

Aop的作用:在不修改源代码的情况下,对类里面的方法进行增强

(前置,后置,环绕,异常)

​​​​​​​11.2使用方法​​​​​​​

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

创建Man测试

@Component
public class TestAop {
    public void eat(String food){
       // int a = 10/0;
        System.out.println("吃"+food);

    }
}

 创建切面类

package com.wxz.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * TODO
 *
 * @author wxz
 * @date 2022/3/2014:16
 */
@Component
@Aspect
public class TestAspect {

    public static final String pc ="execution(* com.wxz.domain.*.*(..))";

   // @Before(pc)
    public  void before(){
        System.out.println("饭前来点水果");
    }
   // @After(pc)
    public  void after(){
        System.out.println("饭后来一把");
    }
    @Around(pc)
    public Object around(ProceedingJoinPoint point){
        before();
        Object[] args = point.getArgs();
        String game = args[0].toString();
        System.out.println(game);
        //执行目标方法,获得返回值
        Object proceed = null;
        try {
            proceed = point.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        after();
        //返回出去
        return proceed;
    }

    @AfterThrowing(value = pc, throwing = "tw")
    public void afterThrowing(Throwable tw) {
        System.out.println("出现异常" + tw.getMessage());
    }

}

12.WEB静态资源访问规则

  • classpath:META-INF/resources   优先级最高
  • classpath:resources   其次
  • classpath:static
  • classpath:public

查看WebMvcAutoConfiguration里面的静态类WebMvcAutoConfigurationAdapter

关于资源管的方法addResourceHandlers

如果这四个目录下面都有相同的文件,那么访问的优先级为:

META-INF/resources>resources>static>public

 13.Thymeleaf模板的使用

​​​​​​​Thymeleaf概述

 Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP 。相较与其他的模板引擎,它有如下三个极吸引人的特点:

1、Thymeleaf 在有网络和无网络的环境下皆可

运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。

2、Thymeleaf 开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、改jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。

3、Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能

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

Spring Boot项目Thymeleaf模板页面存放位置

查看ThymeleafAutoConfiguration自动配置类,找到模板存放的位置 

 

 禁用缓存

 templates目录不能直接访问,需要controller进行页面跳转

 测试访问:

 Thymeleaf的相关语法:​​​​​​​

1,简单表达式   

  1、变量的表达式:${...}   取作用域里面的值(request,session,applicationContext)
  2、选择变量表达式:*{...}
  3、信息表达式:#{...}    取IOC容器里面的值  
  4、链接URL表达式:@{...}   <a href="user/query.action">       <a th:href="@{user/query.action}"
2,字面值  th:text
  1、文本文字:'one text', 'Another one!',…
  2、文字数量:0, 34, 3.0, 12.3,…
  3、布尔型常量:true, false
  4、空的文字:null
  5、文字标记:one, sometext, main,…
3,文本处理
  1、字符串并置:+
  2、文字替换:|The name is ${name}|
4,表达式基本对象
  1、#ctx:上下文对象
  2、#vars:上下文变量
  3、#locale:上下文语言环境
  4、#httpServletRequest:(只有在Web上下文)HttpServletRequest对象   
  5、#httpSession:(只有在Web上下文)HttpSession对象。              
用法:<span th:text="${#locale.country}">US</span>.

5,实用工具对象 
#dates: java.util的实用方法。对象:日期格式、组件提取等.
#calendars:类似于#日期,但对于java.util。日历对象
#numbers:格式化数字对象的实用方法。
#strings:字符串对象的实用方法:包含startsWith,将/附加等。

 Thymeleaf读取Model里面的对象

创建类Hero:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hero {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
    private String country;
    private String phone;
    private Date birth;
    private Double salary;
}
@RequestMapping("helloHero")
public String helloHero(Model model){
    Hero hero=new Hero(1,"后羿","男",18,"中国","110",new Date());
    model.addAttribute("hero",hero);
    return "showHero";
}

修改页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--<link rel="stylesheet" href="/resources/layui/css/layui.css">-->
    <link rel="stylesheet" th:href="@{/resources/layui/css/layui.css}">
</head>
<body>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
    <legend>英雄面板</legend>
</fieldset>
<div style="padding: 20px; background-color: #F2F2F2;">
    <div class="layui-row layui-col-space15">
        <div class="layui-col-md3">
            <div class="layui-card">
                <span >英雄id</span> <span th:text="${hero.id}"></span> <br>
                <span>英雄名字</span> <span th:text="${hero.name}"></span> <br>
                <span>英雄性别</span> <span th:text="${hero.sex}"></span> <br>
                <span>英雄年龄</span> <span th:text="${hero.age}"></span> <br>
                <span>英雄年龄</span> <span th:text="${#numbers.formatDecimal(hero.age,0,2)}"></span> <br>
                <span>英雄国家</span> <span th:text="${hero.country}"></span> <br>
                <span>英雄电话</span> <span th:text="${hero.phone}"></span> <br>
                <span>英雄生日</span> <span th:text="${hero.birth}"></span> <br>
                <span>英雄生日</span> <span th:text="${#dates.format(hero.birth,'yyyy-MM-dd')}"></span> <br>
            </div>
        </div>
    </div>
</div>
</body>
<script th:src="@{/resources/layui/layui.js}" ></script>
</html>

测试访问

 Thymeleaf读取Model里面的集合

@RequestMapping("helloHeroList")
public String helloHeroList(Model model){
    List<Hero> list=new ArrayList<>();
    for (int i = 1; i <=5 ; i++) {
        list.add(new Hero(i,"后羿"+i,"男",18,"中国","110",new Date()))
    }
    model.addAttribute("heros",list);
    return "showHeroList";
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--<link rel="stylesheet" href="/resources/layui/css/layui.css">-->
    <link rel="stylesheet" th:href="@{/resources/layui/css/layui.css}">
</head>
<body>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
    <legend>英雄面板</legend>
</fieldset>
<div style="padding: 20px; background-color: #F2F2F2;">
    <div class="layui-row layui-col-space15">
        <div class="layui-col-md3"  th:each="hero:${heros}">
            <div class="layui-card">
                <span>英雄id</span> <span th:text="${hero.id}"></span> <br>
                <span>英雄名字</span> <span th:text="${hero.name}"></span> <br>
                <span>英雄性别</span> <span th:text="${hero.sex}"></span> <br>
                <span>英雄年龄</span> <span th:text="${hero.age}"></span> <br>
                <span>英雄年龄</span> <span th:text="${#numbers.formatDecimal(hero.age,0,2)}"></span> <br>
                <span>英雄国家</span> <span th:text="${hero.country}"></span> <br>
                <span>英雄电话</span> <span th:text="${hero.phone}"></span> <br>
                <span>英雄生日</span> <span th:text="${hero.birth}"></span> <br>
                <span>英雄生日</span> <span th:text="${#dates.format(hero.birth,'yyyy-MM-dd')}"></span> <br>
            </div>
        </div>
    </div>
</div>
</body>
<script th:src="@{/resources/layui/layui.js}" ></script>
</html>

 测试访问

 ThymeleafObjects的使用

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--<link rel="stylesheet" href="/resources/layui/css/layui.css">-->
    <link rel="stylesheet" th:href="@{/resources/layui/css/layui.css}">
</head>
<body>
model:<div th:text="${name}"></div><hr>
request:<div th:text="${#httpServletRequest.getAttribute('name')}"></div><hr>
session:<div th:text="${#httpSession.getAttribute('name')}"></div><hr>
session:<div th:text="${session.name}"></div><hr>
servletContext:<div th:text="${#servletContext.getAttribute('name')}"></div><hr>
<div  th:text="${#locale.getCountry()}"></div>
<div  th:text="${#locale.getLanguage()}"></div>


</body>
<script th:src="@{/resources/layui/layui.js}" ></script>
</html>

14. 内嵌tomcat加载原理分析

我们在使用springboot项目的时候并没有使用外部的tomcat,那么springboot是如何帮我们管理内置的服务器的呢?

​​​​​​​查看启动原理

查看ServletWebServerFactoryAutoConfiguration

发现包含了3个内嵌的服务器,默认是使用了内嵌的tomcat

点击EmbeddedTomcat 进入

继续往下走查看TomcatServletWebServerFactory

点击getTomcatWebServer

 点击TomcatWebServer

15.启动内嵌jetty服务器【了解】

在springboot里面默认使用内嵌的tomcat 可以改成jetty

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- 在web的starter里面排除tomcat的启动器-->
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-start-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 引入jetty的启动器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
@Configuration
public class JettyConfig {

    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        JettyServletWebServerFactory jettyServletWebServerFactory = new JettyServletWebServerFactory();
        return jettyServletWebServerFactory;
    }
}

启动查看

16.注册Web三大组件【重点】

web的三大件有哪些

Servlet     ---web.xml    @WebServlet

Filter       --web.xml   @WebFilter

Listener    --web.xml   @WebListener

16.1首先查看注册组件的结构 

16.2注册自己的Servlet 

我们可以模仿DispatcherServlet的注册方式

创建UserServlet

package com.wxz.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


public class UserServlet  extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("hello-userServlet");
        writer.flush();
        writer.close();
        System.out.println("hello-UserServlet");
    }
}

在配置类注册自己的servlet

@Configuration
public class WebConfig {
    @Bean
    public UserServlet userServlet() {
        return new UserServlet();
    }

    @Bean
    public ServletRegistrationBean<UserServlet> userServletServletRegistrationBean(UserServlet userServlet) {
        ServletRegistrationBean<UserServlet> servletRegistrationBean = new ServletRegistrationBean<>();
        servletRegistrationBean.setServlet(userServlet);
        servletRegistrationBean.setUrlMappings(Arrays.asList("/user1.action", "/user2.action"));
        return servletRegistrationBean;
    }
}

16.3注册自己的Filter

创建MyFilter


public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("前置过滤");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("后置过滤");
    }

    @Override
    public void destroy() {

    }
}

在配置类注册自己的Filter

 @Bean
public MyFilter myFilter() {
    return new MyFilter();
}

/**
 * 注册自己的filter
 *
 * @param myFilter
 * @return
 */
@Bean
public FilterRegistrationBean<MyFilter> myFilterFilterRegistrationBean(MyFilter myFilter) {
    FilterRegistrationBean<MyFilter> myFilterFilterRegistrationBean = new FilterRegistrationBean<>();
    myFilterFilterRegistrationBean.setFilter(myFilter);
    //设置所有的请求都走过滤器
    myFilterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
    return myFilterFilterRegistrationBean;
}

16.4注册自己的Listener

创建MyListener

public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContextEvent被创建");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContextEvent被销毁");
    }
}

在配置类注册自己的Listener


@Bean
public MyListener myListener() {
    return new MyListener();
}

@Bean
public ServletListenerRegistrationBean listenerRegistrationBean(MyListener myListener) {
    ServletListenerRegistrationBean<MyListener> listenerRegistrationBean = new ServletListenerRegistrationBean<>();
    listenerRegistrationBean.setListener(myListener);
    return listenerRegistrationBean;
}

17.数据源配置和自动管理【重中之中】

选择依赖

 17.1使用DriverManagerDataSource

​​​​​​​修改配置文件

spring:
  datasource: #数据源的配置
    driver-class-name: com.mysql.cj.jdbc.Driver
    url:  jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
    type: org.springframework.jdbc.datasource.DriverManagerDataSource   #spring自带的数据源

测试连接

​​​​​​​17,2使用Druid数据源【自己配置】

数据源

数据源的监控页面

添加durid的依赖

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

添加MyDruidProperties配置文件类


@Data
@ConfigurationProperties(prefix = "my.druid")
public class MyDruidProperties {

    private String url;
    private String username;
    private String password;
    private String driverClassName;
    //初始化数量
    private Integer initialSize;
    //最大活跃数
    private Integer maxActive;
    //最小连接数
    private Integer minIdle;
    //检查连接的
    private String validationQuery;

    private StatView statView;
    /**
     * 监控配置
     */
    @Data
    static class StatView {
        //监控登录名
        private String loginUsername;
        //监控登录密码
        private String loginPassword;
        //白名单
        private String allow;
        //黑名单
        private String deny;
        //映射路径
        private String[] urlMapping;
    }
}

添加MyDruidAutoConfiguration自动配置类


@Data
@Configuration
@Log4j2
@EnableConfigurationProperties(MyDruidProperties.class)
@ConditionalOnClass(value = {DruidDataSource.class}) //必须有DruidDataSource的这个类
public class MyDruidAutoConfiguration {

//    @Autowired  如果不想使用Autowired就必须提交一个构造方法,如下
    private MyDruidProperties myDruidProperties;

    public MyDruidAutoConfiguration(MyDruidProperties myDruidProperties) {
        this.myDruidProperties = myDruidProperties;
    }

    /**
     * 创建数据源对象
     *
     */
    @Bean(initMethod = "init",destroyMethod = "close")
    public DruidDataSource druidDataSource(){
        if (myDruidProperties.getUrl() == null) {
            log.error("URL can not be null");
            throw new RuntimeException("URL can not be null");
        }
        DruidDataSource dataSource=new DruidDataSource();
        dataSource.setDriverClassName(myDruidProperties.getDriverClassName());
        dataSource.setUrl(myDruidProperties.getUrl());
        dataSource.setUsername(myDruidProperties.getUsername());
        dataSource.setPassword(myDruidProperties.getPassword());
        dataSource.setMaxActive(myDruidProperties.getMaxActive());
        dataSource.setMinIdle(myDruidProperties.getMinIdle());
        dataSource.setValidationQuery(myDruidProperties.getValidationQuery());
        return dataSource;
    }

    /**
     * 注册StatViewServlet
     */
    @Bean
    @ConditionalOnClass(StatViewServlet.class)
    public ServletRegistrationBean<StatViewServlet> statViewServletServletRegistrationBean(){
        StatViewServlet  statViewServlet=new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> registrationBean=new ServletRegistrationBean<>();
        registrationBean.setServlet(statViewServlet);
        registrationBean.addInitParameter("loginUsername",myDruidProperties.getStatView().getLoginUsername());
        registrationBean.addInitParameter("loginPassword",myDruidProperties.getStatView().getLoginPassword());
        registrationBean.addInitParameter("allow",myDruidProperties.getStatView().getAllow());
        registrationBean.addInitParameter("deny",myDruidProperties.getStatView().getDeny());

        registrationBean.addUrlMappings(myDruidProperties.getStatView().getUrlMapping());

        return registrationBean;
    }
}

修改yml配置文件

my:
  druid:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
    initial-size: 5
    max-active: 10
    min-idle: 2
    validation-query: select 'x'
    stat-view:
      login-username: admin
      login-password: admin
      allow:
      deny:
      url-mapping:
        - /druid/*
        - /druid2/*

测试访问

​​​​​​​17.3使用Druid数据源【官方starter】

添加durid starter的依赖

<!--  引入druid starter-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.21</version>
</dependency>

 修改配置文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
#    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      max-active: 10
      min-idle: 2
      validation-query: select 'x'
      stat-view-servlet:
        login-username: admin
        enabled: true  #启用监控页
        login-password: admin
        allow:
        deny:
        url-pattern: /druid/*

18.集成JdbcTemplate【熟悉】

我们之前在学spring的时候接触过JdbcTemplate,这个东西是官方的,那么在springboot里面怎么用,需要配置什么呢,答案:只要有数据源就行了

测试

因为数据源已经搞定了,所以直接测试即可

原理

找到JdbcTemplateAutoConfiguration 

找到JdbcTemplateConfiguration 

 从上面的代码可以看出,只要有数据源,就直接创建了,所以我们可以直接使用

19.集成mybatis【重点】

​​​​​​​​​​​​​​19.1注解方式的整合

创建项目

<!--  引入druid starter-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.21</version>
</dependency>

 创建数据库

 创建User对象

package com.wxz.domain;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    /**
    * 用户编号
    */
    private Integer id;

    /**
    * 用户姓名
    */
    private String name;

    /**
    * 用户地址
    */
    private String address;

    /**
    * 出生时间
    */
    private Date birth;

    /**
    * 是否删除1删除0未删除
    */
    private Integer flag;
}

创建UserMapper

package com.wxz.mapper;

import com.wxz.domain.User;
import org.apache.ibatis.annotations.*;


public interface UserMapper {
    @Delete("delete from sys_user where id= #{id}")
    int deleteByPrimaryKey(Integer id);

    @Insert("insert  into sys_user(id,name,address,birth,flag) values(#{user.id},#{user.name},#{user.address} ,#{user.birth},#{user.flag})")
    int insert(@Param("user") User record);

    @Select("select * from sys_user where id=#{id} ")
    User selectByPrimaryKey(Integer id);

    @Update("update  sys_user set name=#{user.name},address=#{user.address} ,birth=#{user.birth} ,flag=#{user.flag} where id=#{user.id} ")
    int updateByPrimaryKey(@Param("user") User user);
}

 修改yml配置文件

server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
#    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      max-active: 10
      min-idle: 2
      validation-query: select 'x'
      stat-view-servlet:
        login-username: admin
        enabled: true  #启用监控页
        login-password: admin
        allow:
        deny:
        url-pattern: /druid/*
#配置mybatis
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #输出sql

启动了上扫描mapper所在的包 

测试查询

19.2配置文件方式整合(推荐)

生成UserMapper接口


public interface UserMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(User record);

    User selectByPrimaryKey(Integer id);

    int updateByPrimaryKey(User record);

    List<User> selectAll();
}

在resources/mapper生成UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wxz.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="com.wxz.domain.User">
    <!--@mbg.generated-->
    <!--@Table sys_user-->
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="address" jdbcType="VARCHAR" property="address" />
    <result column="birth" jdbcType="TIMESTAMP" property="birth" />
    <result column="flag" jdbcType="INTEGER" property="flag" />
  </resultMap>
  <sql id="Base_Column_List">
    <!--@mbg.generated-->
    id, `name`, address, birth, flag
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    <!--@mbg.generated-->
    select 
    <include refid="Base_Column_List" />
    from sys_user
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    <!--@mbg.generated-->
    delete from sys_user
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.bjsxt.domain.User" useGeneratedKeys="true">
    <!--@mbg.generated-->
    insert into sys_user (`name`, address, birth, 
      flag)
    values (#{name,jdbcType=VARCHAR}, #{address,jdbcType=VARCHAR}, #{birth,jdbcType=TIMESTAMP}, 
      #{flag,jdbcType=INTEGER})
  </insert>
  <update id="updateByPrimaryKey" parameterType="com.wxz.domain.User">
    <!--@mbg.generated-->
    update sys_user
    set `name` = #{name,jdbcType=VARCHAR},
      address = #{address,jdbcType=VARCHAR},
      birth = #{birth,jdbcType=TIMESTAMP},
      flag = #{flag,jdbcType=INTEGER}
    where id = #{id,jdbcType=INTEGER}
  </update>
  <select id="selectAll" resultMap="BaseResultMap">
   <!--@mbg.generated-->
    select
    <include refid="Base_Column_List" />
    from sys_user
</select>
</mapper>

修改yml配置文件

#配置mybatis
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #输出sql
  mapper-locations:
    - classpath:mapper/*Mapper.xml

启动类添加mapper扫描

19.3配置PageHelper插件分页

依赖pageHelper的starter 

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.13</version>
</dependency>

 测试:

​​​​​​​19.4事务管理

和spring里面一样的,但是在boot里面只要在XXXXServiceImpl上Transactionl

 20.集成mybatisplus【重点】

创建项目引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.21</version>
</dependency>

配置yml

server:
  port: 8080
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: 123456
      initial-size: 5
      max-active: 10
      min-idle: 2
      validation-query: select 'x'
      stat-view-servlet:
        login-username: admin
        login-password: 123456
        enabled: true  #启用监控页面
        url-pattern: /druid/*
#配置mybatis-plus
mybatis-plus:
  configuration:
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations:
     - classpath:mapper/*.xml

生成User UserMapper  UserMapper.xml

修改启动类

测试

 配置分页


package com.wxz.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        // paginationInterceptor.setLimit(500);
        // 开启 count 的 join 优化,只针对部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}
@SpringBootTest
class ApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void queryForPage() {
        Integer pageNum=1;
        Integer pageSize=10;
        Page<User> page=new Page<>(pageNum,pageSize);
        this.userMapper.selectPage(page,null);
        List<User> records = page.getRecords();
        long total = page.getTotal();
        System.out.println("总条数:"+total);
        for (User record : records) {
            System.out.println(record);
        }
    }
}

21.集成swagger3.0【熟悉】

​​​​​​​使用步骤

创建项目加入依赖

<!--swagger starter-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

创建SwaggerProperties信息配置类


@Data
@ConfigurationProperties(prefix = "swagger2")
public class SwaggerProperties {
    //作者姓名
    private String name;
    //作者主页链接
    private String url;
    //作者邮箱
    private String email;
    //版本号
    private String version;
    //分组名称
    private String groupName;
    //文档标题
    private String title;
    //文档描述
    private String description;
    //组织地址
    private String termsOfServiceUrl;
}

创建SwaggerAutoConfiguration自动配置类


package com.wxz.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.ArrayList;


@Configuration
@EnableConfigurationProperties(value = {SwaggerProperties.class})
@EnableOpenApi  //启用swagger
public class SwaggerAutoConfiguration {

    private SwaggerProperties swaggerProperties;

    public SwaggerAutoConfiguration(SwaggerProperties swaggerProperties) {
        this.swaggerProperties = swaggerProperties;
    }

    /**
     * 创建文档
     *
     * @return
     */
    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.OAS_30)
                .apiInfo(apiInfo())
                .groupName(swaggerProperties.getGroupName())
                .select()
                //要扫描的包
                .apis(RequestHandlerSelectors.basePackage("com.wxz"))
//                .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
//                .apis(RequestHandlerSelectors.withMethodAnnotation(GetMapping.class))
                .build();
    }

    /**
     * 自定义文档信息
     *
     * @return
     */
    private ApiInfo apiInfo() {
        return new ApiInfo(swaggerProperties.getTitle(),    //文档标题
                swaggerProperties.getDescription(),         //文档描述
                swaggerProperties.getVersion(),             //版本号
                swaggerProperties.getTermsOfServiceUrl(),   //组织信息
                new Contact(swaggerProperties.getName(),    //个人信息
                        swaggerProperties.getUrl(),
                        swaggerProperties.getEmail()
                ),
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList());
    }
}

修改yml文件

swagger2:
  name: "小白"
  description: "测试的接口"
  email: 78414842@qq.com
  title: "后台"
  version: 1.0
  group-name: leige
  terms-of-service-url: https://gitee.com/smiledouble
  url: https://gitee.com/smiledouble
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  mvc:
    format:
      date-time: yyyy-MM-dd HH:mm:ss

创建Hero类


@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "Hero", description = "英雄对象")
public class Hero {
    @ApiModelProperty(value = "英雄的id")
    private Integer id;
    @ApiModelProperty(value = "英雄的名称")
    private String name;
    @ApiModelProperty(value = "英雄的地址")
    private String address;
    @ApiModelProperty(value = "英雄的生日")
    private Date birth;
    @ApiModelProperty(value = "英雄的爱好")
    private List<String> hobby;
    @ApiModelProperty(value = "英雄的map")
    private Map<String, String> map;
}

创建Controller


@Api(tags = "英雄的管理接口")  //标记一个controller信息
@RestController
public class TestController {
    /**
     * 获取一个英雄
     *
     * @param id 使用restful风格,讲参数拼接在url上
     * @return
     */
    @GetMapping("getOneHero/{id}")
    @ApiOperation(value = "获取一个英雄", notes = "根据id获取一个英雄对象")
    @ApiImplicitParam(name = "id", value = "英雄编号(必填)", required = true, dataType = "Integer", paramType = "path")
    public Hero getOne(@PathVariable(value = "id") Integer id) {
        Map<String, String> map = new HashMap<>(4);
        map.put("q", "哈萨克");
        return new Hero(id, "亚索", "峡谷", new Date(), Arrays.asList("吹风,笛子"), map);
    }

    /**
     * 添加英雄
     *
     * @param hero
     * @return
     */
    @PostMapping("addHero")
    @ApiOperation(value = "创建英雄", notes = "根据Hero创建英雄对象")
    @ApiImplicitParam(name = "hero", value = "英雄对象", required = true, dataTypeClass = Hero.class, paramType = "body")
    public Map<String,Object> addHero(@RequestBody Hero hero) {
        System.out.println(hero);
        Map<String,Object> res=new HashMap<>();
        res.put("code",200);
        res.put("msg","添加成功");
        return res;
    }

    /**
     * 删除一个英雄
     *
     * @param id
     * @return
     */
    @DeleteMapping("deleteHero")
    @ApiOperation(value = "删除一个英雄", notes = "根据删除一个英雄对象")
    @ApiImplicitParam(name = "id", value = "英雄编号(必填)", required = true, dataType = "Integer", paramType = "query")
    public Map<String,Object> deleteHero(@RequestParam("id") Integer id) {
        System.out.println(id);
        Map<String,Object> res=new HashMap<>();
        res.put("code",200);
        res.put("msg","删除成功");
        return res;
    }
}

​​​​​​​测试访问文档页面

启动项目访问 http://localhost:8080/swagger-ui/index.html 

​​​​​​​测试接口

 补充注解说明

https://gumutianqi1.gitbooks.io/specification-doc/content/tools-doc/spring-boot-swagger2-guide.html

 更换页面ui

<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>swagger-bootstrap-ui</artifactId>
    <version>1.9.6</version>
</dependency>

 注解总结

model(实体类)   的注解 
|--@ApiModel(value = "Hero", description = "英雄对象")  标记这个类的描述
|--@ApiModelProperty(value = "英雄的id") 标记类的属性的作用

controller上的注解
	|--@Api(tags = "英雄测试类接口",description = "描述")
		|--作用在类上,标记这个contorller的作用
	|--@ApiOperation(value = "根据ID获取英雄对象",notes = "根据id获取一个英雄对象")  //当前接口的描述
	|--作用在方法上,标记这个方法的作用			

	|--@ApiImplicitParam(name = "hero", value = "英雄对象", required = true, dataTypeClass = Hero.class, paramType = "body")
		|--作用在方法上,标记这个方法需要哪些参数 及需要的数据格式

配置类
	|--@EnableOpenApi  //启用swagger

22.集成shiro非前后端分离【重点】

​​​​​​​之前SSM的配置

  1. UserRealm
  2. shiro.xml
  3. springmvc.xml
  4. web.xml

导入sql建表 

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50724
 Source Host           : localhost:3306
 Source Schema         : shiro

 Target Server Type    : MySQL
 Target Server Version : 50724
 File Encoding         : 65001

 Date: 15/12/2020 14:53:39
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `perid` int(11) NOT NULL AUTO_INCREMENT,
  `pername` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `percode` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`perid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, '用户查询', 'user:query');
INSERT INTO `permission` VALUES (2, '用户添加', 'user:add');
INSERT INTO `permission` VALUES (3, '用户修改', 'user:update');
INSERT INTO `permission` VALUES (4, '用户删除', 'user:delete');
INSERT INTO `permission` VALUES (5, '导出用户', 'user:export');

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `roleid` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`roleid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, '超级管理员');
INSERT INTO `role` VALUES (2, 'CEO');
INSERT INTO `role` VALUES (3, '保安');

-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission`  (
  `perid` int(255) NOT NULL,
  `roleid` int(11) NOT NULL,
  PRIMARY KEY (`perid`, `roleid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1);
INSERT INTO `role_permission` VALUES (1, 2);
INSERT INTO `role_permission` VALUES (1, 3);
INSERT INTO `role_permission` VALUES (2, 1);
INSERT INTO `role_permission` VALUES (2, 2);
INSERT INTO `role_permission` VALUES (3, 1);
INSERT INTO `role_permission` VALUES (3, 2);
INSERT INTO `role_permission` VALUES (4, 1);
INSERT INTO `role_permission` VALUES (5, 3);

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `userid` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `userpwd` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `sex` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`userid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'zhangsan', '639ffb0cbcca39d4fff8348844b1974e', '男', '武汉');
INSERT INTO `user` VALUES (2, 'lisi', '0d303fa8e2e2ca98555f23a731a58dd9', '女', '北京');
INSERT INTO `user` VALUES (3, 'wangwu', '473c41db9af5cc0d90e7adfd2b6d9180', '女', '成都');

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `userid` int(11) NOT NULL,
  `roleid` int(11) NOT NULL,
  PRIMARY KEY (`userid`, `roleid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1);
INSERT INTO `user_role` VALUES (2, 2);
INSERT INTO `user_role` VALUES (3, 3);

SET FOREIGN_KEY_CHECKS = 1;

分析表的数据

zhangsan/123456  超级管理员  1234号权限user:query user:add user:update  user:delete

lisi/123456   CEO   123号权限user:query user:add user:update

wangwu/123456  保安  15号权限  user:query user:export

创建项目选择依赖

 

 

   <!--  引入druid starter-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.21</version>
        </dependency>
        <!-- shiro的依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!-- shiro和thymeleraf的依赖 -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

 ​​​​​​​修改主启动类

​​​​​​​创建yml

#配置数据源
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/0817-shiro?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      #      filters: log4j,stat
      max-active: 20
      min-idle: 5
      validation-query: select x
      initial-size: 3
      max-wait: 5000
      stat-view-servlet:
        login-username: root
        login-password: 123456
        allow:
        deny:
        url-pattern: /druid/*
        enabled: true   #启用数据源监控

#mybatis的配置
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: com.wxz.domain  #配置字别名

#shiro的配置
shiro:
  hash-iterations: 2
  hash-algorithm-name: md5
  login-url: /index.html
  unauthorized-url: /unauthorized.html
  log-out-url: /login/logout
  anon-url:  #这些地址不用认证
    - /index.html*
    - /login.html*
    - /login/toLogin*
    - /login/doLogin*
  authc-url:  #下面的需要认证
    - /**

 生成User

package com.wxz.domain;

public class User {
    /*
     *用户id
     */
    private Integer userid;
    /*
     *用户名
     */
    private String username;
    /*
     *密码
     */
    private String userpwd;
    /*
     *性别
     */
    private String sex;
    /*
     *地址
     */
    private String address;

  

    public Integer getUserid() {
        return userid;
    }

    public void setUserid(Integer userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUserpwd() {
        return userpwd;
    }

    public void setUserpwd(String userpwd) {
        this.userpwd = userpwd;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    
}

UserMapper

package com.wxz.mapper;

import com.wxz.domain.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {

    User queryUserByUserName(@Param("username") String username);

}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wxz.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="com.wxz.domain.User">
    <!--@mbg.generated-->
    <!--@Table `user`-->
    <id column="userid" jdbcType="INTEGER" property="userid" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="userpwd" jdbcType="VARCHAR" property="userpwd" />
    <result column="sex" jdbcType="VARCHAR" property="sex" />
    <result column="address" jdbcType="VARCHAR" property="address" />
  </resultMap>
  <sql id="Base_Column_List">
    <!--@mbg.generated-->
    userid, username, userpwd, sex, address
  </sql>

  <select id="queryUserByUserName" resultMap="BaseResultMap">
   <!--@mbg.generated-->
    select
    <include refid="Base_Column_List" />
    from `user`
    where username = #{username}
</select>
</mapper>

UserService

package com.wxz.service;

import com.wxz.domain.User;
public interface UserService{


    /**
     * 据用户名去数据库查询用户
     * @param username
     * @return
     */
    User queryUserByUserName(String username);
}

UserServiceImpl

package com.wxz.service.impl;

import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import com.wxz.domain.User;
import com.wxz.mapper.UserMapper;
import com.wxz.service.UserService;
@Service
public class UserServiceImpl implements UserService{

    @Resource
    private UserMapper userMapper;

    @Override
    public User queryUserByUserName(String username) {
        return this.userMapper.queryUserByUserName(username);
    }
}

生成Role

package com.wxz.domain;

public class Role {
    /*
     *角色id
     */
    private Integer roleid;
    /*
     *角色名
     */
    private String rolename;

    public Integer getRoleid() {
        return roleid;
    }

    public void setRoleid(Integer roleid) {
        this.roleid = roleid;
    }

    public String getRolename() {
        return rolename;
    }

    public void setRolename(String rolename) {
        this.rolename = rolename;
    }
}

RoleMapper

package com.wxz.mapper;

import com.wxz.domain.Role;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface RoleMapper {


    List<Role> queryRolesByUserId(@Param("userid") Integer userid);
}

RoleMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wxz.mapper.RoleMapper">
  <resultMap id="BaseResultMap" type="com.wxz.domain.Role">
    <!--@mbg.generated-->
    <!--@Table `role`-->
    <id column="roleid" jdbcType="INTEGER" property="roleid" />
    <result column="rolename" jdbcType="VARCHAR" property="rolename" />
  </resultMap>
  <sql id="Base_Column_List">
    <!--@mbg.generated-->
    roleid, rolename
  </sql>

  <select id="queryRolesByUserId" resultMap="BaseResultMap">
    select t1.* from  role t1 inner join user_role t2
    on(t1.roleid=t2.roleid) where t2.userid=#{userid}

</select>
</mapper>

RoleService

package com.wxz.service;

import com.wxz.domain.Role;

import java.util.List;

public interface RoleService{

    /**
     * 根据用户ID查询用户拥有的角色
     * @param userid
     * @return
     */
    List<String> queryRolesByUserId(Integer userid);
}

RoleServiceImpl

package com.wxz.service.impl;

import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import com.wxz.mapper.RoleMapper;
import com.wxz.domain.Role;
import com.wxz.service.RoleService;

import java.util.ArrayList;
import java.util.List;

@Service
public class RoleServiceImpl implements RoleService{

    @Resource
    private RoleMapper roleMapper;

    @Override
    public List<String> queryRolesByUserId(Integer userid) {
        List<Role> roleList=this.roleMapper.queryRolesByUserId(userid);

        List<String> roles=new ArrayList<>();
        for (Role role : roleList) {
            roles.add(role.getRolename());
        }
        return roles;
    }
}

生成Permission

package com.wxz.domain;

public class Permission {
    /*
     *权限id
     */
    private Integer perid;
    /*
     *权限名
     */
    private String pername;

    private String percode;

    public Integer getPerid() {
        return perid;
    }

    public void setPerid(Integer perid) {
        this.perid = perid;
    }

    public String getPername() {
        return pername;
    }

    public void setPername(String pername) {
        this.pername = pername;
    }

    public String getPercode() {
        return percode;
    }

    public void setPercode(String percode) {
        this.percode = percode;
    }
}

PermissionMapper

package com.wxz.mapper;

import com.wxz.domain.Permission;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface PermissionMapper {

    List<Permission> queryPermissionsByUserId(@Param("userid") Integer userid);
}

PermissionMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wxz.mapper.PermissionMapper">
  <resultMap id="BaseResultMap" type="com.wxz.domain.Permission">
    <!--@mbg.generated-->
    <!--@Table permission-->
    <id column="perid" jdbcType="INTEGER" property="perid" />
    <result column="pername" jdbcType="VARCHAR" property="pername" />
    <result column="percode" jdbcType="VARCHAR" property="percode" />
  </resultMap>
  <sql id="Base_Column_List">
    <!--@mbg.generated-->
    perid, pername, percode
  </sql>

    <select id="queryPermissionsByUserId" resultMap="BaseResultMap">
        
    select distinct t1.* from permission t1 inner join role_permission t2 inner join user_role t3
    on(t1.perid=t2.perid and t2.roleid=t3.roleid)
    where t3.userid=#{userid}

    </select>
</mapper>

PermissionService

package com.wxz.service;

import java.util.List;

public interface PermissionService{


    /**
     * 根据用户ID查询用户拥有的权限
     * @param userid
     * @return
     */
    List<String> queryPermissionsByUserId(Integer userid);
}

 PermssionServiceImpl

package com.wxz.service.impl;

import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import com.wxz.mapper.PermissionMapper;
import com.wxz.domain.Permission;
import com.wxz.service.PermissionService;

import java.util.ArrayList;
import java.util.List;

@Service
public class PermissionServiceImpl implements PermissionService{

    @Resource
    private PermissionMapper permissionMapper;


    @Override
    public List<String> queryPermissionsByUserId(Integer userid) {
        List<Permission> permissionList=this.permissionMapper.queryPermissionsByUserId(userid);

        List<String> permissions=new ArrayList<>();
        for (Permission permission : permissionList) {
            permissions.add(permission.getPercode());
        }
        return permissions;
    }
}

创建ActiveUser

package com.wxz.common;

import com.wxz.domain.User;
import com.wxz.domain.User;

import java.util.List;

public class ActiveUser {

    private User user;
    private List<String> roles;

    private List<String> permissions;

    public ActiveUser() {
        this.user = user;
        this.roles = roles;
        this.permissions = permissions;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public List<String> getRoles() {
        return roles;
    }

    public void setRoles(List<String> roles) {
        this.roles = roles;
    }

    public List<String> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<String> permissions) {
        this.permissions = permissions;
    }
}

创建UserRealm

package com.wxz.realm;

import com.wxz.common.ActiveUser;
import com.wxz.domain.User;
import com.wxz.service.PermissionService;
import com.wxz.service.RoleService;
import com.wxz.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;

public class UserRealm  extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private PermissionService permissionService;


    /**
     * 做认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //得到用户名
        String username = token.getPrincipal().toString();
        //根据用户名去数据库查询用户
        User user=this.userService.queryUserByUserName(username);
        if(null!=user){
            ActiveUser activeUser = new ActiveUser();
            activeUser.setUser(user);
            //根据用户ID查询用户拥有的角色
            List<String> roles=this.roleService.queryRolesByUserId(user.getUserid());
            //根据用户ID查询用户拥有的权限
            List<String> permissions=this.permissionService.queryPermissionsByUserId(user.getUserid());
            activeUser.setRoles(roles);
            activeUser.setPermissions(permissions);
            //进行密码匹配
            ByteSource salt=ByteSource.Util.bytes(user.getUsername()+user.getAddress());
            SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(activeUser,user.getUserpwd(),salt,this.getName());
            return info;
        }else{
            //用户名不存在
            return null;
        }
    }


    /**
     * 作授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        //取出activeUser
        ActiveUser activeUser= (ActiveUser) principals.getPrimaryPrincipal();
        List<String> roles = activeUser.getRoles();
        List<String> permissions = activeUser.getPermissions();
        if(null!=roles&&!roles.isEmpty()){ //一定要判断,如果roles为空shiro会出异常
            info.addRoles(roles);
        }
        if(permissions!=null&&!permissions.isEmpty()){
            info.addStringPermissions(permissions);
        }
        return info;
    }

}

创建ShiroProperties配置信息类


package com.wxz.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;


@Data
@ConfigurationProperties("shiro")
public class ShiroProperties {
    //散列次数
    private Integer hashIterations=2;
    //加密方式
    private String hashAlgorithmName="MD5";
    //登录
    private String loginUrl;
    //未授权证
    private String unauthorizedUrl;
    //登出
    private String logOutUrl;
    //放行的请求
    private String[] anonUrl;
    //需要认证的请求
    private String[] authcUrl;
}

创建ShiroAutoConfiguration配置类


package com.wxz.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.wxz.realm.UserRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;


@Configuration
@EnableConfigurationProperties(value = ShiroProperties.class)
public class ShiroAutoConfiguration {

    private static final String SHIRO_FILTER = "shiroFilter";

    private ShiroProperties properties;

    public ShiroAutoConfiguration(ShiroProperties properties) {
        this.properties = properties;
    }

    /**
     * 创建凭证匹配器
     * @return
     */
    @Bean
    public CredentialsMatcher credentialsMatcher(){
        HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
        //设置加密算法
        credentialsMatcher.setHashAlgorithmName(properties.getHashAlgorithmName());
        //设置散列次数
        credentialsMatcher.setHashIterations(properties.getHashIterations());
        return credentialsMatcher;
    }

    /**
     * 配置自定义的realm
     * @param credentialsMatcher
     * @return
     */
    @Bean
    public UserRealm userRealm(CredentialsMatcher credentialsMatcher){
        UserRealm userRealm=new UserRealm();
        //注入一个凭证匹配器
        userRealm.setCredentialsMatcher(credentialsMatcher);
        return userRealm;
    }

    /**
     * 配置shiro的核心管理器
     */
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(UserRealm userRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 配置过滤器链
     */
    @Bean(SHIRO_FILTER)
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean filterFactoryBean=new ShiroFilterFactoryBean();
        //注入安全管理器
        filterFactoryBean.setSecurityManager(securityManager);
        //设置登陆页面
        filterFactoryBean.setLoginUrl(properties.getLoginUrl());
        //设置未授权的页面
        filterFactoryBean.setUnauthorizedUrl(properties.getUnauthorizedUrl());
        //注入过滤器链
        Map<String,String> map=new HashMap<>();
        String[] anonUrl = properties.getAnonUrl();//不用认证
        String[] authcUrl = properties.getAuthcUrl();//需要认证之后才能访问的
        //注入不用认证的路径
        if(null!=anonUrl&&anonUrl.length>0){
            for (String anon : anonUrl) {
                map.put(anon,"anon");
            }
        }
        //注入要认证的路径
        if(null!=authcUrl&&authcUrl.length>0){
            for (String authc : authcUrl) {
                map.put(authc,"authc");
            }
        }
        filterFactoryBean.setFilterChainDefinitionMap(map);
        return filterFactoryBean;
    }
    /**
     * 替换web.xml的配置  注册过滤器
     */
    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxyFilterRegistrationBean(){
        DelegatingFilterProxy filterProxy=new DelegatingFilterProxy();
        FilterRegistrationBean<DelegatingFilterProxy> filterProxyFilterRegistrationBean=new FilterRegistrationBean<>();
        filterProxyFilterRegistrationBean.setFilter(filterProxy);
        filterProxyFilterRegistrationBean.addInitParameter("targetBeanName",SHIRO_FILTER);
        filterProxyFilterRegistrationBean.addInitParameter("targetFilterLifecycle","true");
        //设置拦击的servlet  就是前端控制器
        filterProxyFilterRegistrationBean.setServletNames(Arrays.asList(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));

        return filterProxyFilterRegistrationBean;
    }
    /**
     * 这里是为了能在html页面引用shiro标签,不然不会走授权方法
     */
    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}

创建LoginController


package com.wxz.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;


@Controller
@RequestMapping("login")
public class LoginController {

    /**
     * 跳转到登陆页面
     */
    @GetMapping("toLogin")
    public String toLogin(){
        return "login";
    }

    /**
     * 做登陆
     */
    @PostMapping("doLogin")
    public String doLogin(String username, String password, Model model){
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try{
            Subject subject = SecurityUtils.getSubject();
            subject.login(token);
            return "list";
        }catch (UnknownAccountException e){
            model.addAttribute("error","用户名不存在");
        }catch (IncorrectCredentialsException e){
            model.addAttribute("error","密码不正确");
        }catch (Exception e){
            model.addAttribute("error","登陆失败,原因:"+ e.getMessage());
        }
        return "login";
    }
}

创建static/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script>
    window.location.href="/login/toLogin"
</script>
</html>

创建template/login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>用户登陆</title>
</head>
<body>
<h1>用户登陆</h1>
<h2 style="color: red" th:text="${error}"></h2>
<form action="/login/doLogin" method="post">
    用户名: <input type="text" name="username"><br>
    密码: <input type="password" name="password"><br>
    <input type="submit" value="登陆" />
</form>
</body>
</html>

创建template/list.html

<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>用户管理</title>
</head>
<body>
<h1><a href="" shiro:hasPermission="user:query">用户查询</a></h1>
<h1><a href="" shiro:hasPermission="user:add">用户添加</a></h1>
<h1><a href="" shiro:hasPermission="user:update">用户修改</a></h1>
<h1><a href="" shiro:hasPermission="user:delete">用户删除</a></h1>
<h1><a href="" shiro:hasPermission="user:export">用户导出</a></h1>

</body>
</html>

启动项目测试:

zhangsan/123456  超级管理员  1234号权限user:query user:add user:update  user:delete

lisi/123456   CEO   123号权限user:query user:add user:update

wangwu/123456  保安  15号权限  user:query user:export

zhangsan登录:

 

lisi登录 

 

 

 wangwu登录:

23.集成shiro前后端分离

复制上一个项目

​​​​​​​创建ResultObj

 情况说明:

  1. 用户没有登陆,直接访问资源 返回一个json提示您没有登陆
  2. 用户已经登陆,访问有权限的资源,返回controller返回的数据
  3. 用户已登陆,没有资源访问权限,返回一个您没有权限的json串

创建全局异常监控

package com.wxz.exception;

import com.wxz.commons.ResultObj;
import org.apache.shiro.authz.UnauthorizedException;
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.RestControllerAdvice;


@RestControllerAdvice  //如果出在异常,直接以json串的形式返回
public class GlobalExceptionHandler {
    /**
     * 捕获未授权的全局异常
     */
    @ExceptionHandler(value = {UnauthorizedException.class})
    public ResultObj unauthorizedExp(){
        return new ResultObj(302,"您没有访问权限","");
    }
}

创建LoginFormAuthenticationFilter 处理未登陆重定向的问题

package com.wxz.config;

import com.alibaba.fastjson.JSON;
import com.wxz.commons.ResultObj;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class LoginFormAuthenticationFilter extends FormAuthenticationFilter {

    /**
     * @param servletRequest
     * @param servletResponse
     * @return 返回值  如果为false就代表拦截
     * 没有登陆才会走这个方法
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request= (HttpServletRequest) servletRequest;
        ResultObj resultObj=new ResultObj(302,"您没有登陆","");
        String json= JSON.toJSONString(resultObj);
        servletResponse.setCharacterEncoding("UTF-8");
        servletResponse.setContentType("application/json");
        servletResponse.getWriter().write(json);
        return false;
    }
}

加入fastjson依赖

  <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>

修改ShiroAutoConfiguration

/**
 * shiro的filter
 */
@Bean(SHIRO_FILTER)
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
    ShiroFilterFactoryBean filterFactoryBean=new ShiroFilterFactoryBean();
    filterFactoryBean.setSecurityManager(securityManager);

    filterFactoryBean.setLoginUrl(shiroProperties.getLoginUrl());
    filterFactoryBean.setUnauthorizedUrl(shiroProperties.getUnauthorizedUrl());

    //覆盖之前的authc过滤器
    Map<String, Filter> filters=new HashMap<>();
    filters.put("authc",new LoginFormAuthenticationFilter());
    filterFactoryBean.setFilters(filters);

    String[] anonUrl = shiroProperties.getAnonUrl();//不用认证
    String[] authcUrl = shiroProperties.getAuthcUrl();//需要认证之后才能访问的
    Map<String,String> map=new HashMap<>();
    if(anonUrl!=null&&anonUrl.length>0){
        for (String url : anonUrl) {
            map.put(url,"anon");
        }
    }
    if(authcUrl!=null&&authcUrl.length>0){
        for (String url : authcUrl) {
            map.put(url,"authc");
        }
    }
    map.put(shiroProperties.getLogOutUrl(), "logout");
    filterFactoryBean.setFilterChainDefinitionMap(map);
    return filterFactoryBean;
}


/**
 * 替换web.xml的配置  注册过滤器
 */
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxyFilterRegistrationBean(){
    DelegatingFilterProxy filterProxy=new DelegatingFilterProxy();
    FilterRegistrationBean<DelegatingFilterProxy> filterProxyFilterRegistrationBean=new FilterRegistrationBean<>();
    filterProxyFilterRegistrationBean.setFilter(filterProxy);
    filterProxyFilterRegistrationBean.addInitParameter("targetBeanName",SHIRO_FILTER);
    filterProxyFilterRegistrationBean.addInitParameter("targetFilterLifecycle","true");

    //设置拦击的servlet  就是前端控制器
    filterProxyFilterRegistrationBean.setServletNames(Arrays.asList(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));

    return filterProxyFilterRegistrationBean;
}
/*加入注解的使用,不加入这个注解不生效--开始*/

/**
 * @param securityManager
 *
 *     <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
 *
 *     <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
 *           depends-on="lifecycleBeanPostProcessor"/>
 *     <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
 *         <property name="securityManager" ref="securityManager"/>
 *     </bean>
 * @return
 */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
}

@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    advisorAutoProxyCreator.setProxyTargetClass(true);
    return advisorAutoProxyCreator;
}
/*加入注解的使用,不加入这个注解不生效--结束*/

修改LoginController

package com.wxz.controller;

import com.wxz.commons.ActiverUser;
import com.wxz.commons.ResultObj;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("login")
public class LoginController {
    /**
     * 登陆
     */
    @GetMapping("doLogin")
    public ResultObj doLogin(String username, String password, Model model){
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        String msg="";
        try{
            Subject subject = SecurityUtils.getSubject();
            subject.login(token);
            return new ResultObj(200,"登陆成功",username);
        }catch (UnknownAccountException e){
            model.addAttribute("error","用户名不存在");
            msg="用户名不存在";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("error","密码不正确");
            msg="密码不正确";
        }catch (Exception e){
            model.addAttribute("error","登陆失败,原因:"+ e.getMessage());
            msg="登陆失败,原因:"+ e.getMessage();
        }
        return new ResultObj(401,msg,"");
    }
}

修改UserController

package com.wxz.controller;

import com.wxz.commons.ResultObj;
import org.apache.shiro.authz.annotation.RequiresPermissions;
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 {

    @GetMapping("query")
    @RequiresPermissions(value = {"user:query"})
    public Object query(){
        return new ResultObj(200,"查询成功","");
    }

    @GetMapping("add")
    @RequiresPermissions(value = {"user:add"})
    public Object add(){
        return new ResultObj(200,"添加成功","");
    }

    @GetMapping("update")
    @RequiresPermissions(value = {"user:update"})
    public Object update(){
        return new ResultObj(200,"更新成功","");
    }

    @GetMapping("delete")
    @RequiresPermissions(value = {"user:delete"})
    public Object delete(){
        return new ResultObj(200,"删除成功","");
    }

    @GetMapping("export")
    @RequiresPermissions(value = {"user:export"})
    public Object export(){
        return new ResultObj(200,"导出成功","");
    }
}

测试

http://127.0.0.1:8080/login/doLogin?username=lisi&password=123456 
http://127.0.0.1:8080/user/query
http://127.0.0.1:8080/user/export

24.普通缓存(非redis)

在SpringBoot中可以使用注解式开发缓存,默认没有开启缓存中间件,那么使用的就是存储在Map中的原理,但是我们还可以配置自己的缓存中间件,比如redis

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.21</version>
</dependency>
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

 创建yaml

server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      max-active: 10
      min-idle: 2
      validation-query: select 'x'
      stat-view-servlet:
        login-username: admin
        enabled: true  #启用监控页
        login-password: admin
        allow:
        deny:
        url-pattern: /druid/*
#配置mybatis
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #输出sql
  mapper-locations:
    - classpath:mapper/*Mapper.xml

开启缓存

创建UserController

package com.wxz.controller;

import com.wxz.domain.User;
import com.wxz.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 查询ID查询一个用户
     */
    @GetMapping("queryUserById")
    public User queryUserById(Integer id){
        return this.userService.queryUserById(id);
    }

    /**
     * 添加一个用户
     */
    @GetMapping("addUser")
    public User addUser(User user){
        return this.userService.addUser(user);
    }

    /**
     * 修改一个用户
     */
    @GetMapping("updateUser")
    public User updateUser(User user){
        return this.userService.updateUser(user);
    }

    /**
     * 删除一个用户
     */
    @GetMapping("deleteUserById")
    public String deleteUserById(Integer id){
        int i=this.userService.deleteUserById(id);
        return i>0?"删除成功":"删除失败";
    }

}

 ​​​​​​​创建UserService

package com.wxz.service;

import com.wxz.domain.User;

public interface UserService {
    User queryUserById(Integer id);

    User addUser(User user);

    User updateUser(User user);

    Integer deleteUserById(Integer id);
}

创建UserServiceImpl

package com.wxz.service.impl;

import com.wxz.domain.User;
import com.wxz.mapper.UserMapper;
import com.wxz.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;


@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

  
    @Cacheable(value = "user",key = "#id",unless = "#result==null",sync = true)
    @Override
    public User queryUserById(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    @Override
    @CachePut(value = "user",key = "#user.id")
    public User addUser(User user) {
        userMapper.insertSelective(user);
        return user;
    }

    @Override
    @CachePut(value = "user",key = "#user.id")
    public User updateUser(User user) {
        userMapper.updateByPrimaryKeySelective(user);
        return this.userMapper.selectByPrimaryKey(user.getId());
    }

    @Override
    @CacheEvict(value = "user",key = "#id")
    public Integer deleteUserById(Integer id) {
        return this.userMapper.deleteByPrimaryKey(id);
    }
}

修改启动类

25.Spring Boot定时任务 

相关注解

@Scheduled()

@PostConstruct   这个注解不是定时任务的

当IOC容器里面的所有对象全部初始化完成之后会调用的方法

- sprignBoot定时任务是与quartz整合,不需要添加任何的依赖

- 在springBoot的启动类上添加`@EnableScheduling`注解开启定时调度

- 在需要定时调度的方法上添加`@Scheduled`这个注解即可,其中可以指定**cron表达式和其他的定时方式

 开启定时任务

执行定时任务

package com.wxz.task;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;


@Component
public class MyTask {

//    @Scheduled(fixedDelay = 2000)  //固定延时2秒执行一次
//    @Scheduled(fixedRate=2000) //fixedRate 每过多少秒执行一次
//    @Scheduled(initialDelay = 2000, fixedRate = 5000) //第一次延迟两秒执行,后面按照fixedRate的规则执行
    @Scheduled(cron = "0 30 0 1/1 * ? *")  //每天晚上12点半执行一次https://www.matools.com/cron/
    public void task1(){
        System.out.println("定时任务执行了。。。。。。");
    }
}
https://cron.qqe2.com/

 26.Spring Boot邮件发送

SpringBoot实现邮件功能是非常的方便快捷的,因为SpringBoot默认有starter实现了Mail。

发送邮件应该是网站的必备功能之一,什么注册验证,忘记密码或者是给用户发送营销信息。

最早期的时候我们会使用JavaMail相关api来写发送邮件的相关代码,后来spring退出了JavaMailSender更加简化了邮件发送的过程,在之后springboot对此进行了封装就有了现在的spring-boot-starter-mail。

准备工作

先去qq邮箱设置smtp开启,并获得授权码

邮箱->设置->账户->POP3/SMTP服务:开启服务后会获得权码、

 

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

编写测试类:

package com.wxz;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;

@SpringBootTest
class SpringbootHelloApplicationTests {


    @Autowired
    private JavaMailSender javaMailSender;

    @Test
    void contextLoads() {
        System.out.println(javaMailSender);
    }

    @Test
    void sendSimpleMail(){
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        //设置发送人
        simpleMailMessage.setFrom("111@qq.com");
        //设置接收人
        simpleMailMessage.setTo("222@qq.com");
        //设置主题
        simpleMailMessage.setSubject("这是一个测试开发的邮件发送");
        //设置内容
        simpleMailMessage.setText("收到后请认真查看 http://www.leige.plus:8088?id=123456");
        javaMailSender.send(simpleMailMessage);
    }

    /**
     * 发送一个带图片,带附件的邮件
     */
    @Test
    void sendComplexMail() throws MessagingException {
        //创建mimeMessage
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        //创建一个MimeMessageHelper,来组装参数 true表示开启附件,utf-8表示编码
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
        //设置发送人
        mimeMessageHelper.setFrom("111@qq.com");
        //设置接收人
        String[] to ={"2222@qq.com"};
        mimeMessageHelper.setTo(to);
        //设置主题
        mimeMessageHelper.setSubject("这是一个复杂的邮件,带html解析和附件的哦");
        //拼接内容参数
        StringBuilder sb = new StringBuilder();
        sb.append("<html> <body> <h1 style='color:red'>springboot 测试邮件发送复杂格式o</h1>");
        sb.append("<p style='color:blue,font-size:16px'>哈哈哈</p>");
        sb.append("<p style='text-align:center'>居中</p>");
        sb.append("<img src='cid:picture'/> </body></html>");  //如果要插入图片src='cid:picture'
        //设置内容,可以被html解析
        mimeMessageHelper.setText(sb.toString(), true);
        //添加内容图片
        mimeMessageHelper.addInline("picture", new File("D:\\img\\bg_car.jpg"));
        //添加附件
        mimeMessageHelper.addAttachment("美女大图.zip", new File("D:\\img\\dog.jpg"));
        javaMailSender.send(mimeMessage);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值