計算機網絡之應用層

應用層協議

應用層協議 (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)。

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

【其他文章推薦】

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

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

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

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

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

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

使用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獲取完整的代碼.

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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'],
}

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

【其他文章推薦】

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

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

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

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

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

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

C#多線程編程(一)進程與線程

一、 進程

        簡單來說,進程是對資源的抽象,是資源的容器,在傳統操作系統中,進程是資源分配的基本單位,而且是執行的基本單位,進程支持併發執行,因為每個進程有獨立的數據,獨立的堆棧空間。一個程序想要併發執行,開多個進程即可。

Q1:在單核下,進程之間如何同時執行?

        首先要區分兩個概念——併發和并行

  • 併發:併發是指在一段微小的時間段中,有多個程序代碼段被CPU執行,宏觀上表現出來就是多個程序能”同時“執行。
  • 并行:并行是指在一個時間點,有多個程序段代碼被CPU執行,它才是真正的同時執行。

        所以應該說進程之間是併發執行。對於CPU來講,它不知道進程的存在,CPU主要與寄存器打交道。有一些常用的寄存器,如程序計數器寄存器,這個寄存器存儲了將要執行的指令的地址,這個寄存器的地址指向哪,CPU就去哪。還有一些堆棧寄存器和通用寄存器等等等,總之,這些數據構成了一個程序的執行環境,這個執行環境就叫做”上下文(Context)“,進程之間切換本質就是保存這些數據到內存,術語叫做”保存現場“,然後恢復某個進程的執行環境,也即是”恢復現場“,整個過程術語叫做“上下文切換”,具體點就是進程上下文切換,這就是進程之間能併發執行的本質——頻繁的切換進程上下文。這個功能是由操作系統提供的,是內核態的,對應用軟件開發人員透明。

二、 線程

        進程雖然支持併發,但是對併發不是很友好,不友好是指每開啟一個進程,都要重新分配一部分資源,而線程相對進程來說,創建線程的代價比創建進程要小,所以引入線程能更好的提高併發性。在現代操作系統中,進程變成了資源分配的基本單位,而線程變成了執行的基本單位,每個線程都有獨立的堆棧空間,同一個進程的所有線程共享代碼段和地址空間等共享資源。相應的上下文切換從進程上下文切換變成了線程上下文切換

三、 為什麼要引入進程和線程

  1. 提高CPU利用率,在早期的單道批處理系統中,如果執行中的代碼需要依賴與外部條件,將會導致CPU空閑,例如文件讀取,等待鍵盤信號輸入,這將浪費大量的CPU時間。引入多進程和線程可以解決CPU利用率低這個問題。
  2. 隔離程序之間的數據(每個進程都有單獨的地址空間),保證系統運行的穩定性。
  3. 提高系統的響應性和交互能力。

四、 在C#中創建託管線程

1. Thread類

在.NET中,託管線程分為:

  • 前台線程
  • 後台線程

一個.Net程序中,至少要有一個前台線程,所有前台線程結束了,所有的後台線程將會被公共語言運行時(CLR)強制銷毀,程序執行結束。

如下將在控制台程序中創建一個後台線程

 1 static void Main(string[] args)
 2 {
 3      var t = new Thread(() =>
 4      {
 5          Thread.Sleep(1000);
 6          Console.WriteLine("執行完畢");
 7      });
 8     t.IsBackground = true;
 9      t.Start();
10 }

View Code

 

主線程(默認是前台線程)執行完畢,程序直接退出。

但IsBackground 屬性改為false時,控制台會打印“執行完畢”。

2. 有什麼問題

直接使用Thread類來進行多線程編程浪費資源(服務器端更加明顯)且不方便,舉個栗子。

假如我寫一個Web服務器程序,每個請求創建一個線程,那麼每一次我都要new一個Thread對象,然後傳入處理HttpRequest的委託,處理完之後,線程將會被銷毀,這將會導致浪費大量CPU時間和內存,在早期CPU性能不行和內存資源珍貴的情況下這個缺點會被放大,在現在這個缺點不是很明顯,原因是硬件上來了。

不方便體現在哪呢?

  • 無法直接獲取另一個線程內未被捕捉的異常
  • 無法直接獲取線程函數的返回值

 

 1 public static void ThrowException()
 2 {
 3      throw new Exception("發生異常");
 4 }
 5 static void Main(string[] args)
 6 {
 7      var t = new Thread(() =>
 8      {
 9          Thread.Sleep(1000);
10          ThrowException();
11      });
12     t.IsBackground = false;
13      try
14      {
15          t.Start();
16      }
17      catch(Exception e)
18      {
19          Console.WriteLine(e.Message);
20      }
21 }

View Code

 

上述代碼將會導致程序奔潰,如下圖。

 

要想直接獲取返回值和可以直接從主線程捕捉線程函數內未捕捉的異常,我們可以這麼做。

