電動車發展腳步快,石油恐2040年前觸頂

Thomson Reuters報導,嘉能可(Glencore)董事長Tony Hayward 22日表示,電動車的快速進步意味著石油需求可能會在2040年以前觸頂,深海鑽油、加拿大油砂等高成本原油生產商恐將先被淘汰出局,擁有生產成本優勢的石油輸出國組織(OPEC)相對較不受衝擊。Hayward曾任英國石油公司(BP Plc)執行長。

嘉能可執行長Ivan Glasenberg表示,石油需求可能提前觸頂對嘉能可有利、因為旗下投資組合並沒有太多的原油。Glasenberg指出,如果電動車在2035年拿下90-95%的市占率,全球年度銅需求量可望較目前的2,300萬噸呈現倍增。德國總理梅克爾(Angela Merkel)22日指出,鋰電池技術已經進步到可以讓電動車擁有1千公里的續航力、遠高於目前的200-300公里,德國必須大舉投資以確保產業繼續保有優勢。

戴姆勒(Daimler AG)董事長Deiter Zetsche 22日表示,預估到2022年旗下將有超過10款的純電動轎車系列。戴姆勒旗下全資子公司ACCUMOTIVE 22日在德國卡門茨(Kamenz)為第二座電池工廠舉行奠基儀式、邀請梅克爾出席。這座工廠耗資5億歐元、預計在2018年年中正式營運。

英國金融時報去年8月報導,麥格理集團全球能源策略師Vikas Dwivedi指出,沙烏地阿拉伯對電動車的長期發展存有戒心,這可能就是它為何宣布將讓沙烏地阿拉伯國家石油公司(Saudi Aramco)初次公開發行(IPO)的原因之一。

通用汽車(General Motors)Chevrolet Bolt電動車續航力達238英里(383公里)、建議零售價37,495美元起(註:最多可取得7,500美元的聯邦折抵稅額、扣除後入手價相當於29,995美元)。美聯社報導,IHS Markit汽車業分析師Stephanie Brinley指出,Bolt續航力遠高於美國平均來回通勤距離(40英里),但有時人們回家後可能忘了或沒有足夠時間進行充電,這是電動車主得多加費心的地方。

