【狂神說】SpringBoot

oneSeventeenCode發表於2020-12-15

本文章僅個人學習筆記,內容來源b站up主:遇見狂神說


SpringBoot

自動配置:

pom.xml

  • spring-boot-dependencies:核心以來在父工程中!
  • 我們在寫或者引入一些springboot依賴的時候,不需要制定版本,就因為有這些版本倉庫

啟動器

  • 啟動器:說白了是Springboot的啟動場景
  • 比如spring-boot-starter-web,他就會幫我們自動匯入web環境所有的依賴
  • springboot會將所有的功能場景,變成一個個的啟動器
  • 我們要什麼用什麼功能,就只需要找到對應的啟動器就可以了starter

主程式

//@SpringBootApplication 來標註一個主程式類
//說明這是一個Spring Boot應用
@SpringBootApplication
public class SpringbootApplication {

   public static void main(String[] args) {
     //以為是啟動了一個方法,沒想到啟動了一個服務
      SpringApplication.run(SpringbootApplication.class, args);
   }

}

但是**一個簡單的啟動類並不簡單!**我們來分析一下這些註解都幹了什麼

@SpringBootApplication

作用:標註在某個類上說明這個類是SpringBoot的主配置類 , SpringBoot就應該執行這個類的main方法來啟動SpringBoot應用;

進入這個註解:可以看到上面還有很多其他註解!

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    // ......
}

@ComponentScan

這個註解在Spring中很重要 ,它對應XML配置中的元素。

作用:自動掃描並載入符合條件的元件或者bean , 將這個bean定義載入到IOC容器中

@SpringBootConfiguration

作用:SpringBoot的配置類 ,標註在某個類上 , 表示這是一個SpringBoot的配置類;

我們繼續進去這個註解檢視

// 點進去得到下面的 @Component
@Configuration
public @interface SpringBootConfiguration {}

@Component
public @interface Configuration {}

這裡的 @Configuration,說明這是一個配置類 ,配置類就是對應Spring的xml 配置檔案;

裡面的 @Component 這就說明,啟動類本身也是Spring中的一個元件而已,負責啟動應用!

我們回到 SpringBootApplication 註解中繼續看。

@EnableAutoConfiguration

@EnableAutoConfiguration :開啟自動配置功能

以前我們需要自己配置的東西,而現在SpringBoot可以自動幫我們配置 ;@EnableAutoConfiguration告訴SpringBoot開啟自動配置功能,這樣自動配置才能生效;

點進註解接續檢視:

@AutoConfigurationPackage :自動配置包

結論:

1、SpringBoot在啟動的時候從類路徑下的META-INF/spring.factories中獲取EnableAutoConfiguration指定的值
2、將這些值作為自動配置類匯入容器 , 自動配置類就生效 , 幫我們進行自動配置工作;
3、整個J2EE的整體解決方案和自動配置都在springboot-autoconfigure的jar包中;
4、它會給容器中匯入非常多的自動配置類 (xxxAutoConfiguration), 就是給容器中匯入這個場景需要的所有元件 , 並配置好這些元件 ;
5、有了自動配置類 , 免去了我們手動編寫配置注入功能元件等的工作;

6、使用Spring Initializer快速建立Spring Boot專案

1、IDEA:使用 Spring Initializer快速建立專案

IDE都支援使用Spring的專案建立嚮導快速建立一個Spring Boot專案;

選擇我們需要的模組;嚮導會聯網建立Spring Boot專案;

預設生成的Spring Boot專案;

主程式已經生成好了,我們只需要我們自己的邏輯
resources資料夾中目錄結構
static:儲存所有的靜態資源; js css images;
templates:儲存所有的模板頁面;(Spring Boot預設jar包使用嵌入式的Tomcat,預設不支援JSP頁面);可以使用模板引擎(freemarker、thymeleaf);
application.properties:Spring Boot應用的配置檔案;可以修改一些預設設定;

2、STS使用 Spring Starter Project快速建立專案

第8P yaml語法講解 https://www.bilibili.com/video/BV1PE411i7CV?p=8

二、配置檔案

1、配置檔案
SpringBoot使用一個全域性的配置檔案,配置檔名是固定的;

  • application.properties

  • application.yml

配置檔案的作用:修改SpringBoot自動配置的預設值;SpringBoot在底層都給我們自動配置好;

YAML(YAML Ain’t Markup Language)

​ YAML A Markup Language:是一個標記語言

​ YAML isn’t Markup Language:不是一個標記語言;

標記語言:

​ 以前的配置檔案;大多都使用的是 xxxx.xml檔案;

​ YAML:以資料為中心,比json、xml等更適合做配置檔案;

​ YAML:配置例子

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

2、YAML語法:

1、基本語法

k:(空格)v:表示一對鍵值對(空格必須有);

空格的縮排來控制層級關係;只要是左對齊的一列資料,都是同一個層級的

server:
    port: 8081
    path: /hello

屬性和值也是大小寫敏感;

2、值的寫法

字面量:普通的值(數字,字串,布林)
​ k: v:字面直接來寫;

​ 字串預設不用加上單引號或者雙引號;

​ “”:雙引號;不會轉義字串裡面的特殊字元;特殊字元會作為本身想表示的意思

​ name: “zhangsan \n lisi”:輸出;zhangsan 換行 lisi

​ ‘’:單引號;會轉義特殊字元,特殊字元最終只是一個普通的字串資料

​ name: ‘zhangsan \n lisi’:輸出;zhangsan \n lisi

物件、Map(屬性和值)(鍵值對):
​ k: v:在下一行來寫物件的屬性和值的關係;注意縮排

​ 物件還是k: v的方式

friends:
		lastName: zhangsan
		age: 20

行內寫法:

friends: {lastName: zhangsan,age: 18}

陣列(List、Set):
用- 值表示陣列中的一個元素

pets:
 - cat
 - dog
 - pig

行內寫法

pets: [cat,dog,pig]

第9P 給屬性賦值的幾種方式 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

3、配置檔案值注入

配置檔案

person:
    lastName: hello
    age: 18
    boss: false
    birth: 2017/12/12
    maps: {k1: v1,k2: 12}
    lists:
      - lisi
      - zhaoliu
    dog:
      name: 小狗
      age: 12