新建一個MyTask.cs文件,內容如下

 1 using System;
 2 using System.Threading;
 3 namespace ConsoleApp1
 4 {
 5      public class MyTask
 6      {
 7          private Thread _thread;
 8          private Action _action;
 9          private Exception _innerException;
10         public MyTask()
11          {
12         }
13          public MyTask(Action action)
14          {
15              _action = action;
16          }
17          protected virtual void Excute()
18          {
19              try
20              {
21                  _action();
22              }
23              catch(Exception e)
24              {
25                  _innerException = e;
26              }
27       
28          }
29          public void Start()
30          {
31              if (_thread != null) throw new InvalidOperationException("任務已經開始");
32              _thread = new Thread(() => Excute());
33              _thread.Start();
34          }
35          public void Start(Action action)
36          {
37              _action = action;
38              if (_thread != null) throw new InvalidOperationException("任務已經開始");
39              _thread = new Thread(() => Excute());
40              _thread.Start();
41          }
42         public void Wait()
43          {
44              _thread.Join();
45              if (_innerException != null) throw _innerException;
46          }
47      }
48     public class MyTask<T> : MyTask
49      {
50          private Func<T> _func { get; }
51          private T _result;
52          public T Result {
53              
54              private set => _result = value;
55              get 
56              {
57                  base.Wait();
58                  return _result;
59              }
60          }
61          public MyTask(Func<T> func)
62          {
63              _func = func;
64          }
65         public new void Start() 
66          {
67              base.Start(() =>
68              {
69                  Result = _func();
70              });
71          }
72     }
73 }

View Code

 

簡單的包裝了一下(不要在意細節),我們便可以實現我們想要的效果。

測試代碼如下

 1 public static void ThrowException()
 2 {
 3      throw new Exception("發生異常");
 4 }
 5 public static void Test3()
 6 {
 7      MyTask<string> myTask = new MyTask<string>(() =>
 8      {
 9          Thread.Sleep(1000);
10          return "執行完畢";
11      });
12     myTask.Start();
13     try
14      {
15          Console.WriteLine(myTask.Result);
16      }
17      catch (Exception e)
18      {
19          Console.WriteLine(e.Message);
20      }
21 }
22 public static void Test2()
23 {
24      MyTask<string> myTask = new MyTask<string>(() =>
25      {
26          Thread.Sleep(1000);
27          ThrowException();
28          return "執行完畢";
29      });
30     myTask.Start();
31     try
32      {
33          Console.WriteLine(myTask.Result);
34      }
35      catch(Exception e)
36      {
37          Console.WriteLine(e.Message);
38      }
39 }
40 public static void Test1()
41 {
42      MyTask myTask = new MyTask(() =>
43      {
44          Thread.Sleep(1000);
45          ThrowException();
46      });
47      myTask.Start();
48     try
49      {
50          myTask.Wait();
51      }
52      catch (Exception e)
53      {
54          Console.WriteLine(e.Message);
55      }
56 }
57 static void Main(string[] args)
58 {
59      Test1();
60      Test2();
61      Test3();
62 }

 

可以看到,我們可以通過簡單包裝Thread對象,便可實現如下效果

  • 直接讀取線程函數返回值
  • 直接捕捉線程函數未捕捉的異常(前提是調用了Wait()函數或者Result屬性)

這是理解和運用Task的基礎,Task功能非常完善,但是運用好Task需要掌握許多概念,下面再說。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

50年來最嚴重水患 威尼斯世界遺產遭淹

摘錄自2019年11月15日大紀元綜合報導

威尼斯遭到五十多年來最嚴重的水患,學校關閉停課、世界遺產聖馬可大教堂受損。威尼斯市長表示這是一場「災難」,且威尼斯的雨勢也尚未停止。

德國之聲13日報導,聯合國世界文化遺產、威尼斯著名的聖馬可廣場(St. Mark’s Square)被洪水淹沒,大水還因強勁風勢掀起波浪。起初遊客和當地住民尚能腳著膠靴穿行,晚些時候廣場上就只能看見駕艇的警察了。午夜時分,水面高度已逾海平面1.87米。據該市公布的數字,這是1966年之後,最嚴重的水患。

聖馬可大教堂工程師坎波斯特里尼(Pierpaolo Campostrini)表示,「我們正想法設法,減少損失。」他指出,水有在退,並且在蒸發,但鹽分留在了牆體內。

威尼斯市長布魯納羅(Luigi Brugnaro)稱這一創紀錄大水是一場「災難」,並動員了所有力量投入救災工作。他強調,氣候轉變是導致水患頻發的根本原因。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

IEA:離岸風力發電可完全滿足全球電力需求

摘錄自2019年10月26日鉅亨網報導

國際能源總署(IEA)25 日發布指出,海上風力發電可以產生足夠的電力,供應地球上每個家庭和企業,IEA 形容這份報告是歷來對海上風電「最全面的全球研究」結果,該研究針對數十萬英里的海岸線進行分析研究。

IEA 指出,雖然離岸風電在 2010 年至 2018 年高速發展,但是當今的海上風電市場甚至還沒有充分挖掘出全部潛力。

該報告指出,僅開發近岸的主要風力電場所能提供的電力就已超過當今全球消耗的電力總量。但是,海上風電所能生產的最大潛力超過 12 萬百萬瓩,是 2040 年預計全球電力需求的 11 倍,不過這個估算並未將傳輸和儲存電力的困難納入考慮範圍。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

