世界最大廢水養殖系統在印度 濕地淨化勝過污水處理廠 仍不敵政府開發

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

日本防非洲豬瘟 罰則提高為300萬日圓

摘錄自2020年2月5日民視報導

日本農林水產省表示,要避免非洲豬瘟入境,危害日本養豬和肉品產業,便考慮大幅提高非法攜帶肉品入境的罰則,個人違規將從原本的100萬日圓,提高到300萬日圓,相當於台幣84萬。

而公司法人還一口氣提高50倍,將罰5000萬日圓,約台幣1400萬,相關法案預計在這次會期,提交國會審議。日本海關光是去年10~12月,沒收的違規肉品當中,就有86件驗出非洲豬瘟,當中甚至有部分病毒,仍具有傳染力。

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

土耳其東部連續兩日雪崩 最少38人死

摘錄自2020年2月6日星島日報報導

土耳其東部連續第二日發生雪崩,導致最少38人死亡,多人被活埋。

事發在當地周二(4日)晚,鄰近伊朗邊境的東部邊境凡省的山區發生雪崩,當時一架剷雪車及一架小巴被埋,造成至少五人死亡,兩人失蹤。當局派出300名救援人員周三中午到場尋找失蹤者,卻遭遇第二次雪崩。

當局指事件合共有38人喪生,多人被活埋。當中包括軍人、警察、消防員和志願者。另外有53人受傷,仍有多人被埋在雪下。當地政府指已救出25個被埋的救援人員,但無交代他們的情況。目前仍有超過50人可能被困,警告死傷人數可能增加。

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

CSS(6)—浮動(float)

CSS(6)—通俗講解浮動(float)

CSS有三模塊:盒子模型浮動定位。上篇博客有講到 盒子模型地址:

一、理解浮動

1、概念

概念 浮動可以理解為讓某個div元素脫離標準流,漂浮在標準流之上,和標準流不是一個層次。

如果是第一次聽說肯定還是還是一臉懵,下面我一步一步通過例子來解釋這句話。

舉例說明

我們知道div是塊級元素,在頁面中獨佔一行,自上而下排列,也就是傳說中的標準流

如下圖

可以看出,因為div是塊級元素,所以即使div2的寬度很小,頁面中一行可以容下div2和div3,div3也不會排在div2後邊,因為div元素是獨佔一行的。

那麼我們再看下浮動的作用,這裏我將div2浮動(對div2添加float:left;左浮動屬性)

刷新頁面

通過上下兩張圖片對比,我們可以直觀感覺到,div2有種浮起來的感覺,從之前的平面到立體的感覺。也因為div2浮起來了,那麼它之前所佔的位置也就空出來了,

那麼div3和div4就可以佔據之前div2的位置,所以它們都往上移動了。這樣我們最終看到的效果就是div2和div3,div4有重疊,而且div2是在最上層。

那如果這是我在把div3也設置左浮動呢 (對div3添加float:left;左浮動屬性)

再次刷新頁面

同樣我們可以很直觀的看到,因為div2和div3目前都是左浮動,所以它們的位置都空出來了,這個時候div4就可以往上移動,所以div2和div3都把div4部分給覆蓋了。

通過上面示例,我們應該可以理解什麼是浮動。這裏附上上面示例的代碼,可以自行再研究下

<!DOCTYPE html>
<html> 
<head>
    <title>css浮動</title>
    <style type="text/css">
        div {
           text-align: center;
        }
        .one {
            background-color: gray;
            width: 300px;
            height: 50px;
        }
        .two {
            background-color: yellow;
            width: 100px;
            height: 120px;
            /*float:left;*/
        }
        .three {
            background-color: red;
            width: 150px;
            height: 50px;
            /*float:left;*/ 
        }

        .four {
            background-color: green;
            width: 300px;
            height: 50px;
        }
    </style>
</head>

<body>
    <div class="one"> div1</div>
    <div class="two"> div2</div>
    <div class="three">div3 </div>
    <div class="four"> div4</div>
</body>
</html>

通過上面也可以得出一些結論:

1、假如某個div元素A是浮動的,如果A元素上一個元素也是浮動的,那麼A元素會跟隨在上一個元素的後邊(如果一行放不下這兩個元素,那麼A元素會被擠到下一行);

2、如果A元素上一個元素是標準流中的元素,那麼A的相對垂直位置不會改變,也就是說A的頂部總是和上一個元素的底部對齊。

2、浮動的作用

浮動它主要有兩個作用:1、實現文本圍繞效果2、實現塊級元素在一行显示布局

1)實現文本圍繞效果

示例

<!DOCTYPE html>
<html> 
<head>
    <title>css浮動</title>
    <style type="text/css">
        .father {
            border: 3px solid #005588;
            padding: 1px;
           width: 300px;
        }
        img {
            width: 150px; 
            height: 150px;
            float:left;
        }
    </style>
</head>

<body>
<div class = "father">
    <img src="1.jpeg"/>
    這件衣服價值百萬,奢侈品牌是指服務於奢侈品的品牌。它是品牌等級分類中的最高等級品牌。在生活當中,奢侈品牌享有很特殊的市場和很高的社會地位。在商品分類里,與奢侈品相對應的是大眾商品。奢侈品不僅是提供使用價值的商品,更是提供高附加值的商品。
</div> 
</body>
</html>

運行結果

2)實現塊級元素在一行显示布局

現在很多時候會通過浮動,讓多個div實現一行显示。當然當我們沒有了解浮動之前我們可以通過將塊級元素轉換為行內塊級元素來實現(display: inline-block)。

如圖

這樣確實可以將多個div實現在同一行显示。但這裡會有兩個小問題

 1、我們可以看到div之前會有小縫隙,很難去除。
 2、如果我想讓其中一個div显示在最右邊,實現起來會比較麻煩。

而上面兩個問題可以通過浮動很輕易的解決。

示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>cssdiv元素局一行</title>
    <style>
    div {
        display: inline-block;
        width: 120px;
        height: 80px;
        /*float: left;*/
    }
    .one {
        background-color: pink;
    }
    .two {
        background-color: purple;
    }
    .three {
        background-color: red;
            /*float: right;*/
    }
    </style>
</head>
<body>
    <div class="one">div1</div>
    <div class="two">div2</div>
    <div class="three">div3</div>
</body>
</html>

運行結果

很明顯已經解決。

二、浮動語法

1、浮動的語法

在 CSS 中,我們通過 float 屬性實現元素的浮動。float 屬性定義元素在哪個方向浮動。

基本語法格式

選擇器 {float:屬性值;}

屬性值

2、浮動特性

浮動脫離標準流,不佔位置,會影響標準流。浮動只有左右浮動。

注意 浮動的元素總是找理它最近的父級元素對齊。但是不會超出內邊距的範圍。

如圖

浮動特性