javaBean:

/**
 * 將配置檔案中配置的每一個屬性的值,對映到這個元件中
 * @ConfigurationProperties:告訴SpringBoot將本類中的所有屬性和配置檔案中相關的配置進行繫結;
 *      prefix = "person":配置檔案中哪個下面的所有屬性進行一一對映
 *
 * 只有這個元件是容器中的元件,才能容器提供的@ConfigurationProperties功能;
 *
 */
@Component
@ConfigurationProperties(prefix = "person")
public class Person {

    private String lastName;
    private Integer age;
    private Boolean boss;
    private Date birth;

    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

1、properties配置檔案在idea中預設utf-8可能會亂碼
2、@Value獲取值和@ConfigurationProperties獲取值比較
在這裡插入圖片描述
配置檔案yml還是properties他們都能獲取到值;
如果說,我們只是在某個業務邏輯中需要獲取一下配置檔案中的某項值,使用@Value;
如果說,我們專門編寫了一個javaBean來和配置檔案進行對映,我們就直接使用@ConfigurationProperties;

第10P JSR303校驗 [ https://www.bilibili.com/video/BV1PE411i7CV?p=10]

3、配置檔案注入值資料校驗

@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {

    /**
     * <bean class="Person">
     *      <property name="lastName" value="字面量/${key}從環境變數、配置檔案中獲取值/#{SpEL}"></property>
     * <bean/>
     */

   //lastName必須是郵箱格式
    @Email
    //@Value("${person.last-name}")
    private String lastName;
    //@Value("#{11*2}")
    private Integer age;
    //@Value("true")
    private Boolean boss;

    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

@ImportResource:匯入Spring的配置檔案,讓配置檔案裡面的內容生效;

Spring Boot裡面沒有Spring的配置檔案,我們自己編寫的配置檔案,也不能自動識別;

想讓Spring的配置檔案生效,載入進來;@ImportResource標註在一個配置類上

@ImportResource(locations = {"classpath:beans.xml"})
匯入Spring的配置檔案讓其生效

SpringBoot推薦給容器中新增元件的方式;推薦使用全註解的方式

1、配置類**@Configuration**------>Spring配置檔案

2、使用**@Bean**給容器中新增元件

/**
 * @Configuration:指明當前類是一個配置類;就是來替代之前的Spring配置檔案
 *
 * 在配置檔案中用<bean><bean/>標籤新增元件
 *
 */
@Configuration
public class MyAppConfig {

    //將方法的返回值新增到容器中;容器中這個元件預設的id就是方法名
    @Bean
    public HelloService helloService02(){
        System.out.println("配置類@Bean給容器中新增元件了...");
        return new HelloService();
    }
}

4、配置檔案佔位符

1、隨機數

${random.value}、${random.int}、${random.long}
${random.int(10)}、${random.int[1024,65536]}

2、佔位符獲取之前配置的值,如果沒有可以是用:指定預設值

person.last-name=張三${random.uuid}
person.age=${random.int}
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.hello:hello}_dog
person.dog.age=15

第11P 多環境配置及配置檔案位置 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

5、Profile

1、多Profile檔案

我們在主配置檔案編寫的時候,檔名可以是 application-{profile}.properties/yml

預設使用application.properties的配置;

2、yml支援多文件塊方式


server:
  port: 8081
spring:
  profiles:
    active: prod

---
server:
  port: 8083
spring:
  profiles: dev


---

server:
  port: 8084
spring:
  profiles: prod  #指定屬於哪個環境

3、啟用指定profile

​ 1、在配置檔案中指定 spring.profiles.active=dev

​ 2、命令列:

​ java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;

​ 可以直接在測試的時候,配置傳入命令列引數

​ 3、虛擬機器引數;

​ -Dspring.profiles.active=dev

6、配置檔案載入位置

springboot 啟動會掃描以下位置的application.properties或者application.yml檔案作為Spring boot的預設配置檔案

–file:./config/

–file:./

–classpath:/config/

–classpath:/

優先順序由高到底,高優先順序的配置會覆蓋低優先順序的配置;

SpringBoot會從這四個位置全部載入主配置檔案;互補配置

7、外部配置載入順序

SpringBoot也可以從以下位置載入配置; 優先順序從高到低;高優先順序的配置覆蓋低優先順序的配置,所有的配置會形成互補配置

1.命令列引數

所有的配置都可以在命令列上進行指定

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc

多個配置用空格分開; --配置項=值

2.來自java:comp/env的JNDI屬性

3.Java系統屬性(System.getProperties())

4.作業系統環境變數

5.RandomValuePropertySource配置的random.*屬性值

由jar包外向jar包內進行尋找;

優先載入帶profile

6.jar包外部的application-{profile}.properties或application.yml(帶spring.profile)配置檔案

7.jar包內部的application-{profile}.properties或application.yml(帶spring.profile)配置檔案

再來載入不帶profile

8.jar包外部的application.properties或application.yml(不帶spring.profile)配置檔案

9.jar包內部的application.properties或application.yml(不帶spring.profile)配置檔案

10.@Configuration註解類上的@PropertySource

11.通過SpringApplication.setDefaultProperties指定的預設屬性

所有支援的配置載入來源;

第12P 多環境配置及配置檔案位置 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

8、自動配置原理

配置檔案到底能寫什麼?怎麼寫?自動配置原理;
配置檔案能配置的屬性參照

1、原理

1、SpringBoot啟動會載入大量的自動配置類

2、我們看我們需要的功能有沒有在SpringBoot預設寫好的自動配置類當中;

3、我們再來看這個自動配置類中到底配置了哪些元件;(只要我們要用的元件存在在其中,我們就不需要再手動配置了)

4、給容器中自動配置類新增元件的時候,會從properties類中獲取某些屬性。我們只需要在配置檔案中指定這些屬性的值即可;

**xxxxAutoConfigurartion:自動配置類;**給容器中新增元件

xxxxProperties:封裝配置檔案中相關屬性;

開啟debug可以在啟動的時候在控制檯輸出配置

debug: true

第35P 使用者認證和授權 [ https://www.bilibili.com/video/BV1PE411i7CV?p=35]

SpringSecurity(安全)

安全簡介

在 Web 開發中,安全一直是非常重要的一個方面。安全雖然屬於應用的非功能性需求,但是應該在應用開發的初期就考慮進來。如果在應用開發的後期才考慮安全的問題,就可能陷入一個兩難的境地:一方面,應用存在嚴重的安全漏洞,無法滿足使用者的要求,並可能造成使用者的隱私資料被攻擊者竊取;另一方面,應用的基本架構已經確定,要修復安全漏洞,可能需要對系統的架構做出比較重大的調整,因而需要更多的開發時間,影響應用的釋出程式。因此,從應用開發的第一天就應該把安全相關的因素考慮進來,並在整個應用的開發過程中。

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

認證,授權

認識SpringSecurity

Spring Security 是針對Spring專案的安全框架,也是Spring Boot底層安全模組預設的技術選型,他可以實現強大的Web安全控制,對於安全控制,我們僅需要引入 spring-boot-starter-security 模組,進行少量的配置,即可實現強大的安全管理!

記住幾個類:

  • WebSecurityConfigurerAdapter:自定義Security策略
  • AuthenticationManagerBuilder:自定義認證策略
  • @EnableWebSecurity:開啟WebSecurity模式
  1. 引入 Spring Security 模組
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 編寫基礎配置類
package com.kuang.config;

@EnableWebSecurity // 開啟WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {

  }
}
  1. 定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
   // 定製請求的授權規則
   // 首頁所有人可以訪問
   http.authorizeRequests().antMatchers("/").permitAll()
  .antMatchers("/level1/**").hasRole("vip1")
  .antMatchers("/level2/**").hasRole("vip2")
  .antMatchers("/level3/**").hasRole("vip3");
}

  1. 測試一下:發現除了首頁都進不去了!因為我們目前沒有登入的角色,因為請求需要登入的角色擁有對應的許可權才可以!
  2. 在configure()方法中加入以下配置,開啟自動配置的登入功能!
