Spring Boot

Spring Boot

在idea中双击shift键盘查询全部的类什么的

微服务阶段

1、Spring的理念(目的)

Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。

2、Spring是如何简化Java开发的

4种关键策略:
1、基于POJO的轻量级的最小侵入性编程;
2、通过IOC、依赖注入(DI)和面向接口实现松耦合;
3、基于切面(AOP)和惯例进行声明式编程;
4、通过切面和模板减小样式代码。

3、过程

web应用:Servlet结合Tomcat
框架Struts:https://xxx.do
SpringMVC
SpringBoot:就是一个javaweb的开发框架。简化开发,约定大于配置。
衍生出一些一站式解决方案。核心思想约定大于配置。
约定大于配置出现在:maven、spring、springmvc、……docker、k8s
集成大量常用第三方库配置:Redis、MongoDB、Jpa、RabbitMQ、Quartz等。
新服务架构:服务网格!

4、Spring Boot的主要优点

为所有Spring开发者更快的入门
开箱即用,提供各种默认配置来简化项目配置
内嵌式容器简化Web项目
没有荣誉代码生产和XML配置的要求

5、微服务

1、什么是微服务?

微服务是一种架构风格。将应用构建成一系列小服务的组合;可以通过http访问(补充一条:rpc)

2、单体应用架构

含义:将一个应用中的所有应用服务都封装在一个应用中。
好处:易于开发和测试;方便部署;需要扩展时,只需将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了。
缺点:修改一个非常小的地方,都需停掉整个服务,重新打包,部署这个应用war包。对于一个大型应用,不可能把所有内容放在一个应用里面,我们如何维护、如何分工合作都是问题。
image-20210529165937694

3、微服务架构

all in one的架构方式,就是把功能单元放在一个应用里面,然后我们把整个应用部署到服务器上。

微服务架构,就是打破之前all in one的架构方式,把每个元素独立出来。把独立出来的功能元素动态组合,需要的功能元素才去拿来组合,需要多一些时间可以整合多个功能元素。

好处:

节省了调用资源。
每个功能元素的服务都是一个可替换的、可独立升级的软件代码。
image-20210530090827547

原文地址:

翻译地址:

4、如何构建微服务

构建一个个功能独立的微服务应用单元,可以使用spring boot,可以帮我们快速创建一个应用;大型分布式网络服务的调用,这部分由spring cloud来完成,实现分布式;在分布式中间,进行流式数据计算、批处理,我们由spring cloud data flow。spring为我们想清楚了整个从开始构建应用到大型分布式应用全流程方案。
image-20210530092348086

第一个Spring Boot程序

到底多么简单:1、jdk1.82、maven 3.6.13、spring boot:最新版4、IDEA

官方:提供了一个快捷生成的网站!IDEA集成了这个网站!

image-20210530095150799 image-20210530095222420
可以在官网直接下载后,导入idea开发(官网在哪)直接使用idea创建一个spring boot项目(一般开发直接在IDEA中创建)

1、pom.xml

parent:继承spring-boot-starter-parent的依赖管理,控制版本与打包等内容。dependencies:项目具体依赖,包含了spring-boot-starter-web(该依赖包含了spring mvc),官方描述:使用spring mvc构建web(包括RESTful)应用程序的入门者,使用Tomcat作为默认嵌入式容器。spring-boot-starter-test用于编写单元测试的依赖包。还有更多功能build:构建配置部分。默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把spring boot应用打包成jar来直接运行。

2、编写hello接口

  1. 在主程序的同级目录下,新建一个controller包【一定要在同级目录下,否则识别不到】

    image-20210530192423805
  2. 在包中新建一个Controller类

    @RestControllerpublic class HelloController {    //接口:http://localhost:8080/hello    @RequestMapping("/hello")    public String hello(){        //调用业务,接受前端的参数!        return "hello,World";    }}
    
  3. 编写完毕后,从主程序启动项目。浏览器发起请求,看页面返回。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btlxFzJ4-1623924293477)(SpringBoot.assets/image-20210530192755092.png)]

  4. 将项目打成jar包

    image-20210530194932498

3、cmd运行jar包

在jar包所在位置运行cmd,输入命令:java -jar ks-helloword-0.0.1-SNAPSHOT.jar
image-20210530200601752

4、修改端口号

server.port=8081

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WNOvoiMt-1623924293479)(SpringBoot.assets/image-20210530203308699.png)]

5、自定义banner图

spring boot banner在线生成网址:

image-20210530203126893

原理初探

1、自动配置

pom.xml

  • spring-boot-dependencies:核心依赖都在父工程中
    
  • 我们在写或者引入一些spring boot依赖的时候,不需要指定版本,就因为有这些版本仓库

image-20210530204029928 image-20210530204131656 image-20210530204307289

spring boot所有依赖网址:

启动器

  • <!--启动器--><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter</artifactId></dependency>
    
  • 启动器:说白了就是spring boot的启动场景;

  • 比如:spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖!

  • spring boot会将所有的功能场景,都变成一个个的启动器

  • 我们要使用什么功能,就只需要找到对应的启动器就可以了 starter

spring boot所有启动器网址:

主程序

//本身就是spring的一个组件//程序的主入口//@SpringBootApplication:标注这个类是一个springboot的应用@SpringBootApplicationpublic class KsHellowordApplication {    public static void main(String[] args) {        //将springboot应用启动        SpringApplication.run(KsHellowordApplication.class, args);    }}
  • 注解

    @SpringBootConfiguration:spring boot的配置   @Configuration:spring配置类   @Component:说明这也是一个spring的组件@EnableAutoConfiguration:自动配置   @AutoConfigurationPackage:自动配置包       @Import(AutoConfigurationPackages.Registrar.class):自动配置 `包注册`   @Import(AutoConfigurationImportSelector.class)://获取所有的配置List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    

    获取候选的配置

     ~~~xml protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),          getBeanClassLoader());    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "          + "are using a custom packaging, make sure that file is correct.");    return configurations; } ~~~
    

META-INF/spring.factories:自动配置的核心文件

image-20210530213413736
Properties properties = PropertiesLoaderUtils.loadProperties(resource);所有资源加载到配置类中!

结论:

spring boot所有自动配置都是在启动的时候扫描并加载:`spring.factories`所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!
spring boot在启动的时候,从类路径下/META-INF/spring.factories获取指定的值;将这些自动配置的类导入容器,自动配置就会生效,帮我们进行自动配置!以前我们需要自动配置的东西,现在spring boot帮我们做了!整合JavaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.5.0.jar这个包下它把所用需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration,JavaConfig!有了自动配置类,免去了我们手动编写配置文件的工作!

spring boot自动配置原理思维导图看外边:spring boot自动配置原理.pdf

2、主程序

Run

我最以为就是运行了一个main方法,没想到却开启了一个服务;

@SpringBootApplicationpublic class KsHellowordApplication {    public static void main(String[] args) {        //该方法返回一个ConfigurableApplicationContext对象        //参数一:应用入口的类  参数类:命令行参数        SpringApplication.run(KsHellowordApplication.class, args);    }}

SpringApplication.run分析

分析该方法主要分两部分,一部分是SpringApplication的实例化;二是run方法的执行。

SpringApplication

这个类主要做了以下四件事情1、推断应用的类型是普通的项目还是Web项目2、查找并加载所有可用初始化器,设置到initializers属性中3、找出所有的应用程序监听器,设置到listeners属性中4、推断并设置main方法的定义类,找到运行的主类

run方法流程分析图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YjOOudz5-1623924293480)(SpringBoot.assets/run方法流程分析图)]

javaConfig @Configuration @Bean

Docker:进程 ???

关于spring boot,谈谈你的理解:

  • 自动装配
  • run()
    1. 推断应用的类型是普通的项目还是Web项目
    2. 推断并设置main方法的定义类,找到运行的主类
    3. run里面有些监听器,全局存在,获取上下文处理咱们的bean,加载——生产——初始化

Spring cloud 全面接管springmvc的配置!实操! ???

Spring Boot配置

1、热部署:

image-20210530234256099

所有yaml官方文档网址:(在88.4下面)

2、配置文件

spring boot使用一个全局的配置文件,配置文件名称是固定的。

  • application.properties
    • 语法结构:key=value
  • application.yml
    • 语法结构:key 空格 value

配置文件的作用:修改spring boot自动配置的默认值,因为spring boot在底层都给我们自动配置好了。

3、YAML

YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。

标记语言

以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml

yaml配置:

server:  port: 8080

xml配置:

<server>    <port>8081</port></server>

4、YAML语法

基础语法:
k:(空格) v

注意:属性和值的大小写都是十分敏感的。

常用yaml:
# spring boot这个配置文件中到底可以配置哪些东西呢?#官方的配置太多了#了解原理:一通百通#k=v#对空格的要求十分高#普通的key-value#注入到我们的配置类中name: qinjiang#对象student:  name: qinjiang  age: 30#行内写法 student: {name: qinjiang,age: 30}#数组pets:  - cat  - dog  - pig pets: [cat,dog,pig]
yaml可以直接给实体类赋值(通过yaml赋值全过程)
image-20210531094235034
@ConfigurationProperties(prefix = "person")把yaml文件绑定起来了=============================Person======================@Component@ConfigurationProperties(prefix = "person")public class person {    private String name;    private Integer age;    private Boolean happy;    private Date birth;    private Map<String,Object> maps;    private List<Object> lists;    private Dog dog;    public person() {    }    public person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {        this.name = name;        this.age = age;        this.happy = happy;        this.birth = birth;        this.maps = maps;        this.lists = lists;        this.dog = dog;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Integer getAge() {        return age;    }    public void setAge(Integer age) {        this.age = age;    }    public Boolean getHappy() {        return happy;    }    public void setHappy(Boolean happy) {        this.happy = happy;    }    public Date getBirth() {        return birth;    }    public void setBirth(Date birth) {        this.birth = birth;    }    public Map<String, Object> getMaps() {        return maps;    }    public void setMaps(Map<String, Object> maps) {        this.maps = maps;    }    public List<Object> getLists() {        return lists;    }    public void setLists(List<Object> lists) {        this.lists = lists;    }    public Dog getDog() {        return dog;    }    public void setDog(Dog dog) {        this.dog = dog;    }    @Override    public String toString() {        return "person{" +                "name='" + name + '\'' +                ", age=" + age +                ", happy=" + happy +                ", birth=" + birth +                ", maps=" + maps +                ", lists=" + lists +                ", dog=" + dog +                '}';    }}
=============================yaml========================person:  name: fengjiu  age: 16  happy: false  birth: 2021/5/31  maps: {k1: vl,k2: v2}  lists:      - code      - music      - girl  dog:    name: xiaokeai    age: 5
====================test测试=============================@SpringBootTestclass KsBoot02ConfigApplicationTests {    @Autowired    private Person person;    @Test    void contextLoads() {        System.out.println(person);    }}===========================结果==========================person{name='fengjiu', age=16, happy=false, birth=Mon May 31 00:00:00 CST 2021, maps={k1=vl, k2=v2}, lists=[code, music, girl], dog=Dog{name='xiaokeai', age=5}}    
pom(解决添加@ConfigurationProperties爆红)
==================解决pojo爆红原因========================<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-configuration-processor</artifactId>    <optional>true</optional></dependency>
@component和@ConfigurationProperties简述
@component :    把普通pojo实例化到spring容器中,相当于配置文件中的 <bean id="" class=""/>    注册bean@ConfigurationProperties作用:    将配置文件中配置的每一个属性的值,映射到这个组件中;    告诉spring boot将本类中的所有属性和配置文件中相关的配置进行绑定;    参数(prefix = "person"):将配置文件中的person下面的所有属性一一对应    只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能
解决idea设置properties乱码问题
image-20210531101555193
使用properties给实体类赋值

SPEL表达式???

@PropertySource(value = “classpath:application.properties”):加载指定的配置文件

image-20210531102609784

javaConfig绑定我们配置文件的值,可以采取这些方式!

yaml占位符
person:  name: fengjiu${random.uuid}  age: ${random.int}  happy: false  birth: 2021/5/31  maps: {k1: vl,k2: v2}  hello: happy  lists:      - code      - music      - girl  dog:    name: ${person.hello:hello}_小可爱    age: 5=====================结果======================person{name='fengjiud7acc6a1-ff4c-4b4f-8728-759e98fbe3d6', age=1252694831, happy=false, birth=Mon May 31 00:00:00 CST 2021, maps={k1=vl, k2=v2}, lists=[code, music, girl], dog=Dog{name='happy_小可爱', age=5}}=====================解释======================${random.uuid}:随机生成一个uuid${random.int}:随机数 ${person.hello:hello}_小可爱:如果person.hello存在就是person.hello里的值_小可爱,不存在就是hello_小可爱
yaml和properties功能对比图
image-20210531103930563
松散绑定:比如yaml中写的last-name,这个和lastName是一样的,-后面跟着的字母默认是大写的。这就是松散绑定。JSR303数据校验:就是我们可以在字段添加一层过滤器验证,可以保证数据的合法性复杂类型封装:yaml中可以封装对象。使用@value不支持
结论
配置yaml和配置properties都可以获取到值,强烈推荐yml(注:yaml和yml是一个东西)如果我们在某个业务中,只需获取配置文件的某个值,可以使用一下@Value如果说,我们专门编写了一个JavaBean来和配置文件进行映射,就直接使用@ConfigurationProperties,不要犹豫!

5、JSR303校验

使用数据校验,可以保证数据的正确性;

JSR303校验使用

前提:pom.xml

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

应用:

@Validated  //数据校验public class Person { @Email(message = "邮箱格式错误") private String name;}    

常见参数

@NotNull(message="名字不能为空")private String userName;@Max(value=120,message="年龄最大不能查过120")private int age;@Email(message="邮箱格式错误")private String email;空检查    @Null       验证对象是否为null    @NotNull    验证对象是否不为null, 无法查检长度为0的字符串@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.    @NotEmpty   检查约束元素是否为NULL或者是EMPTY.    Booelan检查    @AssertTrue     验证 Boolean 对象是否为 true            @AssertFalse    验证 Boolean 对象是否为 false      长度检查    @Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内      @Length(min=, max=) string is between min and max included.日期检查    @Past       验证 DateCalendar 对象是否在当前时间之前      @Future     验证 DateCalendar 对象是否在当前时间之后      @Pattern    验证 String 对象是否符合正则表达式的规则:正则表达式.......等等除此以外,我们还可以自定义一些数据校验规则
JSR303校验源码所在位置
image-20210531120729626

6、yaml配置文件位置优先级

image-20210602155927897
1、properties多环境配置切换
#spring boot的多环境配置,可以选择激活哪一个配置文件spring.profiles.active=test
image-20210602160657128
2、yaml多环境配置切换
#spring:  #  profiles: dev  被弃用#推荐使用 #spring:    #  config:  #    activate:  #      on-profile: devspring:  profiles:    active: test---server:     port: 8081spring:  profiles: test---server:     port: 8082spring:  profiles: dev

7、自动配置原理再解读

yaml配置文件到底写什么—联系—spring.factories

在我们这配置文件中能配置的东西,都存在一个固有的规律
xxxAutoConfiguration:默认值 xxxProperties 和 配置文件绑定,我们就可以会用自定义的配置了!

//表示这是一个配置类@Configuration(proxyBeanMethods = false)//自动配置属性:ServerProperties@EnableConfigurationProperties(ServerProperties.class)//spring的底层注解:根据不同的条件,来判断当前配置或者类是否生效!@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
1、conditional注解扩展图
image-20210602164231055

8、精髓

这就是自动装配的原理!

1、spring boot启动会加载大量的自动配置类;

2、我们看我们需要的功能有没有在spring boot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存放在其中,我们就不需要在手动配置了)

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

xxxAutoConfiguration:自动配置类;给容器中添加组件

xxxProperties:封装配置文件中相关属性;

9、可以通过 debug=true 来查看,哪些自动配置类生效,哪些没有生效!

#可以通过 debug=true 来查看,哪些自动配置类生效,哪些没有生效!debug: true

spring boot Web开发

jar:webapp!

自动装配

1、spring boot到底帮我们配置了什么?我们能不能进行修改?能修改哪些东西?能不能扩展?
  • xxxxAutoConfiguration…向容器中自动配置组件
  • xxxxProperties:自动配置类,装配配置文件中自定义的一些内容!
2、要解决的问题:
  • 导入静态资源…
  • 首页
  • jsp、模板引擎Thymeleaf
  • 装配扩展SpringMVC
  • 增删改查
  • 拦截器
  • 国际化!