1、浮動脫離標準流,不佔位置,會影響標準流。浮動只有左右浮動。
2、加了浮動的元素盒子是浮起來的,漂浮在其他的標準流盒子上面。
3、加了浮動的盒子,不佔位置的,它浮起來了,它原來的位置會給後面標準流的盒子。
4、一個父盒子裏面的子盒子,如果其中一個子級有浮動的,則其他子級都需要浮動。這樣才能一行對齊显示。
5、元素添加浮動后,元素會具有行內塊元素的特性。元素的大小完全取決於定義的大小或者默認的內容多少浮動根據元素書寫的位置來显示相應的浮動。
6、假如在一行之上只有極少的空間可供浮動元素,那麼這個元素會跳至下一行,這個過程會持續到某一行擁有足夠的空間為止。

總結 浮動的目更多的是為了讓多個塊級元素同一行上显示。

參考

1、

2、

3、

4、

你如果願意有所作為,就必須有始有終。(8)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

Go組件學習——Web框架Gin

以前學Java的時候,和Spring全家桶打好關係就行了,從Spring、Spring MVC到SpringBoot,一脈相承。

對於一個Web項目,使用Spring MVC,就可以基於MVC的思想開發項目了,不管是應對前後端分離還是不分離的場景,你都可以輕鬆駕馭。因為你只要知道,你用的是一個Web開發框架就行了。

相比於Spring在Java一家獨大的局面,Go生態中的Web框架還在百家爭鳴的階段。從今天開始學習一款基於Go語言開發的Web開發框架Gin。

簡介

Github:https://github.com/gin-gonic/gin

語言:Go語言

官網:https://gin-gonic.com/

 

環境搭建

Go版本:1.12.4

系統:macOS

依賴管理工具:go mod

IDE:Goland

因為我使用了go mod,所以引用gin的依賴算是很方便了。

如何創建一個go mod管理的新項目以及如何將老項目改造為go mod,可以參見這篇文章:https://juejin.im/post/5c8e503a6fb9a070d878184a,寫的很詳細了。

這就是我的go-demo:https://github.com/DMinerJackie/go-demo項目的所有第三方依賴了。

那麼如何添加gin的依賴呢?有以下三種方式

  • 直接新建一個基於gin的example程序文件,然後執行 go build xxx.go或者 go run xxx.go命令,go mod就會自動幫你下載gin依賴並更新go.mod文件。

  • 同上,還是新建一個example程序文件,然後在項目根目錄下執行 go mod tidy命令,go mod會幫你安排上。這個命令可以幫助你移除不需要的依賴,並拉取引用你需要的依賴。

  • 在go.mod文件中手動添加依賴類似 github.com/gin-gonic/gin v1.4.0這種。

幾乎不用什麼繁瑣的步驟,就完成了環境搭建。下面開始寫第一個基於Gin的demo

 

第一個Demo

1、新建文件helloworld.go

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // 監聽並在 0.0.0.0:8080 上啟動服務
}

  

2、點擊執行該程序

從控制台程序可以看出服務已經啟動,並且開始監聽8080端口

3、訪問接口

接下來我們在瀏覽器輸入localhost:8080/ping即可看到程序返回的結果

一個極簡的Web服務器就這樣搭建完成並對外訪問了。

上面的代碼中

通過 r:=gin.Default()聲明一個gin的引擎,後續的操作都是基於這個引擎的。

通過 r.GET申明一個可以訪問的路由,定義的HTTP請求方式為GET請求。同時定義了請求后對應的處理方式,即一個閉包函數聲明以JSON格式返回的鍵值對。

通過 r.Run()監聽指定端口並啟動服務

 

其他Demo

1、渲染HTML

雖然現在很多都倡導並實行前後端分離了,即後端只提供HTTP接口,前端負責調用HTTP接口以及頁面渲染。

但還是有前後端揉在一起的使用場景,gin就提供了這種能力。

具體的做法是提供一個HTML模板,服務端將得到的數據填充到模板中實現頁面的渲染。

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	router := gin.Default()
	router.LoadHTMLGlob("main/src/gin-example/examples/templates/**/*")
	router.GET("/posts/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
			"title": "Posts",
		})
	})
	router.GET("/users/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
			"title": "Users",
		})
	})
	router.Run(":8080")
}

  

index.tmpl

{{ define "posts/index.tmpl" }}
<html><h1>
	{{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}

  

user.tmpl

{{ define "users/index.tmpl" }}
<html><h1>
	{{ .title }}
</h1>
<p>Using users/index.tmpl</p>
</html>
{{ end }}

  

對應的HTML模板文件目錄結構如下

代碼部分

router.LoadHTMLGlob用於指明HTML模板文件的路徑

router.GET同上,定義訪問路由和返回結果,不同於第一個Demo的是,這裡有賦值填充的過程,比如

c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
			"title": "Posts",
		})

  

將index.tmpl中定義的 .title替換為”Posts”

執行結果如下

2、PureJSON

func main() {
	r := gin.Default()
	
	// 提供 unicode 實體
	r.GET("/json", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"html": "<b>Hello, 世界!</b>",
		})
	})
	
	// 提供字面字符
	r.GET("/purejson", func(c *gin.Context) {
		c.PureJSON(200, gin.H{
			"html": "<b>Hello, 世界!</b>",
		})
	})
	
	// 監聽並在 0.0.0.0:8080 上啟動服務
	r.Run(":8080")
}

  

這裏兩個GET方法唯一不同的就是要渲染的內容一個使用JSON()方法一個使用PureJSON()方法。

啟動程序后,我們看下訪問結果有什麼不同

可以看出JSON()渲染的會有中文以及標籤轉為unicode編碼,但是使用PureJSON()渲染就是原樣輸出(我的瀏覽器裝了插件,會自動解碼,所以不點擊右邊的”RAW“兩個接口返回的結果是一樣的)。

這個問題,本周我們服務端在和客戶端對接的時候還遇到了,因為框架返回的JSON串就是經過編碼的,但是單獨請求放到瀏覽器是沒有問題的,客戶端收到的卻是經過編碼的,最後排查發現是瀏覽器插件解碼了。

3、渲染多種數據交換格式的數據

gin支持渲染XML、JSON、YAML和ProtoBuf等多種數據格式

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/testdata/protoexample"
	"net/http"
)