// 開啟自動配置的登入功能
// "/login" 請求來到登入頁
// /login?error 重定向到這裡表示登入失敗
http.formLogin();

此時如果沒有許可權就會跳轉至登陸頁

  1. 定義認定規則,重寫configure(AuthenticationManagerBuilder auth)方法
//認證
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  //資料正常應該從資料庫中取,現在從記憶體中取
  auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
    .withUser("admin").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2")
    .and()
    .withUser("root").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2","vip3")
    .and()
    .withUser("guest").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");

這裡設定密碼加密auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())否則會報There is no PasswordEncoder mapped for the id "null"錯誤,建立的每個使用者也必須新增密碼加密**.password(new BCryptPasswordEncoder().encode(“123”))**

  1. 測試,發現,登入成功,並且每個角色只能訪問自己認證下的規則!搞定

第36P 許可權控制和登出 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

許可權控制和登出

登出

  1. 開啟自動配置的登出的功能
//定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
   //....
   //開啟自動配置的登出的功能
      // /logout 登出請求
   http.logout();
}

  1. 我們在前端,增加一個登出的按鈕,index.html 導航欄中
<a class="btn btn-primary" th:href="@{/logout}">登出</a>

  1. 測試發現,點選登出按鈕後會跳轉到登陸頁面,此時想要在登出成功後跳轉到指定頁面需要在請求後新增.logoutSuccessUrl("/");
// .logoutSuccessUrl("/"); 登出成功來到首頁
http.logout().logoutSuccessUrl("/");

許可權控制

**需求:**使用者沒有登入的時候,導航欄上只顯示登入按鈕,使用者登入之後,導航欄可以顯示登入的使用者資訊及登出按鈕!還有就是,比如admin這個使用者,它只有 vip2,vip3功能,那麼登入則只顯示這兩個功能,而vip1的功能選單不顯示!

  1. 匯入maven依賴
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>

  1. 匯入名稱空間
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"

  1. 修改index頁面,增加判斷
<div>
  <!--如果未登入則顯示以下內容-->
  <div sec:authorize="!isAuthenticated()">
    <a class="btn btn-primary" th:href="@{/toLogin}">登陸</a>
  </div>
  <!--如果已登入則顯示以下內容-->
  <div sec:authorize="isAuthenticated()">
    使用者名稱<p sec:authentication="name"></p>
    角色<p sec:authentication="principal.authorities"></p>
    <a class="btn btn-primary" th:href="@{/logout}">登出</a>
  </div>
</div>
<!--如果使用者擁有這個角色,則顯示該div內的內容,如果沒有則不顯示-->
<div class="div" sec:authorize="hasRole('vip1')">
  <a th:href="@{/level1/1}">level1-1</a><br/>
  <a th:href="@{/level1/2}">level1-2</a>
</div>
<div class="div" sec:authorize="hasRole('vip2')">
  <a th:href="@{/level2/1}">level2-1</a><br/>
  <a th:href="@{/level2/2}">level2-2</a>
</div>
<div class="div" sec:authorize="hasRole('vip3')">
  <a th:href="@{/level3/1}">level3-1</a><br/>
  <a th:href="@{/level3/2}">level3-2</a>
</div>

  1. 如果登出404了,就是因為它預設防止csrf跨站請求偽造,因為會產生安全問題,我們可以將請求改為post表單提交,或者在spring security中關閉csrf功能;我們試試:在 配置中增加 http.csrf().disable();

第37P 記住我及首頁配置 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

記住我

  1. 開啟記住我功能
//定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
//。。。。。。。。。。。
   //記住我,儲存2周
   http.rememberMe();
}

  1. 測試,關閉瀏覽器再次開啟使用者依舊存在
    本質上是儲存到cookie,通過瀏覽器審查元素的application中可以看到
  2. 如果使用自己的頁面中的按鈕,可以給按鈕設定name,再在配置後面加上如下方法
http.rememberMe().rememberMeParameter("remember");

定製登入頁

  1. 在配置中設定,用.loginProcessingUrl("/login")並將前端form表單的action設定為括號內相同即可,但如果前端使用者名稱和密碼前後端不一樣,則需要進行設定,括號內的屬性為前端頁面的name屬性