3、静态资源
public void addResourceHandlers(ResourceHandlerRegistry registry) {    if (!this.resourceProperties.isAddMappings()) {        logger.debug("Default resource handling disabled");        return;    }    addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");    addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {        registration.addResourceLocations(this.resourceProperties.getStaticLocations());        if (this.servletContext != null) {            ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);            registration.addResourceLocations(resource);        }    });}
1、第一种拿到静态资源的方式
从WebJars 官网找到所需静态资源对应的jar包版本,通过maven引入对应的文件引入,启动spring boot,访问对应的http://localhost:8080/webjars/xxx/xxx/xxx,可以通过左边jar包结构写对应xxx。例如:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

什么是webjars?

WebJars 官网

引入jquery后启动访问网址

image-20210602175651927
2、第二种拿到静态资源的方式
在resources文件夹下创建public/resources/static目录,在目录下放入所需jar包启动,访问http://localhost:8080/xxx,例:http://localhost:8080/jquery.js

优先级:resources最大,static,public最小

一般:public放些公共资源,static放些图片,resources放些上传文件。

创建包,放入js,访问网址

image-20210602175834864
3、自定义访问静态资源方式
#自定义访问静态资源路径,一般不这么干,会使默认静态资源四种方式失效#spring.mvc.static-path-pattern=/hello/,classpath:/kuang/
4、总结
1、在spring boot,我们可以使用以下方式处理静态资源      webjars  localhost:8080/webjars/…      public,static,/**,resources  localhost:8080/…2、优先级:resources>static(默认)>public      
4、图标定制
把图片改为favicon.ico,然后放在public/resources/static目录下,启动,清理浏览器缓存。
image-20210602204343664
5、Thymeleaf模板引擎

1、Thymeleaf官网:

2、Thymeleaf在Github的主页:

3、Spring官方文档:

1、 Thymeleaf应用
==============thymeleaf的pom.xml===================================<!--thymeleaf,我们都是基于3.x开发--><dependency>    <groupId>org.thymeleaf</groupId>    <artifactId>thymeleaf-spring5</artifactId></dependency><dependency>    <groupId>org.thymeleaf.extras</groupId>    <artifactId>thymeleaf-extras-java8time</artifactId></dependency>
=====================controller==============================//在templates目录下的所有页面,只能通过controller来跳转!//这个需要模板引擎的支持!thymeleaf@Controllerpublic class IndexController {    @RequestMapping("/index")    public String index(Model model){        model.addAttribute("msg","你好!");        return "index";    }}
==================templates目录下的html文件======================<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8">    <title>Title</title></head><body><!--所有的html元素都可以被Thymeleaf替换接管:th:元素名-->    <h1 th:text="${msg}">首页</h1></body></html>
image-20210602215221104

结论:只要需要使用Thymeleaf,只需要导入对应的依赖就可以了!我们将html放在我们的templates目录下即可!

public static final String DEFAULT_PREFIX = "classpath:/templates/";public static final String DEFAULT_SUFFIX = ".html";

头部引入

<html lang="en" xmlns:th="http://www.thymeleaf.org">
2、Thymeleaf模板元素全部使用方式
  • Variable Expressions: ${...}(变量)
  • Selection Variable Expressions: *{...}(选择表达式)
  • Message Expressions: #{...}(消息)
  • Link URL Expressions: @{...}(url)
  • Fragment Expressions: ~{...}(片段表达式)
  • Literals
    • Text literals: 'one text', 'Another one!',…
    • Number literals: 0, 34, 3.0, 12.3,…
    • Boolean literals: true, false
    • Null literal: null
    • Literal tokens: one, sometext, main,…
  • Text operations:
    • String concatenation: +
    • Literal substitutions: |The name is ${name}|
  • Arithmetic operations:
    • Binary operators: +, -, *, /, %
    • Minus sign (unary operator): -
  • Boolean operations:
    • Binary operators: and, or
    • Boolean negation (unary operator): !, not
  • Comparisons and equality:
    • Comparators: >, <, >=, <= (gt, lt, ge, le)
    • Equality operators: ==, != (eq, ne)
  • Conditional operators:
    • If-then: (if) ? (then)
    • If-then-else: (if) ? (then) : (else)
    • Default: (value) ?: (defaultvalue)
  • Special tokens:
    • No-Operation: _
0、头部引入 xmlns:th="http://www.thymeleaf.org"例:<html lang="en" xmlns:th="http://www.thymeleaf.org">1、日期转换:${#dates.format(需要转换的值/变量,'yyyy-MM-dd HH:mm:ss')}例:<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>2、三元运算${条件?'true':'false'}例:<td th:text="${emp.getGender()==0?'':''}"></td>3、each循环 th:each="赋为的值:${传过来的变量}"例:<tr th:each="emp:${emps}"></tr>4、抽取公共部分 th:fragment="起的名" 和 引用 th:replace="~{抽取公共部分所放的位置::抽取公共部分起的名}"例:<div th:fragment="sidebar">    <div th:replace="~{commons/commons::sidebar}">5、传参(active='参数')例:如果要传递参数,可以直接使用()传参,接受判断即可!    <!--传递参数给组件(active='main.html')-->    <div th:replace="~{commons/commons::sidebar(active='main.html')}"></div> 6、if判断th:if="${判断条件}"例:<!--如果msg的值为空,则不显示消息-->    <p  style="color:red;" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>    7、取变量的两种方式 ${}或[[${}]]例:<button th:text="${session.loginUser}">xxx</button>推荐使用                 <button>[[${session.loginUser}]]</button>不推荐使用 8、路径 @{}或@{~/}例:<link th:href="@{css/style.css}" rel="stylesheet"> //有时这样写不对,路径有问题,需写出下面的方式     <link th:href="@{~/css/style.css}" rel="stylesheet">9、消息 #{}       例:<h1 class="sign-title" th:text="#{login.tip}">请登录</h1>//i18n国际化例子
3、 th:元素应用图
image-20210602215352592
4、th:text和th:utext实例(前提:thymeleaf的pom.xml必须引入)
====================controller===================================//在templates目录下的所有页面,只能通过controller来跳转!//这个需要模板引擎的支持!thymeleaf@Controllerpublic class IndexController {    @RequestMapping("/index")    public String index(Model model){        model.addAttribute("msg","<h1>你好,Spring Boot!</h1>");        return "index";    }}
==============html========================<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8">    <title>Title</title></head><body><!--所有的html元素都可以被Thymeleaf替换接管:th:元素名-->    <div th:text="${msg}">首页</div>//    <div th:utext="${msg}">首页</div></body></html>
image-20210602220248530
5、each使用(数组遍历)(前提:thymeleaf的pom.xml必须引入)
====================controller===================================//在templates目录下的所有页面,只能通过controller来跳转!//这个需要模板引擎的支持!thymeleaf@Controllerpublic class IndexController {    @RequestMapping("/index")    public String index(Model model){        model.addAttribute("users", Arrays.asList("qinjiang","kuangshen"));        return "index";    }}
==============html========================<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8">    <title>Title</title></head><body><!--推荐使用--><h3 th:each="user:${users}" th:text="${user}"></h3><!--不推荐使用--><!--<h3 th:each="user:${users}">[[${user}]]</h3>--></body></html>
6、MVC原理

官网

image-20210602225256374

ctrl+o打开类重写的方法

//如果你想diy一些定制化的功能,只要写这个组件,然后将它交给spring boot,spring boot就会帮我们自动装配!//扩展springmvc    dispatchservlet@Configurationpublic class MyMvcConfig implements WebMvcConfigurer {    //public interface ViewResolver 实现了视图解析器接口的类,我们就可以把它看做视图解析器    @Bean    public ViewResolver myViewResolver(){        return new MyViewResolver();    }        //自定义了一个自己的视图解析器MyViewResolver    public static class MyViewResolver implements ViewResolver{        @Override        public View resolveViewName(String viewName, Locale locale) throws Exception {            return null;        }    }}
7、扩展spring mvc
#自定义的配置日期格式化!#已过时#spring.mvc.date-format=#spring.mvc.format.date=
//如果我们要扩展springmvc,官方建议我们这样去做!@Configurationpublic class MyMvcConfig implements WebMvcConfigurer {    //视图跳转    @Override    public void addViewControllers(ViewControllerRegistry registry) {        registry.addViewController("/kuang").setViewName("index");    }}
//@EnableWebMvc//这玩意就是导入了一个类:DelegatingWebMvcConfiguration:从容器中获取所有的WebMvcConfig:注意:千万不要加入@EnableWebMvc,否则所有东西都失效
总结
在spring boot中,有非常多的xxxConfiguration帮助我们进行扩展配置,只要看见了这个东西,我们就要注意了!
8、员工管理系统
1、准备工作
  1. 引入静态资源,页面等
  2. 创建实体类
<!--lombok--><dependency>    <groupId>org.projectlombok</groupId>    <artifactId>lombok</artifactId></dependency>
=================================部门表========================//部门表@Data@AllArgsConstructor@NoArgsConstructorpublic class Department {    private Integer id;    private String departmentName;}=================================员工表========================@Data@NoArgsConstructorpublic class Employee {    private Integer id;    private String lastName;    private String email;    private Integer gender;//0:女  1:男    private Department department;    private Date birth;    public Employee(Integer id, String lastName, String email, Integer gender, Department department) {        this.id = id;        this.lastName = lastName;        this.email = email;        this.gender = gender;        this.department = department;        //默认的创建日期!        this.birth = new Date();    }}    

3.模拟数据库数据

==============================部门dao======================================//部门dao@Repositorypublic class DepartmentDao {    //模拟数据库中的数据    private static Map<Integer, Department> departments=null;    static {        departments=new HashMap<Integer, Department>();//创建一个部门表        departments.put(101,new Department(101,"教学部"));        departments.put(102,new Department(102,"市场部"));        departments.put(103,new Department(103,"教研部"));        departments.put(104,new Department(104,"运营部"));        departments.put(105,new Department(105,"后勤部"));    }    //获得所有部门信息    public Collection<Department> getDepartments(){        return departments.values();    }    //通过id得到部门    public Department getDepartmentById(Integer id){        return departments.get(id);    }}==============================员工Dao======================================//员工Dao@Repositorypublic class EmployeeDao {    //模拟数据库中的数据    public static Map<Integer, Employee> employees=null;    //员工有所属的部门    @Autowired    private DepartmentDao departmentDao;    static {        employees=new HashMap<Integer, Employee>();        employees.put(1001,new Employee(1001,"AA","A23252679@qq.com",0,new Department(101,"教学部")));        employees.put(1002,new Employee(1002,"BB","B23252679@qq.com",1,new Department(102,"市场部")));        employees.put(1003,new Employee(1003,"CC","C23252679@qq.com",0,new Department(103,"教研部")));        employees.put(1004,new Employee(1004,"DD","D23252679@qq.com",1,new Department(104,"运营部")));        employees.put(1005,new Employee(1005,"EE","E23252679@qq.com",0,new Department(105,"后勤部")));    }    //主键自增!    private static Integer initId=1006;    //增加一个员工    public void save(Employee employee){        if (employee.getId()==null){            employee.setId(initId++);        }        employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));        employees.put(employee.getId(),employee);    }    //查询全部员工信息    public Collection<Employee> getAll(){        return employees.values();    }    //通过id查询员工    public Employee getEmployeeById(Integer id){        return employees.get(id);    }    //删除员工通过id    public void delete(Integer id){        employees.remove(id);    }}
2、首页实现
  1. 访问首页:
====================推荐:================================@Configurationpublic class MyMvcConfig implements WebMvcConfigurer {    @Override    public void addViewControllers(ViewControllerRegistry registry) {        registry.addViewController("/").setViewName("login");        registry.addViewController("/login.html").setViewName("login");    }}=====================等同于上面==================================== @Controllerpublic class IndexController {    @RequestMapping({"/","/index.html"})   public String index(){       return "index";   }}
# 关闭模板引擎的缓存spring.thymeleaf.cache=false#注:高版本好像不需要这步

3.页面配置:

  1. 注意点,所有的静态资源都需要使用Thymeleaf接管;

    <html lang="en" xmlns:th="http://www.thymeleaf.org">    <link th:href="@{~/css/style.css}" rel="stylesheet">    <!-- 响应式样式 --><link th:href="@{~/css/style-responsive.css}" rel="stylesheet">注: 有时@{}路径不对,需要写为@{~} 
    
  2. url:@{}

3、国际化

页面国际化:(实现中英切换)

  1. 我们需要配置i18n文件

    ==============login.properties======================login.btn=登录login.forget=忘记密码login.notip=还没有账号?login.password=密码login.register=去注册login.rempass=记住密码login.tip=请登录login.username=用户名==============login_zh_CN.properties======================login.btn=登录login.forget=忘记密码login.notip=还没有账号?login.password=密码login.register=去注册login.rempass=记住密码login.tip=请登录login.username=用户名==============login_en_US.properties======================login.btn=Sign inlogin.forget=Forget passwordlogin.notip=No account yet?login.password=Passwordlogin.register=To registerlogin.rempass=Remember passwordlogin.tip=Please sign inlogin.username=Username
    
    image-20210604225710183

    注意添加:

    #我们的配置文件的真实位置spring.messages.basename=i18n.login
    
  2. 我们如果需要在项目中进行按钮自动切换,我们需要自定义一个组件LocaleResolver

    public class MyLocaleResolver implements LocaleResolver {    //解析请求    @Override    public Locale resolveLocale(HttpServletRequest request) {        //获取请求中的语言参数        String language=request.getParameter("l");        Locale locale=Locale.getDefault();//如果没有就使用默认的;        //如果请求的链接携带了国际化的参数        if (!ObjectUtils.isEmpty(language)){            //zh_CN            String[] split=language.split("_");            //国家,地区            locale=new Locale(split[0],split[1]);        }        return locale;    }    @Override    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {    }}
    
    image-20210604230000897
  3. 记得将自己写的组件配置到spring容器@Bean

    @Configurationpublic class MyMvcConfig implements WebMvcConfigurer {    //自定义的国际化组件就生效了    @Bean    public LocaleResolver localeResolver(){         return new MyLocaleResolver();    }}
    
    image-20210604230100711
  4. #{}

     <div class="language">        <a class="btn btn-sm btn-zh" th:href="@{/login.html(l='zh_CN')}">中文</a>        <a class="btn btn-sm btn-en" th:href="@{/login.html(l='en_US')}">English</a></div>
    
    image-20210604230242550
4、登录功能实现
  1. 登录html页面:
=============================登录页面主要部分============================================  <form class="form-signin" th:action="@{/user/login}">        <div class="form-signin-heading text-center">            <h1 class="sign-title" th:text="#{login.tip}">请登录</h1>            <!--如果msg的值为空,则不显示消息-->            <p  style="color:red;" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>            <img th:src="@{~/images/login-logo.png}" alt=""/>        </div>        <div class="login-wrap">            <input type="text" name="username" th:placeholder="#{login.username}" class="form-control" autofocus>            <input type="password" name="password" th:placeholder="#{login.password}" class="form-control">            <button class="btn btn-lg btn-login btn-block" type="submit" th:text="#{login.btn}">                <!--[[#{login.btn}]]与 th:text="#{login.btn}写法一样-->                <!--<i class="fa fa-check"></i>-->            </button>            <div class="registration">                [[#{login.notip}]]                <a class="" href="registration.html" th:text="#{login.register}">                    去注册                </a>            </div>            <label class="checkbox">                <input type="checkbox" value="remember-me" th:text="#{login.rempass}">                <span class="pull-right">                    <a data-toggle="modal" href="#myModal" th:text="#{login.forget}"></a>                </span>            </label>        </div></form>      
image-20210605114944642

2.登录Controller层实现

=======================登录Controller================================@Controllerpublic class LoginController {    @RequestMapping({"/user/login"})    public String login(@RequestParam("username")String username, @RequestParam("password")String password,                        Model model, HttpSession session){        //具体的业务:        if (!ObjectUtils.isEmpty(username)&&password.equals("123")){            session.setAttribute("loginUser",username);            return "redirect:/main.html";        }else {            //告诉用户,你登录失败了            model.addAttribute("msg","用户名或者密码错误");            return "login";        }    }}
image-20210605115618664

3.虚拟路径跳转位置:

@Configurationpublic class MyMvcConfig implements WebMvcConfigurer {    @Override    public void addViewControllers(ViewControllerRegistry registry) {        registry.addViewController("/").setViewName("login");        registry.addViewController("/login.html").setViewName("login");        registry.addViewController("/main.html").setViewName("index");//虚拟路径跳转到主页    }}

4.注意:

以上有个问题,直接访问http://localhost:8080/main.html,就会直接进入主页。

解决方案:配置登录拦截器

5、登录拦截器
  1. 自定义配置拦截器:
//拦截器public class LoginHandlerInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        //登录成功之后,应该有用户的session        Object loginUser =request.getSession().getAttribute("loginUser");        if (loginUser==null){//没有登录            request.setAttribute("msg","没有权限,请先登录");            request.getRequestDispatcher("/login.html").forward(request,response);            return false;        }else {            return true;        }    }}

2.拦截器生效:

@Configurationpublic class MyMvcConfig implements WebMvcConfigurer {    //配置拦截器    //.addPathPatterns("/**")拦截全部    //excludePathPatterns("/login.html","/","user/login","/css/**","/fonts/**","/images/**","/js/**")不拦截登录和静态资源    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(new LoginHandlerInterceptor())                .addPathPatterns("/**").excludePathPatterns("/login.html","/","/user/login",                "/css/**","/fonts/**","/images/**","/js/**");    }}

3.错误页面提示:

====================login.html页面===============================<!--如果msg的值为空,则不显示消息--><p  style="color:red;" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

4.成功页面取出session: [[${session.loginUser}]]

====================index.html页面===============================<a href="#" class="btn btn-default dropdown-toggle" data-toggle="dropdown">    <img src="images/photos/user-avatar.png" alt="" />    [[${session.loginUser}]]    <span class="caret"></span></a>
6、员工列表展示

注:(前提:需要1、准备工作的pojo和模拟数据)

  1. 提取公共页面

    1、<html lang="en" xmlns:th="http://www.thymeleaf.org">2、<div th:fragment="sidebar">3、<div th:replace="~{commons/commons::sidebar}">4、如果要传递参数,可以直接使用()传参,接受判断即可!<!--传递参数给组件(active='main.html')--><div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>   <li th:class="${active=='main.html'?'active':''}">    
    
    image-20210605161756227 image-20210605162007646 image-20210605162211540
  2. 列表循环展示

    所用:

    循环遍历th:each,取值th:text="${变量}",三元运算th:text="${条件?:}",日期转换th:text="${#dates.format(转换的值或变量,'yyyy-MM-dd HH:mm:ss')}"
    

    具体实现:

    controller层

    =====================controller层=================================@Controllerpublic class EmployeeController {    @Autowired    EmployeeDao employeeDao;    @RequestMapping("/emps")    public String list(Model model){        Collection<Employee> employees=employeeDao.getAll();        model.addAttribute("emps",employees);        return "emp/list";    }}
    

    list.html页面

    <table  class="display table table-bordered table-striped" id="dynamic-table">    <thead>        <tr>            <th>id</th>            <th>lastName</th>            <th>email</th>            <th>gender</th>            <th>department</th>            <th>birth</th>            <th>操作</th>        </tr>    </thead>    <tbody>        <tr th:each="emp:${emps}" >            <td th:text="${emp.getId()}"></td>            <td th:text="${emp.getLastName()}"></td>            <td th:text="${emp.getEmail()}"></td>            <td th:text="${emp.getGender()==0?'':''}"></td>            <td th:text="${emp.getDepartment.getDepartmentName()}"></td>            <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>            <td>                <button class="btn btn-sm btn-primary">编辑</button>                <button class="btn btn-sm btn-danger">删除</button>            </td>        </tr>    </tbody></table>
    
7、添加员工实现
  1. 按钮提交

    ===============================list.html==============================<h2>    <a th:href="@{/emp}" class="btn btn-sm btn-success">添加员工</a></h2>
    
  2. 跳转到添加页面

    controller

    @Controllerpublic class EmployeeController {    @Autowired    EmployeeDao employeeDao;    @Autowired    DepartmentDao departmentDao;    @GetMapping("/emp")    public String toAddPage(Model model){        //查出所有部门的信息        Collection<Department> departments = departmentDao.getDepartments();        model.addAttribute("departments",departments);        return "emp/add";    }}
    

    add.html

    <form th:action="@{/emp}" method="post">    <div class="form-group">        <label>lastName</label>        <input type="text" name="lastName" class="form-control" placeholder="xiaokeai">    </div>    <div class="form-group">        <label>email</label>        <input type="email" name="email" class="form-control" placeholder="25638342@qq.com">    </div>    <div class="form-group">        <label>gender</label><br/>        <div class="radio">            <label>                <input type="radio" name="gender"  value="1" checked></label>        </div>        <div class="radio">            <label>                <input type="radio" name="gender"  value="0"></label>        </div>    </div>    <div class="form-group">        <label>department</label>        <select class="form-control" name="department.id">            <!--我们在controller接收的是一个Employee,所以我们需要提交的是其中的一个属性!-->            <option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>        </select>    </div>    <div class="form-group">        <label>birth</label>        <input type="text" name="birth" class="form-control" placeholder="keai">    </div>    <button type="submit" class="btn btn-primary">添加</button></form>
    
  3. 添加员工成功,返回首页

    controller

    @Controllerpublic class EmployeeController {    @Autowired    EmployeeDao employeeDao;    @Autowired    DepartmentDao departmentDao;    @PostMapping("/emp")    public String addEmp(Employee employee){        System.out.println("employee");        employeeDao.save(employee);//调用底层业务方法保存员工信息        System.out.println(employee);        return "redirect:/emps";    }}
    
8、修改员工信息
  1. 按钮提交

    ===============================list.html==============================<td>    <a class="btn btn-sm btn-primary" th:href="@{'/emp/'+${emp.getId()}}">编辑</a>    <button class="btn btn-sm btn-danger">删除</button></td>
    
    image-20210606171251647
  2. 跳转到修改页面

    controller

    @Controllerpublic class EmployeeController {    @Autowired    EmployeeDao employeeDao;    @Autowired    DepartmentDao departmentDao;        //去员工的修改页面    @GetMapping("/emp/{id}")    public String toUpdate(@PathVariable("id")Integer id,Model model){        //查出员工的数据        Employee employee = employeeDao.getEmployeeById(id);        model.addAttribute("emp",employee);        //查出所有部门的信息        Collection<Department> departments = departmentDao.getDepartments();        model.addAttribute("departments",departments);        return "emp/update";    }}
    

    update.html

    <form th:action="@{/updateEmp}" method="post">    <input type="hidden" name="id" th:value="${emp.getId()}">    <div class="form-group">        <label>lastName</label>        <input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="xiaokeai">    </div>    <div class="form-group">        <label>email</label>        <input th:value="${emp.getEmail()}"type="email" name="email" class="form-control" placeholder="25638342@qq.com">    </div>    <div class="form-group">        <label>gender</label><br/>        <div class="radio">            <label>                <input type="radio" name="gender"  value="1" checked th:checked="${emp.getGender()==1}"></label>        </div>        <div class="radio">            <label>                <input type="radio" name="gender"  value="0" th:checked="${emp.getGender()==0}"></label>        </div>    </div>    <div class="form-group">        <label>department</label>        <select class="form-control" name="department.id">            <!--我们在controller接收的是一个Employee,所以我们需要提交的是其中的一个属性!-->            <option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>        </select>    </div>    <div class="form-group">        <label>birth</label>        <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" type="text" name="birth" class="form-control" placeholder="keai">    </div>    <button type="submit" class="btn btn-primary">修改</button></form>
    
  3. 修改成功,回到首页

    controller

    @Controllerpublic class EmployeeController {    @Autowired    EmployeeDao employeeDao;    @Autowired    DepartmentDao departmentDao;        @PostMapping("/updateEmp")    public String updateEmp(Employee employee){        employeeDao.save(employee);        return "redirect:/emps";    }}
    
9.删除员工信息
  1. 按钮提交

    ===============================list.html==============================<td>    <a class="btn btn-sm btn-primary" th:href="@{'/emp/'+${emp.getId()}}">编辑</a>    <a class="btn btn-sm btn-danger" th:href="@{'/deleteEmp/'+${emp.getId()}}">删除</a></td>
    
  2. 删除成功,返回页面

    controller

    @Controllerpublic class EmployeeController {    @Autowired    EmployeeDao employeeDao;    @GetMapping("/deleteEmp/{id}")    public String delete(@PathVariable("id")Integer id){        employeeDao.delete(id);        return "redirect:/emps";    }}
    
10、404处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hieWgl9B-1623924293483)(SpringBoot.assets/image-20210606175902841.png)]

11、用户退出

方式一:(不推荐使用)因为有session,直接访问http://localhost:8080/main.html,就会进入主页。

<li><a th:href="@{/login.html}"><i class="fa fa-sign-out"></i> 登出</a></li>

方式二:(推荐)因为清掉了session,直接访问http://localhost:8080/main.html,不会进入主页。

html

<li><a th:href="@{/user/logout}"><i class="fa fa-sign-out"></i> 登出</a></li>

controller

@Controllerpublic class LoginController {    @RequestMapping("/user/logout")    public String logout(HttpSession session){        session.invalidate();//移除session        return "redirect:/login.html";    }}
9、如何写一个网站

前端:

  • 模板:别人写好的,我们拿来改成自己需要的
  • 框架:组件:自己手动组合拼接!Bootstarp、Layui、semantic-ui…
    • 栅格系统
    • 导航栏
    • 侧边栏
    • 表单
# 1、前端搞定: 页面长什么样子: 数据# 2、设计数据库(数据库设计难点!)# 3、前端让他能够自动运行,独立化工程# 4、数据接口如何对接:json,对象 all in one!# 5、前后端联调测试!1.有一套自己熟悉的后台模板:工作必要!x-admin2.前端界面:至少自己能够通过前端框架,组合出来一个网站页面   -index   -about   -blog   -post   -user3.让这个网站能够独立运行一个月!   

上周回顾

  • spring boot是什么?
  • 微服务
  • HelloWorld~
  • 探究源码~ 自动装配原理~
  • 配置 yaml
  • 多文档环境切换
  • 静态资源映射
  • Thymeleaf th:xxx
  • spring boot如何扩展MVC
  • 如何修改spring boot的默认~
  • CRUD
  • 国际化
  • 拦截器
  • 定制首页,错误页~

这周:

  • JDBC
  • Mybatis:重点
  • Druid:重点
  • Shiro:安全:重点
  • Spring Security:安全:重点
  • 异步任务~,邮件发送,定时任务
  • Swagger
  • Dubbo+Zookeeper

DATA

简介

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

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

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

数据库相关的启动器:可以参考官方文档: https://docs.spring.io/spring-boot/docs/2.1.7.RELEASE/reference/htmlsingle/#using-boot-starter

整合JDBC使用

image-20210608154551000

pom.xml

<dependencies>    <!--web-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <!--JDBC-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-jdbc</artifactId>    </dependency>    <!--MySQL-->    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <scope>runtime</scope>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency></dependencies>

test

@SpringBootTestclass KsBoot04DataApplicationTests {    @Autowired    DataSource dataSource;    @Test    void contextLoads() throws SQLException {        //查看一下默认的数据源:class com.zaxxer.hikari.HikariDataSource        System.out.println(dataSource.getClass());        //获得数据库连接        Connection connection = dataSource.getConnection();        System.out.println(connection);        //xxxx Template : SpringBoot已经配置好模板bean,拿来即用 CRUD        //关闭        connection.close();    }}

application.yaml

spring:  datasource:    username: root    password: root    #假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC    url: jdbc:mysql://localhost:3306/ceshi?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8    driver-class-name: com.mysql.cj.jdbc.Driver

controller

@RestControllerpublic class JDBCController {    @Autowired    JdbcTemplate jdbcTemplate;    //查询数据库的所有信息    //没有实体类,数据库中的东西,怎么获取?Map    @GetMapping("/userList")    public List<Map<String,Object>> userList(){        String sql="select * from user";        List<Map<String,Object>> list_maps=jdbcTemplate.queryForList(sql);        return list_maps;    }    @GetMapping("/addUser")    public String addUser(){        String sql="insert into user(id,username,password) values(3,'quest','quest')";        jdbcTemplate.update(sql);        return "add-ok";    }    @GetMapping("/updateUser/{id}")    public String updateUser(@PathVariable("id") int id){        String sql="update user set username=?,password=? where id="+id;        //封装        Object[] objects=new Object[2];        objects[0] = "小可爱";        objects[1] = "vip";        jdbcTemplate.update(sql,objects);        return "update-ok";    }    @GetMapping("/deleteUser/{id}")    public String deleteUser(@PathVariable("id") int id){        String sql="delete from user where id=?";        jdbcTemplate.update(sql,id);        return "delete-ok";    }}

运行

image-20210608154751265 image-20210608154817059 image-20210608154843962 image-20210608154906382

整合Druid数据源

引入Druid官网: https://mvnrepository.com/artifact/com.alibaba/druid

Hikari和Druid都是当前Java Web上最优秀的数据源。HikariDataSource号称Java Web当前速度最快的数据源,相比于传统的C3P0、DBCP、Tomcat  jdbc等连接池更加优秀。Druid可以更好的监控DB池连接和SQL的执行情况。
1、pom.xml
<!--Druid--><dependency>    <groupId>com.alibaba</groupId>    <artifactId>druid</artifactId>    <version>1.2.3</version></dependency>
2、指定数据源Druid(type: com.alibaba.druid.pool.DruidDataSource)
spring:  datasource:    username: root    password: root    #假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC    url: jdbc:mysql://localhost:3306/ceshi?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8    driver-class-name: com.mysql.cj.jdbc.Driver    type: com.alibaba.druid.pool.DruidDataSource
3、Druid其他配置
  #Spring Boot 默认是不注入这些属性值的,需要自己绑定    #druid 数据源专有配置    initialSize: 5    minIdle: 5    maxActive: 20    maxWait: 60000    timeBetweenEvictionRunsMillis: 60000    minEvictableIdleTimeMillis: 300000    validationQuery: SELECT 1 FROM DUAL    testWhileIdle: true    testOnBorrow: false    testOnReturn: false    poolPreparedStatements: true    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j    filters: stat,wall,log4j    maxPoolPreparedStatementPerConnectionSize: 20    useGlobalDataSourceStat: true    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
4、关联yaml 里的配置、后台监控和过滤器
@Configurationpublic class DruidConfig {    @ConfigurationProperties(prefix = "spring.datasource")    @Bean    public DataSource druidDataSource(){        return new DruidDataSource();    }    //后台监控 : web.xml,ServletRegistrationBean    //因为Spring Boot内置了servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean    @Bean    public ServletRegistrationBean statViewServlet(){        ServletRegistrationBean<StatViewServlet> bean=new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*");        //后台需要有人登录,账号密码配置        HashMap<String,String> initParameters=new HashMap<>();        //增加配置        initParameters.put("loginUsername","admin");//登录key 是固定的 loginUsername loginPassword        initParameters.put("loginPassword","admin");        //允许谁可以访问        initParameters.put("allow","");        //禁止谁能访问  initParameters.put("xiaokeai","192.168.13.14");        bean.setInitParameters(initParameters);//设置初始化参数        return bean;    }    //filter    @Bean    public FilterRegistrationBean webStatFilter(){        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();        bean.setFilter(new WebStatFilter());        //可与过滤哪些请求呢?        HashMap<String, String> initParameters = new HashMap<>();        //这些东西不进行统计        initParameters.put("exclusions","*.js,*.css,/druid/*");        bean.setInitParameters(initParameters);        return bean;    }}
5、访问
image-20210611134304109

登录成功后:

image-20210611134628837

整合Mybatis

整合包

mybatis-spring-boot-starter

mybatis-spring-boot-starter官方文档

M:数据和业务

C:交接

V:HTML

1、pom.xml
<dependencies>    <!-- mybatis-spring-boot-starter:整合 -->    <dependency>        <groupId>org.mybatis.spring.boot</groupId>        <artifactId>mybatis-spring-boot-starter</artifactId>        <version>2.2.0</version>    </dependency>    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>    </dependency>    <!--官方的-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-jdbc</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <scope>runtime</scope>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency></dependencies>
2、properties配置数据库
spring.datasource.username=rootspring.datasource.password=rootspring.datasource.url=jdbc:mysql://localhost:3306/ceshi?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3、测试是否连接数据库
@SpringBootTestclass KsBoot05MybatisApplicationTests {    @Autowired    DataSource dataSource;    @Test    void contextLoads() throws SQLException {        System.out.println(dataSource.getClass());        System.out.println(dataSource.getConnection());    }}
image-20210611142829245
4、pojo实体类
@Data@AllArgsConstructor@NoArgsConstructorpublic class User {    private int id;    private String username;    private String password;}
5、整合mybatis的properties配置
# 整合mybatismybatis.type-aliases-package=com.kuang.pojomybatis.mapper-locations=classpath:mybatis/mapper/*.xml
image-20210611151627768
6、编写mapper接口和mapper.xml文件(dao层)

mapper接口:

//这个注解表示了这是一个 mybatis 的 mapper类;@Mapper//方式一  //方式二 在主程序上加这个@MapperScan("com.kuang.mapper")@Repositorypublic interface UserMapper {    List<User> queryUserList();    User queryUserById(Integer id);    int addUser(User user);    int updateUser(User user);    int deleteUser(Integer id);}

方式二所写位置:

@SpringBootApplication//@MapperScan("com.kuang.mapper")方式二public class KsBoot05MybatisApplication {    public static void main(String[] args) {        SpringApplication.run(KsBoot05MybatisApplication.class, args);    }}

mapper.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.kuang.mapper.UserMapper">    <select id="queryUserList" resultType="User">        select * from user    </select>    <select id="queryUserById" resultType="User">        select * from user where id=#{id}    </select>    <insert id="addUser" parameterType="User">        insert into user(username,password) values (#{username},#{password})    </insert>    <update id="updateUser" parameterType="User">        update user set username=#{username},password=#{password} where id=#{id}    </update>    <delete id="deleteUser" parameterType="int">        delete from user where id=#{id}    </delete></mapper>
7、controller
@RestControllerpublic class UserController {    @Autowired    private UserMapper userMapper;    @GetMapping("/queryUserList")    public List<User> queryUserList(){        List<User> users = userMapper.queryUserList();        return users;    }    @GetMapping("/queryUserById/{id}")    public User queryUserById(@PathVariable("id") int id){        User user = userMapper.queryUserById(id);        return user;    }    @GetMapping("/addUser")    public String addUser(){        userMapper.addUser(new User(3,"quest","quest"));        return "ok";    }    @GetMapping("/updateUser")    public String updateUser(){        userMapper.updateUser(new User(3,"测试","quest"));        return "ok";    }    @GetMapping("/deleteUser")    public String deleteUser(){        userMapper.deleteUser(3);        return "ok";    }}
8、测试
image-20210611152147415

SpringSecurity(安全)

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

功能性需求:否

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

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

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

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

AOP:横切~ 配置类~

简介

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

记住几个类:

  • WebSecurityConfigureAdapter:自定义Security策略
  • AuthenticationManagerBuild:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式 @Enablexxx开启某个功能

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

“认证”(Authentication)

“授权”(Authorization)

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

Spring Security官网: https://spring.io/projects/spring-security

Spring Security对应的帮助文档: https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle/

1、Spring Security环境搭建

1、pom.xml
<dependencies>    <!--security-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-security</artifactId>    </dependency>    <!--web-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <!--thymeleaf-->    <dependency>        <groupId>org.thymeleaf</groupId>        <artifactId>thymeleaf-spring5</artifactId>    </dependency>    <dependency>        <groupId>org.thymeleaf.extras</groupId>        <artifactId>thymeleaf-extras-java8time</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency></dependencies>
2、引入静态资源
image-20210611202204585
3、properties配置关闭模板缓存
# 关闭模板缓存spring.thymeleaf.cache=false
4、controller访问静态页面
@Controllerpublic class RouterController {    @RequestMapping({"/","/index"})    public String index(){        return "index";    }    @RequestMapping("toLogin")    public String toLogin(){        return "views/login";    }    @RequestMapping("/level1/{id}")    public String level1(@PathVariable("id") int id){        return "views/level1/"+id;    }    @RequestMapping("/level2/{id}")    public String level2(@PathVariable("id") int id){        return "views/level2/"+id;    }    @RequestMapping("/level3/{id}")    public String level3(@PathVariable("id") int id){        return "views/level3/"+id;    }}

2、用户认证和授权

1、在Spring Security环境搭建完成基础下执行以下步骤
2、config编写认证和授权
@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    //链式编程    //授权    @Override    protected void configure(HttpSecurity http) throws Exception {        //首页所有人都可以访问, 功能页只有对应有权限的人才能访问        //请求权限的规则        http.authorizeRequests()                .antMatchers("/").permitAll()                .antMatchers("/level1/**").hasRole("vip1")                .antMatchers("/level2/**").hasRole("vip2")                .antMatchers("/level3/**").hasRole("vip3");        //没有权限默认会到登录页面,需要开启登录的页面           //Login        http.formLogin();    }    //认证 spring boot 2.1.X 可以直接使用    //密码编码:PasswordEncoder    //在Spring Security 5.0+ 新增了很多的加密方式~    //解决方式:    // .passwordEncoder(new BCryptPasswordEncoder())    //.password(new BCryptPasswordEncoder().encode("root"))    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        //这些数据正常应该从数据库中读        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())                .withUser("admin").password(new BCryptPasswordEncoder().encode("admin")).roles("vip1","vip2","vip3")                .and()                .withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("vip1","vip2")                .and()                .withUser("quest").password(new BCryptPasswordEncoder().encode("quest")).roles("vip1");    }}
3、效果
image-20210611202443353 image-20210611202647329

3、注销及权限控制

0、在1和2的环境搭建全的基础上执行以下步骤
1、注销功能实现

congfig中写功能(http.logout().logoutSuccessUrl("/");)

@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    //链式编程    //授权    @Override    protected void configure(HttpSecurity http) throws Exception {        //首页所有人都可以访问, 功能页只有对应有权限的人才能访问        //请求权限的规则        http.authorizeRequests()                .antMatchers("/").permitAll()                .antMatchers("/level1/**").hasRole("vip1")                .antMatchers("/level2/**").hasRole("vip2")                .antMatchers("/level3/**").hasRole("vip3");        //没有权限默认会到登录页面,需要开启登录的页面           //Login        http.formLogin();        //注销,开启了注销功能        http.logout().logoutSuccessUrl("/");    }}

所在位置:

image-20210612155341065

html

<!--注销--><a class="item" th:href="@{/logout}">    <i class="sign-out icon"></i> 注销</a>
2、权限控制
1、pom.xml

由于thymeleaf-extras-springsecurity4只支持spring boot2.0.9版本和spring boot2.0.7版本,这两个版本是支持里面最好使的两版本。不满足spring boot2.5.1版本。可能不满足spring boot2.0.9版本以上。

我用的spring boot2.5.1版本,比上面两个版本样式好看多了,thymeleaf-extras-springsecurity5满足支持spring boot2.5.1版本。【推荐使用】

<!--security-thymeleaf整合包--><!--<dependency>--><!--<groupId>org.thymeleaf.extras</groupId>--><!--<artifactId>thymeleaf-extras-springsecurity4</artifactId>--><!--<version>3.0.4.RELEASE</version>--><!--</dependency>--><dependency>    <groupId>org.thymeleaf.extras</groupId>    <artifactId>thymeleaf-extras-springsecurity5</artifactId>    <version>3.0.4.RELEASE</version></dependency>
2、spring security 中html头部引入
<html lang="en" xmlns:th="http://www.thymeleaf.org"      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
3、html页面登录、注销、用户和角色权限控制

如果输入sec:authorize="!isAuthenticated()"不提示,可以ctrl+F9。

sec:authorize="!isAuthenticated()"判断登录状态(登录或未登录)

sec:authentication="name"获取用户名

sec:authentication="principal.authorities"获取用户包含的角色

<!--登录注销--><div class="right menu">    <!--如果未登录-->    <div sec:authorize="!isAuthenticated()">        <a class="item" th:href="@{/toLogin}">            <i class="address card icon"></i> 登录        </a>    </div>    <!--如果登录:用户名,注销-->    <div sec:authorize="isAuthenticated()">        <a class="item">            用户名:<span sec:authentication="name"></span>            角色:  <span sec:authentication="principal.authorities"></span>        </a>    </div>    <div sec:authorize="isAuthenticated()">        <!--注销-->        <a class="item" th:href="@{/logout}">            <i class="sign-out icon"></i> 注销        </a>    </div></div>
4、注意用thymeleaf-extras-springsecurity4可能出现错误

问题:注销注销不了,报错

解决方案:加这个 http.csrf().disable();//关闭csrf功能。 登录失败可能存在的原因~

@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    //链式编程    //授权    @Override    protected void configure(HttpSecurity http) throws Exception {        //首页所有人都可以访问, 功能页只有对应有权限的人才能访问        //请求权限的规则        http.authorizeRequests()                .antMatchers("/").permitAll()                .antMatchers("/level1/**").hasRole("vip1")                .antMatchers("/level2/**").hasRole("vip2")                .antMatchers("/level3/**").hasRole("vip3");        //没有权限默认会到登录页面,需要开启登录的页面           //Login        http.formLogin();        //注销,开启了注销功能        //防止网站攻击  get,post        http.csrf().disable();//关闭csrf功能。 注销失败可能存在的原因~        http.logout().logoutSuccessUrl("/");    }}
5、html主内容(权限判断显示内容)

sec:authorize=“hasRole(‘vip1’)” 根据判断用户所具备的角色判断所展示对应的页面

<div class="ui three column stackable grid">    <div class="column" sec:authorize="hasRole('vip1')">        <div class="ui raised segment">            <div class="ui">                <div class="content">                    <h5 class="content">Level 1</h5>                    <hr>                    <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>                    <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>                    <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>                </div>            </div>        </div>    </div>    <div class="column" sec:authorize="hasRole('vip2')">        <div class="ui raised segment">            <div class="ui">                <div class="content">                    <h5 class="content">Level 2</h5>                    <hr>                    <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>                    <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>                    <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>                </div>            </div>        </div>    </div>    <div class="column" sec:authorize="hasRole('vip3')">        <div class="ui raised segment">            <div class="ui">                <div class="content">                    <h5 class="content">Level 3</h5>                    <hr>                    <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>                    <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>                    <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>                </div>            </div>        </div>    </div></div>
6、效果
image-20210612170413323 image-20210612170448879

4、记住我及首页定制

0、在1和2的环境搭建全的基础上执行以下步骤
1、记住我
1、记住我功能实现,在它默认的登录上使用( http.rememberMe();)
@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    //链式编程    //授权    @Override    protected void configure(HttpSecurity http) throws Exception {        //首页所有人都可以访问, 功能页只有对应有权限的人才能访问        //请求权限的规则        http.authorizeRequests()                .antMatchers("/").permitAll()                .antMatchers("/level1/**").hasRole("vip1")                .antMatchers("/level2/**").hasRole("vip2")                .antMatchers("/level3/**").hasRole("vip3");        //没有权限默认会到登录页面,需要开启登录的页面           //Login        http.formLogin();        //注销,开启了注销功能        //防止网站攻击  get,post        http.csrf().disable();//关闭csrf功能。 注销失败可能存在的原因~        http.logout().logoutSuccessUrl("/");        //开启记住我功能 本质上是cookie,默认保存两周        http.rememberMe();    }}
2、自定义首页时,自定义记住我

config:【 http.rememberMe().rememberMeParameter(“remember”);】

@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    //链式编程    //授权    @Override    protected void configure(HttpSecurity http) throws Exception {        //首页所有人都可以访问, 功能页只有对应有权限的人才能访问        //请求权限的规则        http.authorizeRequests()                .antMatchers("/").permitAll()                .antMatchers("/level1/**").hasRole("vip1")                .antMatchers("/level2/**").hasRole("vip2")                .antMatchers("/level3/**").hasRole("vip3");        //没有权限默认会到登录页面,需要开启登录的页面           //Login        //默认是login页面,定制登录页.loginPage("/toLogin"),toLogin我们写的页面        //如果login登录页面用户名的name值是username,密码password的name值是就不用写.usernameParameter("xxx").passwordParameter("xxx")        //如果login登录页面用户名自定义name值是user,密码自定义name值是pwd就需要写.usernameParameter("user").passwordParameter("pwd")        //登录成功from跳转路径如果与.loginPage("/toLogin")里的值对应就不用写.loginProcessingUrl("/xxx"),不对应则写对应的from表单提交的地址.loginProcessingUrl("/login")。        http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");        //注销,开启了注销功能        //防止网站攻击  get,post        http.csrf().disable();//关闭csrf功能。 注销失败可能存在的原因~        http.logout().logoutSuccessUrl("/");        //开启记住我功能 本质上是cookie,默认保存两周。自定义接受前端的参数.rememberMeParameter("remember")        http.rememberMe().rememberMeParameter("remember");    }}

html:【记住我】

<div class="field">    <input type="checkbox" name="remember">记住我</div>
2、首页定制
1、首页功能定制功能实现

config: 【http.formLogin().loginPage("/toLogin");】

@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    //链式编程    //授权    @Override    protected void configure(HttpSecurity http) throws Exception {        //首页所有人都可以访问, 功能页只有对应有权限的人才能访问        //请求权限的规则        http.authorizeRequests()                .antMatchers("/").permitAll()                .antMatchers("/level1/**").hasRole("vip1")                .antMatchers("/level2/**").hasRole("vip2")                .antMatchers("/level3/**").hasRole("vip3");        //没有权限默认会到登录页面,需要开启登录的页面           //Login        //默认是login页面,定制登录页.loginPage("/toLogin"),toLogin我们写的页面        http.formLogin().loginPage("/toLogin");        //注销,开启了注销功能        //防止网站攻击  get,post        http.csrf().disable();//关闭csrf功能。 注销失败可能存在的原因~        http.logout().logoutSuccessUrl("/");        //开启记住我功能 本质上是cookie,默认保存两周        http.rememberMe();    }}

html:【】

<form th:action="@{/toLogin}" method="post">    <div class="field">        <label>Username</label>        <div class="ui left icon input">            <input type="text" placeholder="Username" name="username">            <i class="user icon"></i>        </div>    </div>    <div class="field">        <label>Password</label>        <div class="ui left icon input">            <input type="password" name="password">            <i class="lock icon"></i>        </div>    </div>    <input type="submit" class="ui blue submit button"/></form>
2、注意1可能出现的问题及解决
如果login登录页面用户名的name值是username,密码password的name值是就不用写.usernameParameter("xxx").passwordParameter("xxx")如果login登录页面用户名自定义name值是user,密码自定义name值是pwd就需要写.usernameParameter("user").passwordParameter("pwd")登录成功from跳转路径如果与.loginPage("/toLogin")里的值对应就不用写.loginProcessingUrl("/xxx"),不对应则写对应的from表单提交的地址.loginProcessingUrl("/login")。

config: 【 http.formLogin().loginPage("/toLogin").usernameParameter(“user”).passwordParameter(“pwd”).loginProcessingUrl("/login");】

@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    //链式编程    //授权    @Override    protected void configure(HttpSecurity http) throws Exception {        //首页所有人都可以访问, 功能页只有对应有权限的人才能访问        //请求权限的规则        http.authorizeRequests()                .antMatchers("/").permitAll()                .antMatchers("/level1/**").hasRole("vip1")                .antMatchers("/level2/**").hasRole("vip2")                .antMatchers("/level3/**").hasRole("vip3");        //没有权限默认会到登录页面,需要开启登录的页面           //Login        //默认是login页面,定制登录页.loginPage("/toLogin"),toLogin我们写的页面        //如果login登录页面用户名的name值是username,密码password的name值是就不用写.usernameParameter("xxx").passwordParameter("xxx")        //如果login登录页面用户名自定义name值是user,密码自定义name值是pwd就需要写.usernameParameter("user").passwordParameter("pwd")        //登录成功from跳转路径如果与.loginPage("/toLogin")里的值对应就不用写.loginProcessingUrl("/xxx"),不对应则写对应的from表单提交的地址.loginProcessingUrl("/login")。        http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");        //注销,开启了注销功能        //防止网站攻击  get,post        http.csrf().disable();//关闭csrf功能。 注销失败可能存在的原因~        http.logout().logoutSuccessUrl("/");        //开启记住我功能 本质上是cookie,默认保存两周        http.rememberMe();    }}

html【、、】

<form th:action="@{/login}" method="post">    <div class="field">        <label>Username</label>        <div class="ui left icon input">            <input type="text" placeholder="Username" name="user">            <i class="user icon"></i>        </div>    </div>    <div class="field">        <label>Password</label>        <div class="ui left icon input">            <input type="password" name="pwd">            <i class="lock icon"></i>        </div>    </div>    <input type="submit" class="ui blue submit button"/></form>
image-20210612182707025

Shiro

简介

1、什么是Shiro?
  • Apache Shiro是一个Java 的安全(权限)框架。
  • Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
  • Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。
  • Shiro下载地址: http://shiro.apache.org/
image-20210612185523053
2、有哪些功能?
image-20210612185918430
  • Authentication: 身份认证、登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限!
  • Session Manager: 会话管理,即用户登录后就是第-次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;
  • Cryptography: 加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;
  • Web Support: Web支持,可以非常容易的集成到Web环境;
  • Caching: 缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率
  • Concurrency: Shiro支持多线程应用的并发验证,即,如在-个线程中开启另-一个线程,能把权限自动的传
    播过去
  • Testing:提供测试支持;
  • RunAs:允许一个用户假装为另-一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一-次登录后, 下次再来的话不用登录了
3、Shiro架构(外部)
image-20210612190207378
  • subject: 应用代码直接交互的对象是Subject, 也就是说Shiro的对外API核心就是Subject, Subject代表了当前的用户,这个用户不-定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等,与Subject的所有交互都会委托给SecurityManager; Subject其实是一一个门面, SecurityManageer 才是
    实际的执行者
  • SecurityManager: 安全管理器,即所有与安全有关的操作都会与SercurityManager交互, 并且它管理着所有的Subject,可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色
  • Realm: Shiro从Realm获取安全数据 (如用户,角色,权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把Realm看DataSource;
4、Shiro架构(内部)
image-20210612190452095
  • Subject: 任何可以与应用交互的用户;
  • Security Manager:相当于SpringMVC中的DispatcherSerlet; 是Shiro的心脏, 所有具体的交互都通过Security Manager进行控制,它管理者所有的Subject, 且负责进行认证,授权,会话,及缓存的管理。
  • Authenticator:负责Subject认证, 是-一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
  • Authorizer:授权器,即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中
    的那些功能;
  • Realm: 可以有-一个或者多个的realm, 可以认为是安全实体数据源,即用于获取安全实体的,可以用JDBC实现,也可以是内存实现等等,由用户提供;所以- -般在应用中都需要实现自己的realm
  • SessionManager:管理Session生 命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用在普通的JavaSE环境中
  • CacheManager: 缓存控制器,来管理如用户,角色,权限等缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能;
  • Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于密码加密, 解密等

1、Shiro快速开始

0、先创建一个maven项目,删除src,创建一个Module。
1、准备工作
1、pom.xml

shiro依赖包网址: https://github.com/apache/shiro/blob/main/samples/quickstart/pom.xml

image-20210612191834449
<dependencies>    <dependency>        <groupId>org.apache.shiro</groupId>        <artifactId>shiro-core</artifactId>        <version>1.7.1</version>    </dependency>    <!-- configure logging -->    <dependency>        <groupId>org.slf4j</groupId>        <artifactId>jcl-over-slf4j</artifactId>        <version>1.7.21</version>    </dependency>    <dependency>        <groupId>org.slf4j</groupId>        <artifactId>slf4j-log4j12</artifactId>        <version>1.7.21</version>    </dependency>    <dependency>        <groupId>log4j</groupId>        <artifactId>log4j</artifactId>        <version>1.2.17</version>    </dependency></dependencies>
2、log4j.properties
log4j.rootLogger=INFO, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n# General Apache librarieslog4j.logger.org.apache=WARN# Springlog4j.logger.org.springframework=WARN# Default Shiro logginglog4j.logger.org.apache.shiro=INFO# Disable verbose logginglog4j.logger.org.apache.shiro.util.ThreadContext=WARNlog4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
3、shiro.ini

首先安装ini插件:

image-20210612193443381

shiro.ini:

[users]# user 'root' with password 'secret' and the 'admin' roleroot = secret, admin# user 'guest' with the password 'guest' and the 'guest' roleguest = guest, guest# user 'presidentskroob' with password '12345' ("That's the same combination on# my luggage!!!" ;)), and role 'president'presidentskroob = 12345, president# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'darkhelmet = ludicrousspeed, darklord, schwartz# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'lonestarr = vespa, goodguy, schwartz# -----------------------------------------------------------------------------# Roles with assigned permissions## Each line conforms to the format defined in the# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc# -----------------------------------------------------------------------------[roles]# 'admin' role has all permissions, indicated by the wildcard '*'admin = *# The 'schwartz' role can do anything (*) with any lightsaber:schwartz = lightsaber:*# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with# license plate 'eagle5' (instance specific id)goodguy = winnebago:drive:eagle5

所在位置:

log4j和shiro.ini查找网址:

image-20210612195906252
4、Quickstart类

网址找的报错,把爆红灰的import删了,重新导入即可

import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.session.Session;import org.apache.shiro.subject.Subject;import org.apache.shiro.util.Factory;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * Simple Quickstart application showing how to use Shiro's API. * * @since 0.9 RC2 */public class Quickstart {    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);    public static void main(String[] args) {        // The easiest way to create a Shiro SecurityManager with configured        // realms, users, roles and permissions is to use the simple INI config.        // We'll do that by using a factory that can ingest a .ini file and        // return a SecurityManager instance:        // Use the shiro.ini file at the root of the classpath        // (file: and url: prefixes load from files and urls respectively):        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");        SecurityManager securityManager = factory.getInstance();        // for this simple example quickstart, make the SecurityManager        // accessible as a JVM singleton.  Most applications wouldn't do this        // and instead rely on their container configuration or web.xml for        // webapps.  That is outside the scope of this simple quickstart, so        // we'll just do the bare minimum so you can continue to get a feel        // for things.        SecurityUtils.setSecurityManager(securityManager);        // Now that a simple Shiro environment is set up, let's see what you can do:        // get the currently executing user:        Subject currentUser = SecurityUtils.getSubject();        // Do some stuff with a Session (no need for a web or EJB container!!!)        Session session = currentUser.getSession();        session.setAttribute("someKey", "aValue");        String value = (String) session.getAttribute("someKey");        if (value.equals("aValue")) {            log.info("Retrieved the correct value! [" + value + "]");        }        // let's login the current user so we can check against roles and permissions:        if (!currentUser.isAuthenticated()) {            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");            token.setRememberMe(true);            try {                currentUser.login(token);            } catch (UnknownAccountException uae) {                log.info("There is no user with username of " + token.getPrincipal());            } catch (IncorrectCredentialsException ice) {                log.info("Password for account " + token.getPrincipal() + " was incorrect!");            } catch (LockedAccountException lae) {                log.info("The account for username " + token.getPrincipal() + " is locked.  " +                        "Please contact your administrator to unlock it.");            }            // ... catch more exceptions here (maybe custom ones specific to your application?            catch (AuthenticationException ae) {                //unexpected condition?  error?            }        }        //say who they are:        //print their identifying principal (in this case, a username):        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");        //test a role:        if (currentUser.hasRole("schwartz")) {            log.info("May the Schwartz be with you!");        } else {            log.info("Hello, mere mortal.");        }        //test a typed permission (not instance-level)        if (currentUser.isPermitted("lightsaber:wield")) {            log.info("You may use a lightsaber ring.  Use it wisely.");        } else {            log.info("Sorry, lightsaber rings are for schwartz masters only.");        }        //a (very powerful) Instance Level permission:        if (currentUser.isPermitted("winnebago:drive:eagle5")) {            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +                    "Here are the keys - have fun!");        } else {            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");        }        //all done - log out!        currentUser.logout();        System.exit(0);    }}

所在位置:

Quickstart类查找网址:

image-20210612200244192

2、Shiro的Subject分析

Subject currentUser = SecurityUtils.getSubject();//获得SubjectSession session = currentUser.getSession();//拿到sessioncurrentUser.isAuthenticated()//是否被认证currentUser.getPrincipal()//获取当前用户的认证currentUser.hasRole("schwartz")//这个用户是否拥有什么角色currentUser.isPermitted("lightsaber:wield")//根据当前用户获取一些权限currentUser.logout();//注销

Quickstart类分析:

import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.session.Session;import org.apache.shiro.subject.Subject;import org.apache.shiro.util.Factory;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * Simple Quickstart application showing how to use Shiro's API. * * @since 0.9 RC2 */public class Quickstart {    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);    public static void main(String[] args) {        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");        SecurityManager securityManager = factory.getInstance();        SecurityUtils.setSecurityManager(securityManager);        // get the currently executing user:          //获取当前的用户对象Subject        Subject currentUser = SecurityUtils.getSubject();        //通过当前用户拿到Session        Session session = currentUser.getSession();        session.setAttribute("someKey", "aValue");        String value = (String) session.getAttribute("someKey");        if (value.equals("aValue")) {            log.info("Subject=>session[" + value + "]");        }        // 判断当前的用户是否被认证        if (!currentUser.isAuthenticated()) {            //Token : 令牌,没有获取,随机生成            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");            token.setRememberMe(true);//设置记住我            try {                currentUser.login(token);//执行登录操作~            } catch (UnknownAccountException uae) {                log.info("There is no user with username of " + token.getPrincipal());            } catch (IncorrectCredentialsException ice) {                log.info("Password for account " + token.getPrincipal() + " was incorrect!");            } catch (LockedAccountException lae) {                log.info("The account for username " + token.getPrincipal() + " is locked.  " +                        "Please contact your administrator to unlock it.");            }            // ... catch more exceptions here (maybe custom ones specific to your application?            catch (AuthenticationException ae) {                //unexpected condition?  error?            }        }        //say who they are:        //print their identifying principal (in this case, a username):        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");        //test a role:        if (currentUser.hasRole("schwartz")) {            log.info("May the Schwartz be with you!");        } else {            log.info("Hello, mere mortal.");        }        //粗粒度        //test a typed permission (not instance-level)        if (currentUser.isPermitted("lightsaber:wield")) {            log.info("You may use a lightsaber ring.  Use it wisely.");        } else {            log.info("Sorry, lightsaber rings are for schwartz masters only.");        }        //细粒度        //a (very powerful) Instance Level permission:        if (currentUser.isPermitted("winnebago:drive:eagle5")) {            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +                    "Here are the keys - have fun!");        } else {            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");        }        //注销        //all done - log out!        currentUser.logout();        //结束!        System.exit(0);    }}

3、Spring Boot整合Shiro环境搭建

1、pom.xml
<!--        Subject  用户        SecurityManager 管理所有用户        Realm  连接数据        --><!--shiro整合spring的包--><dependency>    <groupId>org.apache.shiro</groupId>    <artifactId>shiro-spring</artifactId>    <version>1.7.1</version></dependency><!--web--><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId></dependency><!--thymeleaf--><dependency>    <groupId>org.thymeleaf</groupId>    <artifactId>thymeleaf-spring5</artifactId></dependency><dependency>    <groupId>org.thymeleaf.extras</groupId>    <artifactId>thymeleaf-extras-java8time</artifactId></dependency>
2、config两个类

ShiroConfig:

@Configurationpublic class ShiroConfig {    //ShiroFilterFactoryBean:3    @Bean    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();        //设置安全管理器        bean.setSecurityManager(defaultWebSecurityManager);        return bean;    }    //DefaultWebSecurityManager:2    @Bean(name = "securityManager")    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();        //关联UserRealm        securityManager.setRealm(userRealm);        return securityManager;    }    //创建realm对象,需要自定义类:1    @Bean    public UserRealm userRealm(){        return new UserRealm();    }}

UserRealm:

//自定义的 UserRealm  extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm {    //授权    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        System.out.println("执行了=>授权doGetAuthorizationInfo");        return null;    }    //认证    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {        System.out.println("执行了=>认证doGetAuthorizationInfo");        return null;    }}
3、前端页面

index.html:

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

user/add.html

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title></head><body><h1>add</h1></body></html>

user/update.html

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title></head><body><h1>update</h1></body></html>
4、controller
@Controllerpublic class MyController {    @RequestMapping({"/","/index"})    public String toIndex(Model model){        model.addAttribute("msg","hello,Shiro");        return "index";    }    @RequestMapping("/user/add")    public String add(){        return "user/add";    }    @RequestMapping("/user/update")    public String update(){        return "user/update";    }}
5、效果
image-20210612215821145

4、Shiro实现登录拦截

1、login.html页面
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8">    <title>Title</title></head><body><h1>登录</h1><form action="">    <p>用户名:<input type="text" name="username"></p>    <p>密码:<input type="password" name="username"></p>    <p><input type="submit"></p></form></body></html>
2、controller
@Controllerpublic class MyController {  ……………………(省略前面与本主题无关的代码,前面写的代码)            @RequestMapping("/toLogin")    public String toLogin(){        return "login";    }}
3、ShiroConfig写登录拦截功能

下面核心代码提取:

//登录拦截Map<String, String> filterMap = new LinkedHashMap<>();filterMap.put("/user/*","authc");bean.setFilterChainDefinitionMap(filterMap);//设置登录的请求bean.setLoginUrl("/toLogin");

全:

@Configurationpublic class ShiroConfig {    //ShiroFilterFactoryBean:3    @Bean    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();        //设置安全管理器        bean.setSecurityManager(defaultWebSecurityManager);        //添加shiro的内置过滤器        /*           anon:无需认证就可以访问           authc:必须认证了才能访问           user:必须拥有 记住我 功能才能用           perms:拥有对某个资源的权限才能访问           role:拥有某个角色权限才能访问//        filterMap.put("/user/add","authc");//        filterMap.put("/user/update","authc");         */        //登录拦截        Map<String, String> filterMap = new LinkedHashMap<>();        filterMap.put("/user/*","authc");        bean.setFilterChainDefinitionMap(filterMap);        //设置登录的请求        bean.setLoginUrl("/toLogin");        return bean;    }      ……………………(省略后面与本主题无关的代码,前面写的代码)      }

5、Shiro实现用户认证

0、在上面完成的基础上
1、Controller
@Controllerpublic class MyController {  ……………………(省略前面与本主题无关的代码,前面写的代码)          @RequestMapping("/login")    public String login(String username,String password,Model model){        //获取当前用的用户        Subject subject = SecurityUtils.getSubject();        //封装用户的登录数据        UsernamePasswordToken token=new UsernamePasswordToken(username,password);        try {            subject.login(token);//执行登录方法,如果没有异常就说明OK了            return "index";        } catch (UnknownAccountException e) {//用户名不存在            model.addAttribute("msg","用户名错误");            return "login";        }catch (IncorrectCredentialsException e){//密码不存在            model.addAttribute("msg","密码错误");            return "login";        }    }}
2、login.html页面
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8">    <title>Title</title></head><body><h1>登录</h1><hr/><p th:text="${msg}" style="color: red;"></p><form th:action="@{/login}">    <p>用户名:<input type="text" name="username"></p>    <p>密码:<input type="text" name="password"></p>    <p><input type="submit"></p></form></body></html>
3、config中UserRealm类写功能
//自定义的 UserRealm  extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm {    //授权    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        System.out.println("执行了=>授权doGetAuthorizationInfo");        return null;    }    //认证    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {        System.out.println("执行了=>认证doGetAuthorizationInfo");        //用户名,密码~ 数据中取        String name="root";        String password="123";        UsernamePasswordToken userToken = (UsernamePasswordToken) token;        if (!userToken.getUsername().equals(name)){            return null;//抛出异常  UnknownAccountException        }        //密码认证,shiro做~        return new SimpleAuthenticationInfo("",password,"");    }}

6、Shiro整合Mybatis

0、在上面完成的基础上
1、pom.xml(注:前四个依赖包为重点)
<dependencies>    <!--        Subject  用户        SecurityManager 管理所有用户        Realm  连接数据        -->    <!--mysql-->    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>    </dependency>    <!--log4j-->    <dependency>        <groupId>log4j</groupId>        <artifactId>log4j</artifactId>        <version>1.2.17</version>    </dependency>    <!--druid-->    <dependency>        <groupId>com.alibaba</groupId>        <artifactId>druid</artifactId>        <version>1.2.3</version>    </dependency>    <!--引入mybatis,这是mybatis官方提供的适配Spring Boot的-->    <dependency>        <groupId>org.mybatis.spring.boot</groupId>        <artifactId>mybatis-spring-boot-starter</artifactId>        <version>2.2.0</version>    </dependency>    <!--lombok-->    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <version>1.18.20</version>    </dependency>    <!--shiro整合spring的包-->    <dependency>        <groupId>org.apache.shiro</groupId>        <artifactId>shiro-spring</artifactId>        <version>1.7.1</version>    </dependency>    <!--web-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <!--thymeleaf-->    <dependency>        <groupId>org.thymeleaf</groupId>        <artifactId>thymeleaf-spring5</artifactId>    </dependency>    <dependency>        <groupId>org.thymeleaf.extras</groupId>        <artifactId>thymeleaf-extras-java8time</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency></dependencies>
2、application.yaml
spring:  datasource:    username: root    password: root    #假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC    url: jdbc:mysql://localhost:3306/ceshi?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8    driver-class-name: com.mysql.cj.jdbc.Driver    type: com.alibaba.druid.pool.DruidDataSource    #Spring Boot 默认是不注入这些属性值的,需要自己绑定    #druid 数据源专有配置    initialSize: 5    minIdle: 5    maxActive: 20    maxWait: 60000    timeBetweenEvictionRunsMillis: 60000    minEvictableIdleTimeMillis: 300000    validationQuery: SELECT 1 FROM DUAL    testWhileIdle: true    testOnBorrow: false    testOnReturn: false    poolPreparedStatements: true    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j    filters: stat,wall,log4j    maxPoolPreparedStatementPerConnectionSize: 20    useGlobalDataSourceStat: true    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
3、application.properties
mybatis.type-aliases-package=com.kuang.pojomybatis.mapper-locations=classpath:mapper/*.xml
4、pojo实体类
@Data@AllArgsConstructor@NoArgsConstructorpublic class User {    private int id;    private String username;    private String password;}
5、编写Mapper接口和Mapper.xml

UserMapper接口:

@Repository@Mapperpublic interface UserMapper {    public User queryUserByName(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.kuang.mapper.UserMapper">   <select id="queryUserByName" parameterType="String" resultType="User">       select * from ceshi.user where username=#{username}   </select></mapper>
6、编写service接口和serviceImpl实现类

UserService接口:

public interface UserService {    public User queryUserByName(String username);}

UserServiceImpl实现类:

@Servicepublic class UserServiceImpl implements UserService {    @Autowired    UserMapper userMapper;    @Override    public User queryUserByName(String username) {        return userMapper.queryUserByName(username);    }}
7、测试前面是否没问题
@SpringBootTestclass ShiroSpringbootApplicationTests {    @Autowired    UserServiceImpl userService;    @Test    void contextLoads() {        System.out.println(userService.queryUserByName("root"));    }}
image-20210613214503594
8、把前面模拟数据替换为数据库数据

config/UserRealm类

//自定义的 UserRealm  extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm {    @Autowired//自定义的 UserRealm  extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm {    @Autowired    UserService userService;    //授权    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        System.out.println("执行了=>授权doGetAuthorizationInfo");        return null;    }    //认证    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//        System.out.println("执行了=>认证doGetAuthorizationInfo");        //用户名,密码~ 数据中取//        String name="root";//        String password="123";        UsernamePasswordToken userToken = (UsernamePasswordToken) token;        if (!userToken.getUsername().equals(name)){//            return null;//抛出异常  UnknownAccountException//        }        //密码认证,shiro做~//        return new SimpleAuthenticationInfo("",password,"");        UsernamePasswordToken userToken = (UsernamePasswordToken) token;        //连接真实的数据库        User user=userService.queryUserByName(userToken.getUsername());        if (user==null){//没有这个人            return null; //UnknownAccountException        }        //可以加密: MD5   MD5盐值加密        //密码认证,shiro做~        return new SimpleAuthenticationInfo("",user.getPassword(),"");    }}
9、可以网上学习加密有 MD5 MD5盐值加密

7、Shiro请求授权实现及注销功能实现

0、在上面完成的基础上
1、config/ShiroConfig:第三步完善授权
@Configurationpublic class ShiroConfig {    //ShiroFilterFactoryBean:3    @Bean    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();        //设置安全管理器        bean.setSecurityManager(defaultWebSecurityManager);        //添加shiro的内置过滤器        /*           anon:无需认证就可以访问           authc:必须认证了才能访问           user:必须拥有 记住我 功能才能用           perms:拥有对某个资源的权限才能访问           role:拥有某个角色权限才能访问//        filterMap.put("/user/add","authc");//        filterMap.put("/user/update","authc");         */        //登录拦截        Map<String, String> filterMap = new LinkedHashMap<>();        //授权,正常的情况下,没有授权会跳转到未授权页面        filterMap.put("/user/add","perms[user:add]");        filterMap.put("/user/update","perms[user:update]");        filterMap.put("/user/*","authc");        bean.setFilterChainDefinitionMap(filterMap);        //设置登录的请求        bean.setLoginUrl("/toLogin");        //未授权页面        bean.setUnauthorizedUrl("/noauth");        return bean;    }    //DefaultWebSecurityManager:2    @Bean(name = "securityManager")    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();        //关联UserRealm        securityManager.setRealm(userRealm);        return securityManager;    }    //创建realm对象,需要自定义类:1    @Bean    public UserRealm userRealm(){        return new UserRealm();    }}
2、config/UserRealm :主要授权功能完善,认证注意第一个值改为user。return new SimpleAuthenticationInfo(user,user.getPassword(),"");
//自定义的 UserRealm  extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm {    @Autowired    UserService userService;    //授权    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        System.out.println("执行了=>授权doGetAuthorizationInfo");        //SimpleAuthorizationInfo        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();        //添加权限        info.addStringPermission("user:add");        //拿到当前登录的这个对象        Subject subject = SecurityUtils.getSubject();        User currentUser = (User) subject.getPrincipal();//拿到User对象        //设置当前用户的权限        info.addStringPermission(currentUser.getPerms());        //return info;        return info;    }    //认证    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//        System.out.println("执行了=>认证doGetAuthorizationInfo");        //用户名,密码~ 数据中取//        String name="root";//        String password="123";        UsernamePasswordToken userToken = (UsernamePasswordToken) token;        if (!userToken.getUsername().equals(name)){//            return null;//抛出异常  UnknownAccountException//        }        //密码认证,shiro做~//        return new SimpleAuthenticationInfo("",password,"");        UsernamePasswordToken userToken = (UsernamePasswordToken) token;        //连接真实的数据库        User user=userService.queryUserByName(userToken.getUsername());        if (user==null){//没有这个人            return null; //UnknownAccountException        }        //可以加密: MD5   MD5盐值加密        //密码认证,shiro做~        return new SimpleAuthenticationInfo(user,user.getPassword(),"");    }}
3、pojo:新增一个权限属性
@Data@AllArgsConstructor@NoArgsConstructorpublic class User {    private int id;    private String username;    private String password;    private String perms;}

config:新增注销访问和未授权访问

@Controllerpublic class MyController {    @RequestMapping({"/","/index"})    public String toIndex(Model model){        model.addAttribute("msg","hello,Shiro");        return "index";    }    @RequestMapping("/user/add")    public String add(){        return "user/add";    }    @RequestMapping("/user/update")    public String update(){        return "user/update";    }    @RequestMapping("/toLogin")    public String toLogin(){        return "login";    }        @RequestMapping("/login")    public String login(String username,String password,Model model){        //获取当前用的用户        Subject subject = SecurityUtils.getSubject();        //封装用户的登录数据        UsernamePasswordToken token = new UsernamePasswordToken(username, password);        try {            subject.login(token);//执行登录方法,如果没有异常就说明OK了            return "index";        } catch (UnknownAccountException e) {//用户名不存在            model.addAttribute("msg","用户名错误");            return "login";        }catch (IncorrectCredentialsException e){//密码不存在            model.addAttribute("msg","密码错误");            return "login";        }    }    @RequestMapping("/noauth")    @ResponseBody    public String unauthorized(){        return "未经授权无法访问此页面";    }    @RequestMapping("/logout")    public String logout(){        //获取当前用的用户        Subject subject = SecurityUtils.getSubject();        //注销        subject.logout();        return "index";    }}
4、index.html:新增注销标签
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta charset="UTF-8">    <title>Title</title></head><body>   <h1>首页</h1>   <p th:text="${msg}"></p>   <hr/><a th:href="@{/user/add}">add</a>  |  <a th:href="@{/user/update}">update</a><a class="item" th:href="@{/logout}">   <i class="sign-out icon"></i> 注销</a></body></html>

8、Shiro整合Thymeleaf(解释就是实现用户具备权限的页面展示对应的页面;session有无,登录注销显示不显示)

0、在上面完成的基础上
1、pom.xml:

在上面7里pom.xml基础上,新增一个依赖包。

<!--shiro-Thymeleaf整合包--><dependency>    <groupId>com.github.theborakompanioni</groupId>    <artifactId>thymeleaf-extras-shiro</artifactId>    <version>2.0.0</version></dependency>
2、ShiroConfig:新增一个整合ShiroDialect:用来整合 shiro Thymeleaf
@Configurationpublic class ShiroConfig {         ……………………(省略后面与本主题无关的代码,前面写的代码)             //整合ShiroDialect:用老整合 shiro Thymeleaf    @Bean    public ShiroDialect getShiroDialect(){        return new ShiroDialect();    }}
3、UserRealm:新增一个重点取session

重点: Subject currentSubject=SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute(“loginUser”,user);

//自定义的 UserRealm  extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm {    @Autowired    UserService userService;  ……………………授权代码(省略后面与本主题无关的代码,前面写的代码)    //认证    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {        UsernamePasswordToken userToken = (UsernamePasswordToken) token;        //连接真实的数据库        User user=userService.queryUserByName(userToken.getUsername());        if (user==null){//没有这个人            return null; //UnknownAccountException        }        Subject currentSubject=SecurityUtils.getSubject();        Session session = currentSubject.getSession();        session.setAttribute("loginUser",user);        //可以加密: MD5   MD5盐值加密        //密码认证,shiro做~        return new SimpleAuthenticationInfo(user,user.getPassword(),"");    }}
4、Shiro中index头部引入
<html lang="en" xmlns:th="http://www.thymeleaf.org"      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
5、index页面
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"><head>    <meta charset="UTF-8">    <title>Title</title></head><body>   <h1>首页</h1>   <!--方式一:<div th:if="${session.loginUser==null}">-->   <!--方式二:-->   <div shiro:notAuthenticated="session.loginUser">       <a th:href="@{/toLogin}">登录</a>   </div>   <!--方式一:<div th:if="${session.loginUser!=null}">-->   <!--方式二:-->   <div shiro:user="session.loginUser">       <a th:href="@{/logout}">注销</a>   </div>   <p th:text="${msg}"></p>   <hr/>    <div shiro:hasPermission="user:add">      <a th:href="@{/user/add}">add</a>    </div>    <div shiro:hasPermission="user:update">       <a th:href="@{/user/update}">update</a>    </div></body></html>
6、效果
image-20210614011122976 image-20210614011150468

9、开源项目分析

开源项目: https://github.com/WinterChenS/my-site?tdsourcetag=s_pcqq_aiomsg

Swagger介绍及集成

0、学习目标:

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

1、Swagger简介

前后端分离

Vue+SpringBoot

后端时代:前端只用管理静态页面;html==>后端。模板引擎 JSP==>后端是主力

前后端分离式时代:

  • 后端:后端控制层,服务层,数据访问层【后端团队】
  • 前端:前端控制层,视图层【前端团队】
    • 伪造后端数据,json。已经存在了,不需要后端,前端工程依旧能够跑起来
  • 前端后如何交互? ===>API
  • 前后端相对独立,松耦合;
  • 前后端甚至可以部署在不同的服务器上;

产生一个问题:

  • 前后端集成联调,前端人员和后端人员无法做到“即使协商,尽早解决”,最终导致问题集中爆发;

解决方案:

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

2、Swagger

  • 号称世界上最流行的Api框架;
  • RestFul Api文档在线动生成工具=>Api文档与API定义同步更新
  • 直接运行,可以在线测试API接口;
  • 支持多种语言:(Java,Php……)

swagger官网: https://swagger.io/

在项目使用Swagger需要springbox;

  • swagger2
  • ui

3、Spring Boot 集成 Swagger

1、新建一个Spring Boot-web项目
2、导入相关依赖
<!--swagger2--><dependency>    <groupId>io.springfox</groupId>    <artifactId>springfox-swagger2</artifactId>    <version>2.9.2</version></dependency><!--swagger-ui--><dependency>    <groupId>io.springfox</groupId>    <artifactId>springfox-swagger-ui</artifactId>    <version>2.9.2</version></dependency>
注:导入相关依赖出现的问题及解决

问题:swagger2和swagger-ui依赖包版本为3.0.0可能变动了,http://localhost:8080/swagger-ui.html访问不了

解决图:

image-20210614162328021
3、编写一个Hello工程
@RestControllerpublic class HelloController {    @RequestMapping("/hello")    public String hello(){        return "hello";    }}
4、配置Swagger==>Config
@Configuration@EnableSwagger2  //开启Swagger2public class SwaggerConfig {    }
5、测试测试

访问网址: http://localhost:8080/swagger-ui.html

image-20210614162728038

4、配置Swagger信息

0、在上面完成的基础上
1、config
@Configuration@EnableSwagger2  //开启Swagger2public class SwaggerConfig {    //配置了Swagger的Docket的bean实例    @Bean    public Docket docket(){        return new Docket(DocumentationType.SWAGGER_2)                .apiInfo(apiInfo());    }    //配置swagger信息=apiInfo    private ApiInfo apiInfo(){        Contact contact = new Contact("灵语日记", "http://1.15.235.248:8080/h", "2054425044@qq.com");        return  new ApiInfo("风久的SwaggerAPI文档",                "相信明天会更好",                "v1.0",                "http://1.15.235.248:8080/h",                contact,                "Apache 2.0",                "http://www.apache.org/licenses/LICENSE-2.0",                new ArrayList()        );    }}
2、效果
image-20210614165916097

5、配置扫描接口及开关

0、在上面完成的基础上
1、配置扫描接口

Docket.select()

@Configuration@EnableSwagger2  //开启Swagger2public class SwaggerConfig {    //配置了Swagger的Docket的bean实例    @Bean    public Docket docket(){        return new Docket(DocumentationType.SWAGGER_2)                .apiInfo(apiInfo())                .select()                //RequestHandlerSelectors:配置要扫描接口的方式                //basePackage():指定要扫描的包                //any():扫描全部                //none():不扫描                //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象                //withMethodAnnotation:扫描方法上的注解                .apis(RequestHandlerSelectors.basePackage("com.kuang.controller"))                //paths():过滤什么路径                .paths(PathSelectors.ant("/kuang/**"))                .build();    }     ……………………(省略后面与本主题无关的代码,此为前面写的代码)         }
2、配置是否启动Swagger

.enable(false)

@Configuration@EnableSwagger2  //开启Swagger2public class SwaggerConfig {    //配置了Swagger的Docket的bean实例    @Bean    public Docket docket(){        return new Docket(DocumentationType.SWAGGER_2)                .apiInfo(apiInfo())                //enable是否启动Swagger,如果为false,则Swagger不能再浏览器中访问                .enable(false)                .select()                //RequestHandlerSelectors:配置要扫描接口的方式                //basePackage():指定要扫描的包                //any():扫描全部                //none():不扫描                //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象                //withMethodAnnotation:扫描方法上的注解                .apis(RequestHandlerSelectors.basePackage("com.kuang.controller"))                //paths():过滤什么路径                //.paths(PathSelectors.ant("/kuang/**"))                .build();    }        ……………………(省略后面与本主题无关的代码,此为前面写的代码)         }
3、问题:我只希望我的Swagger在生产环境中使用,在发布的时候不适用?
  • 判断是不是生产环境 flag=false

    //设置要显示的Swagger环境Profiles profiles=Profiles.of("dev","test");//通过environment.acceptsProfiles判断是否处在自己设定的环境当中boolean flag = environment.acceptsProfiles(profiles);
    
  • 注入enable(false)

    .enable(flag)
    
@Configuration@EnableSwagger2  //开启Swagger2public class SwaggerConfig {    //配置了Swagger的Docket的bean实例    @Bean    public Docket docket(Environment environment){        //设置要显示的Swagger环境        Profiles profiles=Profiles.of("dev","test");        //通过environment.acceptsProfiles判断是否处在自己设定的环境当中        boolean flag = environment.acceptsProfiles(profiles);        return new Docket(DocumentationType.SWAGGER_2)                .apiInfo(apiInfo())                //enable是否启动Swagger,如果为false,则Swagger不能再浏览器中访问                .enable(flag)                .select()                //RequestHandlerSelectors:配置要扫描接口的方式                //basePackage():指定要扫描的包                //any():扫描全部                //none():不扫描                //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象                //withMethodAnnotation:扫描方法上的注解                .apis(RequestHandlerSelectors.basePackage("com.kuang.controller"))                //paths():过滤什么路径                //.paths(PathSelectors.ant("/kuang/**"))                .build();    }        ……………………(省略后面与本主题无关的代码,此为前面写的代码)         }

application.properties

spring.profiles.active=dev

application-dev.properties

server.port=8081

application-pro.properties

server.port=8082

当生产环境时 dev:访问网址: http://localhost:8081/swagger-ui.html

image-20210614190256476

application.properties

spring.profiles.active=pro

当发布的时候 pro:访问网址: http://localhost:8082/swagger-ui.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L79kbQby-1623924293485)(SpringBoot.assets/image-20210614190602330.png)]

6、分组和接口注释及小结

0、在上面完成的基础上
1、配置API文档的分组

.groupName(“风久”)

@Configuration@EnableSwagger2  //开启Swagger2public class SwaggerConfig {    //配置了Swagger的Docket的bean实例    @Bean    public Docket docket(Environment environment){        //设置要显示的Swagger环境        Profiles profiles=Profiles.of("dev","test");        //通过environment.acceptsProfiles判断是否处在自己设定的环境当中        boolean flag = environment.acceptsProfiles(profiles);        return new Docket(DocumentationType.SWAGGER_2)                .apiInfo(apiInfo())                .groupName("风久")                //enable是否启动Swagger,如果为false,则Swagger不能再浏览器中访问                .enable(flag)                .select()                //RequestHandlerSelectors:配置要扫描接口的方式                //basePackage():指定要扫描的包                //any():扫描全部                //none():不扫描                //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象                //withMethodAnnotation:扫描方法上的注解                .apis(RequestHandlerSelectors.basePackage("com.kuang.controller"))                //paths():过滤什么路径                //.paths(PathSelectors.ant("/kuang/**"))                .build();    }        ……………………(省略后面与本主题无关的代码,此为前面写的代码)         }

效果:

image-20210614191944724
2、如何配置多个分组;多个Docker实例即可
@Configuration@EnableSwagger2  //开启Swagger2public class SwaggerConfig {    @Bean    public Docket docket1(){        return new Docket(DocumentationType.SWAGGER_2).groupName("A");    }    @Bean    public Docket docket2(){        return new Docket(DocumentationType.SWAGGER_2).groupName("B");    }    @Bean    public Docket docket3(){        return new Docket(DocumentationType.SWAGGER_2).groupName("C");    }     ……………………(省略后面与本主题无关的代码,此为前面写的代码)         }

效果:

image-20210614192459694
3、实体类配置
//@Api(注释)@ApiModel("用户实体类")public class User {    @ApiModelProperty("用户名")    public String username;    @ApiModelProperty("密码")    public String password;}

controller

@RestControllerpublic class HelloController {    @GetMapping("/hello")    public String hello(){        return "hello";    }    //只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中    @PostMapping("/user")    public User user(){        return new User();    }    //Operation接口    @ApiOperation("Hello控制类")    @GetMapping("/hello2")    public String hello2(@ApiParam("用户名") String username){        return "hello"+username;    }    @ApiOperation("Post测试类")    @PostMapping("/postt")    public User postt(@ApiParam("用户名") User user){        int i=5/0;        return user;    }}
总结:
  1. 我们可以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
  2. 接口文档实时更新
  3. 可以在线测试

Swagger是一个优秀的工具,几乎所有大公司都有使用它

【注意点】在正式发布的时候,关闭Swagger!!!出于安全考虑。而且节省运行的内存。

任务

1、异步任务

1、service
@Servicepublic class AsyncService {    @Async    public void hello(){       try {           Thread.sleep(3000);       } catch (InterruptedException e) {           e.printStackTrace();       }       System.out.println("数据正在处理……");    }}
2、controller
@RestControllerpublic class AsyncController {    @Autowired    AsyncService asyncService;    @RequestMapping("/hello")    public String hello(){        asyncService.hello();//停止三秒,转圈~        return "OK";    }}
3、主程序
@EnableAsync//开启异步注解功能@SpringBootApplicationpublic class KsBoot09TestApplication {    public static void main(String[] args) {        SpringApplication.run(KsBoot09TestApplication.class, args);    }}

2、定时任务

TaskScheduler 任务调度程序TaskExecutor  任务执行者@EnableScheduling//开启定时功能的注解@Scheduled//什么时候执行Cron表达式
1、主程序
@EnableAsync//开启异步注解功能@EnableScheduling//开启定时功能的注解@SpringBootApplicationpublic class KsBoot09TestApplication {    public static void main(String[] args) {        SpringApplication.run(KsBoot09TestApplication.class, args);    }}
2、service
@Servicepublic class ScheduledService {    //在一个特定的时间执行这个方法~ Timer    //cron 表达式~    //秒  分  时  日  月  周几~   /*       15 52 15 * * ?  每天15点52分10秒  执行一次       15 0/1 15,16 * * ?   每天15点和16点,以15分为起点,每隔一分钟执行一次。       0 58 15 ? * 1-6  每个月的周一到周六15点58分执行一次       0/2 * * * * ? 每天每隔2秒钟执行一次    */    @Scheduled(cron = "0/2 * * * * ?")    public void hello(){        System.out.println("hello,你被执行了~");    }}

3、邮件发送

1、pom.xml
<dependencies>    <!--web-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <!--javax.mail 邮件依赖包-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-mail</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency></dependencies>
2、application.properties
spring.mail.username=123345425044@qq.comspring.mail.password=ijtmgjxxhxxcebjgspring.mail.host=smtp.qq.com#开启加密验证spring.mail.properties.mail.smtp.ss.enable=true
3、test测试
@SpringBootTestclass KsBoot09TestApplicationTests {    @Autowired    JavaMailSenderImpl mailSender;    @Test    void contextLoads() {            //一个简单的邮件~            SimpleMailMessage mailMessage = new SimpleMailMessage();            mailMessage.setSubject("收件人你好呀~");            mailMessage.setText("时常的忙碌,不代表遗忘;夏日的来到,愿你心情痛快,曾落下的问候,这一刻补偿,所有的关心,凝结在这条短信,祝端午节快乐!");            mailMessage.setTo("1233459521@qq.com");//金//        mailMessage.setTo("1233454574@qq.com");//乔            mailMessage.setFrom("12334525044@qq.com");            mailSender.send(mailMessage);    }    @Test    void contextLoads2() throws MessagingException {        //一个复杂的邮件~        MimeMessage mailMessage =mailSender.createMimeMessage();        //组装~        MimeMessageHelper helper=new MimeMessageHelper(mailMessage,true);        //正文        helper.setSubject("收件人你好呀~plus");        helper.setText("<p style='color:green'>时常的忙碌,不代表遗忘;夏日的来到,愿你心情痛快,曾落下的问候,这一刻补偿,所有的关心,凝结在这条短信,祝端午节快乐!</p>",true);        //附件        helper.addAttachment("happy.jpeg",new File("C:\\Users\\admin\\Desktop\\wu.jpeg"));        helper.addAttachment("Ankang.jpeg",new File("C:\\Users\\admin\\Desktop\\duan.jpeg"));        helper.setTo("123345521@qq.com");//金//        helper.setTo("1233454574@qq.com");//乔        helper.setFrom("1233455044@qq.com");        mailSender.send(mailMessage);    }}
4、根据这个方法思路可以过节跟每个人发信息,把一些数据存数据库中
/**     *     * @param html:true     * @param subject:主体     * @param text:内容     * @throws MessagingException     *///封装为一个方法public void sendMail(Boolean html,String subject,String text) throws MessagingException {    //一个复杂的邮件~    MimeMessage mailMessage =mailSender.createMimeMessage();    //组装~    MimeMessageHelper helper=new MimeMessageHelper(mailMessage,html);    //正文    helper.setSubject(subject);    helper.setText(text,html);    //附件    helper.addAttachment("happy.jpeg",new File("C:\\Users\\admin\\Desktop\\wu.jpeg"));    helper.addAttachment("Ankang.jpeg",new File("C:\\Users\\admin\\Desktop\\duan.jpeg"));    //        helper.setTo("123345521@qq.com");    helper.setTo("1233454574@qq.com");    helper.setFrom("1233455044@qq.com");    mailSender.send(mailMessage);}

Spring Boot整合Redis

简介

Spring Boot 操作数据:spring-data jpa jdbc mongodb redis!

Spring Data 也是和 Spring Boot齐名的项目!

说明:在Spring Boot2.x之后,原来使用的 jedis 被替换为了 lettuce?

jedis :采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池!更像 BIO 模式

lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式

源码分析:
@Bean@ConditionalOnMissingBean(name = "redisTemplate")//我们可以自己定义一个redisTemplate来替换这个默认的!@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {    //默认的 RedisTemplate 没有过多的配置,redis 对象都是需要序列化!    //两个泛型都是 Object, Object 的类型,我们后使用需要强制转换 <String, Object>     RedisTemplate<Object, Object> template = new RedisTemplate<>();    template.setConnectionFactory(redisConnectionFactory);    return template;}@Bean@ConditionalOnMissingBean//由于 String 是redis中最常使用的类型,所以说单独提出来了一个bean!@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {    StringRedisTemplate template = new StringRedisTemplate();    template.setConnectionFactory(redisConnectionFactory);    return template;}

1、整合测试

0、前提

1、Redis下载地址: https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100

2、运行程序,启动服务

1、pom.xml
<dependencies>    <!--操作redis-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-data-redis</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-devtools</artifactId>        <scope>runtime</scope>        <optional>true</optional>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-configuration-processor</artifactId>        <optional>true</optional>    </dependency>    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <optional>true</optional>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency></dependencies>
2、配置连接
# Spring Boot 所有的配置类,都有一个自动配置类 RedisAutoConfiguration# 自动配置类都会绑定一个 properties 配置文件 RedisProperties# 配置redisspring.redis.host=127.0.0.1spring.redis.port=6379
3、test测试
@SpringBootTestclass KsBoot10RedisApplicationTests {    @Autowired    private RedisTemplate redisTemplate;    @Test    void contextLoads() {        // redisTemplate 操作不同的数据类型,api和我们的指令是一样的        // opsForValue() 操作字符串 类似String        // opsForList()  操作List 类似List        // opsForSet()        // opsForHash()        // opsForZSet()        // opsForGeo()        // opsForHyperLogLog()        //除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的CRUD        //获取redis的连接对象//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();//        connection.flushDb();//        connection.flushAll();        redisTemplate.opsForValue().set("mykey","关闭狂神说公众号");        System.out.println(redisTemplate.opsForValue().get("mykey"));    }}
image-20210616170801010 image-20210616170717618

2、自定义RedisTemplate

1、pojo
@Component@AllArgsConstructor@NoArgsConstructor@Data//在企业中,我们的所有pojo都会序列化!public class User implements Serializable {    private String name;    private int age;}
2、test测试
@SpringBootTestclass KsBoot10RedisApplicationTests {    @Autowired    private RedisTemplate redisTemplate;        @Test    public void test() throws JsonProcessingException {        //真是的开发一般都使用json来传递对象        User user = new User("小可爱", 3);//        方式一://        String jsonUser = new ObjectMapper().writeValueAsString(user);//        redisTemplate.opsForValue().set("user",jsonUser);//        方式二:前提pojo实体类需要序列化 implements Serializable        redisTemplate.opsForValue().set("user",user);        System.out.println(redisTemplate.opsForValue().get("user"));    }}
3、config
@Configurationpublic class RedisConfig {    /**     *  编写自定义的 redisTemplate     *  这是一个比较固定的模板,拿去就可以直接使用     */    @Bean    @SuppressWarnings("all")    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {        // 为了开发方便,直接使用<String, Object>        RedisTemplate<String, Object> template = new RedisTemplate();        template.setConnectionFactory(redisConnectionFactory);        // Json 配置序列化        // 使用 jackson 解析任意的对象        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);        // 使用 objectMapper 进行转义        ObjectMapper objectMapper = new ObjectMapper();        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);        // String 的序列化        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();        // key 采用 String 的序列化方式        template.setKeySerializer(stringRedisSerializer);        // Hash 的 key 采用 String 的序列化方式        template.setHashKeySerializer(stringRedisSerializer);        // value 采用 jackson 的序列化方式        template.setValueSerializer(jackson2JsonRedisSerializer);        // Hash 的 value 采用 jackson 的序列化方式        template.setHashValueSerializer(jackson2JsonRedisSerializer);        // 把所有的配置 set 进 template        template.afterPropertiesSet();        return template;    }}

所有的redis操作,其实对于java开发人员来说,十分的简单,更重要是要去理解redis的思想和每一种数据结构的用处和作用场景!

4、RedisUtil工具类
//在我们真实的开发中,或者你们在公司,一般都可以看到一个公司自己封装的RedidUtil@Componentpublic final class RedisUtil {    @Autowired    private RedisTemplate<String, Object> redisTemplate;    // =============================common============================    /**     * 指定缓存失效时间     * @param key  键     * @param time 时间(秒)     */    public boolean expire(String key, long time) {        try {            if (time > 0) {                redisTemplate.expire(key, time, TimeUnit.SECONDS);            }            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 根据key 获取过期时间     * @param key 键 不能为null     * @return 时间(秒) 返回0代表为永久有效     */    public long getExpire(String key) {        return redisTemplate.getExpire(key, TimeUnit.SECONDS);    }    /**     * 判断key是否存在     * @param key 键     * @return true 存在 false不存在     */    public boolean hasKey(String key) {        try {            return redisTemplate.hasKey(key);        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 删除缓存     * @param key 可以传一个值 或多个     */    @SuppressWarnings("unchecked")    public void del(String... key) {        if (key != null && key.length > 0) {            if (key.length == 1) {                redisTemplate.delete(key[0]);            } else {                redisTemplate.delete(String.valueOf(CollectionUtils.arrayToList(key)));            }        }    }    // ============================String=============================    /**     * 普通缓存获取     * @param key 键     * @return 值     */    public Object get(String key) {        return key == null ? null : redisTemplate.opsForValue().get(key);    }    /**     * 普通缓存放入     * @param key   键     * @param value 值     * @return true成功 false失败     */    public boolean set(String key, Object value) {        try {            redisTemplate.opsForValue().set(key, value);            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 普通缓存放入并设置时间     * @param key   键     * @param value 值     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期     * @return true成功 false 失败     */    public boolean set(String key, Object value, long time) {        try {            if (time > 0) {                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);            } else {                set(key, value);            }            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 递增     * @param key   键     * @param delta 要增加几(大于0)     */    public long incr(String key, long delta) {        if (delta < 0) {            throw new RuntimeException("递增因子必须大于0");        }        return redisTemplate.opsForValue().increment(key, delta);    }    /**     * 递减     * @param key   键     * @param delta 要减少几(小于0)     */    public long decr(String key, long delta) {        if (delta < 0) {            throw new RuntimeException("递减因子必须大于0");        }        return redisTemplate.opsForValue().increment(key, -delta);    }    // ================================Map=================================    /**     * HashGet     * @param key  键 不能为null     * @param item 项 不能为null     */    public Object hget(String key, String item) {        return redisTemplate.opsForHash().get(key, item);    }    /**     * 获取hashKey对应的所有键值     * @param key 键     * @return 对应的多个键值     */    public Map<Object, Object> hmget(String key) {        return redisTemplate.opsForHash().entries(key);    }    /**     * HashSet     * @param key 键     * @param map 对应多个键值     */    public boolean hmset(String key, Map<String, Object> map) {        try {            redisTemplate.opsForHash().putAll(key, map);            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * HashSet 并设置时间     * @param key  键     * @param map  对应多个键值     * @param time 时间(秒)     * @return true成功 false失败     */    public boolean hmset(String key, Map<String, Object> map, long time) {        try {            redisTemplate.opsForHash().putAll(key, map);            if (time > 0) {                expire(key, time);            }            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 向一张hash表中放入数据,如果不存在将创建     *     * @param key   键     * @param item  项     * @param value 值     * @return true 成功 false失败     */    public boolean hset(String key, String item, Object value) {        try {            redisTemplate.opsForHash().put(key, item, value);            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 向一张hash表中放入数据,如果不存在将创建     *     * @param key   键     * @param item  项     * @param value 值     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间     * @return true 成功 false失败     */    public boolean hset(String key, String item, Object value, long time) {        try {            redisTemplate.opsForHash().put(key, item, value);            if (time > 0) {                expire(key, time);            }            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 删除hash表中的值     *     * @param key  键 不能为null     * @param item 项 可以使多个 不能为null     */    public void hdel(String key, Object... item) {        redisTemplate.opsForHash().delete(key, item);    }    /**     * 判断hash表中是否有该项的值     *     * @param key  键 不能为null     * @param item 项 不能为null     * @return true 存在 false不存在     */    public boolean hHasKey(String key, String item) {        return redisTemplate.opsForHash().hasKey(key, item);    }    /**     * hash递增 如果不存在,就会创建一个 并把新增后的值返回     *     * @param key  键     * @param item 项     * @param by   要增加几(大于0)     */    public double hincr(String key, String item, double by) {        return redisTemplate.opsForHash().increment(key, item, by);    }    /**     * hash递减     *     * @param key  键     * @param item 项     * @param by   要减少记(小于0)     */    public double hdecr(String key, String item, double by) {        return redisTemplate.opsForHash().increment(key, item, -by);    }    // ============================set=============================    /**     * 根据key获取Set中的所有值     * @param key 键     */    public Set<Object> sGet(String key) {        try {            return redisTemplate.opsForSet().members(key);        } catch (Exception e) {            e.printStackTrace();            return null;        }    }    /**     * 根据value从一个set中查询,是否存在     *     * @param key   键     * @param value 值     * @return true 存在 false不存在     */    public boolean sHasKey(String key, Object value) {        try {            return redisTemplate.opsForSet().isMember(key, value);        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 将数据放入set缓存     *     * @param key    键     * @param values 值 可以是多个     * @return 成功个数     */    public long sSet(String key, Object... values) {        try {            return redisTemplate.opsForSet().add(key, values);        } catch (Exception e) {            e.printStackTrace();            return 0;        }    }    /**     * 将set数据放入缓存     *     * @param key    键     * @param time   时间(秒)     * @param values 值 可以是多个     * @return 成功个数     */    public long sSetAndTime(String key, long time, Object... values) {        try {            Long count = redisTemplate.opsForSet().add(key, values);            if (time > 0)                expire(key, time);            return count;        } catch (Exception e) {            e.printStackTrace();            return 0;        }    }    /**     * 获取set缓存的长度     *     * @param key 键     */    public long sGetSetSize(String key) {        try {            return redisTemplate.opsForSet().size(key);        } catch (Exception e) {            e.printStackTrace();            return 0;        }    }    /**     * 移除值为value的     *     * @param key    键     * @param values 值 可以是多个     * @return 移除的个数     */    public long setRemove(String key, Object... values) {        try {            Long count = redisTemplate.opsForSet().remove(key, values);            return count;        } catch (Exception e) {            e.printStackTrace();            return 0;        }    }    // ===============================list=================================    /**     * 获取list缓存的内容     *     * @param key   键     * @param start 开始     * @param end   结束 0 到 -1代表所有值     */    public List<Object> lGet(String key, long start, long end) {        try {            return redisTemplate.opsForList().range(key, start, end);        } catch (Exception e) {            e.printStackTrace();            return null;        }    }    /**     * 获取list缓存的长度     *     * @param key 键     */    public long lGetListSize(String key) {        try {            return redisTemplate.opsForList().size(key);        } catch (Exception e) {            e.printStackTrace();            return 0;        }    }    /**     * 通过索引 获取list中的值     *     * @param key   键     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推     */    public Object lGetIndex(String key, long index) {        try {            return redisTemplate.opsForList().index(key, index);        } catch (Exception e) {            e.printStackTrace();            return null;        }    }    /**     * 将list放入缓存     *     * @param key   键     * @param value 值     */    public boolean lSet(String key, Object value) {        try {            redisTemplate.opsForList().rightPush(key, value);            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 将list放入缓存     * @param key   键     * @param value 值     * @param time  时间(秒)     */    public boolean lSet(String key, Object value, long time) {        try {            redisTemplate.opsForList().rightPush(key, value);            if (time > 0)                expire(key, time);            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 将list放入缓存     *     * @param key   键     * @param value 值     * @return     */    public boolean lSet(String key, List<Object> value) {        try {            redisTemplate.opsForList().rightPushAll(key, value);            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 将list放入缓存     *     * @param key   键     * @param value 值     * @param time  时间(秒)     * @return     */    public boolean lSet(String key, List<Object> value, long time) {        try {            redisTemplate.opsForList().rightPushAll(key, value);            if (time > 0)                expire(key, time);            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 根据索引修改list中的某条数据     *     * @param key   键     * @param index 索引     * @param value 值     * @return     */    public boolean lUpdateIndex(String key, long index, Object value) {        try {            redisTemplate.opsForList().set(key, index, value);            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }    /**     * 移除N个值为value     *     * @param key   键     * @param count 移除多少个     * @param value 值     * @return 移除的个数     */    public long lRemove(String key, long count, Object value) {        try {            Long remove = redisTemplate.opsForList().remove(key, count, value);            return remove;        } catch (Exception e) {            e.printStackTrace();            return 0;        }    }}

test测试应用

@SpringBootTestclass KsBoot10RedisApplicationTests {        @Autowired    private RedisUtil redisUtil;    @Test    public void test1(){        redisUtil.set("name","乔帅");        System.out.println(redisUtil.get("name"));    }}

分布式Dubbo+Zookeeper+Spring Boot

分布式理论

1、什么是分布式系统?

定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”。

其目的是利用更多的机器,处理更多的数据

分布式系统(distributed system)是建立在网络之上的软件系统。

首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。

2、Dubbo文档

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。

在Dubbo的官网文档有这样一张图

image-20210616192929695

3、单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

image-20210616193923064

适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。

缺点:

1、性能扩展比较难

2、协同开发问题

3、不利于升级维护

4、垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

image-20210616194042992

通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。

缺点:公用模块无法重复利用,开发性的浪费

5、分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架(RPC)**是关键。

image-20210616194245597

6、流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。

image-20210616194424598

RPC

1、什么是RPC

RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;

推荐阅读文章:https://www.jianshu.com/p/2accc2840a1b

2、RPC基本原理

image-20210616195557746

3、步骤解析

image-20210616195651206

4、RPC两个核心模块:通讯,序列化。

RPC

HTTP SpringCloud(生态

序列化:数据传输需要转换。

Netty

Dubbo~ 18年重启! Dubbo 3.x RPC Error Exception~

专业的事,交给专业的人来做~ 不靠谱!

Dubbo及Zookeeper

1、Dubbo概念

1、什么是dubbo?

Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

dubbo官网:https://dubbo.apache.org/zh/docs/

dubbo基本概念

image-20210616201544000

服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。

服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者

监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

调用关系说明

l 服务容器负责启动,加载,运行服务提供者。

l 服务提供者在启动时,向注册中心注册自己提供的服务。

l 服务消费者在启动时,向注册中心订阅自己所需的服务。

l 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

2、Dubbo环境搭建

点进dubbo官方文档,推荐我们使用Zookeeper Zookeeper 注册中心: https://dubbo.apache.org/zh/docs/v2.7/user/references/registry/zookeeper/

什么是zookeeper呢?可以查看 ZooKeeper官方文档: https://zookeeper.apache.org/

3、Window下安装zookeeper

1、下载zookeeper :https://downloads.apache.org/zookeeper/, 我们下载3.5.9 !解压zookeeper

2、运行/bin/zkServer.cmd ,初次运行会报错,没有zoo.cfg配置文件;

可能遇到问题:闪退 !

解决方案:编辑zkServer.cmd文件末尾添加pause 。这样运行出错就不会退出,会提示错误信息,方便找到原因。

image-20210616205115296

3、修改zoo.cfg配置文件

将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。

注意几个重要位置:

dataDir=./ 临时数据存储的目录(可写相对路径)

clientPort=2181 zookeeper的端口号

修改完成后再次启动zookeeper

image-20210616210132202

4、使用zkCli.cmd测试

ls /:列出zookeeper根下保存的所有节点

[zk: localhost:2181(CONNECTED) 0] ls /[zookeeper]

create –e /kuangshen 123:创建一个kuangshen节点,值为123

[zk: localhost:2181(CONNECTED) 1] create -e /kuangshen 123Created /kuangshen

get /kuangshen:获取/kuangshen节点的值

[zk: localhost:2181(CONNECTED) 3] get /kuangshen123

我们再来查看一下节点

[zk: localhost:2181(CONNECTED) 2] ls /[kuangshen, zookeeper]
image-20210616211642639

2、Dubbo-admin 安装测试

1、window下安装dubbo-admin

dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。

但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。

我们这里来安装一下:

1、下载dubbo-admin

地址 :https://github.com/apache/dubbo-admin/tree/master

2、解压进入目录

修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址

server.port=7001spring.velocity.cache=falsespring.velocity.charset=UTF-8spring.velocity.layout-url=/templates/default.vmspring.messages.fallback-to-system-locale=falsespring.messages.basename=i18n/messagespring.root.password=rootspring.guest.password=guest# 注册中心的地址dubbo.registry.address=zookeeper://127.0.0.1:2181

3、在项目目录下打包dubbo-admin

前提:配好maven环境变量

mvn clean package -Dmaven.test.skip=true

第一次打包的过程有点慢,需要耐心等待!直到成功!

image-20210616222458707

4、执行 dubbo-admin\target 下的dubbo-admin-0.0.1-SNAPSHOT.jar

java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

【注意:zookeeper的服务一定要打开!】

执行完毕,我们去访问一下 http://localhost:7001/ , 这时候我们需要输入登录账户和密码,我们都是默认的root-root;

登录成功后,查看界面

image-20210616223822345
3、总结

zookeeper:注册中心

dubbo-admin:是一个监控管理后台查看我们注册了哪些服务,哪些服务被消费了

Dubbo:jar包~

3、服务注册发现实战

框架搭建
1、创建一个项目
2、创建一个模块,实现服务提供者:provider-server 模块,选择web依赖
3、service
public interface TicketService {    public String getTicket();}

serviceImpl

public class TicketServiceImpl implements TicketService {    @Override    public String getTicket() {        return "《小可爱》";    }}
4、创建一个模块,实现服务消费者:consumer-server 模块,选择web依赖
5、service
public class UserService {    //想拿到provider-server提供的票}
服务提供者 provider-server
1、pom.xml
<dependencies>    <!--将服务提供者注册到注册中心,我们需要整合Dubbo和zookeeper-->    <!--导入依赖:Dubbo+zookeeper-->    <dependency>        <groupId>org.apache.dubbo</groupId>        <artifactId>dubbo-spring-boot-starter</artifactId>        <version>2.7.3</version>    </dependency>        <!--zookeeper的包我们去maven仓库下载,zkclient;    <!--zkclient-->    <dependency>        <groupId>com.github.sgroschupf</groupId>        <artifactId>zkclient</artifactId>        <version>0.1</version>    </dependency>        <!--【新版的坑】zookeeper及其依赖包,解决日志冲突,还需要剔除日志依赖;-->    <!--日志会冲突~-->    <!-- 引入zookeeper -->    <dependency>        <groupId>org.apache.curator</groupId>        <artifactId>curator-framework</artifactId>        <version>2.12.0</version>    </dependency>    <dependency>        <groupId>org.apache.curator</groupId>        <artifactId>curator-recipes</artifactId>        <version>2.12.0</version>    </dependency>    <dependency>        <groupId>org.apache.zookeeper</groupId>        <artifactId>zookeeper</artifactId>        <version>3.4.14</version>        <!--排除这个slf4j-log4j12-->        <exclusions>            <exclusion>                <groupId>org.slf4j</groupId>                <artifactId>slf4j-log4j12</artifactId>            </exclusion>        </exclusions>    </dependency>    <!--web-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency></dependencies>
2、application.properties
server.port=8081# 服务应用名字dubbo.application.name=provider-server# 注册中心地址dubbo.registry.address=zookeeper://127.0.0.1:2181# 哪些服务要被注册dubbo.scan.base-packages=com.kuang.service
3、service

service

public interface TicketService {    public String getTicket();}

serviceImpl

import org.apache.dubbo.config.annotation.Service;import org.springframework.stereotype.Component;@Service  //可以被扫描到,在项目一启动就自动注册到注册中心@Component //使用了Dubbo后尽量不要用Service注解public class TicketServiceImpl implements TicketService {    @Override    public String getTicket() {        return "《小可爱》";    }}
服务消费者consumer-server
1、pom.xml
<dependencies>    <!--dubbo-->    <!-- Dubbo Spring Boot Starter -->    <dependency>        <groupId>org.apache.dubbo</groupId>        <artifactId>dubbo-spring-boot-starter</artifactId>        <version>2.7.3</version>    </dependency>    <!--zookeeper-->    <dependency>        <groupId>com.github.sgroschupf</groupId>        <artifactId>zkclient</artifactId>        <version>0.1</version>    </dependency>    <!-- 引入zookeeper -->    <dependency>        <groupId>org.apache.curator</groupId>        <artifactId>curator-framework</artifactId>        <version>2.12.0</version>    </dependency>    <dependency>        <groupId>org.apache.curator</groupId>        <artifactId>curator-recipes</artifactId>        <version>2.12.0</version>    </dependency>    <dependency>        <groupId>org.apache.zookeeper</groupId>        <artifactId>zookeeper</artifactId>        <version>3.4.14</version>        <!--排除这个slf4j-log4j12-->        <exclusions>            <exclusion>                <groupId>org.slf4j</groupId>                <artifactId>slf4j-log4j12</artifactId>            </exclusion>        </exclusions>    </dependency>    <!--web-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency></dependencies>
2、application.properties
server.port=8082# 消费者去哪里拿到服务需要暴露自己的名字dubbo.application.name=consumer-server# 注册中心的地址,可以在任何电脑上!dubbo.registry.address=zookeeper://127.0.0.1:2181
3、 service

本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;

image-20210617160520090

UserService

import com.alibaba.dubbo.config.annotation.Reference;import org.springframework.stereotype.Service;@Service //放到容器中~public class UserService {    //想拿到provider-server提供的票,要去注册中心拿到服务    @Reference //引用, Pom坐标,可以定义路径相同的接口名    TicketService ticketService;    public void buyTicket(){        String ticket=ticketService.getTicket();        System.out.println("在注册中心拿到=>"+ticket);    }}
4、test测试
@SpringBootTestclass ConsumerServerApplicationTests {    @Autowired    UserService userService;    @Test    void contextLoads() {        userService.buyTicket();    }}
启动测试
  1. 启动zookeeper

    image-20210617161051622
  2. 打开dubbo-admin实现监控【可以不用做】

    image-20210617161148944 image-20210617161243671
  3. 开启服务者

    image-20210617161445950
  4. 消费者消费测试

    image-20210617161535064
  5. 结果:

image-20210617161653751
总结步骤

前提:zookeeper服务已开启!

  1. 提供者提供服务!
    1. 导入依赖
    2. 配置注册中心的地址,以及服务发现名,和要扫描的包~
    3. 在想要被注册的服务上面~ 增加一个注解 @service
  2. 消费者如何消费
    1. 导入依赖
    2. 配置注册中心的地址,配置自己的服务名~
    3. 从远程注入服务~ @Reference
image-20210617162553104 image-20210617162612944

聊聊未来和现在

回顾以前,架构!

三层架构 :MVC    架构 --本质--> 解耦    开发框架       Spring        IOC  AOP           IOC:控制反转              约会:                  吃饭,购物……,约会                  附近的人,打招呼。加微信,聊天,天天聊,---> 约会              相亲(容器):餐厅,商场,约会                  直接去相亲,就有人和你一起了!              原来我们都是自己一步一步操作,现在交给容器了!我们需要什么就去拿就可以了           AOP:切面(本质,动态代理)              为了解决什么?不影响业务本来的情况下,实现动态增加功能,大量应用在日志,事务……等等方面            Spring是一个轻量级的Java开源框架,容器        目的:解决企业开发的复杂性问题        Spring是春天,觉得他是春天,也十分复杂,配置文件!         Spring Boot          Spring Boot并不是新东西,就是Spring的升级版!        新一代JavaEE的开发标准,开箱即用!->拿过来就可以用!        它自动帮我们配置了非常多的东西,我们拿来即用!        特性:约定大于配置随着公司体系越来越大,用户越来越多!    模块化,功能化!    用户,支付,签到,娱乐,……;    人多余多;一台服务器解决不了;在增加服务器!  横向    假设A服务器占用98%资源,B服务器值占用了10%--负载均衡;        将原来的整体项目,分成模块化,用户就是一个单独的项目,签到也是一个单独的项目,项目和项目之前需要通信,如何通信?    用户非常多,而签到十分少! 给用户多一点服务器,给签到少一点服务器!    微服务架构问题?    分布式架构会遇到的四个核心问题?    1.这么多服务,客户端该如何去访问?    2.这么多服务,服务之间如何进行通信?    3.这么多服务,如何治理呢?    4.服务挂了,怎么办?    解决方案:    SpringCloud,是一套生态,就是来解决以上分布式架构的4个问题    想使用SpringCloud,必须掌握Spring Boot,因为SpringCloud是基于Spring Boot1.Spring Cloud NetFlix,出来了一套解决方案! 一站式解决方案。我们都可以直接去这里拿?       Api网关,zuul组件       Feign-->HttpClient-->HTTP的通信方式,同步并阻塞       服务注册与发现,Eureka       熔断机制,Hystrix              2018年年底,NetFlix宣布无限期停止维护。生态不再维护,就会脱节。        2.Apache Dubbo zookeeper,第二套解决方案       API:没有!要么找第三方组件,要么自己实现       Dubbo是一个高性能的基于Java实现的 RPC通信框架! 2.6.x       服务注册与发现,zookeeper:动物园管理员(HadoopHive)       没有:借助了 Hystrix              不完善,Dubbo 3.0.x        3.SpringCloud Alibaba 一站式解决方案!    目前,又提出了一种方案:    服务网格:下一代微服务标准,Server Mesh    代表解决方案: istio(你们未来可能需要掌握!)    万变不离其宗;一通百通!    1.API网关,服务器由    2.HTTP,RPC框架,异步调用    3.服务注册与发现,高可用    4.熔断机制,服务降级    如果,你们基于这四个问题,开发一套解决方案,也叫Spring Cloud!为什么要解决这个问题?本质:网络是不可靠的!程序猿,不要停下学习的脚步!    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值