func main() {
	r := gin.Default()

	// gin.H 是 map[string]interface{} 的一種快捷方式
	r.GET("/someJSON", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	})

	r.GET("/moreJSON", func(c *gin.Context) {
		// 你也可以使用一個結構體
		var msg struct {
			Name    string `json:"user"`
			Message string
			Number  int
		}
		msg.Name = "Lena"
		msg.Message = "hey"
		msg.Number = 123
		// 注意 msg.Name 在 JSON 中變成了 "user"
		// 將輸出:{"user": "Lena", "Message": "hey", "Number": 123}
		c.JSON(http.StatusOK, msg)
	})

	r.GET("/someXML", func(c *gin.Context) {
		c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	})

	r.GET("/someYAML", func(c *gin.Context) {
		c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	})

	r.GET("/someProtoBuf", func(c *gin.Context) {
		reps := []int64{int64(1), int64(2)}
		label := "test"
		// protobuf 的具體定義寫在 testdata/protoexample 文件中。
		data := &protoexample.Test{
			Label: &label,
			Reps:  reps,
		}
		// 請注意,數據在響應中變為二進制數據
		// 將輸出被 protoexample.Test protobuf 序列化了的數據
		c.ProtoBuf(http.StatusOK, data)
	})

	// 監聽並在 0.0.0.0:8080 上啟動服務
	r.Run(":8080")
}

  

今天先到這,後面再看看gin的源碼。

 

如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

全棧項目|小書架|服務器開發-JWT 詳解

JWT

文章基本是官網內容的翻譯,英文不錯的同學可點擊上面的鏈接直接看英文文檔。

什麼是 JWT

JWT全稱是JSON Web Token(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間作為JSON對象安全地傳輸信息。由於此信息是經過数字簽名的,因此可以被驗證和信任。

可以使用密鑰(HMAC算法)或使用RSAECDSA的公用/專用密鑰對對JWT進行簽名。

什麼時候使用 JWT 驗證

  • 授權(Authorization)
    這是使用JWT的最常見情況。一旦用戶登錄,每個後續請求將包括JWT,從而允許用戶訪問該令牌允許的路由,服務和資源。單一登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小並且可以在不同的域中輕鬆使用。
  • 信息交換(Information Exchange)
    JWT是在各方之間安全地傳輸信息的好方法。因為可以對JWT進行簽名(例如,使用公鑰/私鑰對),所以您可以確保發件人是他們所說的人。另外,由於簽名是使用Headerpayload計算的,因此您還可以驗證內容是否未被篡改。

JWT 的結構格式

由三部分組成,這些部分由點.分隔,分別是:

  • Header
  • Payload
  • Signature

因此,JWT通常如下所示。

xxxxx.yyyyy.zzzzz

Header

通常由兩部分組成:

  • 令牌的類型(即JWT
  • 所使用的簽名算法,例如: 或。

例如:

{
  "alg": "HS256",
  "typ": "JWT"
}

然後,將此JSON通過Base64Url編碼以形成JWT的第一部分。

Payload

令牌的第二部分是有效負載,其中包含聲明。聲明是有關實體(通常是用戶)和其他數據的聲明。共有三種類型的索賠: registered、public、private claims

  • Registered claims
    這些是一組預定義的權利要求,不是強制性的,而是建議使用的,以提供一組有用的可互操作的權利要求。其中一些是:iss(發出者),exp(到期時間),sub(主題),aud(受眾) 等。
    Tip: 請注意,聲明名稱僅是三個字符,因為JWT是緊湊的。
  • Public claims
    這些可以由使用JWT的人員隨意定義。但是為避免衝突,應在IANA JSON Web令牌註冊表中定義它們,或將其定義為包含抗衝突名稱空間的URI
  • Private claims
    這些是自定義聲明,旨在在同意使用它們的各方之間共享信息,既不是註冊聲明也不是公共聲明。

有效負載示例:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

同樣需要Base64Url編碼,以形成JWT的第二部分。

Signature

簽名(Signature)用於驗證消息在整個過程中沒有更改,並且對於使用私鑰進行簽名的令牌,它還可以驗證JWT的發送者是它所說的真實身份。

例如,如果要使用HMAC SHA256算法,則將通過以下方式創建簽名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

將這三部分合併

輸出是三個由.分隔的Base64-URL字符串,可以在HTMLHTTP環境中輕鬆傳遞這些字符串,與基於XML的標準(例如SAML)相比,它更緊湊。

下圖显示了一個JWT,它已對先前的HeaderPayload進行了編碼,並用一個Signature

可以在這個網頁 驗證和生成JWT

JWT 如何工作

在身份驗證中,當用戶使用其憑據成功登錄時,將返回令牌。由於令牌是憑據,因此必須格外小心以防止安全問題。通常,令牌的有效時間不宜設置過長。

Tip: 由於缺乏安全性,您也不應該將敏感的會話數據存儲在瀏覽器存儲中。

每當用戶想要訪問受保護的路由或資源時,用戶代理通常應在Bearer模式中使用授權頭髮送JWTHeader的內容應如下所示:

Authorization: Bearer <token>

在某些情況下,接口訪問並不需要身份授權。服務器的受保護路由將在Authorization Header中檢查JWT令牌是否有效,如果存在且有效,則將允許用戶訪問受保護的資源。

如果JWT包含必要的數據,則可以減少查詢數據庫中某些操作的需求。

如果令牌是在Authorization Header中發送的,則跨域資源共享 不會成為問題,因為它不使用cookie

下圖显示了如何獲取JWT並將其用於訪問API或資源

  1. 應用程序或客戶端向授權服務器請求授權。生產JWT令牌
  2. 授予授權后,授權服務器會將訪問令牌返回給應用程序。
  3. 應用程序使用訪問令牌來訪問受保護的資源(例如API)。
  4. 服務器檢查JWT令牌是否有效,返回對應結果給客戶端

下圖詳細的流程:

ps:請注意,使用簽名令牌,令牌或令牌中包含的所有信息都會暴露給用戶或其他方,即使他們無法更改它。這意味着您不應將機密信息放入令牌中。

為什麼需要 JWT

對比 Simple Web Tokens (SWT)Security Assertion Markup Language Tokens (SAML),看看使用JSON Web Tokens (JWT) 有什麼好處。

  • 由於JSON不如XML冗長,因此在編碼時JSON的大小也較小,從而使JWTSAML更緊湊。這使得JWT是在HTMLHTTP環境中傳遞的不錯的選擇。
  • 在安全方面,SWT只能使用HMAC算法進行對稱簽名。但是JWTSAML令牌可以使用X.509證書形式的公用/專用密鑰對進行簽名。與簽名JSON的簡單性相比,使用XML Digital Signature簽名XML而不引入模糊的安全漏洞是非常困難的。
  • JSON解析器在大多數編程語言中都很常見,因為它們直接映射到對象。相反,XML沒有自然的文檔到對象映射。與SAML斷言相比,這使使用JWT更加容易。
  • 關於用法,JWT是在Internet規模上使用的。這強調了在多個平台(尤其是移動平台)上對JSON Web令牌進行客戶端處理的簡便性。

如果您想了解有關JSON Web令牌的更多信息,甚至開始使用它們在自己的應用程序中執行身份驗證,請瀏覽到 頁面。

諮詢請加微信:輕撩即可。

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

Redis持久化的幾種方式——深入解析RDB

Redis 的讀寫都是在內存中,所以它的性能較高,但在內存中的數據會隨着服務器的重啟而丟失,為了保證數據不丟失,我們需要將內存中的數據存儲到磁盤,以便 Redis 重啟時能夠從磁盤中恢復原有的數據,而整個過程就叫做 Redis 持久化。

Redis 持久化也是 Redis 和 Memcached 的主要區別之一,因為 Memcached 是不具備持久化功能的。

1.持久化的幾種方式

Redis 持久化擁有以下三種方式:

  • 快照方式(RDB, Redis DataBase)將某一個時刻的內存數據,以二進制的方式寫入磁盤;
  • 文件追加方式(AOF, Append Only File),記錄所有的操作命令,並以文本的形式追加到文件中;
  • 混合持久化方式,Redis 4.0 之後新增的方式,混合持久化是結合了 RDB 和 AOF 的優點,在寫入的時候,先把當前的數據以 RDB 的形式寫入文件的開頭,再將後續的操作命令以 AOF 的格式存入文件,這樣既能保證 Redis 重啟時的速度,又能簡單數據丟失的風險。

因為每種持久化方案,都有特定的使用場景,讓我們先從 RDB 持久化說起吧。

2.RDB簡介

RDB(Redis DataBase)是將某一個時刻的內存快照(Snapshot),以二進制的方式寫入磁盤的過程。

3.持久化觸發

RDB 的持久化觸發方式有兩類:一類是手動觸發,另一類是自動觸發。

1)手動觸發

手動觸發持久化的操作有兩個: save 和 bgsave ,它們主要區別體現在:是否阻塞 Redis 主線程的執行。

① save 命令

在客戶端中執行 save 命令,就會觸發 Redis 的持久化,但同時也是使 Redis 處於阻塞狀態,直到 RDB 持久化完成,才會響應其他客戶端發來的命令,所以在生產環境一定要慎用

save 命令使用如下:

從圖片可以看出,當執行完 save 命令之後,持久化文件 dump.rdb 的修改時間就變了,這就表示 save 成功的觸發了 RDB 持久化。
save 命令執行流程,如下圖所示:

② bgsave 命令

bgsave(background save)既後台保存的意思, 它和 save 命令最大的區別就是 bgsave 會 fork() 一個子進程來執行持久化,整個過程中只有在 fork() 子進程時有短暫的阻塞,當子進程被創建之後,Redis 的主進程就可以響應其他客戶端的請求了,相對於整個流程都阻塞的 save 命令來說,顯然 bgsave 命令更適合我們使用。
bgsave 命令使用,如下圖所示:

bgsave 執行流程,如下圖所示:

2)自動觸發