http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login");

第38P Shiro 快速開始 [ https://www.bilibili.com/video/BV1PE411i7CV?p=39]

Shiro

shiro簡介

基本功能點

Shiro 可以非常容易的開發出足夠好的應用,其不僅可以用在 JavaSE 環境,也可以用在 JavaEE 環境。Shiro 可以幫助我們完成:認證、授權、加密、會話管理、與 Web 整合、快取等。其基本功能點如下圖所示:
在這裡插入圖片描述

  • Authentication:身份認證 / 登入,驗證使用者是不是擁有相應的身份;
  • Authorization:授權,即許可權驗證,驗證某個已認證的使用者是否擁有某個許可權;即判斷使用者是否能做事情,常見的如:驗證某個使用者是否擁
  • 有某個角色。或者細粒度的驗證某個使用者對某個資源是否具有某個許可權;
  • Session Manager:會話管理,即使用者登入後就是一次會話,在沒有退出之前,它的所有資訊都在會話中;會話可以是普通 JavaSE
    環境的,也可以是如 Web 環境的;
  • Cryptography:加密,保護資料的安全性,如密碼加密儲存到資料庫,而不是明文儲存;
  • Web Support:Web 支援,可以非常容易的整合到 Web 環境;
  • Caching:快取,比如使用者登入後,其使用者資訊、擁有的角色 / 許可權不必每次去查,這樣可以提高效率;
  • Concurrency:shiro 支援多執行緒應用的併發驗證,即如在一個執行緒中開啟另一個執行緒,能把許可權自動傳播過去;
  • Testing:提供測試支援;
  • Run As:允許一個使用者假裝為另一個使用者(如果他們允許)的身份進行訪問;
  • Remember Me:記住我,這個是非常常見的功能,即一次登入後,下次再來的話不用登入了。

記住一點,Shiro 不會去維護使用者、維護許可權;這些需要我們自己去設計 / 提供;然後通過相應的介面注入給 Shiro 即可。

Shiro的架構

外部

我們從外部來看 Shiro ,即從應用程式角度的來觀察如何使用 Shiro 完成工作。如下圖:
在這裡插入圖片描述
可以看到:應用程式碼直接互動的物件是 Subject,也就是說 Shiro 的對外 API 核心就是 Subject;其每個 API 的含義:
**Subject:**主體,代表了當前 “使用者”,這個使用者不一定是一個具體的人,與當前應用互動的任何東西都Subject,如網路爬蟲,機器人等;即一個抽象概念;所有 Subject 都繫結到 SecurityManager,與 Subject 的所有互動都會委託給 SecurityManager;可以把 Subject 認為是一個門面;SecurityManager 才是實際的執行者;

**SecurityManager:**安全管理器;即所有與安全有關的操作都會與 SecurityManager 互動;且它管理著所有 Subject;可以看出它是 Shiro 的核心,它負責與後邊介紹的其他元件進行互動,如果學習過 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

**Realm:**域,Shiro 從 Realm 獲取安全資料(如使用者、角色、許可權),就是說 SecurityManager 要驗證使用者身份,那麼它需要從 Realm 獲取相應的使用者進行比較以確定使用者身份是否合法;也需要從 Realm 得到使用者相應的角色 / 許可權進行驗證使用者是否能進行操作;可以把 Realm 看成 DataSource,即安全資料來源。

也就是說對於我們而言,最簡單的一個 Shiro 應用:

  1. 應用程式碼通過 Subject 來進行認證和授權,而 Subject 又委託給 SecurityManager;
  2. 我們需要給 Shiro 的 SecurityManager 注入 Realm,從而讓 SecurityManager 能得到合法的使用者及其許可權進行判斷。

從以上也可以看出,Shiro 不提供維護使用者 / 許可權,而是通過 Realm 讓開發人員自己注入。

內部

接下來我們來從 Shiro 內部來看下 Shiro 的架構,如下圖所示:
在這裡插入圖片描述
**Subject:**主體,可以看到主體可以是任何可以與應用互動的 “使用者”;

**SecurityManager:**相當於 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心臟;所有具體的互動都通過 SecurityManager 進行控制;它管理著所有 Subject、且負責進行認證和授權、及會話、快取的管理。

**Authenticator:**認證器,負責主體認證的,這是一個擴充套件點,如果使用者覺得 Shiro 預設的不好,可以自定義實現;其需要認證策略(Authentication Strategy),即什麼情況下算使用者認證通過了;

**Authrizer:**授權器,或者訪問控制器,用來決定主體是否有許可權進行相應的操作;即控制著使用者能訪問應用中的哪些功能;

**Realm:**可以有 1 個或多個 Realm,可以認為是安全實體資料來源,即用於獲取安全實體的;可以是 JDBC 實現,也可以是 LDAP 實現,或者記憶體實現等等;由使用者提供;注意:Shiro 不知道你的使用者 / 許可權儲存在哪及以何種格式儲存;所以我們一般在應用中都需要實現自己的 Realm;

**SessionManager:**如果寫過 Servlet 就應該知道 Session 的概念,Session 呢需要有人去管理它的生命週期,這個元件就是 SessionManager;而 Shiro 並不僅僅可以用在 Web 環境,也可以用在如普通的 JavaSE 環境、EJB 等環境;所有呢,Shiro 就抽象了一個自己的 Session 來管理主體與應用之間互動的資料;這樣的話,比如我們在 Web 環境用,剛開始是一臺 Web 伺服器;接著又上了臺 EJB 伺服器;這時想把兩臺伺服器的會話資料放到一個地方,這個時候就可以實現自己的分散式會話(如把資料放到 Memcached 伺服器);

**SessionDAO:**DAO 大家都用過,資料訪問物件,用於會話的 CRUD,比如我們想把 Session 儲存到資料庫,那麼可以實現自己的 SessionDAO,通過如 JDBC 寫到資料庫;比如想把 Session 放到 Memcached 中,可以實現自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 進行快取,以提高效能;

**CacheManager:**快取控制器,來管理如使用者、角色、許可權等的快取的;因為這些資料基本上很少去改變,放到快取中後可以提高訪問的效能

**Cryptography:**密碼模組,Shiro 提高了一些常見的加密元件用於如密碼加密 / 解密的。

shiro元件

身份驗證

身份驗證,即在應用中誰能證明他就是他本人。一般提供如他們的身份 ID 一些標識資訊來表明他就是他本人,如提供身份證,使用者名稱 / 密碼來證明。

在 shiro 中,使用者需要提供 principals (身份)和 credentials(證明)給 shiro,從而應用能驗證使用者身份:

**principals:**身份,即主體的標識屬性,可以是任何東西,如使用者名稱、郵箱等,唯一即可。一個主體可以有多個 principals,但只有一個 Primary principals,一般是使用者名稱 / 密碼 / 手機號。

**credentials:**證明 / 憑證,即只有主體知道的安全值,如密碼 / 數字證照等。

最常見的 principals 和 credentials 組合就是使用者名稱 / 密碼了。接下來先進行一個基本的身份認證。

另外兩個相關的概念是之前提到的 SubjectRealm,分別是主體及驗證主體的資料來源。

快速開始(helloworld)

  1. 匯入依賴
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>1.4.1</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.7.21</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jcl-over-slf4j</artifactId>
  <version>1.7.21</version>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
</dependency>
<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.1.1</version>
</dependency>

  1. 配置檔案(shiro.ini)
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

[roles]
# '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

  1. log4j.properties
log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

  1. Quickstart.java
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author lee
 * @date 2020/8/11 - 6:24 下午
 */

public class Quickstart {


  private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);

  public static void main(String[] args) {

	//----------------a方法 開始----------------
	//此段可能會過期,如過期用b方法
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    //----------------a方法 結束----------------

	//---------------b方法  開始-----------------
	DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
    defaultSecurityManager.setRealm(iniRealm);
	//---------------b方法  結束-----------------

    //獲取當前的使用者物件 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);
  }
}