日產將推出聆風純電動車改進款 價格下降13%續航里程提升14%

日產汽車公司日前稱,將推出聆風改款車型,以提振銷售業績,通過改進技術和成本控制,新車價格將比舊款下降約13%,續航里程將提升14%。  

改款聆風采用輕量化鋰離子電池架構,改進了動力總成佈局,因此重量減輕了80千克(180磅)。減重後,充滿電量後的續航里程為228公里(140英里),原來為200公里。第一代聆風開啟空調後續航里程削減為120公里,新車有所改進,但日產未提供具體資料。此外,新車的行李廂空間比舊款更大,充電器被轉移到車的前部,加熱系統效率也有所提升。

聆風在日本的售價為380萬日元,改進款新車價格下跌至330萬日元(約40700美元)。加上日本的新能源車補貼,售價將在260萬日元左右。

新車於20日開始在日本國內銷售,2013年第一季度將登陸美國,歐洲的銷售時間表還未確定。

日產汽車CEO卡洛斯戈恩(Carlos Ghosn)日前表示,本財年(2012年4月至2013年3月)原計劃在全球銷售40000輛聆風,但目前看來該目標難以實現。今年4月至9月,即上半財年,日產在全球共售出11720輛聆風。

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

【其他文章推薦】

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

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

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

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

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

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

日本研發“軟綿綿”電動汽車最高時速50公里

近日,日本廣島大學相關人員創辦的高新技術企業在廣島市內召開新車發佈會,展示了一款最新研發的,車體附有特殊海綿袋,“無論撞上多少次,車身都會恢復原樣”。

這款電動汽車名為“i SAVE YOU”。身為公司社長的廣島大學大學院教授升島努表示:“這款汽車是為老年人和主婦設計的”。該電動汽車為三輪,最高時速50公里。帳篷質地的袋子裡塞入海綿,然後覆蓋在車身四周吸收撞擊時的衝擊力。若發生碰撞時的車速為每小時約10公里,則完全不會對車身產生影響,搭乘者也不會受傷。充電1次可行駛約30公里,駕駛員須持有普通駕照。

這款汽車的研發得到了馬自達公司資深技術人員的協助、以及縣內中小企業的支持,耗時4年左右才完成。車輛售價為89萬日元和79萬日元兩種,銷售目標為一年1千輛。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

加州野火禍首企業與受害者和解 先謀破產脫困

摘錄自2019年12月17日中央社報導

加州去年底發生極具破壞性的「坎普野火」(Camp Fire),造成86人死亡及鉅額財產損失,太平洋瓦斯電力公司(PG&E Corp)的管線被指為起火元凶。美國破產法官17日同意PG&E Corp與野火受害者以135億美元達成和解,讓這家公用事業獲得如期於2020年6月前擺脫破產困境的動力,股價應聲上漲。

這項和解將現金和太平洋瓦斯電力公司股份交付信託,受益人是野火受害者。蒙塔利還同意一項與保險公司達成的110億美元協議,穩妥解決了最後兩個、也是最重要的債權方。

加州州長紐松(Gavin Newsom)的律師密契爾(Nancy Mitchell)告訴蒙塔利,紐松認為野火和解方案很公平,因此不會阻攔;這讓太平洋瓦斯電力公司的脫困計畫又增添一份力量。

 

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

時代2019風雲人物 瑞典環保鬥士童貝里

摘錄自2019年12月12日公視報導

今年的時代雜誌風雲人物在11日出爐。瑞典16歲的環保鬥士童貝里(Greta Thunberg),在眾多競爭者,包括美國總統川普、香港反送中示威者等,脫穎而出,獲選為2019年風雲人物。

小小年紀就為氣候變遷挺身而戰,勇敢批評全球領袖的瑞典16歲環保鬥士童貝里,擊敗眾多對手,獲選為今年的風雲人物。時代雜誌記者阿爾特說:「我們選了童貝里成為今年的風雲人物,因為她在全球面臨的最重要的議題上,發出最有說服力的聲音。」

時代雜誌指出,童貝里勇於發聲,她去年8月展開環保運動,16個月來持續不斷。更走進聯合國,呼籲全球領袖對氣候變遷採取行動,甚至與美國總統川普爭論。她的行動,啟發400萬人在今年9月參加全球氣候罷課,成為有史以來規模最大的氣候示威。

她也是92年來,時代選出的最年輕的風雲人物。童貝里成為風雲人物後,對美聯社表示,沒想過會獲選,但覺得很榮幸,並繼續呼籲全球關注氣候議題。

與童貝里一起競爭年度風雲人物,進入五強的,還有香港反送中示威者。反送中示威者先前在讀者票選中,獲得89%的支持率,可惜最後沒能獲選。不過今年7月,他們已入選時代雜誌25位網路名人榜。另外進入決選的,還有美國總統川普、美國眾議院議長裴洛西、以及促成眾院展開川普彈劾調查「烏克蘭門」的吹哨者。中國國家主席習近平,只入選到前10強,沒有進入決選。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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