說完了 RDB 的手動觸發方式,下面來看如何自動觸發 RDB 持久化?
RDB 自動持久化主要來源於以下幾種情況。

① save m n

save m n 是指在 m 秒內,如果有 n 個鍵發生改變,則自動觸發持久化。
參數 m 和 n 可以在 Redis 的配置文件中找到,例如,save 60 1 則表明在 60 秒內,至少有一個鍵發生改變,就會觸發 RDB 持久化。
自動觸發持久化,本質是 Redis 通過判斷,如果滿足設置的觸發條件,自動執行一次 bgsave 命令。
注意:當設置多個 save m n 命令時,滿足任意一個條件都會觸發持久化。
例如,我們設置了以下兩個 save m n 命令:

  • save 60 10
  • save 600 1

當 60s 內如果有 10 次 Redis 鍵值發生改變,就會觸發持久化;如果 60s 內 Redis 的鍵值改變次數少於 10 次,那麼 Redis 就會判斷 600s 內,Redis 的鍵值是否至少被修改了一次,如果滿足則會觸發持久化。

② flushall

flushall 命令用於清空 Redis 數據庫,在生產環境下一定慎用,當 Redis 執行了 flushall 命令之後,則會觸發自動持久化,把 RDB 文件清空。
執行結果如下圖所示:

③ 主從同步觸發

在 Redis 主從複製中,當從節點執行全量複製操作時,主節點會執行 bgsave 命令,並將 RDB 文件發送給從節點,該過程會自動觸發 Redis 持久化。

4.配置說明

合理的設置 RDB 的配置,可以保障 Redis 高效且穩定的運行,下面一起來看 RDB 的配置項都有哪些?

RDB 配置參數可以在  Redis 的配置文件中找見,具體內容如下:

# RDB 保存的條件
save 900 1
save 300 10
save 60 10000

# bgsave 失敗之後,是否停止持久化數據到磁盤,yes 表示停止持久化,no 表示忽略錯誤繼續寫文件。
stop-writes-on-bgsave-error yes

# RDB 文件壓縮
rdbcompression yes

# 寫入文件和讀取文件時是否開啟 RDB 文件檢查,檢查是否有無損壞,如果在啟動是檢查發現損壞,則停止啟動。
rdbchecksum yes

# RDB 文件名
dbfilename dump.rdb

# RDB 文件目錄
dir ./

其中比較重要的參數如下列表:
① save 參數
它是用來配置觸發 RDB 持久化條件的參數,滿足保存條件時將會把數據持久化到硬盤。
默認配置說明如下:

  • save 900 1:表示 900 秒內如果至少有 1 個 key 值變化,則把數據持久化到硬盤;
  • save 300 10:表示 300 秒內如果至少有 10 個 key 值變化,則把數據持久化到硬盤;
  • save 60 10000:表示 60 秒內如果至少有 10000 個 key 值變化,則把數據持久化到硬盤。

② rdbcompression 參數
它的默認值是 yes 表示開啟 RDB 文件壓縮,Redis 會採用 LZF 算法進行壓縮。如果不想消耗 CPU 性能來進行文件壓縮的話,可以設置為關閉此功能,這樣的缺點是需要更多的磁盤空間來保存文件。
③ rdbchecksum 參數
它的默認值為 yes 表示寫入文件和讀取文件時是否開啟 RDB 文件檢查,檢查是否有無損壞,如果在啟動是檢查發現損壞,則停止啟動。

5.配置查詢

Redis 中可以使用命令查詢當前配置參數。查詢命令的格式為:config get xxx ,例如,想要獲取 RDB 文件的存儲名稱設置,可以使用 config get dbfilename ,執行效果如下圖所示:

查詢 RDB 的文件目錄,可使用命令 config get dir ,執行效果如下圖所示:

6.配置設置

設置 RDB 的配置,可以通過以下兩種方式:

  • 手動修改 Redis 配置文件;
  • 使用命令行設置,例如,使用 config set dir "/usr/data" 就是用於修改 RDB 的存儲目錄。

注意:手動修改 Redis 配置文件的方式是全局生效的,即重啟 Redis 服務器設置參數也不會丟失,而使用命令修改的方式,在 Redis 重啟之後就會丟失。但手動修改 Redis 配置文件,想要立即生效需要重啟 Redis 服務器,而命令的方式則不需要重啟 Redis 服務器。