以下方法Spring Security都有:

Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
currentUser.isAuthenticated()
currentUser.getPrincipal()
currentUser.hasRole("schwartz")
currentUser.isPermitted("lightsaber:wield")
currentUser.logout();

第39P Shiro的Subject分析 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

第40P 在SpringBoot中整合Shiro [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

  1. 引入maven
<!--
            Subject 使用者
            SecurityManager  管理所有使用者
            Realm  連線資料
        -->

        <!--shiro整合spring的包-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>

        <!--   thymeleaf模板-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>
  1. 編寫UserRealm類
//自定義的UserRealm
public class UserRealm extends AuthorizingRealm {
  //授權
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("執行了=》授權");
    return null;
  }
  //認證
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    System.out.println("執行了=》認證");
    return null;
  }
}

  1. 編寫Shiroconfig類(三個方法,從下往上寫)
package com.example.demo.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public 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();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //建立realm物件:1
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
}

  1. 做一些前期準備

MyController.java

package com.example.demo.controller;


import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

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


    @RequestMapping("/user/add")
    public String toAdd(Model model){
        model.addAttribute("msg","add");
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String toUpdate(Model model){
        model.addAttribute("msg","add");
        return "user/update";
    }


}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首頁</h1>
<p th:text="${msg}"></p>

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


</body>
</html>

add.html

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

</body>
</html>

update.html

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

</body>
</html>

第41P Shiro實現登入攔截 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

實現登陸攔截

  1. 配置ShiroConfig配置類
 //新增shiro的內建過濾器

        /*
        *  anon:無需認證就可以訪問
        *  authc:必須認證了才可以訪問
        *  user:必須 記住我 才可以訪問
        *  perms: 擁有對某個資源的許可權才可以訪問
        *  role: 必須擁有角色許可權才可以訪問
        *
        * */

        Map<String,String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterMap);

        //設定登入的請求
        bean.setLoginUrl("/toLogin");
  1. 在controller類實現/toLogin請求
@RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

  1. 登入頁
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>登入</h1>

<hr>
<form action="">
    <p>使用者名稱<input type="text" name="username"></p>
    <p>密碼<input type="text" name="password"></p>
    <input type="submit">
</form>

</body>
</html>

第42P Shiro實現使用者認證 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

實現使用者認證

  1. 在controller配置請求
@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);//執行登陸方法,如果沒有異常就說明登陸成功
    return "index";
  }catch (UnknownAccountException e){//使用者名稱不存在
    model.addAttribute("msg","使用者名稱不存在");
    return "login";
  }catch (IncorrectCredentialsException e){
    model.addAttribute("msg","密碼錯誤");
    return "login";
  }

}

  1. 在UserRealm類中配置認證
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  System.out.println("執行了=》認證");

  //使用者名稱密碼   資料庫取
  String name = "root";
  String password = "123";

  UsernamePasswordToken userToken = (UsernamePasswordToken) token;
  System.out.println(userToken.toString());
  if(!userToken.getUsername().equals(name)){
    return null; //丟擲異常  UnknownAccountException
  }

  return new SimpleAuthenticationInfo("",password,"");
}

  1. login.html 改造
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">

使用者名稱驗證自己來做,密碼驗證shiro來做

第43P Shiro整合Mybatis [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

整合Mybatis

1.引入pom

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>

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

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

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

2.建立實體類User

package com.example.demo.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

3.建立Mapper.java

package com.example.demo.mapper;

import com.example.demo.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

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

4.建立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">
<!--namespace=繫結一個對應的Dao/Mapper介面-->
<mapper namespace="com.example.demo.mapper.UserMapper">
    <!--    查詢語句 id相當與要重寫的方法名-->
    <select id="queryUserByName" resultType="User" parameterType="String">
        select * from  User where name = #{name}
    </select>

</mapper>

5、建立service及imp

package com.example.demo.service;

import com.example.demo.pojo.User;

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

package com.example.demo.service;

import com.example.demo.mapper.UserMapper;
import com.example.demo.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userMapper;

    @Override
    public User queryUserByName(String username) {

        return userMapper.queryUserByName(username);
    }
}

6.測試

package com.example.demo;

import com.example.demo.service.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    UserServiceImpl userService;

    @Test
    void contextLoads() {
       System.out.println( userService.queryUserByName("a"));

    }

}