(本文內容由授權使用。圖片出處:public domain CC0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

【Spring註解驅動開發】如何使用@Bean註解指定初始化和銷毀的方法?看這一篇就夠了!!

寫在前面

在【String註解驅動開發專題】中,前面的文章我們主要講了有關於如何向Spring容器中註冊bean的知識,大家可以到【String註解驅動開發專題】中系統學習。接下來,我們繼續肝Spring,只不過從本篇文章開始,我們就進入Spring容器中有關Bean的生命周期的學習。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

Bean的生命周期

通常意義上講的bean的名稱周期,指的是bean從創建到初始化,經過一系列的流程,最終銷毀的過程。只不過,在Spring中,bean的生命周期是由Spring容器來管理的。在Spring中,我們可以自己來指定bean的初始化和銷毀的方法。當我們指定了bean的初始化和銷毀方法時,當容器在bean進行到當前生命周期的階段時,會自動調用我們自定義的初始化和銷毀方法。

如何定義初始化和銷毀方法?

我們已經知道了由Spring管理bean的生命周期時,我們可以指定bean的初始化和銷毀方法,那具體該如何定義這些初始化和銷毀方法呢?接下來,我們就介紹第一種定義初始化和銷毀方法的方式: 通過@Bean註解指定初始化和銷毀方法。

如果是使用XML文件的方式配置bean的話,可以在 標籤中指定bean的初始化和銷毀方法,如下所示。

<bean id = "person" class="io.mykit.spring.plugins.register.bean.Person" init-method="init" destroy-method="destroy">
    <property name="name" value="binghe"></property>
    <property name="age" value="18"></property>
</bean>

這裏,需要注意的是,在我們寫的Person類中,需要存在init()方法和destroy()方法。而且Spring中規定,這裏的init()方法和destroy()方法必須是無參方法,但可以拋異常。

如果我們使用註解的方式,該如何實現指定bean的初始化和銷毀方法呢?接下來,我們就一起來搞定它!!

首先,創建一個名稱為Student的類,這個類的實現比較簡單,如下所示。

package io.mykit.spring.plugins.register.bean;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試bean的初始化和銷毀方法
 */
public class Student {
    
    public Student(){
        System.out.println("Student類的構造方法");
    }

    public void init(){
        System.out.println("初始化Student對象");
    }

    public void destroy(){
        System.out.println("銷毀Student對象");
    }
}

接下來,我們將Student類對象通過註解的方式註冊到Spring容器中,具體的做法就是新建一個LifeCircleConfig類作為Spring的配置類,將Student類對象通過LifeCircleConfig類註冊到Spring容器中,LifeCircleConfig類的代碼如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.plugins.register.bean.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description Bean的生命周期
 */
@Configuration
public class LifeCircleConfig {
    @Bean
    public Student student(){
        return new Student();
    }
}

接下來,我們就新建一個BeanLifeCircleTest類來測試容器中的Student對象,BeanLifeCircleTest類的部分代碼如下所示。

package io.mykit.spring.test;

import io.mykit.spring.plugins.register.config.LifeCircleConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試bean的生命周期
 */
public class BeanLifeCircleTest {

    @Test
    public void testBeanLifeCircle01(){
        //創建IOC容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LifeCircleConfig.class);
        System.out.println("容器創建完成...");
    }
}

在前面的文章中,我們說過:對於單實例bean對象來說,在Spring容器創建完成后,就會對單實例bean進行實例化。那麼,我們先來運行下BeanLifeCircleTest類中的testBeanLifeCircle01()方法,輸出的結果信息如下所示。

Student類的構造方法
容器創建完成...

可以看到,在Spring容器創建完成時,自動調用單實例bean的構造方法,對單實例bean進行了實例化操作。

總之:對於單實例bean來說,在Spring容器啟動的時候創建對象;對於多實例bean來說,在每次獲取bean的時候創建對象。

現在,我們在Student類中指定了init()方法和destroy()方法,那麼,如何讓Spring容器知道Student類中的init()方法是用來執行對象的初始化操作,而destroy()方法是用來執行對象的銷毀操作呢?如果是使用XML文件配置的話,我們可以使用如下配置來實現。

<bean id="student" class="io.mykit.spring.plugins.register.bean.Student" init-method="init" destroy-method="destroy"></bean>

如果我們在@Bean註解中該如何實現呢?其實就更簡單了,我們來看下@Bean註解的源碼,如下所示。

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {

	@AliasFor("name")
	String[] value() default {};

	@AliasFor("value")
	String[] name() default {};

	@Deprecated
	Autowire autowire() default Autowire.NO;

	boolean autowireCandidate() default true;

	String initMethod() default "";

	String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;

}

看到@Bean註解的源碼,相信小夥伴們會有種豁然開朗的感覺:沒錯,就是使用@Bean註解的initMethod屬性和destroyMethod屬性來指定bean的初始化方法和銷毀方法。

所以,我們在LifeCircleConfig類中的@Bean註解中指定initMethod屬性和destroyMethod屬性,如下所示。

@Bean(initMethod = "init", destroyMethod = "destroy")
public Student student(){
    return new Student();
}

此時,我們再來運行BeanLifeCircleTest類中的testBeanLifeCircle01()方法,輸出的結果信息如下所示。

Student類的構造方法
初始化Student對象
容器創建完成...

從輸出結果可以看出,在Spring容器中,先是調用了Student類的構造方法來創建Student對象,接下來調用了Student對象的init()方法來進行初始化。

那小夥伴們可能會問,運行上面的代碼沒有打印出bean的銷毀方法中的信息啊,那什麼時候執行bean的銷毀方法呢? 這個問題問的很好, bean的銷毀方法是在容器關閉的時候調用的。

接下來,我們在BeanLifeCircleTest類中的testBeanLifeCircle01()方法中,添加關閉容器的代碼,如下所示。

@Test
public void testBeanLifeCircle01(){
    //創建IOC容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LifeCircleConfig.class);
    System.out.println("容器創建完成...");
    context.close();
}

我們再來運行BeanLifeCircleTest類中的testBeanLifeCircle01()方法,輸出的結果信息如下所示。

Student類的構造方法
初始化Student對象
容器創建完成...
銷毀Student對象

可以看到,此時輸出了對象的銷毀方法中的信息,說明執行了對象的銷毀方法。

指定初始化和銷毀方法的使用場景

一個典型的使用場景就是對於數據源的管理。例如,在配置數據源時,在初始化的時候,對很多的數據源的屬性進行賦值操作;在銷毀的時候,我們需要對數據源的連接等信息進行關閉和清理。此時,我們就可以在自定義的初始化和銷毀方法中來做這些事情!

初始化和銷毀方法調用的時機

  • bean對象的初始化方法調用的時機:對象創建完成,如果對象中存在一些屬性,並且這些屬性也都賦值好之後,會調用bean的初始化方法。對於單實例bean來說,在Spring容器創建完成后,Spring容器會自動調用bean的初始化和銷毀方法;對於單實例bean來說,在每次獲取bean對象的時候,調用bean的初始化和銷毀方法。
  • bean對象的銷毀方法調用的時機:對於單實例bean來說,在容器關閉的時候,會調用bean的銷毀方法;對於多實例bean來說,Spring容器不會管理這個bean,也不會自動調用這個bean的銷毀方法。不過,小夥伴們可以手動調用多實例bean的銷毀方法。

前面,我們已經說了單實例bean的初始化和銷毀方法。接下來,我們來說下多實例bean的初始化和銷毀方法。我們將Student對象變成多實例bean來驗證下。接下來,我們在LifeCircleConfig類的student()方法上通過@Scope註解將Student對象設置成多實例bean,如下所示。

@Scope("prototype")
@Bean(initMethod = "init", destroyMethod = "destroy")
public Student student(){
    return new Student();
}

接下來,我們再來運行BeanLifeCircleTest類中的testBeanLifeCircle01()方法,輸出的結果信息如下所示。

容器創建完成...

可以看到,當我們將Student對象設置成多實例bean,並且沒有獲取bean實例對象時,Spring容器並沒有執行bean的構造方法、初始化方法和銷毀方法。

說到這,我們就在BeanLifeCircleTest類中的testBeanLifeCircle01()方法中添加一行獲取Student對象的代碼,如下所示。

@Test
public void testBeanLifeCircle01(){
    //創建IOC容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LifeCircleConfig.class);
    System.out.println("容器創建完成...");
    context.getBean(Student.class);
    context.close();
}

此時,我們再來運行BeanLifeCircleTest類中的testBeanLifeCircle01()方法,輸出的結果信息如下所示。

容器創建完成...
Student類的構造方法
初始化Student對象

可以看到,此時,結果信息中輸出了構造方法和初始化方法中的信息。但是當容器關閉時,並沒有輸出bean的銷毀方法中的信息。

這是因為 將bean設置成多實例時,Spring不會自動調用bean對象的銷毀方法。至於多實例bean對象何時銷毀,那就是程序員自己的事情了!!Spring容器不再管理多實例bean。

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

計算機網絡之應用層

應用層協議

應用層協議 (application-layer protocol) 定義了運行在不同端系統上的應用程序進程如何相互傳遞報文,特別是應用層協議定義了:

  • 交換的報文類型,例如請求報文和響應報文
  • 各種報文類型的語法,如報文中的各個字段及這些字段是如何描述的
  • 字段的語義,即這些字段中包含的信息的含義
  • 一個進程何時以及如何發送報文,對報文進行響應的規則。

  在本文中主要是講5種重要的應用: Web 、文件傳輸、电子郵件、目錄服務和P2P,我們首先討論 Web應用,不僅因為它是極為流行的應用,而且因為它的應用層協議 HTTP 相對比較簡單並且易於理解。討論完 Web ,我們簡要地討論FTP,因為它與 HTTP 形成了很好的對照 我們接下來討論电子郵件應用,這是因特網上最早的招人喜愛的應用程序。說电子郵件比 Web更複雜,是因為它使用了多個而不是一個應用層協議。在电子郵件之後,我們討論DNS它為因特網提供目錄服務,大多數用戶不直接與 DNS 打交道,而是通過其他的應用(包括Web 、文件傳輸和电子郵件)間接使用它,DNS 很好地說明了一種核心的網絡功能(網絡名字到網絡地址的轉換)是怎樣在因特網的應用層實現的。

HTTP概況

  Web 的應用層協議是超文本傳輸協議 (HyperText Transfer Protocol , HTTP) ,它是Web的核心,HTTP 由兩個程序實現:一個客戶程序和一個服務器程序。客戶程序和服務器程序運行在不同的端系統中,通過交換HTTP報文進行會話。 HTTP 定義了這些報文的結構以及客戶和服務器進行報文交換的方式。HTTP使用TCP作為它的支撐運輸協議(而不是在 UDP 上運行)。HTTP 客戶首先發起一個與服務器的TCP連接,一旦連接建立,該瀏覽器和服務器進程就可以通過套接字接口訪問TCP。

注意:服務器向客戶發送被請求的文件,而不存儲任何關於該客戶的狀態信息,假如某個特定的客戶在短短的幾秒鐘內兩次請求同一個對象,服務器並不會因為剛剛為該客戶提供了該對象就不再做出反應,而是重新發送該對象,就像服務器已經完全忘記不久之前所做過的事一樣,因為 HTTP 服務器並不保存關於客戶的任何信息,所以我們說HTTP是一個無狀態協議 (stateless protocol)

非持續連接和持續連接

  在許多因特網應用程序中,客戶和服務器在一個相當長的時間範圍內通信,其中客戶發出一系列請求並且服務器對每個請求進行響應 依據應用程序以及該應用程序的使用方式,這一系列請求可以以規則的間隔周期性地或者間斷性地一個接一個發出。當這種客戶-服務器的交互是經TCP進行的,應用程序的研製者就需要做一個重要決定,即每個請求/響應對是經一個單獨的 TCP 連接發送,還是所有的請求及其響應經相同的TCP連接發送呢?採用前一種方法,該應用程序被稱為使用非持續連接( non- persistent connection) ;採用后一種方法,該應用程序被稱為使用持續連接( persistent connection)。儘管 HTTP在其默認方式下使用持續連接, HTTP 客戶和服務器也能配置成使用非持續連接。

  非持續連接有一些缺點:首先,必須為每一個請求的對象建立和維護一個全新的連接。對於每個這樣的連接,在客戶和服務器巾都要分配 TCP 的緩衝區和保持 TCP 變量。這給 Web 服務器帶來了嚴重的負擔,因為一台 Web 服務器可能同時服務於數以百計不同的客戶的請求。第二,每一個對象經受兩倍 RTT 的交付時延,即一個RTT 用於創建 TCP ,另一個 RTT 用於請求和接收一個對象。在採用持續連接的情況下,服務器在發送響應后保持該 TCP 連接打開。在相同的客戶與服務器之間的後續請求和響應報文能夠通過相同的連接進行傳送

HTTP報文格式

  1. 請求報文
      GET /somedir/page.html HTTP/l.l
      Host: www.someschool.edu
      Connection: close
      User-agent: Mozilla/5.0
      Accept-language: fr

  我們看到該報文由5行組成,每行由一個回車和換行符結束。最後一行后再附加一個回車換行。雖然這個特定的報文僅有5行,但一個請求報文能夠具有更多的行或者至少為一行。HTTP 請求報文的第一行叫做請求行 (request line) ,其後繼的行叫做首部行( headerline)。請求行有3個字段:方法字段、URL字段和HTTP版本字段。方法宇段可以取幾種不同的值,包括 GET、POST、HEAD、PUT和DELETE。絕大部分的 HTTP 請求報文使用GET方法,當瀏覽器請求一個對象時,使用 GET 方法,在 URL 字段帶有請求對象的標識。在本例中,該瀏覽器正在請求對象/somedirl page. html 其版本字段是自解釋的;在本例中,瀏覽器實現的是 HTTP/ l. 版本。

  現在我們看看本例的首部行:首部行 Host: www. someschool. edu 指明了對象所在的主機。你也許認為該首部行是不必要的,因為在該主機中已經有一條 TCP 連接存在了,但是,該首部行提供的信息是 Web 代理高速緩存所要求的,通過包含 Connection: close 首部行,該瀏覽器告訴服務器不希望麻煩地使用持續連接,它要求服務器在發送完被請求的對象后就關閉這條連接。User- agent: 首部行用來指明用戶代理,即向服務器發送請求的瀏覽器的類型。這裏瀏覽器類型是 Mozilla/5. 0,即Firefox 瀏覽器,這個首部行是有用的,因為服務器可以有效地為不同類型的用戶代理,實際發送相同對象的不同版本 (每個版本都由相同的URL尋址)。最後Accept-language:首部行表示用戶想得到該對象的法語版本(如果服務器中有這樣的對象的話);否則,服務器應當發送它的默認版本 Accept -language:首部行僅是 HTTP 中可用的眾多內容協商首部之一。

  HEAD方法類似於GET方法,當服務器收到使用HEAD方法的請求時,將會用一個HTTP報文進行響應,但是並不返回請求對象,應用程序開發者常用HEAD方法進行調試,跟蹤PUT方法常與Web發行工具聯合使用,它允許用戶上傳對象到指定的 Web 服務器上指定的路徑(目錄)。PUT方法也被那些需要向 Web 服務器上傳對象的應用程序使用。DELETE 方法允許用戶或者應用程序刪除 Web 服務器上的對象

  1. HTTP響應報文
    下面我們提供了一條典型的HTTP響應報文 該響應報文可以是對剛剛討論的例子中請求報文的響應:
HTTP/ 1. 1 200 OK
Connection: close 
Date: Tue , 09 Aug 2011 15:44:04 GMT
Server: Apache/2.2.3 (CentOS)
Last-Modified: Tue , 09 Aug 2011 15:11:03 GMT
Content-Length: 6821
Content-Type: text/html 

(data data data data data ...) 

  我們仔細看一下這個響應報文 它有三個部分:一個初始狀態行 (sLatus line) ,6個首部行 (header 1ine) ,然後是實體體 (enLity body) 實體體部分是報文的主要部分,即它包含了所請求的對象本身(表示為 dala dala data data …)。 狀態行有3個字段:協議版本字段、狀態碼和相應狀態信息。在這個例子中,狀態行指示服務器正在使用 HTTP/l.1,並且一切正常(即服務器已經找到並正在發送所請求的對象)。

  我們現在來看看首部行。服務器用 Connection: close 首部行告訴客戶,發送完報文後將關閉該 TCP 連接。Date: 首部行指示服務器產生併發送該響應報文的日期和時間。值得一提的是,這個時間不是指對象創建或者最後修改的時間;而是服務器從它的文件系統中檢索到該對象,插入到響應報文,併發送該響應報文的時間。Server: 首部行指示該報文是由一台 Apache Web 服務器產生的,它類似於 HTTP 請求報文中的 User- agent 首部行。Last- Modified: 首部行指示了對象創建或者最後修改的日期和時間。Lasl-Modified: 首部行對既可能在本地客戶也可能在網絡緩存服務器上的對象緩存來說非常重要。下面將會介紹緩存服務器(也叫代理服務器)。Content- Length: 首部行指示了被發送對象中的字節數;Conlent- Type: 首部行指示了實體體中的對象是 HTML 文本 (該對象類型應該正式地由 Conlent- Type: 首部行而不是用文件擴展名來指示。

Web 緩存

  Web緩存器 (Web cache)也叫代理服務器 (proxy server),它是能夠代表初始 Web 服務器來滿足 HTTP 請求的網絡實體。Web 緩存器有自己的磁盤存儲空間,並在存儲空間中保存最近請求過的對象的副本 如圖 2-11 所示,可以配置用戶的瀏覽器,使得用戶的所有 HTTP請求首先指向 Web 緩存器。一旦某瀏覽器被配置,每個對某對象的瀏覽器請求首先被定向到該 Web 緩存器。舉例來說,假設瀏覽器正在請求對象 http://www.someschool. edu/ campus.giI.將會發生如下情況:

  • 瀏覽器建立一個到Web緩存器的TCP連接,並向 Web 緩存器中的對象發送一個HTTP請求
  • Web 緩存器進行檢查,看看本地是否存儲了該對象副本 如果有, Web 緩存器就向客戶瀏覽器用 HTTP 響應報文返回該對象
  • 如果 Web 緩存器中沒有該對象,它就打開一個與該對象的初始服務器(如www. someschool. edu),TCP 連接 Web 緩存器則在這個緩存器到服務器的TCP連接上發送一個對該對象的 HTTP 請求,在收到該請求后,初始服務器向該 Web緩存器發送具有該對象的 HTTP響應
  • 當 Web 緩存器接收到該對象時,它在本地存儲空間存儲一份副本,並向客戶的瀏覽器用盯TP 響應報文發送該副本(通過現有的客戶瀏覽器和 Web 緩存器之間的TCP連接)

DHCP協議

  DHCP(Dynamic Host Configuration Protocol: 動態主機設置協議,DHCP是一個局域網協議,DHCP是應用UDP協議的應用層協議。使用UDP協議工作,主要有兩個用途:給內部網絡或網絡服務供應商自動分配IP地址,給用戶或者內部網絡管理員作為對所有計算機作中央管理的手段。
對於一個 臨時設備,是如何知道自己的IP地址的?
  DHCP服務器監聽默認端口:67,主機使用UDP協議廣播DHCP發現報文,DHCP服務器發出DHCP提供報文,主機向DHCP服務器發出DHCP請求報文,DHCP服務器回應並提供IP地址。

HTTPS

  由於HTTP是明文傳輸的,則HTTPS(Secure)是安全的HTTP協議,默認端口為443,http(s): //<主機>:<端口>/<路徑>

  • A、B是擁有一定數學關係的一組秘鑰
    • 私鑰:私鑰自己使用,不對外公開
    • 公鑰:公鑰給大家使用,對外公開

      使用公鑰加密,使用私鑰解密。

  • 数字證書是可信任組織頒發給特定對象的認證
證書格式、版本號
證書序列號
簽名算法
有效期
對象名稱
對象公開秘鑰
  • SSL(Secure Sockets Layer: 安全套接層)
    • 數據安全和數據完整
    • 對傳輸層數據進行加密後傳輸

HTTPS 原理

  1. 客戶端將它所支持的算法列表和一個用作產生密鑰的隨機數發送給服務器 ;
  2. 服務器從算法列表中選擇一種加密算法,並將它和一份包含服務器公用密鑰的證書發送給客戶端;該證書還包含了用於認證目的的服務器標識,服務器同時還提供了一個用作產生密鑰的隨機數;
  3. 客戶端對服務器的證書進行驗證(有關驗證證書,可以參考数字簽名),並抽取服務器的公用密鑰;然後,再產生一個稱作 pre_master_secret 的隨機密碼串,並使用服務器的公用密鑰對其進行加密(參考非對稱加 / 解密),並將加密后的信息發送給服務器 ;
  4. 客戶端與服務器端根據 pre_master_secret 以及客戶端與服務器的隨機數值獨立計算出加密和 MAC密鑰(參考 DH密鑰交換算法);
  5. 客戶端將所有握手消息的 MAC 值發送給服務器;
  6. 服務器將所有握手消息的 MAC 值發送給客戶端

文件傳輸協議:FTP

  在一個典型的FTP會話中,用戶坐在一台主機(本地主機)前面,向一台遠程主機傳輸(或接收來自遠程主機的)文件 為使用戶能訪問它的遠程賬戶,用戶必須提供一個用戶標識和口令 在提供了這種授權信息后,用戶就能從本地文件系統向遠程主機文件系統傳送文件,反之亦然 如圖 2-14 所示,用戶通過一個FTP用戶代理與FTP交互。該用戶首先提供遠程主機的主機名,使本地主機的FTP客戶進程建立一個到遠程主機FTP服務器進程的 TCP 連接。該用戶接着提供用戶標識和口令,作為 FTP 命令的一部分在該 TCP連接上傳送。一旦該服務器向該用戶授權,用戶可以將存放在本地文件系統中的一個或者多個文件複製到遠程文件系統(反之亦然)。

  HTTP和FTP 都是文件傳輸協議,並且有很多共同的特點,例如,它們都運行在 TCP上,然而,這兩個應用層協議也有一些重要的區別 其中最顯著的就是FTP 使用了兩個并行的 TCP 連接來傳輸文件,一個是控制連接 (control connection) ,一個是數據連接( data connection) 。控制連接用於在兩主機之間傳輸控制信息,如用戶標識、口令、改變遠程目錄的命令以及”存放 (put) “和”獲取 (get)”文件的命令。數據連接用於實際發送一個文件,因為FTP協議使用一個獨立的控制連接,所以我們也稱FTP的控制信息是帶外(out-of-band)傳送的。如你所知,HTTP協議是在傳輸文件的同一個 TCP 連接中發送請求和響應首部行的 因此,HTTP也可以說是帶內 (in-band) 發送控制信息的。FTP協議的控制連接和數據連接如圖二 15 所示:

  當用戶主機與遠程主機開始一個FTP會話時,FTP的客戶(用戶)端首先在服務器21號端口與服務器(遠程主機)端發起一個用於控制的 TCP 連接。FTP的客戶端也通過該控制連接發送用戶的標識和口令,發送改變遠程目錄的命令,當FTP的服務器端從該連接上收到一個文件傳輸的命令后(無論是向還是來自遠程主機) ,就發起一個到客戶端的 TCP 數據連接 FTP 在該數據連接上準確地傳送一個文件,然後關閉該連接。在同一個會話期間,如果用戶還需要傳輸另一個文件,FTP則打開另一個數據連接,因而對FTP傳輸而言,控制連接貫穿了整個用戶會話期間,但是對會話中的每一次文件傳輸都需要建立一個新的數據連接(即數據連接是非持續的)。

  FTP服務器必須在整個會話期間保留用戶的狀態(state) 特別是,服務器必須把特定的用戶賬戶與控制連接聯繫起來,隨着用戶在遠程目錄樹上徘徊,服務器必須追蹤用戶在遠程目錄樹上的當前位置,對每個進行中的用戶會話的狀態信息進行追蹤,大大限制了FTP同時維持的會話總數。而另一方面,前面講過 HTTP 是無狀態的,即它不必對任何用戶狀態進行追蹤。

因特網中的电子郵件

  圖2-16 給出了因特網电子郵件系統的總體情況,從該圖中我們可以看到它有3個主要組成部分: 用戶代理( user agenl) 、郵件服務器 (mail server) 簡單郵件傳輸協議(Simple Mai] Transfer Prolocol , SMTP) 。

SMTP 是因特網电子郵件中主要的應用層協議,它使用 TCP 可靠數據傳輸服務,從發送方的郵件服務器向接收方的郵件服務器發送郵件,像大多數應用層協議一樣, SMTP有兩個部分:運行在發送方郵件服務器的客戶端和運行在接收方郵件服務器的服務器端,每台郵件服務器上既運行 SMTP 的客戶端也運行 SMTP 的服務器端 。當一個郵件服務器向其他郵件服務器發送郵件時,它就表現為 SMTP 的客戶;當郵件服務器從其他郵件服務器上接收郵件時,它就表現為SMTP的服務器。目前有一些流行的郵件訪問協議,包括第三版的郵局協議 (POSl OfficeProtocol-Version 3 , POP3)、因特網郵件訪問協議 (Intemet Mail Access Protocol , IMAP)以及 HTTP。

總結:應用層為操作系統或網絡應用程序提供訪問網絡服務的接口。數據傳輸基本單位為報文;包含的主要協議:FTP(文件傳送協議)、Telnet(遠程登錄協議)、DNS(域名解析協議)、SMTP(郵件傳送協議),POP3協議(郵局協議),HTTP協議(Hyper Text Transfer Protocol)。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Newtonsoft 六個超簡單又實用的特性,值得一試 【上篇】

一:講故事

看完官方文檔,閱讀了一些 Newtonsoft 源碼,對它有了新的認識,先總結 六個超經典又實用的特性,同大家一起分享,廢話不多說,快來一起看看吧~~~

二:特性分析

1. 代碼格式化

如果你直接使用 JsonConvert.SerializeObject的話,默認情況下所有的json是擠壓在一塊的,特別不方便閱讀,如下所示:


        static void Main(string[] args)
        {
            var reportModel = new ReportModel()
            {
                ProductName = "法式小眾設計感長裙氣質顯瘦純白色仙女連衣裙",
                TotalPayment = 100,
                TotalCustomerCount = 2,
                TotalProductCount = 333
            };

            var json = JsonConvert.SerializeObject(reportModel);

            System.Console.WriteLine(json);
        }
    }

    public class ReportModel
    {
        public string ProductName { get; set; }
        public int TotalCustomerCount { get; set; }
        public decimal TotalPayment { get; set; }
        public int TotalProductCount { get; set; }
    }

那怎麼辦呢? JsonConvert中提供了一個 Formatting.Indented 用來格式化json,這樣在 debug 的過程中就非常友好,改造如下:

2. 踢掉沒有被賦值的字段

如果你寫過給 App 提供數據的後端服務,我相信你對手機流量這個詞特別敏感,往往一個 Model 上有十幾個字段,但需要傳給 App 可能就 三四個字段,這就造成了巨大的流量浪費,如下圖:


        static void Main(string[] args)
        {
            var reportModel = new ReportModel()
            {
                ProductName = "法式小眾設計感長裙氣質顯瘦純白色仙女連衣裙",
                TotalPayment = 100
            };

            var json = JsonConvert.SerializeObject(reportModel, Formatting.Indented);

            System.Console.WriteLine(json);
        }

從圖中可以看到,TotalCustomerCountTotalProductCount 這兩個字段就沒必要了,Netnewsoft 中提供了 DefaultValueHandling.Ignore 剔除默認值的枚舉,太實用了,改造如下:


            var json = JsonConvert.SerializeObject(reportModel, Formatting.Indented,
                                                   new JsonSerializerSettings
                                                   {
                                                       DefaultValueHandling = DefaultValueHandling.Ignore
                                                   });

3. 兼容其他語言的 駝峰,蛇形命名法

每一套編程語言都有各自偏好的命名法,比如 js 中都喜歡採用 駝峰命名法,在 mysql 中我見過最多的 蛇形命名法,而我們在 C# 中序列化的屬性一般都是大寫字母開頭,比如你看到的 特性二 中的字段,那這裏就存在問題了,有沒有辦法兼容一下,給 js 就用 駝峰,給 mysql 就用 蛇形,這樣顯得對別人友好一些,不是嘛,接下來看看怎麼改造。

  • 駝峰命名 CamelCasePropertyNamesContractResolver

            var json = JsonConvert.SerializeObject(reportModel, Formatting.Indented,
                                                   new JsonSerializerSettings
                                                   {
                                                       ContractResolver = new CamelCasePropertyNamesContractResolver()
                                                   });

  • 蛇形命名 SnakeCaseNamingStrategy

            var json = JsonConvert.SerializeObject(reportModel, Formatting.Indented,
                                                   new JsonSerializerSettings
                                                   {
                                                       ContractResolver = new DefaultContractResolver()
                                                       {
                                                           NamingStrategy = new SnakeCaseNamingStrategy()
                                                       }
                                                   });

4. 自定義屬性的名字

如果你和第三方系統進行過對接開發,通常都會遇到這個問題,就拿 OpenTaobao 來說,我的Model總不能按照它文檔這樣定義吧,而且字段名稱也不可能做到完全一致,如下圖:

所以這裏面必然要存在一個 Mapping 的過程,這就可以用 JsonProperty -> propertyName 幫你搞定,為了方便演示,我還是用 reportModel 吧。


    static void Main(string[] args)
    {
        var json = "{'title':'法式小眾設計感長裙氣質顯瘦純白色仙女連衣裙','customercount':1000,'totalpayment':100.0,'productcount':10000}";

        var reportModel = JsonConvert.DeserializeObject<ReportModel>(json);
    }

    public class ReportModel
    {
        [JsonProperty("title")] public string ProductName { get; set; }
        [JsonProperty("customercount")] public int TotalCustomerCount { get; set; }
        [JsonProperty("totalpayment")] public decimal TotalPayment { get; set; }
        [JsonProperty("productcount")] public int TotalProductCount { get; set; }
    }


5. 對字段的 正向剔除 和 反向剔除

可能有些朋友對這兩個概念不是特別了解,這裏我僅显示 Model 中的 ProductName 為例講解一下:

  • 正向剔除: 默認所有都显示,手工踢掉不显示的,使用 MemberSerialization.OptOut 配合 JsonIgnore

 		static void Main(string[] args)
        {
            var reportModel = new ReportModel()
            {
                ProductName = "法式小眾設計感長裙氣質顯瘦純白色仙女連衣裙",
                TotalPayment = 100
            };

            var json = JsonConvert.SerializeObject(reportModel, Formatting.Indented);

            System.Console.WriteLine(json);
        }

    [JsonObject(MemberSerialization.OptOut)]
    public class ReportModel
    {
        public string ProductName { get; set; }
        [JsonIgnore] public int TotalCustomerCount { get; set; }
        [JsonIgnore] public decimal TotalPayment { get; set; }
        [JsonIgnore] public int TotalProductCount { get; set; }
    }

  • 反向剔除: 默認都不显示,手工指定要显示的,使用 MemberSerialization.OptIn 配合 JsonProperty
       
    [JsonObject(MemberSerialization.OptIn)]
    public class ReportModel
    {
        [JsonProperty] public string ProductName { get; set; }
        public int TotalCustomerCount { get; set; }
        public decimal TotalPayment { get; set; }
        public int TotalProductCount { get; set; }
    }

6. 多個json 合併到 一個Model

這個特性當初打破了我對 Newtonsoft 的認知觀,不知道您呢? 通常我們都會認為 一個 json 對應一個 model,一個 model 對應一個 json,居然還可以多個 json 對應一個 model 的情況,這就有意思了,場景大家可以自己想一想哈,這裏使用 PopulateObject 方法就可以輕鬆幫你搞定,接下來看看怎麼寫這個代碼:


        static void Main(string[] args)
        {
            var json1 = "{'ProductName':'法式小眾設計感長裙氣質顯瘦純白色仙女連衣裙'}";
            var json2 = "{'TotalCustomerCount':1000,'TotalPayment':100.0,'TotalProductCount':10000}";

            var reportModel = new ReportModel();

            JsonConvert.PopulateObject(json1, reportModel);
            JsonConvert.PopulateObject(json2, reportModel);
        }

是不是有點意思

三:總結

為了怕影響閱讀體驗,這一篇就先總結六個供大家欣賞,Newtonsoft 這玩意確實非常強大,太多的東西需要去挖掘,希望本篇對你有幫助,謝謝。

如您有更多問題與我互動,掃描下方進來吧~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

Nginx 如何自定義變量?

之前的兩篇文章 Nginx 變量介紹以及利用 Nginx 變量做防盜鏈 講的是 Nginx 有哪些變量以及一個常見的應用。那麼如此靈活的 Nginx 怎麼能不支持自定義變量呢,今天的文章就來說一下自定義變量的幾個模塊以及 Nginx 的 keepalive 特性。

通過映射新變量提供更多的可能性:map 模塊

  • 功能:基於已有變量,使用類似 switch {case: … default: …} 的語法創建新變量,為其他基於變量值實現功能的模塊提供更多的可能性
  • 模塊:ngx_http_map_module 默認編譯進 Nginx,通過 --without-http_map_module 禁用

指令

Syntax: map string $variable { ... }
Default: —
Context: http

Syntax: map_hash_bucket_size size;
Default: map_hash_bucket_size 32|64|128; 
Context: http

Syntax: map_hash_max_size size;
Default: map_hash_max_size 2048; 
Context: http

我們主要看一下 map string $variable { ... } 這個指令。所謂類似 switch case 的語法是指,string 的值可以有多個,可以根據 string 值的不同,來給 $variable 賦不同的值。

規則

  • 已有變量:string 需要是已有的變量,可以分為下面這三種情況
    • 字符串
    • 一個或者多個變量
    • 變量與字符串的組合
  • case 規則:{…} 內的匹配規則需要遵循以下規則,尤其是要注意當使用 hostnames 指令時,與 server name 的匹配規則是一致的,可以看之前的文章 Nginx 的配置指令
    • 字符串嚴格匹配
    • 使用 hostnames 指令,可以對域名使用前綴 * 泛域名匹配
    • ~ 和 ~* 正則表達式匹配,後者忽略大小寫
  • default 規則
    • 沒有匹配到任何規則時,使用 default
    • 確實 default 時,返回空字符串給新變量
  • 其他
    • 使用 include 語法提升可讀性
    • 使用 volatile 禁止變量值緩存

大家看到上面這些規則可能都有些暈,廢話不多說,直接來看一個實戰配置文件就懂了。

實戰

這裏我們有一個配置文件,在這個文件裏面我們定義了兩個 map 塊,分別配置了兩個變量,$name 和 $mobile,$name 中包含 hostnames 指令。

map $http_host $name {
    hostnames;

    default       0;

    ~map\.ziyang\w+\.org.cn 1;
    *.ziyang.org.cn   2;
    map.ziyang.com   3;
    map.ziyang.*    4;
}

map $http_user_agent $mobile {
    default       0;
    "~Opera Mini" 1;
}

server {
	listen 10001;
	default_type text/plain;
	location /{
		return 200 '$name:$mobile\n';
	}
}

下面看一下實際的請求:

  test_nginx curl -H "Host: map.ziyang.org.cn" 127.0.0.1:10001
2:0

為什麼會返回 2:0 呢?我們來看一下匹配順序。

map.ziyang.org.cn 有三個規則可以生效,分別是:

  • ~map.ziyang\w+.org.cn 1;
  • *.ziyang.org.cn 2;
  • map.ziyang.* 4;

而泛域名是優先於正則表達式的,* 在前的泛域名優先於在後面的泛域名,因此最終匹配到的就是:

  • *.ziyang.org.cn 2;

而第二個變量 $mobile 自然走的是 default 規則,不用多說。

這就是 map 模塊的作用,大家可以多嘗試一下。

下面再來看一個與 map 模塊有點類似的 split_clients 模塊,這個模塊也是通過生成新的變量來完成 AB 測試功能的,它可以按照變量的值,按照百分比的方式,生成新的變量。

實現 AB 測試:split_clients 模塊

  • 功能:基於已有變量創建新變量,為其他 AB 測試提供更多的可能性
    • 對已有變量的值執行 MurmurHash2 算法,得到 32 位整形哈希数字,記為 hash
    • 32 位無符號整形的最大数字 2^32-1,記為 max
    • 哈希数字與最大数字相除,hash/max,可以得到百分比 percent
    • 配置指令中指示了各個百分比構成的範圍,如 0-1%,1%-5% 等,及範圍對應的值
    • 當 percent 落在哪個範圍里,新變量的值就對應着其後的參數
  • 模塊:ngx_http_split_clients_module,默認編譯進 Nginx,通過 --without-http_split_clients_module 禁用

規則

  • 已有變量
    • 字符串
    • 一個或者多個變量
    • 變量與字符串的組合
  • case 規則:
    • xx.xx%,支持小數點后 2 位,所有項的百分比相加不能超過 100%
    • *,由它匹配剩餘的百分比(100% 減去以上所有項相加的百分比)

指令

Syntax: split_clients string $variable { ... }
Default: —
Context: http

split_clients 的指令與 map 是非常相似的,可以看一下前面的介紹,這裏不再贅述了。

下面這個配置,來看下有沒有啥問題:

split_clients "${http_testcli}" $variant {
    0.51% .one;
    20.0% .two;
    50.5% .three;
    40% .four;
    * "";
}

細心的同學可能已經發現了,所有的百分比相加已經超過了 100%,所以 Nginx 直接會拋出一個錯誤,禁止執行。

  test_nginx ./sbin/nginx -s reload
nginx: [emerg] percent total is greater than 100% in /Users/mtdp/myproject/nginx/test_nginx/conf/example/17.map.conf:31

然後將 40% .four; 這一行給屏蔽掉再試試看:

  test_nginx curl -H "testcli: split_clients.ziyang.com" --resolve "split_clients.ziyang.com:80:127.0.0.1" http://split_clients.ziyang.com
ABtestfile.three

正常執行。

geo 模塊

geo 模塊與前面兩個模塊也很相似,不同之處在於,這個模塊是基於 IP 地址或者子網掩碼這樣的變量值來生成新的變量的。

  • 功能:根據 IP 地址創建新變量

  • 模塊: ngx_http_geo_module,默認編譯進 Nginx,通過 --without-http_geo_module 禁用

  • 指令

Syntax: geo [$address] $variable { ... }
Default: —
Context: http

規則

  • 如果 geo 指令后不輸入 $address,那麼默認使用 $remote_addr 變量作為 IP 地址

  • {} 內的指令匹配:優先最長匹配

    • 通過 IP 地址及子網掩碼的方式,定義 IP 範圍,當 IP 地址在範圍內時新變量使用其後的參數值

    • default 指定了當以上範圍都未匹配上時,新變量的默認值

    • 通過 proxy 指令指定可信地址(參考 realip 模塊),此時 remote_addr 的值為 X-Forwarded-For 頭部值中最後一個 IP 地址

    • proxy_recursive 允許循環地址搜索

    • include,優化可讀性

    • delete 刪除指定網絡

geo $country {
default ZZ;
#include conf/geo.conf;
#proxy 172.18.144.211;
127.0.0.0/24 US;
127.0.0.1/32 RU;
10.1.0.0/16 RU;
192.168.1.0/24 UK;
}


問題:以下命令執行時,變量 country 的值各為多少?(proxy 實際上為客戶端地址,這裏設置為本機的局域網地址即可,我這裡是 172.18.144.211)

curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.2’ geo.ziyang.com
curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.1’ geo.ziyang.com
curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.1,1.2.3.4’ geo.ziyang.com


結果如下:

```shell
  test_nginx curl -H 'X-Forwarded-For: 10.1.0.0,127.0.0.2' geo.ziyang.com
US
  test_nginx curl -H 'X-Forwarded-For: 10.1.0.0,127.0.0.1' geo.ziyang.com
RU
  test_nginx curl -H 'X-Forwarded-For: 10.1.0.0,127.0.0.1,1.2.3.4' geo.ziyang.com
ZZ

這裏可以看出來,匹配規則實際上是遵循最長匹配的規則的。

geoip 模塊

geoip 模塊可以根據 IP 地址生成對應的地址變量,用法與前面的也都類似,Nginx 是基於 MaxMind 數據庫來生成對應的地址的。

  • 功能:根據 IP 地址創建新變量
  • 模塊: ngx_http_geoip_module,默認未編譯進 Nginx,通過 --with-http_geoip_module 禁用

使用這個模塊是需要安裝 MaxMind 庫的,安裝步驟如下:

  • 安裝 MaxMind 里 geoip 的 C 開發庫(https://dev.maxmind.com/geoip/legacy/downloadable/ )
  • 編譯 Nginx 時帶上 --with-http_geoip_module 參數
  • 下載 MaxMind 中的二進制地址庫,這個地址庫是需要在指令中指定對應的地址的
  • 使用 geoip_country 或者 geoip_city 指令配置好 nginx.conf
  • 運行或者升級 Nginx

geoip_country 指令提供的變量

指令

Syntax: geoip_country file; # 指定國家類的地址文件
Default: —
Context: http

Syntax: geoip_proxy address | CIDR;
Default: —
Context: http

變量

  • $geoip_country_code:兩個字母的國家代碼,比如 CN 或者 US
  • $geoip_country_code3:三個字母的國家代碼,比如 CHN 或者 USA
  • $geoip_country_name:國家名稱,例如 “China”, “United States”

geoip_city 指令提供的變量

指令

Syntax: geoip_city file;
Default: —
Context: http

變量

  • $geoip_latitude:緯度
  • $geoip_longitude:經度
  • $geoip_city_continent_code:位於全球哪個洲,例如 EU 或 AS
  • 與 $geoip_country 指令生成的變量重疊
    • $geoip_country_code:兩個字母的國家代碼,比如 CN 或者 US
    • $geoip_country_code3:三個字母的國家代碼,比如 CHN 或者 USA
    • $geoip_country_name:國家名稱,例如 “China”, “United States”
  • $geoip_region:洲或者省的編碼,例如 02
  • $geoip_region_name:洲或者省的名稱,例如 Zhejiang 或者 Saint Petersburg
  • $geoip_city:城市名
  • $geoip_postal_code:郵編號
  • $geoip_area_code:僅美國使用的郵編號,例如 408
  • $geoip_dma_code:僅美國使用的 DMA 編號,例如 807

keepalive 模塊

前面說的都是 Nginx 的變量相關的內容,其實 Nginx 還有一個很具有特色的模塊,那就是 keepalive 模塊,由於內容不是很多,所以我就直接寫到這篇文章裏面了,單寫一篇顯得內容不夠哈。

這裏指的是 HTTP 的 keepalive,TCP 也有 keepalive,後面會說。

而且是對客戶端的 keepalive,不是對上游服務器的。

  • 功能:多個 HTTP 請求通過復用 TCP 連接,可以實現以下功能:

    • 減少握手次數
    • 通過減少併發連接數減少了服務器資源消耗
    • 降低 TCP 擁塞控制的影響,保證滑動窗口維持在一個最優的大小
  • Connection 頭部

    • close:表示請求處理完就關閉連接
    • keepalive:表示復用連接處理下一條請求
  • Keepalive 頭部:timeout=n,單位是秒,表示連接至少保持 n 秒

指令

對客戶端行為控制的指令:

Syntax: keepalive_disable none | browser ...;
Default: keepalive_disable msie6; 
Context: http, server, location

Syntax: keepalive_requests number;
Default: keepalive_requests 100; 
Context: http, server, location

Syntax: keepalive_timeout timeout [header_timeout];
Default: keepalive_timeout 75s; 
Context: http, server, location
  • keepalive_disable 設置為 none 表示對所有瀏覽器啟用 keepalive,msie6 表示在老版本 MSIE 上禁用 keepalive
  • keepalive_requests 設置允許保持 keepalive 的請求的數量
  • keepalive_timeout 表示超時時間

好了,關於 Nginx 的模塊介紹就已經全部介紹完了,有興趣的同學可以去翻我前面的系列文章。當然還有一部分重要的內容還沒有介紹,那就是關於 Nginx 的反向代理和負載均衡部分,這塊咱們單獨抽出來說,別著急,馬上乾貨就出來。

本文首發於我的個人博客:iziyang.github.io,所有配置文件我已經放在了 Nginx 配置文件,大家可以自取。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

使用SpringCloud Stream結合rabbitMQ實現消息消費失敗重發機制

前言:實際項目中經常遇到消息消費失敗了,要進行消息的重發。比如支付消息消費失敗后,要分不同時間段進行N次的消息重發提醒。

本文模擬場景

  1. 當金額少於100時,消息消費成功
  2. 當金額大於100,小於200時,會進行3次重發,第一次1秒;第二次2秒;第三次3秒。
  3. 當金額大於200時,消息消費失敗,會進行5次重發,第一次1秒;第二次2秒;第三次3秒;第四次4秒;第五次5秒。重試五次后,消息自動進入死信隊列,在死信隊列存活60秒后消失。

代碼實例

特別注意代碼與配置文件中的註釋,各個使用說明都已經詳細寫在配置文件中

pom包引入

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cloudstream</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR5</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- ①關鍵配置:引入stream-rabbit 依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <!-- ②關鍵配置:由於stream是基於spring-cloud的,所以這裏要引入 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

</project>

配置application.yml文件

注意各個配置的縮進格式,別搞錯了

server:
  port: 8081
spring:
  application:
    name: stream-demo
  #rabbitmq連接配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: admin
    password: 123456
  cloud:
    stream:
      bindings:
        #消息生產者,與DelayDemoTopic接口中的DELAY_DEMO_PRODUCER變量值一致
        delay-demo-producer:
          #①定義交換機名
          destination: demo-delay-queue
        #消息消費者,與DelayDemoTopic接口中的DELAY_DEMO_CONSUMER變量值一致
        delay-demo-consumer:
          #定義交換機名,與①一致,就可以使發送和消費都指向一個隊列
          destination: demo-delay-queue
          #分組,這個配置可以開啟消息持久化、可以解決在集群環境下重複消費的問題。
          #比如A、B兩台服務器集群,如果沒有這個配置,則A、B都能收到同樣的消息,如果有該配置則只有其中一台會收到消息
          group: delay-consumer-group
          consumer:
            #最大重試次數,默認為3。不使用默認的,這裏定義為1,由我們程序控制發送時間和次數
            maxAttempts: 1
      rabbit:
        bindings:
          #消息生產者,與DelayDemoTopic接口中的DELAY_DEMO_PRODUCER變量值一致
          delay-demo-producer:
            producer:
              #②申明為延遲隊列
              delayedExchange: true
          #消息消費者,與DelayDemoTopic接口中的DELAY_DEMO_CONSUMER變量值一致
          delay-demo-consumer:
            consumer:
              #申明為延遲隊列,與②的配置的成對出現的
              delayedExchange: true
              #開啟死信隊列
              autoBindDlq: true
              #死信隊列中消息的存活時間
              dlqTtl: 60000

定義隊列通道

  1. 定義通道
/**
 * 定義延遲消息通道
 */
public interface DelayDemoTopic {
    /**
     * 生產者,與yml文件配置對應
     */
    String DELAY_DEMO_PRODUCER = "delay-demo-producer";
    /**
     * 消費者,與yml文件配置對應
     */
    String DELAY_DEMO_CONSUMER = "delay-demo-consumer";

    /**
     * 定義消息消費者,在@StreamListener監聽消息的時候用到
     * @return
     */
    @Input(DELAY_DEMO_CONSUMER)
    SubscribableChannel delayDemoConsumer();

    /**
     * 定義消息發送者,在發送消息的時候用到
     * @return
     */
    @Output(DELAY_DEMO_PRODUCER)
    MessageChannel delayDemoProducer();
}
  1. 綁定通道
/**
 * 配置消息的binding
 *
 */
@EnableBinding(value = {DelayDemoTopic.class})
@Component
public class MessageConfig {

}

消息發送模擬

/**
 * 發送消息
 */
@RestController
public class SendMessageController {
    @Autowired
    DelayDemoTopic delayDemoTopic;

    @GetMapping("send")
    public Boolean sendMessage(BigDecimal money) throws JsonProcessingException {

        Message<BigDecimal> message = MessageBuilder.withPayload(money)
                //設置消息的延遲時間,首次發送,不設置延遲時間,直接發送
                .setHeader(DelayConstant.X_DELAY_HEADER,0)
                //設置消息已經重試的次數,首次發送,設置為0
                .setHeader(DelayConstant.X_RETRIES_HEADER,0)
                .build();
        return delayDemoTopic.delayDemoProducer().send(message);
    }
}

消息監聽處理

@Component
@Slf4j
public class DelayDemoTopicListener {
    @Autowired
    DelayDemoTopic delayDemoTopic;

    /**
     * 監聽延遲消息通道中的消息
     * @param message
     */
    @StreamListener(value = DelayDemoTopic.DELAY_DEMO_CONSUMER)
    public void listener(Message<BigDecimal> message) {
        //獲取重試次數
        int retries = (int)message.getHeaders().get(DelayConstant.X_RETRIES_HEADER);
        //獲取消息內容
        BigDecimal money = message.getPayload();
        try {
            String now = DateUtils.formatDate(new Date(),"yyyy-MM-dd HH:mm:ss");
            //模擬:如果金額大於200,則消息無法消費成功;金額如果大於100,則重試3次;如果金額小於100,直接消費成功
            if (money.compareTo(new BigDecimal(200)) == 1){
                throw new RuntimeException(now+":金額超出200,無法交易。");
            }else if (money.compareTo(new BigDecimal(100)) == 1 && retries <= 3) {
                if (retries == 0) {
                    throw new RuntimeException(now+":金額超出100,消費失敗,將進入重試。");
                }else {
                    throw new RuntimeException(now+":金額超出100,當前第" + retries + "次重試。");
                }
            }else {
                log.info("消息消費成功!");
            }
        }catch (Exception e) {
            log.error(e.getMessage());
            if (retries < DelayConstant.X_RETRIES_TOTAL){
                //將消息重新塞入隊列
                MessageBuilder<BigDecimal> messageBuilder = MessageBuilder.fromMessage(message)
                        //設置消息的延遲時間
                        .setHeader(DelayConstant.X_DELAY_HEADER,DelayConstant.ruleMap.get(retries + 1))
                        //設置消息已經重試的次數
                        .setHeader(DelayConstant.X_RETRIES_HEADER,retries + 1);
                Message<BigDecimal> reMessage = messageBuilder.build();
                //將消息重新發送到延遲隊列中
                delayDemoTopic.delayDemoProducer().send(reMessage);
            }else {
                //超過重試次數,做相關處理(比如保存數據庫等操作),如果拋出異常,則會自動進入死信隊列
                throw new RuntimeException("超過最大重試次數:" + DelayConstant.X_RETRIES_TOTAL);
            }
        }
    }
}

規則定義

目前寫在一個常量類里,實際項目中,通常會配置在配置文件中

public class DelayConstant {
    /**
     * 定義當前重試次數
     */
    public static final String X_RETRIES_HEADER = "x-retries";
    /**
     * 定義延遲消息,固定值,該配置放到消息的header中,會開啟延遲隊列
     */
    public static final String X_DELAY_HEADER = "x-delay";

    /**
     * 定義最多重試次數
     */
    public static final Integer X_RETRIES_TOTAL = 5;

    /**
     * 定義重試規則,毫秒為單位
     */
    public static final Map<Integer,Integer> ruleMap = new HashMap(){{
        put(1,1000);
        put(2,2000);
        put(3,3000);
        put(4,4000);
        put(5,5000);
    }};
}

測試

經過以上配置和實現就可完成模擬的重發場景。

  • 瀏覽器中輸入http://127.0.0.1:8081/send?money=10,可以看到控制台中輸出:
消息消費成功!
  • 瀏覽器中輸入http://127.0.0.1:8081/send?money=110,可以看到控制台中輸出:
2020-06-20 10:59:42:金額超出100,消費失敗,將進入重試。
2020-06-20 10:59:43:金額超出100,當前第1次重試。
2020-06-20 10:59:45:金額超出100,當前第2次重試。
2020-06-20 10:59:48:金額超出100,當前第3次重試。
消息消費成功!

  • 瀏覽器中輸入http://127.0.0.1:8081/send?money=110,可以看到控制台中輸出:

注意事項

由於本文用到了延遲隊列,需要在rabbitMQ中安裝延遲插件,具體安裝方式,可以查看:延遲隊列安裝參考

源碼獲取

以上示例都可以通過我的GitHub獲取完整的代碼.

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

springboot + rabbitmq 做智能家居,我也沒想到會這麼簡單

本文收錄在個人博客:www.chengxy-nds.top,共享技術資源,共同進步

前一段有幸參与到一個智能家居項目的開發,由於之前都沒有過這方面的開發經驗,所以對智能硬件的開發模式和技術棧都頗為好奇。

產品是一款可燃氣體報警器,如果家中燃氣泄露濃度到達一定閾值,報警器檢測到並上傳氣體濃度值給後台,後台以電話、短信、微信等方式,提醒用戶家中可能有氣體泄漏。

用戶還可能向報警器發一些關閉報警、調整音量的指令等。整體功能還是比較簡單的,大致的邏輯如下圖所示:

但當我真正的參与其中開發時,其實有一點小小的失望,因為在整個研發過程中,並沒用到什麼新的技術,還是常規的幾種中間件,只不過換個用法而已。

技術選型用rabbitmq 來做核心的組件,主要考慮到運維成本低,組內成員使用的熟練度比較高。

下面和小夥伴分享一下如何用 springboot + rabbitmq 搭建物聯網(IOT)平台,其實智能硬件也沒想象的那麼高不可攀!

很多小夥伴可能有點懵?rabbitmq 不是消息隊列嗎?怎麼又能做智能硬件了

其實rabbitmq有兩種協議,我們平時接觸的消息隊列是用的AMQP協議,而用在智能硬件中的是MQTT協議。

一、什麼是 MQTT協議?

MQTT 全稱(Message Queue Telemetry Transport):一種基於發布/訂閱(publish/subscribe)模式的輕量級通訊協議,通過訂閱相應的主題來獲取消息,是物聯網(Internet of Thing)中的一個標準傳輸協議。

該協議將消息的發布者(publisher)與訂閱者(subscriber)進行分離,因此可以在不可靠的網絡環境中,為遠程連接的設備提供可靠的消息服務,使用方式與傳統的MQ有點類似。

TCP協議位於傳輸層,MQTT 協議位於應用層,MQTT 協議構建於TCP/IP協議上,也就是說只要支持TCP/IP協議棧的地方,都可以使用MQTT協議。

二、為什麼要用 MQTT協議?

MQTT協議為什麼在物聯網(IOT)中如此受偏愛?而不是其它協議,比如我們更為熟悉的 HTTP協議呢?

  • 首先HTTP協議它是一種同步協議,客戶端請求后需要等待服務器的響應。而在物聯網(IOT)環境中,設備會很受制於環境的影響,比如帶寬低、網絡延遲高、網絡通信不穩定等,顯然異步消息協議更為適合IOT應用程序。

  • HTTP是單向的,如果要獲取消息客戶端必須發起連接,而在物聯網(IOT)應用程序中,設備或傳感器往往都是客戶端,這意味着它們無法被動地接收來自網絡的命令。

  • 通常需要將一條命令或者消息,發送到網絡上的所有設備上。HTTP要實現這樣的功能不但很困難,而且成本極高。

三、MQTT協議介紹

前邊說過MQTT是一種輕量級的協議,它只專註於發消息, 所以此協議的結構也非常簡單。

MQTT數據包

MQTT協議中,一個MQTT數據包由:固定頭(Fixed header)、 可變頭(Variable header)、 消息體(payload)三部分構成。

  • 固定頭(Fixed header),所有數據包中都有固定頭,包含數據包類型及數據包的分組標識。
  • 可變頭(Variable header),部分數據包類型中有可變頭。
  • 內容消息體(Payload),存在於部分數據包類,是客戶端收到的具體消息內容。

1、固定頭

固定頭部,使用兩個字節,共16位:

(4-7)位表示消息類型,使用4位二進製表示,可代表如下的16種消息類型,不過 0 和 15位置屬於保留待用,所以共14種消息事件類型。

DUP Flag(重試標識)

DUP Flag:保證消息可靠傳輸,消息是否已送達的標識。默認為0,只佔用一個字節,表示第一次發送,當值為1時,表示當前消息先前已經被傳送過。

QoS Level(消息質量等級)

QoS Level:消息的質量等級,後邊會詳細介紹

RETAIN(持久化)

  • 值為1:表示發送的消息需要一直持久保存,而且不受服務器重啟影響,不但要發送給當前的訂閱者,且以後新加入的客戶端訂閱了此Topic,訂閱者也會馬上得到推送。
    注意:新加入的訂閱者,只會取出最新的一個RETAIN flag = 1的消息推送。

  • 值為0:僅為當前訂閱者推送此消息。

Remaining Length(剩餘長度)

在當前消息中剩餘的byte(字節)數,包含可變頭部和消息體payload。

2、可變頭

固定頭部僅定義了消息類型和一些標誌位,一些消息的元數據需要放入可變頭部中。可變頭部內容字節長度 + 消息體payload = 剩餘長度。

可變頭部居於固定頭部和payload中間,包含了協議名稱,版本號,連接標誌,用戶授權,心跳時間等內容。

可變頭存在於這些類型的消息:PUBLISH (QoS > 0)、PUBACK、PUBREC、PUBREL、PUBCOMP、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK。

3、消息體payload

消息體payload只存在於CONNECTPUBLISHSUBSCRIBESUBACKUNSUBSCRIBE這幾種類型的消息:

  • CONNECT:包含客戶端的ClientId、訂閱的TopicMessage以及用戶名密碼
  • PUBLISH:向對應主題發送消息。
  • SUBSCRIBE:要訂閱的主題以及QoS
  • SUBACK:服務器對於SUBSCRIBE所申請的主題及QoS進行確認和回復。
  • UNSUBSCRIBE:取消要訂閱的主題。

消息質量(QoS )

消息質量(Quality of Service),即消息的發送質量,發布者(publisher)和訂閱者(subscriber)都可以指定qos等級,有QoS 0QoS 1QoS 2三個等級。

下邊分別說明一下這三個等級的區別。

1、Qos 0At most once(至多一次),只發送一次消息,不保證消息是否成功送達,沒有確認機制,消息可能會丟失或重複。

2、Qos 1At least once(至少一次),相對於QoS 0而言Qos 1增加了ack確認機制,發送者(publisher)推送消息到MQTT代理(broker)時,兩者自身都會先持久化消息,只有當publisher 或者 Broker分別收到 PUBACK確認時,才會刪除自身持久化的消息,否則就會重發。

但有個問題,儘管我們可以通過確認來保證一定收到客戶端 或 服務器的message,可我們卻不能保證僅收到一次message,也就是當客戶端publisher沒收到Brokerpuback或者 Broker沒有收到subscriberpuback,那麼就會一直重發。

publisher -> broker 大致流程:

  1. publisher store msg -> publish ->broker (傳遞message)
  2. broker -> puback -> publisher delete msg (確認傳遞成功)

3、Qos 2Exactly once(只有一次),相對於QoS 1QoS 2升級實現了僅接受一次messagepublisherbroker 同樣對消息進行持久化,其中 publisher 緩存了message和 對應的msgID,而 broker 緩存了 msgID,可以保證消息不重複,由於又增加了一個confirm 機制,整個流程變得複雜很多。

publisher -> broker 大致流程:

  1. publisher store msg -> publish ->broker -> broker store
  2. msgID(傳遞message) broker -> puberc (確認傳遞成功)
  3. publisher -> pubrel ->broker delete msgID (告訴broker刪除msgID)
  4. broker -> pubcomp -> publisher delete msg (告訴publisher刪除msg)

LWT(最後遺囑)

LWT 全稱為 Last Will and Testament,其實遺囑是一個由客戶端預先定義好的主題和對應消息,附加在CONNECT的數據包中,包括遺願主題遺願 QoS遺願消息等。

當MQTT代理 Broker 檢測到有客戶端client非正常斷開連接時,再由服務器主動發布此消息,然後相關的訂閱者會收到消息。

舉個栗子:聊天室中所有人都訂閱一個叫talk的主題 ,但小富由於網絡抖動突然斷開了鏈接,這時聊天室中所有訂閱主題 talk的客戶端都會收到一個 “小富離開聊天室” 的遺願消息。

遺囑的相關參數:

  • Will Flag:是否使用 LWT,1 開啟
  • Will Topic:遺願主題名,不可使用通配符
  • Will Qos:發布遺願消息時使用的 QoS
  • Will Retain:遺願消息的 Retain 標識
  • Will Message:遺願消息內容

那客戶端Client 有哪些場景是非正常斷開連接呢?

  • Broker 檢測到底層的 I/O 異常;
  • 客戶端 未能在心跳 Keep Alive 的間隔內和 Broker 進行消息交互;
  • 客戶端 在關閉底層 TCP 連接前沒有發送 DISCONNECT 數據包;
  • 客戶端 發送錯誤格式的數據包到 Broker,導致關閉和客戶端的連接等。

注意:當客戶端通過發布 DISCONNECT 數據包斷開連接時,屬於正常斷開連接,並不會觸發 LWT 的機制,與此同時Broker 還會丟棄掉當前客戶端在連接時指定的相關 LWT 參數。

四、MQTT協議應用場景

MQTT協議廣泛應用於物聯網、移動互聯網、智能硬件、車聯網、電力能源等領域。使用的場景也是非常非常多,下邊列舉一些:

  • 物聯網M2M通信,物聯網大數據採集
  • Android消息推送,WEB消息推送
  • 移動即時消息,例如Facebook Messenger
  • 智能硬件、智能傢具、智能電器
  • 車聯網通信,電動車站樁採集
  • 智慧城市、遠程醫療、遠程教育
  • 電力、石油與能源等行業市場

五、代碼實現

具體 rabbitmq 的環境搭建就不贅述了,網上教程比較多,有條件的用服務器,沒條件的像我搞個Windows版的也很快樂嘛。

1、啟用 rabbitmq的mqtt協議

我們先開啟 rabbitmqmqtt協議,因為默認安裝下是關閉的,命令如下:

rabbitmq-plugins enable rabbitmq_mqtt

2、mqtt 客戶端依賴包

上一步中安裝rabbitmq環境並開啟 mqtt協議后,實際上mqtt 消息代理服務就搭建好了,接下來要做的就是實現客戶端消息的推送和訂閱。

這裏使用spring-integration-mqttorg.eclipse.paho.client.mqttv3兩個工具包實現。

<!--mqtt依賴包-->
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-mqtt</artifactId>
</dependency>
<dependency>
    <groupId>org.eclipse.paho</groupId>
       <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
    <version>1.2.0</version>
</dependency>

3、消息發送者

消息的發送比較簡單,主要是應用到@ServiceActivator註解,需要注意messageHandler.setAsync屬性,如果設置成false,關閉異步模式發送消息時可能會阻塞。

@Configuration
public class IotMqttProducerConfig {

    @Autowired
    private MqttConfig mqttConfig;

    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        factory.setServerURIs(mqttConfig.getServers());
        return factory;
    }

    @Bean
    public MessageChannel mqttOutboundChannel() {
        return new DirectChannel();
    }

    @Bean
    @ServiceActivator(inputChannel = "iotMqttInputChannel")
    public MessageHandler mqttOutbound() {
        MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(mqttConfig.getServerClientId(), mqttClientFactory());
        messageHandler.setAsync(false);
        messageHandler.setDefaultTopic(mqttConfig.getDefaultTopic());
        return messageHandler;
    }
}

MQTT 對外提供發送消息的API時,需要使用@MessagingGateway 註解,去提供一個消息網關代理,參數defaultRequestChannel 指定發送消息綁定的channel

可以實現三種API接口,payload 為發送的消息,topic 發送消息的主題,qos 消息質量。

@MessagingGateway(defaultRequestChannel = "iotMqttInputChannel")
public interface IotMqttGateway {

    // 向默認的 topic 發送消息
    void sendMessage2Mqtt(String payload);
    // 向指定的 topic 發送消息
    void sendMessage2Mqtt(String payload,@Header(MqttHeaders.TOPIC) String topic);
    // 向指定的 topic 發送消息,並指定服務質量參數
    void sendMessage2Mqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
}

4、消息訂閱

消息訂閱和我們平時用的MQ消息監聽實現思路基本相似,@ServiceActivator註解表明當前方法用於處理MQTT消息,inputChannel 參數指定了用於接收消息的channel

/**
 * @Author: xiaofu
 * @Description: 消息訂閱配置
 * @date 2020/6/8 18:24
 */
@Configuration
public class IotMqttSubscriberConfig {

    @Autowired
    private MqttConfig mqttConfig;

    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        factory.setServerURIs(mqttConfig.getServers());
        return factory;
    }

    @Bean
    public MessageChannel iotMqttInputChannel() {
        return new DirectChannel();
    }

    @Bean
    public MessageProducer inbound() {
        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(mqttConfig.getClientId(), mqttClientFactory(), mqttConfig.getDefaultTopic());
        adapter.setCompletionTimeout(5000);
        adapter.setConverter(new DefaultPahoMessageConverter());
        adapter.setQos(1);
        adapter.setOutputChannel(iotMqttInputChannel());
        return adapter;
    }

    /**
     * @author xiaofu
     * @description 消息訂閱
     * @date 2020/6/8 18:20
     */
    @Bean
    @ServiceActivator(inputChannel = "iotMqttInputChannel")
    public MessageHandler handlerTest() {

        return message -> {
            try {
                String string = message.getPayload().toString();
                System.out.println("接收到消息:" + string);
            } catch (MessagingException ex) {
                //logger.info(ex.getMessage());
            }
        };
    }
}

六、測試消息

額~ 由於本渣渣對硬件一竅不通,為了模擬硬件的發送消息,只能藉助一下工具,其實硬件端實現MQTT協議,跟我們前邊的基本沒什麼區別,只不過換種語言嵌入到硬件中而已。

這裏選的測試工具為mqttbox,下載地址:http://workswithweb.com/mqttbox.html

1、測試消息發送

我們用先用mqttbox模擬向主題mqtt_test_topic發送消息,看後台是否能成功接收到。

看到後台成功拿到了向主題mqtt_test_topic發送的消息。

2、測試消息訂閱

mqttbox模擬訂閱主題mqtt_test_topic,在後台向主題mqtt_test_topic發送一條消息,這裏我簡單的寫了個controller調用API發送消息。

http://127.0.0.1:8080/fun/testMqtt?topic=mqtt_test_topic&message=我是後台向主題 mqtt_test_topic 發送的消息

我們看mqttbox的訂閱消息,已經成功的接收到了後台的消息,到此我們的MQTT通信環境就算搭建成功了。如果把mqttbox工具換成具體硬件設備,整個流程就是我們常說的智能家居了,其實真的沒那麼難。

七、應用注意事項

在我們實際的生產環境中遇到過的問題,這裏分享一下讓大家少踩坑。

clientId 要唯一

在客戶端connect連接的時,會有一個clientId 參數,需要每個客戶端都保持唯一的。但我們在開發測試階段clientId直接在代碼中寫死了,而且服務都是單實例部署,並沒有暴露出什麼問題。

MqttPahoMessageDrivenChannelAdapter(mqttConfig.getClientId(), mqttClientFactory(), mqttConfig.getDefaultTopic());

然而在生產環境內側的時候,由於服務是多實例集群部署,結果出現了下邊的奇怪問題。同一時間內只能有一個客戶端能拿到消息,其他客戶端不但不能消費消息,而且還在不斷的掉線重連:Lost connection: 已斷開連接; retrying...

這就是由於clientId相同導致客戶端間相互競爭消費,最後將clientId獲取方式換成從發號器中拿,問題就好了,所以這個地方是需要特別注意的。

平時程序在開發環境沒問題,可偏偏到了生產環境就一大堆問題,很多都是因為服務部署方式不同導致的。所以多學習分佈式還是很有必要的。

八、其他中間件

MQTT它只是一種協議,支持MQTT協議的消息中間件產品非常多,下邊的也只是其中的一部分

  • Mosquitto
  • Eclipse Paho
  • RabbitMQ
  • Apache ActiveMQ
  • HiveMQ
  • JoramMQ
  • ThingMQ
  • VerneMQ
  • Apache Apollo
  • emqttd Xively
  • IBM Websphere
    …..

總結

我也是第一次做和硬件相關的項目,之前聽到智能家居都會覺得好高大上,但實際上手開發后發現,技術嘛萬變不離其宗,也只是換種用法而已。

雙手奉上項目 demo 的github地址 :https://github.com/chengxy-nds/springboot-rabbitmq-mqtt.git

感興趣的小夥伴可以下載跑一跑,實現起來非常的簡單。

原創不易,燃燒秀髮輸出內容,希望你能有一丟丟收穫!

整理了幾百本各類技術电子書,送給小夥伴們,關注公號回復【666】自行領取。和一些小夥伴們建了一個技術交流群,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就加入我們吧!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

垃圾回收相關算法

這裏介紹的垃圾回收相關算法,主要解決的問題:

判斷哪些內存是垃圾(需要回收的)?

常用的兩種算法:

  • 引用計數
  • 可達性分析(GC Root)

首先介紹算法前,得定義:

如何判斷一個對象的死亡?

我們一般這樣定義:當一個對象不再被任何存活的對象繼續引用的時候,這個對象就死亡了。

引用計數

引用計數算法,是給每一個對象添加一個計數器,當有對象引用它的時候,計數器+1,當有對象取消對它的引用時,計數就會-1。

當計數器的值為 0 時,即說明沒有對象引用它,也就是這個對象死亡了。

這種算法很簡單,但是有個重大缺陷,那就是無法解決循環引用的問題。

什麼是循環引用問題呢?

比如對象A 引用 對象B,對象B 引用 對象A,那麼 對象A 和 對象B 的計數器都為1。但是如果後續的運行環境再也用不到對象A 和 對象B,那麼就造成了內存泄漏。

上圖就是循環引用的例子。對象引用 Obj1 和 Obj2 在棧中,然後分別指向在堆中的具體實例。然後兩個相互實例中的成員互相引用。那麼對於堆中的對象而言,就有2個引用。一個是來自Obj1,一個來自堆對象的另一方。

如果,現在將 Obj1 指向 nu l l,那麼就如下圖:

這個時候,引用已經不可用了,但是堆中的對象仍然相互引用,他們的計數器不為0,所以無法死亡。

但是,Java 沒有使用這種算法,而是使用了我們後面說的可達性算法,所以接下來的演示,GC 會將這種情況的內存給其清理。

package GC;

public class ReferenceCountGC {
    public Object instance = null;

    private static final int _1MB = 1024 * 1024;
    // 每個對象中包含2M的成員,方便觀察
    private byte[] bigSize = new byte[2 * _1MB];
    public static void main(String[] args) {
        ReferenceCountGC objA = new ReferenceCountGC();
        ReferenceCountGC objB = new ReferenceCountGC();
        objA.instance = objB.instance;
        objB.instance = objA.instance;

        //取消對對象的引用
        objA = null;
        objB = null;
      // 是否進行垃圾回收
        System.gc();
    }
}

這段代碼實現的就是上面圖片所描述的情況。

首先,我們將 System.gc() 註釋掉,也就是我們在默認情況下,不去觸發垃圾回收。並在運行的時候,添加參數 -XX:+PrintGCDetails。我們觀察輸出結果

可以看到,這個時候,佔用的空間為8M左右。

如果我們取消註釋,也就是主動去調用垃圾回收器,那麼運行結果為:

佔用空間為2M左右。

可以看出來,Java 的垃圾回收,並非採用我們上面介紹的引用計數方式。

可達性分析

可達性算法,還有一系列的別名:根搜索算法,追蹤性垃圾收集,GC Root。

之後,看到原理,其實這些別名都是描述原理的。

首先,我們選取一些對象,這些對象是存活的,也被稱為 GC Roots,然後根據這些對象的引用關係,凡是直接或者間接跟 GC Roots 相關聯的對象,都是存活的。就像圖中的連通性判斷一樣。

這個算法的想法不難。難的是,如何確定 GC Roots。

我們考慮,我們什麼時候需要用到對象?(我們需要對象的時候,肯定需要這個對象是存活的)

  • 棧中保存着,我們當前或者之後需要運行的方法及相關參數,所以,棧上所引用的堆中對象肯定是存活的。
  • 類中的一些屬性,比如,靜態屬性,因為它不依賴於具體的類
  • 一些常用的對象,以免清理后,又要重複加載,比如常用的異常對象,基本數據類型對應的 Class 對象。

除此之外,還有很多零零碎碎的。

在堆結構周圍的一些結構,其中引用的對象可以作為GC Roots

具體 GC Roots 可以概括為:

  • 虛擬機棧上(確切的說,是棧幀上的本地變量表)所引用的對象

  • 本地方法棧引用的對象

  • 方法區中的靜態屬性,常量引用

  • Java 虛擬機的內部引用,常用數據類型的 Class 對象,常駐的異常對象,系統類加載器

  • 所有被同步鎖持有的對象

除此之外,還有一些臨時的 GC Roots 可以加入進來。這裏涉及到新生代老年代。

比如老年代中的對象一般都存活時間比較久,也就是大概率是活着的對象,也可臨時作為 GC Roots。

可達性算法的一些細節

前面說了可達性算法,我們根據 GC Roots 來進行標記對象的死活。

但是,被判定為不可達的對象,並不立刻死亡。它仍然有次機會進行自救。

這個自救的機會,是需要重寫 finalize()進行自救。

也就是可達性算法的邏輯大致是這樣的:

  • 第一次進行標記,凡是不可達 GC Roots 的對象,都暫時判定為死亡,只是暫時
  • 檢查暫時被判定為死亡對象,檢查是否有重寫 finalize()方法,如果有,則觸發,對象可以在裏面完成自救。

如果沒有自救成功 或者 沒有重寫 finalize()方法,則宣告這個對象的死亡。

除此之外,這個對象中的 finalize()方法,只能被調用一次,一生只有一次自救機會。

這個方法,官方並不推薦,所以不必細究。

接下來,演示下上面的兩次標記過程以及自救過程。

(個人認為,《深入理解 Java 虛擬機》中的此章節代碼,略有點不夠完善,故略微改動)

package GC;

import javax.swing.tree.VariableHeightLayoutCache;

public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    private byte[] bigSize = new byte[5*1024*1024];

    public void isAlive(){
        System.out.println("Yes, i am alive");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Finalize method executed");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws InterruptedException {
        SAVE_HOOK = new FinalizeEscapeGC();
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if(SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
            System.gc();
        }else {
            System.out.println("Dead");
            System.gc();
        }
    }
}

在這個程序中,我們給這個類,添加名為 bigSize 的屬性,其佔用 4M 大小的空間。

大致分析下代碼邏輯:

  • 創建了一個對象,其中有成員佔用 4M 的空間
  • 取消對這個對象的引用
  • 調用垃圾回收(第一次標記)
  • 調用 finalize 方法進行自救
  • 之後再次調用垃圾回收(第二次標記)

所以演示的時候,分為兩種情況:

  • FinalizeEscapeGC.SAVE_HOOK = this; 未註釋,完成自救

運行時,參數仍然設置為 +XX:PrintGCDetails,可以看到輸出結果:

第一次調用垃圾回收,仍然佔用 5M,說明此時即便失去引用,但是仍然未被清理。

在 finalize()中完成自救后,第二次調用垃圾回收的時候,仍然佔用 5M 的內存大小。說明自救成功。

  • FinalizeEscapeGC.SAVE_HOOK = this; 註釋,無法完成自救

第一次垃圾回收,佔用 5M,保留了對象。無法完成自救,然後第二次被清理掉。

所以我發現以下錶述也許更為確切:

  • 當對象重寫了 finalize()方法的時候,第一次垃圾回收的時候,如果為不可達對象,對其進行暫緩,並不清理。
  • 當對象沒有重寫 finalize()方法的時候,且為不可達對象的時候,直接判定死亡。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

selenium自動化操作

在前面爬蟲的相關介紹中,我們介紹了如何抓取靜態頁面信息。但是,在實際的網頁瀏覽過程中,我們可能會經常碰到各種需要進行交互的操作,典型的如輸入信息、點擊按鈕之類。

對於這種場景,之前的靜態頁面操作方式已經不能滿足需求,這時我們需要藉助新的工具,比如selenium或者PhantomJS。由於後者已經停止維護,推薦使用前者。

1.selenium是什麼

如果大家有做過web的自動化測試,相信對於selenium一定不陌生,測試人員經常使用它來進行自動化測試。

selenium最初是一個自動化web測試工具,通過代碼模擬人使用瀏覽器自動訪問目標站點並操作,比如跳轉、輸入、點擊、下拉等。

由於開發者的不斷完善,目前的功能越來越強大,基本支持各種交互操作。同時,不止支持有界瀏覽,還支持無界瀏覽。

2.selenium有什麼用

正如我們前面講過的,爬蟲的本質過程就是模擬人對瀏覽器的操作過程。在爬蟲中使用,selenium主要是為了解決requests無法執行javaScript代碼的問題。

本質上是通過驅動瀏覽器,完全模擬瀏覽的操作,比如跳轉、輸入、點擊、下拉等…進而進行跳轉。

當然,它也有壞處,主要的壞處就是它的速度比較慢。原因是selenium在操作時,需要等瀏覽器對頁面的元素渲染好之後才能操作。而我們知道,由於頁面渲染過程需要加載各種資源,響應速度與網絡帶寬要求非常高。通常情況,它比靜態頁面的響應至少慢一個數量級。

3.如何使用selenium

在知道selenium是什麼以及有什麼用之後,我們來具體學習如何操作這個工具。

由於selenium本質是模擬人對瀏覽器進行輸入、選擇、點擊等操作,因此對於目標標籤的定位非常重要。

在前面的章節,我們對於如何定位目標標籤有過詳細的介紹,這裏就不再贅述。selenium對於目標標籤定位的方式本質與靜態的頁面一樣,只不過因為使用的包不同,因此在beautifulSoup中使用的是find和findAll,而在selenium中使用的接口有所變化。

下圖中已對各種定位方式進行了歸納總結:

在找到目標標籤之後,最重要的是對這些標籤進行模擬操作。Selenium庫下webdriver模塊常用方法主要分類兩類:一類是模擬瀏覽器、鍵盤操作,另一類是模擬鼠標操作。

3.1模擬瀏覽器、鍵盤操作

模擬瀏覽器、鍵盤操作的方法歸納如下:

3.2 模擬鼠標操作

模擬鼠標操作的方法歸納如下:

4.示例演示

在介紹了selenium相關的使用方法之後,我們來進行操作。這裏介紹兩個例子:第一個例子是模擬百度搜索,第二個例子是模擬自動登錄網易163郵箱發送郵件。

在開始示例之前我們需要安裝selinum插件包,同時還需要下載webdriver。在我們的示例中,需要是使用chrome瀏覽器進行操作,需要使用瀏覽器的驅動webdriver。

關於下載什麼版本的webdriver,可以在瀏覽器的屬性中查看,並在http://npm.taobao.org/mirrors/chromedriver/下載對應的版本就好,如果是其他的瀏覽器,則需要下載對應的瀏覽器驅動程序,這種不再做進一步介紹。

4.1模擬百度搜索

第一步還是需要打開目標的地址“w w w.baidu.com”,分析目標網頁中目標元素的特點,如下圖所示:

通過分析,我們很容易就找到搜索框的id為kw,點擊按鈕的id為su,餘下的就是使用方法進行模擬。

實現的代碼如下所示:

from selenium import webdriver

#get 方法 打開指定網址
driver=webdriver.Chrome()
driver.get('http://www.baidu.com')

#選擇網頁元素
element_keyword = driver.find_element_by_id('kw')

#輸入字符
element_keyword.send_keys('python 爬蟲')

#找到搜索按鈕
element_search_button = driver.find_element_by_id('su')
element_search_button.click()

driver.close()

4.2模擬自動登錄網易163郵箱發送郵件

操作過程跟上面相似,第一步也是分析目標網頁http://mail.163.com。

如下圖所示:

找到了目標標籤然後就是模擬登錄。

實現代碼如下:

# coding:UTF-8
import time
from selenium.webdriver.common.keys import Keys
from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(5)  
driver.get('http://mail.163.com/')
driver.switch_to_frame(driver.find_element_by_tag_name('iframe'))
# driver.switch_to_frame('x-URS-iframe')  
driver.find_element_by_name('email').clear()
driver.find_element_by_name('email').send_keys('郵箱地址')
driver.find_element_by_name('password').send_keys('郵箱密碼', Keys.ENTER)
# 跳轉頁面時,強制等待6s
time.sleep(6)   
# 點擊寫信按鈕
driver.find_element_by_xpath("//div[@id='dvNavTop']/ul/li[2]/span[2]").click()   
time.sleep(2)
# 收件人
driver.find_element_by_class_name('nui-editableAddr-ipt').send_keys('目標的郵箱')  
driver.find_element_by_xpath("//input[@class='nui-ipt-input' and @type='text' and @maxlength='256']").send_keys(
    u'測試')   # 主題
xpath = driver.find_element_by_xpath("//div[@class='APP-editor-edtr']/iframe")
# 文本內容在iframe中
driver.switch_to_frame(xpath)   
driver.find_element_by_xpath("//body[@class='nui-scroll' and @contenteditable='true']").send_keys(u'這是一個自動化測試郵件')
# 發送按鈕在iframe外,所以需要跳出
driver.switch_to_default_content()
# 發送
driver.find_element_by_xpath("//div[@class='nui-toolbar-item']/div/span[2]").click()  
driver.close()

當然,在實際過程中,可能往往還有驗證碼的驗證。因為現在的驗證碼難度越來越大,形式也多種多樣,使用常規的方法很難解決,必須藉助機器學習或者第三方接口進行實現,將在後續單獨列一個章節進行介紹如何破解驗證碼。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

11.DRF-權限

Django rest framework源碼分析(2)—-權限

添加權限

(1)API/utils文件夾下新建premission.py文件,代碼如下:

  • message是當沒有權限時,提示的信息
# utils/permission.py

class SVIPPremission(object):
    message = "必須是SVIP才能訪問"
    def has_permission(self,request,view):
        if request.user.user_type != 3:
            return False
        return True


class MyPremission(object):
    def has_permission(self,request,view):
        if request.user.user_type == 3:
            return False
        return True

(2)settings.py全局配置權限

#全局
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',],
    "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPremission'],
}

(3)views.py添加權限

  • 默認所有的業務都需要SVIP權限才能訪問
  • OrderView類裏面沒寫表示使用全局配置的SVIPPremission
  • UserInfoView類,因為是普通用戶和VIP用戶可以訪問,不使用全局的,要想局部使用的話,裏面就寫上自己的權限類
  • permission_classes = [MyPremission,] #局部使用權限方法
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from API import models
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from API.utils.permission import SVIPPremission,MyPremission

ORDER_DICT = {
    1:{
        'name':'apple',
        'price':15
    },
    2:{
        'name':'dog',
        'price':100
    }
}

def md5(user):
    import hashlib
    import time
    #當前時間,相當於生成一個隨機的字符串
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,encoding='utf-8'))
    m.update(bytes(ctime,encoding='utf-8'))
    return m.hexdigest()

class AuthView(APIView):
    '''用於用戶登錄驗證'''

    authentication_classes = []      #裏面為空,代表不需要認證
    permission_classes = []          #不裏面為空,代表不需要權限
    def post(self,request,*args,**kwargs):
        ret = {'code':1000,'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            #為用戶創建token
            token = md5(user)
            #存在就更新,不存在就創建
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '請求異常'
        return JsonResponse(ret)


class OrderView(APIView):
    '''
    訂單相關業務(只有SVIP用戶才能看)
    '''

    def get(self,request,*args,**kwargs):
        self.dispatch
        #request.user
        #request.auth
        ret = {'code':1000,'msg':None,'data':None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)


class UserInfoView(APIView):
    '''
       訂單相關業務(普通用戶和VIP用戶可以看)
       '''
    permission_classes = [MyPremission,]    #不用全局的權限配置的話,這裏就要寫自己的局部權限
    def get(self,request,*args,**kwargs):

        print(request.user)
        return HttpResponse('用戶信息')
# urls.py
from django.contrib import admin
from django.urls import path
from API.views import AuthView,OrderView,UserInfoView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/auth/',AuthView.as_view()),
    path('api/v1/order/',OrderView.as_view()),
    path('api/v1/info/',UserInfoView.as_view()),
]
# API/utils/auth/py
# auth.py

from rest_framework import exceptions
from API import models
from rest_framework.authentication import BaseAuthentication


class Authentication(BaseAuthentication):
    '''用於用戶登錄驗證'''
    def authenticate(self,request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
        return (token_obj.user,token_obj)

    def authenticate_header(self, request):
        pass

(4)測試

普通用戶訪問OrderView,提示沒有權限

普通用戶訪問UserInfoView,可以返回信息

權限源碼流程

(1)dispatch

def dispatch(self, request, *args, **kwargs):
    """
    `.dispatch()` is pretty much the same as Django's regular dispatch,
    but with extra hooks for startup, finalize, and exception handling.
    """
    self.args = args
    self.kwargs = kwargs
    #對原始request進行加工,豐富了一些功能
    #Request(
    #     request,
    #     parsers=self.get_parsers(),
    #     authenticators=self.get_authenticators(),
    #     negotiator=self.get_content_negotiator(),
    #     parser_context=parser_context
    # )
    #request(原始request,[BasicAuthentications對象,])
    #獲取原生request,request._request
    #獲取認證類的對象,request.authticators
    #1.封裝request
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        #2.認證
        self.initial(request, *args, **kwargs)

        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)

    except Exception as exc:
        response = self.handle_exception(exc)

    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

(2)initial

def initial(self, request, *args, **kwargs):
    """
    Runs anything that needs to occur prior to calling the method handler.
    """
    self.format_kwarg = self.get_format_suffix(**kwargs)

    # Perform content negotiation and store the accepted info on the request
    neg = self.perform_content_negotiation(request)
    request.accepted_renderer, request.accepted_media_type = neg

    # Determine the API version, if versioning is in use.
    version, scheme = self.determine_version(request, *args, **kwargs)
    request.version, request.versioning_scheme = version, scheme

    # Ensure that the incoming request is permitted
    #4.實現認證
    self.perform_authentication(request)
    #5.權限判斷
    self.check_permissions(request)
    self.check_throttles(request)

(3)check_permissions

裏面有個has_permission這個就是我們自己寫的權限判斷

def check_permissions(self, request):
    """
    Check if the request should be permitted.
    Raises an appropriate exception if the request is not permitted.
    """
    #[權限類的對象列表]
    for permission in self.get_permissions():
        if not permission.has_permission(request, self):
            self.permission_denied(
                request, message=getattr(permission, 'message', None)
            )

(4)get_permissions

def get_permissions(self):
    """
    Instantiates and returns the list of permissions that this view requires.
    """
    return [permission() for permission in self.permission_classes]

(5)permission_classes

所以settings全局配置就如下

#全局
REST_FRAMEWORK = {
   "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPremission'],
}

內置權限

django-rest-framework內置權限BasePermission

默認是沒有限制權限

class BasePermission(object):
    """
    A base class from which all permission classes should inherit.
    """

    def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

    def has_object_permission(self, request, view, obj):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

我們自己寫的權限類,應該去繼承BasePermission,修改之前寫的permission.py文件

# utils/permission.py

from rest_framework.permissions import BasePermission

class SVIPPremission(BasePermission):
    message = "必須是SVIP才能訪問"
    def has_permission(self,request,view):
        if request.user.user_type != 3:
            return False
        return True


class MyPremission(BasePermission):
    def has_permission(self,request,view):
        if request.user.user_type == 3:
            return False
        return True

總結:

(1)使用

  • 自己寫的權限類:1.必須繼承BasePermission類; 2.必須實現:has_permission方法

(2)返回值

  • True 有權訪問
  • False 無權訪問

(3)局部

  • permission_classes = [MyPremission,]

(4)全局

REST_FRAMEWORK = {
   #權限
    "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPremission'],
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!