小貼士:Redis 的配置文件位於 Redis 安裝目錄的根路徑下,默認名稱為 redis.conf。

7.RDB 文件恢復

當 Redis 服務器啟動時,如果 Redis 根目錄存在 RDB 文件 dump.rdb,Redis 就會自動加載 RDB 文件恢復持久化數據。
如果根目錄沒有 dump.rdb 文件,請先將 dump.rdb 文件移動到 Redis 的根目錄。
驗證 RDB 文件是否被加載
Redis 在啟動時有日誌信息,會显示是否加載了 RDB 文件,我們執行 Redis 啟動命令:src/redis-server redis.conf ,如下圖所示:

從日誌上可以看出, Redis 服務在啟動時已經正常加載了 RDB 文件。

小貼士:Redis 服務器在載入 RDB 文件期間,會一直處於阻塞狀態,直到載入工作完成為止。

8.RDB 優缺點

1)RDB 優點

  • RDB 的內容為二進制的數據,佔用內存更小,更緊湊,更適合做為備份文件;
  • RDB 對災難恢復非常有用,它是一個緊湊的文件,可以更快的傳輸到遠程服務器進行 Redis 服務恢復;
  • RDB 可以更大程度的提高 Redis 的運行速度,因為每次持久化時 Redis 主進程都會 fork() 一個子進程,進行數據持久化到磁盤,Redis 主進程並不會執行磁盤 I/O 等操作;
  • 與 AOF 格式的文件相比,RDB 文件可以更快的重啟。

    2)RDB 缺點

  • 因為 RDB 只能保存某個時間間隔的數據,如果中途 Redis 服務被意外終止了,則會丟失一段時間內的 Redis 數據;
  • RDB 需要經常 fork() 才能使用子進程將其持久化在磁盤上。如果數據集很大,fork() 可能很耗時,並且如果數據集很大且 CPU 性能不佳,則可能導致 Redis 停止為客戶端服務幾毫秒甚至一秒鐘。

    9.禁用持久化

    禁用持久化可以提高 Redis 的執行效率,如果對數據丟失不敏感的情況下,可以在連接客戶端的情況下,執行 config set save "" 命令即可禁用 Redis 的持久化,如下圖所示:

    10.小結

    通過本文我們可以得知,RDB 持久化分為手動觸發和自動觸發兩種方式,它的優點是存儲文件小,Redis 啟動時恢複數據比較快,缺點是有丟失數據的風險。RDB 文件的恢復也很簡單,只需要把 RDB 文件放到 Redis 的根目錄,在 Redis 啟動時就會自動加載並恢複數據。

    11.思考題

    如果 Redis 服務器 CPU 佔用過高,可能是什麼原因導致的?歡迎各位在評論區,寫下你們的答案。

    12.參考&鳴謝

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

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

Python字典 你必須知道的用法系列

本文Python版本為3.7.X,閱讀本文之前需了解python字典的基本用法。

介紹

字典(dict)是Python中內置的一個數據結構,由多個鍵值對組成,鍵(key)和值(value)用冒號分隔,每個鍵值對之間用逗號(,)分隔,整個字典包括在大括號中({}),鍵必須是唯一的,值可以取任何類型,但是鍵必須是不可變類型,如字符串,数字或元組。

底層使用了hash表來關聯key和value,dict是無序的。特點包括:

  1. 查找和插入的速度極快,不會隨着key的增加而變慢;
  2. 需要佔用的內存較多

所以,dict是一種以空間換取時間的數據結構,應用於需要快速查找的場景。

操作

常用方法

get()

返回指定鍵的值,如果key不存在,則返回默認值(默認為None),而不會報錯,語法為dict.get(key)。

dict_1['age'] = 24

In [7]: print(dict_1.get('age'))
24

In [11]: print(dict_1.get('nama'))
None

In [12]: print(dict_1['nama'])
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-12-ef61a380920e> in <module>
----> 1 print(dict_1['nama'])

KeyError: 'nama'

key in dict

使用in操作符來判斷鍵是否存在於字典中,存在則返回True,否則返回False,語法為:key in dict。

In [15]: dict_1
Out[15]: {'name': None, 'age': 24, 'sex': None}

In [16]: print('name' in dict_1)
True

In [17]: print('nama' in dict_1)
False

在python 2中該功能使用has_key()方法實現。

items()

以列表形式返回可遍歷的(鍵, 值)元組數組,語法為dict.items()。

In [18]: dict_1
Out[18]: {'name': None, 'age': 24, 'sex': None}

In [19]: print(dict_1.items())
dict_items([('name', None), ('age', 24), ('sex', None)])

In [20]: for key, value in dict_1.items():
    ...:     print(key, value)
    ...:
name None
age 24
sex None

keys()

以列表返回一個字典的所有鍵:dict.keys()

In [21]: dict_1
Out[21]: {'name': None, 'age': 24, 'sex': None}

In [22]: print(dict_1.keys())
dict_keys(['name', 'age', 'sex'])

values()

以列表形式返回字典中的所有值:dict.values()

In [27]: dict_1
Out[27]: {'name': None, 'age': 24, 'sex': None, 'sub_name': 'Tony'}

In [28]: print(dict_1.values())
dict_values([None, 24, None, 'Tony'])

setdefault()

和get()類似,用戶獲得與給頂尖相關聯的值,不同的是,該方法如果鍵不存在時會添加鍵並將值設為默認值,語法為:dict.setdefault(key, default=None)。

In [23]: dict_1
Out[23]: {'name': None, 'age': 24, 'sex': None}

In [24]: print(dict_1.setdefault('name'))
None

In [25]: print(dict_1.setdefault('name', 'Tony'))
None

In [26]: print(dict_1.setdefault('sub_name', 'Tony'))
Tony

In [27]: dict_1
Out[27]: {'name': None, 'age': 24, 'sex': None, 'sub_name': 'Tony'}

update()

語法為:dict_1.update(dict_2),用於把dict_2的鍵值對更新到dict_1中,如果有相同的鍵會被覆蓋。

In [31]: dict_1
Out[31]: {'name': None, 'age': 24, 'sex': None, 'sub_name': 'Tony'}

In [32]: dict_2
Out[32]: {'name': 'Mary', 'age': 18, 'sex': None, 'sub_name': ''}

In [33]: dict_1.update(dict_2)

In [34]: dict_1
Out[34]: {'name': 'Mary', 'age': 18, 'sex': None, 'sub_name': ''}

clear()

刪除字典中的所有項,dict.clear(),舉個例子:

In [1]: dict_1 = dict(name="Tony", age=24)

In [2]: dict_2 = dict_1

In [3]: print(dict_2)
{'name': 'Tony', 'age': 24}

In [4]: dict_2.clear()

In [5]: dict_2
Out[5]: {}

In [6]: dict_1
Out[6]: {}

copy()

淺拷貝原始字典,返回一個具有相同鍵值對的新字典,dict.copy(),舉個例子:

In [1]: dict_1 = dict(name='Tony', info=['boy', 24])

In [2]: dict_3 = dict_1.copy()

In [3]: dict_3['name'] = "Ring"

In [4]: dict_3['info'].remove('boy')

In [5]: dict_3
Out[5]: {'name': 'Ring', 'info': [24]}

In [6]: dict_1
Out[6]: {'name': 'Tony', 'info': [24]}

fromkeys()

創建一個新字典,dict.fromkeys(seq[, value]),以序列seq中的元素做字典的鍵,value為字典所有鍵對應的初始值,其中value為可選參數, 默認為None。適用於數據初始化,舉個例子:

In [1]: info = ['name', 'age', 'sex']

In [2]: dict_1 = dict.fromkeys(info)

In [3]: dict_1
Out[3]: {'name': None, 'age': None, 'sex': None}

常見操作

合併字典

有四種方式:

  1. 常規處理
In [15]: dict_1
Out[15]: {'Tony': 24}

In [16]: dict_2
Out[16]: {'ben': 18}

In [17]: dict3 = dict()

In [18]: for key, value in dict_1.items():
    ...:     dict_3[key] = value
    ...:

In [19]: for key, value in dict_2.items():
    ...:     dict_3[key] = value
    ...:

In [20]: dict_3
Out[20]: {'Tony': 24, 'ben': 18}
  1. update()
In [9]: dict_1
Out[9]: {'Tony': 24}

In [10]: dict_2
Out[10]: {'ben': 18}

In [12]: dict_3 = dict_1.copy()

In [13]: dict_3.update(dict_2)

In [14]: dict_3
Out[14]: {'Tony': 24, 'ben': 18}
  1. 藉助字典的dict(d1, **d2)方法
In [33]: dict_1
Out[33]: {'Tony': 24}

In [34]: dict_2
Out[34]: {'ben': 18}

In [35]: dict_3 = dict(dict_1, **dict_2)

In [36]: dict_3
Out[36]: {'Tony': 24, 'ben': 18}

進階

字典推導式

和列表推導式類似,優點是底層用C實現,會快很多,推薦使用。

對換字典的鍵值

使用字典推導式可以輕鬆對換一個字典的鍵值:

In [42]: dict_4
Out[42]: {24: 'Tony', 18: 'ben'}

In [43]: dict_3
Out[43]: {'Tony': 24, 'ben': 18}

In [44]: dict_4 = {k:v for v, k in dict_3.items()}

In [45]: dict_4
Out[45]: {24: 'Tony', 18: 'ben'}

從字典中提取子集

想創建一個字典,其本身是另一個字典的子集。

舉個例子:

In [88]: a = {'Ben': 18, 'Jack': 12, 'Ring': 23, 'Tony': 24}

In [89]: b = {k:v for k, v in a.items() if v > 18}

In [90]: b
Out[90]: {'Ring': 23, 'Tony': 24}

生成有序字典

在Python3.6之前的字典是無序的,但是有時候我們需要保持字典的有序性,orderDict可以在dict的基礎上實現字典的有序性,這裏的有序指的是按照字典key插入的順序來排列,這樣就實現了一個先進先出的dict,當容量超出限制時,先刪除最早添加的key。
舉例:

In [49]: from collections import OrderedDict

In [50]: ordered_dict = OrderedDict([('a', 2), ('b', 4), ('c', 5)])

In [51]: for key, value in ordered_dict.items():
    ...:     print(key, value)
    ...:
a 2
b 4
c 5

可以看到OrderedDict是按照字典創建時的插入順序來排序。

原理:OrderedDict內部維護了一個雙向鏈表,它會根據元素加入的順序來排列鍵的位置,這也就導致OrderedDict的大小是普通字典的2倍多。

合併列表中key相同的字典

也就是生成所謂的一鍵多值字典,需要將對應的多個值保存在其它容器比如列表或集合,取決於多值是否需要保證唯一性。

舉個例子:

In [64]: from collections import defaultdict

In [65]: a = [{'a': 1}, {'b': 3}, {'c': 4}, {'a':5}, {'b':2}, {'b': 4}]

In [66]: b = defaultdict(list)

In [67]: [b[k].append(v) for item in a for k, v in item.items()]
Out[67]: [None, None, None, None, None, None]

In [68]: b
Out[68]: defaultdict(list, {'a': [1, 5], 'b': [3, 2, 4], 'c': [4]})

In [69]: b['a']
Out[69]: [1, 5]

尋找兩個字典的異同

場景:尋找兩個字典中的異同,包括相同的鍵或者相同的值。

分析:字典是一系列鍵值之間的映射集合,有以下特點:

  1. keys()會返回字典中的所有鍵,並且字典的鍵是支持集合操作的,所以利用集合的交叉並補即可對字典的鍵進行處理;
  2. items()返回(key, value)組成的對象,支持集合操作;
  3. values()並不支持集合操作,因為並不能保證所有的值是唯一的,但是如果必須要判斷操作,可以先將值轉化為集合來實現。

舉例:

In [78]: a = {'a':1, 'b':2, 'c':3}

In [79]: b = {'b':3, 'c':3, 'd':4}

In [80]: a.keys() & b.keys()
Out[80]: {'b', 'c'}

In [81]: a.keys() - b.keys()
Out[81]: {'a'}

In [82]: a.items() & b.items()
Out[82]: {('c', 3)}

再舉一個例子,在創建一個字典時,期望可以去除某些鍵:

In [85]: a
Out[85]: {'a': 1, 'b': 2, 'c': 3}

In [86]: c = {k: a[key] for k in a.keys() - {'b'}}

In [87]: c
Out[87]: {'a': 3, 'c': 3}

以上。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

總結一下 IEnumerable 的例子

本篇將圍繞 《》和《》給出的例子,總結一下對於 IEnumerable 接口的一些使用方法,希望讀者能夠從中獲得一些啟發。

框架類型的迭代

對於一個實現了 IEnumerable 接口的類型來說,開發中最常用的,就是把這個類型的對象放入到 foreach 等循環關鍵詞中進行迭代,遍歷其中的元素進行處理。

這種遍歷通常分為兩種目的:遍歷和查找。

IEnumerable 及其泛型版本 IEnumerable<T> 定義了一個類型的 “可迭代性”。這點很容易理解,系統中的很多集合類型都實現了該接口。

因此這些集合類型均可以採用 foreach 進行迭代遍歷。但是每個集合類型的迭代方式和結果是不完全相同的,這取決於集合本身的特性。例如:

  • List<>Stack<> 和 Queue<> 的迭代的順序不相同,因為數據結構本身要求是不同的
  • ConcurrentDictionary<,> 和 Dictionary<,> 在迭代時的線程安全性是不同的,因為針對線程安全的設計是不同的
  • BlockingCollection.GetConsumingEnumerable 方法返回一個會產生阻塞的消費者對象,