第44P Shiro請求授權實現 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

實現授權

  1. 對資源設定許可權
//攔截
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/add","perms[user:add]");
filterMap.put("/update","perms[user:update]");
bean.setFilterChainDefinitionMap(filterMap);

//未授權就跳轉到此請求
bean.setUnauthorizedUrl("/noauth");

  1. 對登陸使用者進行授權,該使用者擁有的許可權從資料庫取得
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  System.out.println("執行了=》授權");

  SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

  //拿到當前登陸的物件
  Subject subject = SecurityUtils.getSubject();
  User currentUser = (User)subject.getPrincipal();//拿到User
  info.addStringPermission(currentUser.getPerms());//授權

  return info;
}

第45P Shiro整合Thymeleaf [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

第45P Swagger介紹及整合 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

Swagger

介面文件對於前後端開發人員都十分重要。尤其近幾年流行前後端分離後介面文件又變成重中之重。介面文件固然重要,但是由於項 目週期等原因後端人員經常出現無法及時更新,導致前端人員抱怨接 口文件和實際情況不一致。

很多人員會抱怨別人寫的介面文件不規範,不及時更新。當時當 自己寫的時候確實最煩去寫介面文件。這種痛苦只有親身經歷才會牢 記於心。

如果介面文件可以實時動態生成就不會出現上面問題。

Swagger 可以完美的解決上面的問題。

Swagger 簡介:

Swagger 是一套圍繞 Open API 規範構建的開源工具,可以幫助設 計,構建,記錄和使用 REST API。

Swagger 工具包括的元件:

  1. Swagger Editor :基於瀏覽器編輯器,可以在裡面編寫 Open API規範。類似 Markdown
    具有實時預覽描述檔案的功能。
  2. Swagger UI:將 Open API 規範呈現為互動式 API 文件。用視覺化UI 展示描述檔案。
  3. Swagger Codegen:將 OpenAPI 規範生成為伺服器存根和客戶端 庫。通過 Swagger Codegen
    可以將描述檔案生成 html 格式和 cwiki 形 式的介面文件,同時也可以生成多種言語的客戶端和服務端程式碼。

Swagger Inspector:和 Swagger UI 有點類似,但是可以返回更多 資訊,也會儲存請求的實際引數資料。

Swagger Hub:整合了上面所有專案的各個功能,你可以以專案 和版本為單位,將你的描述檔案上傳到 Swagger Hub 中。在 Swagger Hub 中可以完成上面專案的所有工作,需要註冊賬號,分免費版和收費版。

使用 Swagger,就是把相關的資訊儲存在它定義的描述檔案裡面(yml 或 json 格式),再通過維護這個描述檔案可以去更新介面文件, 以及生成各端程式碼。

Swagger整合

  1. 匯入maven依賴
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.9.2</version>
</dependency>

  1. 建立SwaggerConfig.java
@Configuration
@EnableSwagger2   //開啟Swagger2
public class SwaggerConfig{

}

第48P 配置swagger資訊 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

Swagger配置

在SwaggerConfig進行如下配置即可自定義Swagger配置

@Configuration
@EnableSwagger2   //開啟Swagger2
public class SwaggerConfig{
  @Bean
  public Docket docket(){

    return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
  }
  private ApiInfo apiInfo(){
    //作者資訊
    Contact contact = new Contact("李海洋", "codekitty.cn:8000", "L18249290950@126.com");

    return new ApiInfo("codekitty's Swagger API Documentation", "千里之行,始於足下", "1.0", "codekitty.cn:8000",contact , "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
  }
}

第49P 配置掃描介面及開關 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

Swagger掃描介面

  1. Docket(DocumentationType.SWAGGER_2).select()
new Docket(DocumentationType.SWAGGER_2)
  .apiInfo(apiInfo())
  .enable(b)//是否啟用Swagger,false則不啟用
  .select()
  //RequestHandlerSelectors,配置要掃描介面的方式
  //basePackage,指定掃描包
  //                .apis(RequestHandlerSelectors.any()),掃描全部
  //                .apis(RequestHandlerSelectors.none()),不掃描
  //                withMethodAnnotation(GetMapping.class)) //掃描類上的註解
  //                withClassAnnotation(GetMapping.class)) //掃描方法上的註解
  .apis(RequestHandlerSelectors.basePackage("com.controller"))
  //Path過濾路徑
  //                .paths(PathSelectors.ant("/com/**"))

  .build();

  1. 實現開發環境中使用Swagger,執行上線環境中不使用Swagger
public Docket docket(Environment environment){
//設定要顯示的Swagger環境
Profiles profiles = Profiles.of("dev","test");
//獲取專案的環境:判斷是否處在自己設定的環境中
boolean flag = environment.acceptsProfiles(profiles);
}

在properties中選擇使用的環境,將flag傳入Enable()

  1. 通過groupName("")配置多個Docket,(在多人開發中,每個開發者配置一個自己的Swagger,方便管理)
@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");
}

實體類配置

@ApiModel("使用者實體類")
public class User {
    @ApiModelProperty("使用者名稱")
    private String username;
    @ApiModelProperty("密碼")
    private String password;

在Controller進行配置請求

@ApiOperation("user請求")
    //只要介面中返回值存在實體類,它就會被掃描到Swagger中
    @PostMapping(value = "/user")
    public String user(@ApiParam("使用者名稱")String username){
        return "hello" + username;
    }

就可以在Swagger顯示實體類的資訊,如果屬性是private需要加get,set方法

總結:

  1. 我們可以通過Swagger給一些比較難理解的屬性或介面增加註釋資訊
  2. 介面文件實時更新
  3. 可以線上測試

【注意】在正式釋出時要關閉Swagger!!!可以保證安全和避免浪費效能

第51P 非同步任務 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

任務

非同步任務

定義一個Service

@Service
public class AsyncService {
	//告訴spring這是非同步任務
    @Async
    public void hello(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("資料正在處理");
    }
}

定義一個請求,在請求中呼叫service中的方法

@ResponseBody
@RequestMapping("/hello")
public String hello(){
  asyncService.hello();
  return "OK";
}

在主入口上使用@EnableAsync開啟非同步任務

@EnableAsync
@SpringBootApplication
public class DemoApplication {

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

}

兩個步驟:

  1. 在Service的方法中使用@Async
  2. 在主入口上使用@EnableAsync開啟非同步任務

第52P 郵件傳送 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

郵件傳送

在properties中配置自己的郵件資訊

spring.mail.username=1012074120@qq.com
spring.mail.password=jtnhlvabxxqsbbga
spring.mail.host=smtp.qq.com

#開啟加密驗證(qq郵箱)
spring.mail.properties.mail.smtp.ssl.enable=true

簡單郵件傳送
直接呼叫SpringBoot的JavaMailSenderImpl類,使用SimpleMailMessage傳送簡單郵件

@Autowired
JavaMailSenderImpl mailSender;

@Test
void contextLoads() {
  SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
  simpleMailMessage.setSubject("你好");
  simpleMailMessage.setText("Hello world");
  simpleMailMessage.setTo("L18249290950@126.com");
  simpleMailMessage.setFrom("1012074120@qq.com");
  mailSender.send(simpleMailMessage);
}

複雜郵件傳送
呼叫mailSender.createMimeMessage()並使用MimeMessageHelper配置郵件內容,傳送即可,郵件內容後設定為true可以解析html格式的內容

public void SendMail(Boolean html,String title, String text, File file, String sendTo, String sendFrom) throws MessagingException {
        //複雜郵件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //組裝
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,true);

        mimeMessageHelper.setSubject(title);
        mimeMessageHelper.setText(text,true);//true,開啟html解析
       mimeMessageHelper.addAttachment("1.jpg",file);

        mimeMessageHelper.setTo(sendTo);
        mimeMessageHelper.setFrom(sendFrom);
        mailSender.send(mimeMessage);
    }

第53P 定時執行任務 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

定時任務

Scheduled

TaskScheduler //任務排程程式
TaskExecutor	//任務執行者
@EnableScheduling //開啟定時功能的註解,放在主入口
@Scheduled		//什麼時候執行
  
cron表示式

在方法上面加上@Scheduled(cron = "0 43 14 * * ?") 並加上相應的表示式即可
常用表示式例子:

10 0 2 1 * ? *   表示在每月的1日的凌晨2點調整任務
(20 15 10 ? * MON-FRI   表示週一到週五每天上午10:15執行作
(30 15 10 ? 6L 2002-2006   表示2002-2006年的每個月的最後一個星期五上午10:15執行作
(40 0 10,14,16 * * ?   每天上午10點,下午2點,4點
(50 0/30 9-17 * * ?   朝九晚五工作時間內每半小時
(60 0 12 ? * WED    表示每個星期三中午12點
(70 0 12 * * ?   每天中午12點觸發
(80 15 10 ? * *    每天上午10:15觸發
(90 15 10 * * ?     每天上午10:15觸發
(100 15 10 * * ? *    每天上午10:15觸發
(110 15 10 * * ? 2005    2005年的每天上午10:15觸發
(120 * 14 * * ?     在每天下午2點到下午2:59期間的每1分鐘觸發
(130 0/5 14 * * ?    在每天下午2點到下午2:55期間的每5分鐘觸發
(140 0/5 14,18 * * ?     在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發
(150 0-5 14 * * ?    在每天下午2點到下午2:05期間的每1分鐘觸發
(160 10,44 14 ? 3 WED    每年三月的星期三的下午2:102:44觸發
(170 15 10 ? * MON-FRI    週一至週五的上午10:15觸發
(180 15 10 15 * ?    每月15日上午10:15觸發
(190 15 10 L * ?    每月最後一日的上午10:15觸發
(200 15 10 ? * 6L    每月的最後一個星期五上午10:15觸發
(210 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最後一個星期五上午10:15觸發
(220 15 10 ? * 6#3   每月的第三個星期五上午10:15觸發

quartz

參考:https://blog.csdn.net/qq_42829628/article/details/106767803

分散式Dubbo+Zokeeper+SpringBoot

第56P 分散式系統理論 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

什麼是分散式系統?

在《分散式系統原理與範型》一書中有如下定義:“分散式系統是若干獨立計算機的集合,這些計算機對於使用者來說就像單個相關係統”;

分散式系統是由一組通過網路進行通訊、為了完成共同的任務而協調工作的計算機節點組成的系統。分散式系統的出現是為了用廉價的、普通的機器完成單個計算機無法完成的計算、儲存任務。其目的是利用更多的機器,處理更多的資料。

分散式系統(distributed system)是建立在網路之上的軟體系統。

首先需要明確的是,只有當單個節點的處理能力無法滿足日益增長的計算、儲存任務的時候,且硬體的提升(加記憶體、加磁碟、使用更好的CPU)高昂到得不償失的時候,應用程式也不能進一步優化的時候,我們才需要考慮分散式系統。因為,分散式系統要解決的問題本身就是和單機系統一樣的,而由於分散式系統多節點、通過網路通訊的拓撲結構,會引入很多單機系統沒有的問題,為了解決這些問題又會引入更多的機制、協議,帶來更多的問題。。。

第57P 什麼是RPC [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

什麼是RPC

RPC【Remote Procedure Call】是指遠端過程呼叫,是一種程式間通訊方式,他是一種技術的思想,而不是規範。它允許程式呼叫另一個地址空間(通常是共享網路的另一臺機器上)的過程或函式,而不用程式設計師顯式編碼這個遠端呼叫的細節。即程式設計師無論是呼叫本地的還是遠端的函式,本質上編寫的呼叫程式碼基本相同。

也就是說兩臺伺服器A,B,一個應用部署在A伺服器上,想要呼叫B伺服器上應用提供的函式/方法,由於不在一個記憶體空間,不能直接呼叫,需要通過網路來表達呼叫的語義和傳達呼叫的資料。為什麼要用RPC呢?就是無法在一個程式內,甚至一個計算機內通過本地呼叫的方式完成的需求,比如不同的系統間的通訊,甚至不同的組織間的通訊,由於計算能力需要橫向擴充套件,需要在多臺機器組成的叢集上部署應用。RPC就是要像呼叫本地的函式一樣去調遠端函式;

Dubbo基本概念

在這裡插入圖片描述

**服務提供者(Provider):**暴露服務的服務提供方,服務提供者在啟動時,向註冊中心註冊自己提供的服務。

**服務消費者(Consumer):**呼叫遠端服務的服務消費方,服務消費者在啟動時,向註冊中心訂閱自己所需的服務,服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一臺提供者進行呼叫,如果呼叫失敗,再選另一臺呼叫。

**註冊中心(Registry):**註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連線推送變更資料給消費者

**監控中心(Monitor):**服務消費者和提供者,在記憶體中累計呼叫次數和呼叫時間,定時每分鐘傳送一次統計資料到監控中心

呼叫關係說明

l 服務容器負責啟動,載入,執行服務提供者。

l 服務提供者在啟動時,向註冊中心註冊自己提供的服務。

l 服務消費者在啟動時,向註冊中心訂閱自己所需的服務。

l 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連線推送變更資料給消費者。

l 服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一臺提供者進行呼叫,如果呼叫失敗,再選另一臺呼叫。

l 服務消費者和提供者,在記憶體中累計呼叫次數和呼叫時間,定時每分鐘傳送一次統計資料到監控中心。

第59P Dubbo-admin安裝測試 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

安裝Dubbo-admin

  1. 下載dubbo-admin
    地址 :https://github.com/apache/dubbo-admin/tree/master
  2. 進行打包(可以是Idea打包,也可以是命令列打包)
  3. 使用命令java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
    【注意:zookeeper的服務一定要開啟!】
  4. 訪問localhost:7001即可訪問Dubbo-admin頁面

在這裡插入圖片描述
第60P 服務註冊發現實戰 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

實現跨專案訪問類

  1. 提供者配置檔案
#服務應用名字
dubbo.application.name=provider-server
#註冊中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服務要被註冊(掃描包)
dubbo.scan.base-packages=com.servic
  1. 編寫提供者的介面和實現類
    在這裡插入圖片描述

  2. 消費者配置檔案

#消費者去哪裡拿服務需要暴露自己的名字
dubbo.application.name=customer-server

#註冊中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

  1. 在消費者和提供者相同的包下建立提供者的介面
  2. 消費者服務類
@Service //注入到容器中
public class UserService {

   @Reference //遠端引用指定的服務,他會按照全類名進行匹配,看誰給註冊中心註冊了這個全類名
   TicketService ticketService;

   public void bugTicket(){
       String ticket = ticketService.getTicket();
       System.out.println("在註冊中心買到"+ticket);
  }

  1. 編寫測試類
@Autowired
UserService userService;

@Test
public void contextLoads() {

  userService.bugTicket();

}

啟動測試

  1. 開啟zookeeper

  2. 開啟dubbo-admin實現監控【可以不用做】

  3. 開啟服務者

  4. 消費者消費測試

在這裡插入圖片描述

第61P 回顧,架構! [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

回顧,架構!

層架構+MVC

架構 -->解耦

開發框架

​ Spring

​ IOC : 控制反轉

​ 泡溫泉,泡茶泡友

​ 附近的人,打招呼。加微信,聊天,天天聊>約泡

​ 浴場(容器):溫泉,茶莊泡友

​ 直接進溫泉,就有人和你一起了!

​ 原來我們都是自己一步步操作,現在交給容器了!我們需要什麼就去拿就可以了

AOP:切面(本質,動態代理)

為了解什麼?不影響業本來的情況下,實現動態增加功能,大量應用在日誌,事務等等

​ Spring是一個輕量級的Java開源框架,容器

​ 目的:解決企業開發的複雜性問題

​ Spring是春天,但配置檔案繁瑣

SpringBoot

SpringBoot ,新代javaEE的開發標準,開箱即用!>拿過來就可以用,它自

動幫我們配置了非常多的東西,我們拿來即用,特性:約定大於配置!

隨著公司體系越來越大,使用者越來越多

微服務架構—>新架構

​ 模組化,功能化!

​ 使用者,支付,簽到,娛樂…;

​ 人多餘多一臺伺服器解決不了就再增加一臺伺服器! --橫向擴充套件

​ 假設A伺服器佔用98%資源B伺服器只佔用了10%.–負載均衡;

​ 將原來的整體項,分成模組化,使用者就是一個單獨的專案,簽到也是一個單獨的專案,專案和專案之前需要通訊,如何通訊

​ 使用者非常多而到十分少給使用者多一點伺服器,給簽到少一點伺服器

微服務架構問題?

分散式架構會遇到的四個核心題?

  1. 這麼多服務,客戶端該如何去訪?
  2. 這麼多服務,服務之間如何進行通訊?
  3. 這麼多服務,如何治理呢?
  4. 服務掛了,怎麼辦?

解決方案:SpringCloud

Springcloud是一套生態,就是來解決以上分散式架構的4個問題

想使用Spring Clould ,必須要掌握 springBoot , 因為Springcloud是基於springBoot ;

  1. spring Cloud NetFlix ,出來了一套解決方案!一站式解決方案。可以直接使用

    1. Api閘道器 , zuul元件
    2. Feign --> Httpclient —> http通訊方式,同步並阻塞
    3. 服務註冊與發現 , Eureka
    4. 熔斷機制 , Hystrix
      2018年年底,NetFlix 宣佈無限期停止維護。生態不再維護,就會脫節。
  2. Apache Dubbo zookeeper

    1. API:沒有!要麼找第三方元件,要麼自己實現
    2. Dubbo 是一個高效能的基於ava實現的RPC通訊框架!2.6.x
    3. 服務註冊與發現 , zookeeper :動物管理者 ( Hadoop , Hive )
    4. 沒有:藉助了Hystrix
  3. SpringCloud Alibaba 一站式解決方案

總而言之,要解決的問題就是4個

  1. API閘道器 , 服務路由
  2. HTTP,RPC框架,非同步呼叫
  3. 服務註冊與發現,高可用
  4. 熔斷機制,服務降級

為什麼要解決這個問題?因為網路是不可靠的

相關文章