所以,即使都是丟進 foreach,但是效果也是不完全一樣的。使用這些,需要讀者對這些類型本身需要增進了解。

建議讀者在使用框架中實現了 IEnumerable 的類型時,一定要注意迭代的細節,可以通過 MSDN 上的文檔了解其特殊性。

Linq

Linq 是一個說小不小的話題,這裏只是說其中的 Linq To Object 部分內容。

通過 Linq 中提供的一些擴展方法,可以方便的控制對於一個 IEnumerable 對象的迭代方式。通過這些方法的應用,可以在很多時候避免複雜的條件和循環嵌套。

同時,Linq 中抽象的 Func 和 Action,也要求開發人員在平時的編寫過程中注意對於迭代本身的歸類和整理。Where(IsLeapYear) 會比 Where(x=>(x % 4 == 0 && x % 100 != 0) || x % 400 == 0) 來的更加容易閱讀。

設計複雜的數據結構及其迭代算法

除了基礎的數據結構,開發過程中有時需要自定義一些集合類型。這些集合類型需要自己實現一個迭代過程。例如:二叉樹及其遍歷,對列表進行分頁等等。

這些數據結構的迭代通常需要特定算法的支持。

在《》中關於樹的幾個例子便數據此類中。

本地函數

在 C#7.0 引入了之後, IEnumerable 結合本地函數,快速實現自定義迭代過程的奇怪操作也就跟着出現。

通過這種操作可以在一個函數內採用一些以前不容易實現的方式實現一些操作:

  • 將多重循環拉平
  • 將多級條件判斷變為循環判斷
  • 無需創建新的類就能快速生成一個上下文需要的特殊迭代算法

這相關的例子在《》中較多。

按照月老闆的名言:“業務複雜度是不會因為系統設計變化而減少的,它只是從一個地方轉移到了另外的地方。”,我們可以知道,這種寫法其實沒有使得原來就有的判斷和循環變少。只是改變了語法結構。

讀者可以將這種操作作為一種 “語法糖” 進行使用。如果是在團隊項目中,則需要尊重團隊成員的共同意見,因為這種操作並非所有人都願意接受。

當然,這種做法在一些地方會產生好處。例如在將本地函數、IEnumerable 和 Task 相結合的 T10 測試網絡連接 中。這種寫法就減少了傳統寫法中需要創建一個 List 或者 Array 的開銷。

總之,這種寫法,提供了一種新的思路。是否一定要使用,將取決於讀者團隊的接受程度。

異步迭代器

在 C# 8 和 .netcore 3.0 到來的版本中,我們迎接到了 IAsyncEnumerable 接口來實現異步迭代器的功能。

IEnumerable 是同步方法的迭代器,IAsyncEnumerable 可以看做是其異步版本。有了這個接口,那麼在迭代的過程中也可以充分利用 async/await 帶來的編程快感。

本系列中沒有添加這部分的示例,但是主體思路是一致的。

她的出現,只會使得開發者更容易應用以上總結的幾種主要場景。

詳細的例子,可以。

總結

本系列到此便結束了,希望讀者多在實踐中體會以上總結的幾種使用場景。

本系列中的例子已經全部使用  進行了重寫,讀者可以直接在本博客的頁面上運行這些示例。

如果無法正常的展示示例,讀者也可以。

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

Asp.net Core 系列之–4.事務、日誌及錯誤處理

ChuanGoing 2019-11-17

    這篇原本想把事務處理、日誌處理、錯誤處理、授權與鑒權一併介紹完的,授權和鑒權我想結合自定義權限來介紹,全部放到這裏篇幅可能太長,因此權限部分將會在下篇來介紹。先說下我接下來的打算把,下篇將介紹權限控制,結合Oauth2.0和OpenId(OIDC)以及自定義權限來介紹;完了後會結合之前所介紹的基礎來實現一個簡單的電商網站,當然是利用領域驅動設計來實現。我的這個系列的主題就是領域驅動設計,實現簡單電商網站時將會深入的講解下領域的劃分原則及領域服務的場景,中間可能會嘗試部分業務實現事件驅動。

本篇學習曲線:

1.日誌記錄

2.錯誤處理

3.事務處理

日誌記錄

    NLog是一個記錄日誌組件,和log4net一樣被廣泛使用,它可以將日誌保存到文本文件、CSV、控制台、VS調試窗口、數據庫等。在之前例子中的WebApi項目中添加NLog.Web.AspNetCore的Nuget包,並添加如下配置:

 

 

   簡單介紹下配置信息,“targets”配置每個輸出配置,我這裡有3個輸出:database、allfile、ownfile,分別表示輸出到數據庫和對應路徑的日誌文件下。

“rules”規則配置了4條:

1.將Debug以上級別(含)信息輸出到allfile

2.忽略Microsoft.*開頭的信息(對應的輸出沒有配置到任何文件),此配置一般忽略即可

3.將Debug以上級別(含)信息輸出到ownfile(注意這裏配置和allfile一樣,一般配置級別高點的日誌信息)

4.將Warn以上級別(含)信息輸出到數據庫

完了后,在Program.cs Main方法裏面註冊NLog:

var logger = NLogBuilder.ConfigureNLog($"Nlog.config").GetCurrentClassLogger();
            try
            {
                CreateWebHostBuilder(args).Build().Run();
            }
            catch (Exception ex)
            {
                logger.Error(ex, "Stopped program because of exception");
                throw ex;
            }
            finally
            {
                NLog.LogManager.Shutdown();
            }

注意不要忘了啟用NLog組件使之生效

 

在OrderController的Add方法中加入以下代碼:

 

用postman簡單測試下,我們可以看到執行目錄中多出來了日誌信息

 

 

錯誤處理

  這裏一般我們關心的錯誤大概有兩類:

1.內部錯誤,即通過框架(Mvc)管道準確的傳入到內部系統中併發生錯誤的此類信息

2.框架(Mvc)執行管道的某些中間件時發生的錯誤或被中間件禁止繼續訪問的請求

  因此,定義如下3個類:

public class InnerException : Exception
    {
        /// <summary>
        /// 內部錯誤代碼
        /// </summary>
        public int? ErrorCode { get; }

        public InnerException(int errorCode) : base()
        {
            ErrorCode = errorCode;
        }

        public InnerException(int errorCode, string message) : base(message)
        {
            ErrorCode = errorCode;
        }

        public InnerException(int code, string message, Exception exception) : base(message, exception)
        {
            ErrorCode = code;
        }
    }

InnerException

public class MessageCodes
    {
        #region 公用

        /// <summary>
        /// 成功
        /// </summary>
        public const int Success = 20101000;
        /// <summary>
        /// 警告
        /// </summary>
        public const int Warning = 20102000;
        /// <summary>
        /// 錯誤
        /// </summary>
        public const int Error = 20103000;
        /// <summary>
        /// 數據驗證錯誤
        /// </summary>
        public const int DataValidationError = 20104000;
        /// <summary>
        /// 數據不存在
        /// </summary>
        public const int DataNotFound = 20105000;
        /// <summary>
        /// 非法的數據狀態
        /// </summary>
        public const int IllegalState = 20106000;
        /// <summary>
        /// 參數無效
        /// </summary>
        public const int InvalidParams = 20107000;
        /// <summary>
        /// 輸入非法
        /// </summary>
        public const int IllegalInput = 20108000;
        /// <summary>
        /// 鑒權成功
        /// </summary>
        public const int AuthSuccess = 20109000;

        #endregion

    }

MessageCodes

 public class WebException: InnerException
    {
        public HttpStatusCode HttpStatus { get; set; }

        public HttpRequest Request { get; private set; }

        public WebException(HttpStatusCode httpStatus, int errorCode, string message)
             : base(errorCode, message)
        {
            HttpStatus = httpStatus;
        }

        public WebException(HttpStatusCode httpStatus, int errorCode, string message, HttpRequest request)
            : this(httpStatus, errorCode, message)
        {
            Request = request;
        }

        public WebException(int errorCode, string message)
            : base(errorCode, message)
        {
            HttpStatus = HttpStatusCode.BadRequest;
        }
    }

WebException

通過Aop,很方便就可以實現錯誤信息的處理:

public class ExceptionFilter : IExceptionFilter
    {
        private readonly ILogger<ExceptionFilter> _logger;

        public ExceptionFilter(ILogger<ExceptionFilter> logger)
        {
            _logger = logger;
        }

        public void OnException(ExceptionContext context)
        {
            _logger.LogError(context.Exception, context.Exception.Message);

            #region Ioc/automapper等中間件對錯誤信息進行了包裝,需要解包

            //web錯誤:驗證/鑒權等
            var webException = GetException<Base.Exceptions.WebException>(context.Exception);
            if (webException != null)
            {
                context.Result = new JsonResult(new
                {
                    ErrorCode = webException.ErrorCode ?? MessageCodes.Error,
                    webException.Message
                })
                {
                    StatusCode = (int)webException.HttpStatus
                };
                return;
            }
            //內部錯誤
            var exception = GetException<InnerException>(context.Exception);
            if (exception != null)
            {
                context.Result = new JsonResult(new
                {
                    ErrorCode = exception.ErrorCode ?? MessageCodes.Error,
                    exception.Message
                })
                {
                    StatusCode = (int)HttpStatusCode.InternalServerError
                };
                return;
            }

            #endregion
        }

        private TException GetException<TException>(Exception exception)
          where TException : Exception
        {
            if (exception == null)
            {
                return null;
            }
            if (exception is TException tException)
            {
                return tException;
            }
            else
            {
                return GetException<TException>(exception.InnerException);
            }
        }
    }

ExceptionFilter

同時,Startup.cs的ConfigureServices中註冊一下:

services.AddMvc(mvcOptions =>
                {
                    mvcOptions.Filters.Add<ExceptionFilter>();
                })

即完成了錯誤信息並且錯誤信息會寫入相應配置的輸出中。

事務處理

  UnitOfWork又稱工作單元,為了保證數據操作完整性,我們將處理數據的的操作統一放在一個事務中,我們這裏利用UnitOfWork來實現事務處理。

首先定義IUnitOfWork及UnitOfWork實現:

 public interface IUnitOfWork
    {
        void Begin(IsolationLevel level = IsolationLevel.Unspecified);
        void SaveChanges();
        void Failed();
    }

View Code

public class UnitOfWork : IUnitOfWork
    {
        private ITransactionRepository _repository;

        public UnitOfWork(ITransactionRepository repository)
        {
            _repository = repository;
        }

        public virtual void Begin(IsolationLevel level = IsolationLevel.Unspecified)
        {
            _repository.BeginTransaction(level);
        }

        public virtual void SaveChanges()
        {
            _repository.Commit();
        }

        public virtual void Failed()
        {
            _repository.Rollback();
        }
    }

View Code

其中,UnitOfWork依賴於ITransactionRepository的實現:

public interface ITransactionRepository
    {
        /// <summary>
        /// 打開事務
        /// </summary>
        /// <param name="level"></param>
        void BeginTransaction(IsolationLevel level = IsolationLevel.Unspecified);
        /// <summary>
        /// 提交事務
        /// </summary>
        void Commit();
        /// <summary>
        /// 事務回滾
        /// </summary>
        void Rollback();
    }

ITransactionRepository

利用DapperRepository繼承ITransactionRepository並實現:

public virtual void BeginTransaction(IsolationLevel level = IsolationLevel.Unspecified)
        {
            DbContext.BeginTransaction(level);
        }

        public virtual void Commit()
        {
            DbContext.Commit();
        }

        public virtual void Rollback()
        {
            DbContext.RollBack();
        }

View Code

基本功能實現后,如何使用呢?這裏還是需要利用Aop:

public class UnitOfWorkAttribute : AbstractInterceptorAttribute
    {
        public override Task Invoke(AspectContext context, AspectDelegate next)
        {
            if (context.Implementation is IApplicationService applicationService)
            {
                var uow = applicationService.UnitOfWork;
                uow.Begin();
                var aspectDelegate = next(context);
                if (aspectDelegate.Exception != null)
                {
                    uow.Failed();
                    throw aspectDelegate.Exception;
                }
                else
                {
                    uow.SaveChanges();
                    return aspectDelegate;
                }
            }
            else
            {
                return next(context);
            }
        }
    }

UnitOfWorkAttribute

因此,我們還需要在Application項目中添加如下代碼:

 public class ServiceBase<TEntity, TPrimaryKey> : IApplicationService
        where TEntity : class, IEntity<TPrimaryKey>
    {
        protected IMapper Mapper { get; private set; }
        public virtual IUnitOfWork UnitOfWork { get; private set; }

        public ServiceBase(IComponentContext container, ICommandRepository<TEntity, TPrimaryKey> repository)
        {
            Mapper = container.Resolve<IMapper>();
            UnitOfWork = container.Resolve<IUnitOfWork>(new TypedParameter(typeof(ITransactionRepository), repository));
        }
    }

ServiceBase

Application中的每個服務去繼承上面的ServiceBase,因此每個Application服務都具有了事務處理能力

 public interface IOrderService : IScopeInstance
    {
        [UnitOfWork]
        void Add(OrderViewModel order);
        OrderViewResult Get(string sn);
    }

 

 程序運行時,Add方法前後形成切面,如下圖所示,next(context)這裏執行的就是Add方法,執行前開啟事務,執行后提交

 

 利用Aop特性切面實現事務的無感注入(Ioc/DI小節中引入了AspectCore動態代理),底層還是依賴IDbConnection的事務相關接口,完整的事務處理大概就是這樣了。

詳細代碼在Github的 的Domain分支可以找到。

 

 

    

 

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!