環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
摘錄自2018年8月16日中央社報導
加拿大政府今天(16日)表示,將逐步限用和水生昆蟲及蜜蜂死亡有關連的2款類尼古丁農藥。環保人士認為這是一大勝利,但對販售殺蟲劑的企業而言卻是最新挫敗。
加拿大衛生署(Health Canada)病蟲害管制局(PMRA)表示,未來三到五年之間將逐步禁止在戶外使用賽速安(Thiamethoxam)及可尼丁(Clothianidin)。
加拿大衛生署這項措施將有90天諮詢期,最後在2019年底做出決定。
加拿大衛生署也計畫在今年底之前做出最終決定,是否逐步禁止第3種的類尼古丁農藥,拜耳生產的益達胺(imidacloprid)。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※南投搬家公司費用需注意的眉眉角角,別等搬了再說!
※新北清潔公司,居家、辦公、裝潢細清專業服務
※教你寫出一流的銷售文案?
摘錄自2018年8月16日中央社報導
環境部一名官員今天(16日)說,泰國將在6個月內禁止進口432種廢棄電子用品。中國今年禁止高科技廢棄物進口以來,泰國成為最新呼應的國家。
泰國下達禁令數週前,區域鄰國越南表示,將停止核發新執照給廢棄物進口,並將取締非法運送廢紙、塑膠和金屬。
泰國環境部一名高級官員今天告訴路透社,禁令涵蓋432種電子廢棄物,從電路板到老舊電視機和收音機零件等等,將在6個月內生效。
他並說,禁令是環境部長素拉薩(Surasak Kanchanarat)昨天主持會議時做成決定。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※幫你省時又省力,新北清潔一流服務好口碑
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!
目錄
我們用golang做odbc驅動開發的任務並不多,隔段時間可能會來一個。每次開發會忘記如何配置數據源和對應的數據源鏈接參數配置。這裏做一個整理。
ODBC(Open Database Connectivity)是一組對數據庫訪問的標準API,其最大的優點是以統一的方式處理所有的數據庫。
JDBC(Java Database Connectivity)是Java與數據庫的接口規範,允許Java程序發送SQL指令並處理結果。比較常見JdbcTemplate
打開控制面板找到管理工具,當前目錄有兩個ODBC的配置工具。分別是:ODBC Data Sources (32-bit)、ODBC 數據源(64 位)。顧名思義一個是32位,一個是64位。在配置ACCESS數據源時需要選擇32位。
我們也可以直接在系統目錄下找到對應的可執行文件。
1)32位:%windir%\syswow64\odbcad32.exe
2)64位:%windir%\system32\odbcad32.exe
題外話:syswow64 目錄存放的是32位的程序,system32目錄存放的是64位的程序,在註冊dll的時候需要注意下。ITDragon 在接觸驅動開發之前一直都弄反了。這篇文章做了通俗易懂地解釋https://www.cnblogs.com/hbccdf/p/dllchecktoolandsyswow64.html
網上收集整理,不保證正確性,僅供參考。
| 數據庫 | 連接參數 |
|---|---|
| MySQL | driver={mysql};database=數據庫;uid=賬號;pwd=密碼; |
| MSSQL Server | driver={sql server};server=服務器;database=數據庫;uid=ITDragon;pwd=密碼; |
| Access | driver={microsoft access driver (*.mdb)};dbq=mdb文件全路徑;uid=ITDragon;pwd=密碼; |
| SQLite | driver={SQLite3 ODBC Driver};database=db文件全路徑 |
| PostgreSQL | driver={PostgreSQL ANSI};server=服務器;uid=賬號;pwd=密碼;database=數據庫; |
| DBase | driver={microsoft dbase driver (*.dbf)};driverid=277;dbq=dbf文件全路徑; |
| Oracle | driver={microsoft odbc for oracle};server=服務器;uid=ITDragon;pwd=密碼; |
| MS text | driver={microsoft text driver (* .txt; *.csv)};dbq=文件全路徑;extensions=asc,csv,tab,txt;PersistSecurityInfo=false; |
| Visual Foxpro | driver={microsoft Visual Foxpro driver};sourcetype=DBC;sourceDB=*.dbc;Exclusive=No; |
第一步:雙擊ODBC 數據源(64 位),可以選擇用戶DSN(系統只對當前用戶生效),也可以選擇系統DSN(能登錄當前系統的用戶都生效)
第二步:點擊添加,選擇事先安裝好的MySQL ODBC xx Driver 驅動後點擊完成。注意不同版本之間對某些sql語法的支持略有不同(之前吃過這個虧,我的環境有問題,客戶環境沒問題)。
第三步:完善基本鏈接信息後點擊Test,提示鏈接成功後點擊OK完成配置。
因為ODBC驅動配置已經將數據庫的連接地址、賬號、密碼、數據庫都已經配置完成,連接參數只需要指定驅動名稱即可:DSN=ITDragon_MySQL
Golang偽代碼:
import (
"database/sql"
_ "github.com/alexbrainman/odbc"
)
db, err := sql.Open("odbc", "DSN=ITDragon_MySQL")
if err != nil {
panic(err)
}
defer db.Close()
第一步:雙擊ODBC 數據源(64 位),可以選擇用戶DSN(系統只對當前用戶生效),也可以選擇系統DSN(能登錄當前系統的用戶都生效)
第二步:點擊添加,選擇SQL Server。這一項我ITDragon 並沒有手動安裝,應該是安裝SQL Server數據庫的時候自動安裝上去的。
第三步:鍵盤輸入需要連接的SQLServer服務器,如果是本機,就輸入計算機名。如果是遠程主機就需要輸入IP,Port 。不要點擊下拉框,會卡死。
第四步:選擇SQL Server驗證方式
第五步:選擇默認數據庫,也可以在寫sql語句時將表名的全路徑寫全(我ITDragon 習慣用寫全)
第六步:可以考慮修改SQL Server的系統消息語言,數據的加密,執行字符數據翻譯,修改日誌保存路徑等。也可以默認。
第七步:點擊完成,彈出“按照以下配置創建新的ODBC數據源”,點擊測試數據源,提示測試成功。點擊確定完成創建。
Golang偽代碼:
import (
"database/sql"
_ "github.com/alexbrainman/odbc"
)
db, err := sql.Open("odbc", "driver={sql server};server=DESKTOP-HKC2IA3;DSN=ODBCName;uid=xxx;pwd=xxx;")
if err != nil {
panic(err)
}
defer db.Close()
第一步:雙擊ODBC Data Sources (32-bit),而不是64位。可以選擇用戶DSN(系統只對當前用戶生效),也可以選擇系統DSN(能登錄當前系統的用戶都生效)
第二步:選擇Microsoft Access Driver (*.mdb)。點擊數據庫下發的選擇按照,選擇mdb文件,點擊確定完成配置。
我們其實可以不用配置Access的數據源,直接用DBQ指定mdb的文件路徑,再用pwd輸入密碼訪問。
Golang偽代碼:
import (
"database/sql"
_ "github.com/alexbrainman/odbc"
)
db, err := sql.Open("odbc", "DRIVER={Microsoft Access Driver (*.mdb)};DBQ=全路徑.MDB;pwd=xx;")
if err != nil {
panic(err)
}
defer db.Close()
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※台北網頁設計公司全省服務真心推薦
※想知道最厲害的網頁設計公司"嚨底家"!
※新北清潔公司,居家、辦公、裝潢細清專業服務
※推薦評價好的iphone維修中心
如果你想從頭學習Jmeter,可以看看這個系列的文章哦
https://www.cnblogs.com/poloyy/category/1746599.html
我們通過實際栗子去講述理論知識點
| 字段 | 含義 |
| Apply to | 應用範圍,選默認的 main sample only 就行了 |
| Field to check |
可提取的字段 |
| Names of created variables |
|
| Regular Expression |
正則表達式 |
| Template | 從找到的匹配項中創建字符串的模板 |
| Match No.(0 for Random) |
|
| Default Value |
|
| Use empty default value |
勾選后,提取不到值時,則返回空字符串 |
| 屬性 | 含義 |
| Body | 響應體,不包括響應頭;最常用 |
| Body (unescaped) | 響應體,替換了所有HTML轉義符;不建議使用 |
| Body as a Document | 從不同類型的文件中提取文本;影響性能 |
| Request Headers | 請求頭 |
| Response Headers | 響應頭 |
| URL | URL |
| Response Code | 響應碼 |
| Response Message | 響應信息 |
這個栗子,我都會以這個地址的接口來完成 JSON 提取器的實戰慄子,大家可以註冊個賬號玩一玩哦
http://api.yesapi.cn/docs.php?keyword=%E4%BC%9A%E5%91%98&channel=api
下面多個栗子都以這個測試計劃為基礎哦
登錄是執行其他接口的前置接口,所以要獲取用戶登錄后的 token、uuid
下面的栗子都以這個 JSON 字符串為基礎,从里面提取結果
這 JSON 字符串也是某個接口的響應內容,貨真價實,感興趣也可以自己玩一玩:http://api.yesapi.cn/docs-api-App.User.GetList.html
{ "ret": 200, "msg": "V2.5.1 YesApi App.User.GetList", "data": { "total": 4, "err_msg": "", "err_code": 0, "users": [ { "role": "user", "status_desc": "正常", "reg_time": "2020-06-22 20:45:05", "role_desc": "普通會員", "ext_info": { "yesapi_nickname": "", "yesapi_points": 0 }, "uuid": "0564CE592B4CE914365D8922F6FC4CEC", "username": "luojunjiess286", "status": 0 }, { "role": "user", "status_desc": "正常", "reg_time": "2020-06-22 14:27:17", "role_desc": "普通會員", "ext_info": { "yesapi_nickname": "", "yesapi_points": 0 }, "uuid": "0164DC0680F84DCE40D3DD4A36640ECA", "username": "luojunjiessa", "status": 0 }, { "role": "admin", "status_desc": "正常", "reg_time": "2020-03-23 22:48:32", "role_desc": "管理員", "ext_info": { "yesapi_nickname": "", "yesapi_points": 0 "yesapi_reg_source": "" }, "uuid": "079BF6BB82AFCFC7084F96AECAF0519F", "username": "luojunjiess", "status": 0 } ] } }
就是正則表達式里只有一個 ( ) ,且 Match No. 不是 -1
uuid1= uuid1_g=1 uuid1_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid1_g1=0564CE592B4CE914365D8922F6FC4CEC
如果正則匹配到值,但是沒有填模板,則返回空
uuid2="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid2_g=1 uuid2_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid2_g1=0564CE592B4CE914365D8922F6FC4CEC
uuid3=0564CE592B4CE914365D8922F6FC4CEC uuid3_g=1 uuid3_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid3_g1=0564CE592B4CE914365D8922F6FC4CEC
uuid4=null uuid4_g=1 uuid4_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid4_g1=0564CE592B4CE914365D8922F6FC4CEC
$2$ 模板並不存在,其實就是 uuid4_g2 變量不存在,即使勾了使用空默認值,也返回 null,
有兩種情況
手動分成四部分
uuid1_1=0564CE592B4CE914365D8922F6FC4CEC uuid1_1_g=1 uuid1_1_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC" uuid1_1_g1=0564CE592B4CE914365D8922F6FC4CEC uuid1_2=0164DC0680F84DCE40D3DD4A36640ECA uuid1_2_g=1 uuid1_2_g0="uuid":"0164DC0680F84DCE40D3DD4A36640ECA" uuid1_2_g1=0164DC0680F84DCE40D3DD4A36640ECA uuid1_3=079BF6BB82AFCFC7084F96AECAF0519F uuid1_3_g=1 uuid1_3_g0="uuid":"079BF6BB82AFCFC7084F96AECAF0519F" uuid1_3_g1=079BF6BB82AFCFC7084F96AECAF0519F uuid1_matchNr=3
info1= info1_g=2 info1_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286" info1_g1=0564CE592B4CE914365D8922F6FC4CEC info1_g2=luojunjiess286
info2=0564CE592B4CE914365D8922F6FC4CEC info2_g=2 info2_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286" info2_g1=0564CE592B4CE914365D8922F6FC4CEC info2_g2=luojunjiess286
info3=0564CE592B4CE914365D8922F6FC4CECluojunjiess286 info3_g=2 info3_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286" info3_g1=0564CE592B4CE914365D8922F6FC4CEC info3_g2=luojunjiess286 info4=0564CE592B4CE914365D8922F6FC4CEC,luojunjiess286 info4_g=2 info4_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286" info4_g1=0564CE592B4CE914365D8922F6FC4CEC info4_g2=luojunjiess286
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
chromedp是go寫的,支持Chrome DevTools Protocol 的一個驅動瀏覽器的庫。並且它不需要依賴其他的外界服務(比如 Selenium 和 PhantomJs)。
Chrome DevTools Protocol (CDP) 的主頁在:https://chromedevtools.github.io/devtools-protocol/。 它提供一系列的接口來查看,檢查,調整並且檢查 Chromium 的性能。Chrome 的開發者工具就是使用這一系列的接口,並且 Chrome 開發者工具來維護這些接口。
所謂 CDP 的協議,本質上是什麼呢?本質上是基於 websocket 的一種協議。比如
在我們打開 webtool 調試工具的時候,其實調試工具也是一個web頁面,兩個web頁面通過websocket建立了一個聯繫。
所以我們如果寫了一個客戶端程序,也和目標頁面創建一個基於 CDP 的 websocket連接,我們也可以通過這個協議來對頁面進行操作。
在chrome的開發者工具中
打開實驗選項 Protocol Monitor
重啟chrome,在console的更多裏面就可以打開對應的 Monitor
我們從 Protocol Monitor 面板中可以看到,其中有幾個字樣,Method,Request,Response。
這裏的 Method 就是對應官網 https://chromedevtools.github.io/devtools-protocol/ 左側每個Domain的 Event。
這裏的每個Method方法可能是調試頁面給目標頁面發送的,但是更多是目標頁面給調試頁面發送的消息。所以我們需要讀懂每個Method的內容。不過很可惜,我個人感覺官網的每個Method文檔的描述寫的實在是太簡單了,也沒有看到更詳細的描述,只能通過名字和事件來猜測每個Method意思了。
chromedp的使用最快的方法就是看 https://github.com/chromedp/examples 這個項目
基本我們可以熟悉最常用的幾個方法了:
我們嘗試打開 https://www.cnblogs.com/ 的首頁,然後獲取所有文章的標題和鏈接:
package main
import (
"context"
"fmt"
"log"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/chromedp"
)
func main() {
ctx, cancel := chromedp.NewContext(
context.Background(),
chromedp.WithLogf(log.Printf),
)
defer cancel()
var nodes []*cdp.Node
err := chromedp.Run(ctx,
chromedp.Navigate("https://www.cnblogs.com/"),
chromedp.WaitVisible(`#footer`, chromedp.ByID),
chromedp.Nodes(`.//a[@class="titlelnk"]`, &nodes),
)
if err != nil {
log.Fatal(err)
}
fmt.Println("get nodes:", len(nodes))
// print titles
for _, node := range nodes {
fmt.Println(node.Children[0].NodeValue, ":", node.AttributeValue("href"))
}
}
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
作者:凹凸曼 – 暖暖
SVG 即 Scalable Vector Graphics 可縮放矢量圖形,使用XML格式定義圖形。
SVG 的應用十分廣泛,得益於 SVG 強大的各種特性。
可利用 SVG 矢量的特點,描出深圳地鐵的輪廓:
SVG 可依據一定的規則,轉成 iconfont 使用:
利用 SVG 的 foreignObject 標籤實現截圖功能,原理:foreignObject 內部嵌入 HTML 元素:
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="120" height="60">
<p style="font-size:20px;margin:0;">凹凸實驗室 歡迎您</p>
</foreignObject>
</svg>
截圖實現流程:
<foreignObject></foreignObject> 這對標籤;foreignObject 即可;Blob 構建 svg 對象;URL.createObjectURL(svg) 取出 URL。由於微信編輯器不允許嵌入 <style><script><a> 標籤,利用SVG SMIL 可進行微信公眾號極具創意的圖文排版設計,包括動畫與交互。
但是也要注意,標籤里不允許有id,否則會被過濾或替換掉。
點擊 “凹凸實驗室” 后,圍繞 “凹凸實驗室” 中心旋轉 360度,點擊0.5秒后 出現 https://aotu.io/ ,動畫只運行一次。
下圖為 GIF循環演示:
代碼如下:
<svg width="360" height="300" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- 點擊后 運行transform旋轉動畫,restart="never"表示只運行一次 -->
<animateTransform attributeName="transform" type="rotate" begin="click" dur="0.5s" from="0 100 80" to="360 100 80" fill="freeze" restart="never" />
<g>
<text font-family="microsoft yahei" font-size="20" x="50" y="80">
凹凸實驗室
</text>
</g>
<g style="opacity: 0;">
<!-- 同一個初始位置以及大致的寬高,觸發點擊事件 -->
<text font-family="microsoft yahei" font-size="20" x="50" y="80">https://aotu.io/</text>
<!-- 點擊后 運行transform移動動畫,改變文本的位置 -->
<animateTransform attributeName="transform" type="translate" begin="click" dur="0.1s" to="0 40" fill="freeze" restart="never" />
<!-- 點擊0.5秒后 運行opacity显示動畫 -->
<animate attributeName="opacity" begin="click+0.5s" from="0" to="1" dur="0.5s" fill="freeze" restart="never" />
</g>
</g>
</svg>
以上是鄙人對SVG的大致印象,最近的需求開發再次刷新了我的認知,那就是 SVG實現非比例縮放 以及 小程序不支持SVG標籤的處理,下面容我來講述一番。
我們熟知的 iconfont,可通過改變字體大小縮放,但是這是 比例縮放,那如何實現 SVG 的非比例縮放呢?
如下圖所示,如何將 一隻兔子 非比例縮放?
划重點:實現非比例縮放主要涉及三個知識點:viewport、viewBox和preserveAspectRatio,viewport 與viewBox 結合可實現縮放的功能,viewBox 與 preserveAspectRatio 結合可實現非比例的功能。
viewport 表示SVG可見區域的大小。
viewport 就像是我們的显示器屏幕大小,超出區域則隱藏,原點位於左上角,x 軸水平向右,y 軸垂直向下。
通過類似CSS的屬性 width、height 指定視圖大小:
<svg width="400" height="200"></svg>
viewBox值有4個数字:x, y, width, height 。
其中 x:左上角橫坐標,y:左上角縱坐標,width:寬度,height:高度。
原點默認位於左上角,x 軸水平向右,y 軸垂直向下。
<svg width="400" height="200" viewBox="0 0 200 100"></svg>
显示器屏幕的畫面,可以特寫,可以全景,這就是 viewBox。
viewBox 可以想象成截屏工具選中的那個框框,和 viewport 作用的結果就是 把框框中的截屏內容再次在 显示器 中全屏显示。
(圖片來源:SVG 研究之路 (23) – 理解 viewport 與 viewbox)
上圖的紅色框框和藍色框框,恰好和显示器的比例相同,如果是下圖的綠色框框,怎樣在显示器屏幕中显示呢?
preserveAspectRatio 作用的對象是 viewBox,使用方法如下:
preserveAspectRatio="[defer] <align> [<meetOrSlice>]"
// 例如 preserveAspectRatio="xMidYMid meet"
其中 defer 此時不是重點,暫且忽略,主要了解 align 和 meetOrSlice 的 用法:
align:由兩個名詞組成,分別代表 viewbox 與 viewport 的 x 方向、y方向的對齊方式。| 值 | 含義 |
|---|---|
xMin |
viewport 和 viewBox 左邊對齊 |
xMid |
viewport 和 viewBox x軸中心對齊 |
xMax |
viewport 和 viewBox 右邊對齊 |
YMin |
viewport 和 viewBox 上邊緣對齊。注意Y是大寫。 |
YMid |
viewport 和 viewBox y軸中心點對齊。注意Y是大寫。 |
YMax |
viewport 和 viewBox 下邊緣對齊。注意Y是大寫。 |
meetOrSlice:表示如何維持高寬的比例,有三個值 meet、slice、none。
meet – 默認值,保持縱橫比縮放 viewBox 適應 viewport,可能會有餘留的空白。slice – 保持縱橫比同時比例小的方向放大填滿 viewport,超出的部分被剪裁掉。none – 扭曲縱橫比以充分適應 viewport。例子1:preserveAspectRatio="xMidYMid meet" 表示 綠色框框 與 显示器的 x 方向、y方向的 中心點 對齊;
例子2:preserveAspectRatio="xMidYMin slice" 表示 綠色框框 與 显示器的 x 方向 中心點 對齊,Y 方向 上邊緣對齊,保持比例放大填滿 显示屏 后超出部分隱藏;
例子3:preserveAspectRatio="xMidYMid slice" 表示 綠色框框 與 显示器的 x 方向、y方向的 中心點 對齊,保持比例放大填滿显示屏 后超出部分隱藏;
例子4:preserveAspectRatio="none" 不管三七二十一,隨意縮放綠色框框,填滿 显示屏即可;這就是非比例縮放的答案了。
微信小程序官方不支持 SVG 標籤的,但是決定曲線救國,相當於自己實現了一個SVG標籤:使用小程序內置的 Canvas 渲染器, 在 Cax 中實現 SVG 標準的子集,使用 JSX 或者 HTM(Hyperscript Tagged Markup) 描述 SVG 結構行為表現。
但是今天我想講講其他的。
我們知道,小程序雖然不支持 SVG 標籤,但是支持 svg 轉成 base64 後作為 background-image 的 url,如 background-image: url("data:image/svg+xml.......) 。
但是我這邊還有個需求,隨時更改 SVG 每個路徑的顏色,即 顏色可配置:
來迴轉 Base64 肯定是比較麻煩的,有沒有更好的方式呢?
直接貼答案:對於SVG圖形,還有更好的實現方式,就是直接使用SVG XML格式代碼,無需進行base64轉換。
直接使用 SVG XML 格式代碼,首先要了解 Data URI的格式。
划重點:base64非必選項,不指定的時候,後面的 <data> 將使用 URL編碼。
百分號編碼(Percent-encoding), 也稱作URL編碼(URL encoding),是特定上下文的統一資源定位符 (URL)的編碼機制。
原理:ASCII 字符 = % + 兩位 ASCII 碼(十六進制)。
例如,字符 a 對應的 ASCII 碼為 0x61,那麼 URL 編碼后得到 %61 。
前言:
Data URI 的格式中的 <data> 完全使用URL 編碼也是可以的,如 encodeURIComponent('<svg version="1.1" viewBox= …</svg>')。
但是和轉義前原始SVG相比,可讀性差了很多,而且佔用體積也變大了。
如果深入了解URL 編碼的話,<data> 沒必要全部編碼的。
正文:
RFC3986文檔規定,URL中只允許包含 未保留字符 以及 所有保留字符。
:/?#[]@ (分隔Url的協議、主機、路徑等組件) 和 !$&'()*+,;= (用於在每個組件中起到分隔作用的,如&符號用於分隔查詢多個鍵值對)。%、空格、雙引號"、尖號 <>等等。綜上所述,只需要對 受限字符或不安全字符 進行編碼即可。
svgToUrl (svgData) {
encoded = encoded
.replace(/<!--(.*)-->/g, '') // 親測必須去掉註釋
.replace(/[\r\n]/g, ' ') // 親測最好去掉換行
.replace(/"/g, `'`) // 單引號是保留字符,雙引號改成單引號減少編碼
.replace(/%/g, '%25')
.replace(/&/g, '%26')
.replace(/#/g, '%23')
.replace(/{/g, '%7B')
.replace(/}/g, '%7D')
.replace(/</g, '%3C')
.replace(/>/g, '%3E')
return `data:image/svg+xml,${encoded}`
}
如果使用在 CSS 中,可利用 SASS版本3.3以上 的 三個API 對 SVG字符串做替換處理。
str_insert(string, insert, index): 從 $string 第 $index 插入字符 $insert;str_index(string, substring): 返回 $substring 在 $string 中第一個位置;str_slice(string, start_at, end_at = nil): 返回從字符 $string 中第 $start_at 開始到 $end_at 結束的一個新字符串。前人已有總結,可前往 https://github.com/leeenx/sass-svg/blob/master/sass-encodeuri.scss 查看完整代碼。
一般從 Sketch 導出 SVG ,冗餘代碼比較多,有條件的話建議使用 SVGO 壓縮SVG的原本體積,比如清除換行、重複空格;刪除文檔聲明;刪除註釋;刪除desc描述等等。
SVG強大的地方在於,出其不意,炫酷,與眾不同。
無論是微信公眾號花式排版,foreignObject 標籤實現截圖,實現非比例縮放,或者 背景圖直接使用 SVG XML 格式代碼,還是上文沒有提及的路徑動畫、描邊動畫、圖形裁剪、濾鏡等等,都可以玩出新的花樣。
SVG 一個屬性可成就一篇文章,學習 SVG 可以說是在挑戰自己,歡迎加入 SVG 的學習隊列。
歡迎關注凹凸實驗室博客:aotu.io
或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章:
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※南投搬家公司費用需注意的眉眉角角,別等搬了再說!
※新北清潔公司,居家、辦公、裝潢細清專業服務
※教你寫出一流的銷售文案?
1、首先要在 添加好友 這個按鈕上添加一個事件,也就是在detail.wxml的添加好友這個按鈕的哪裡,添加一個點擊事件 handleAddFriend
並且添加好友還要考慮,現在是已登陸狀態還是未登陸狀態的,只有是登陸狀態的時候,才可以發起添加好友的請求的
所以就要先判斷一下它是否已經登陸了
因為只要是登陸之後,就會把用戶的id寫入到全局的userinfo下面的
handleAddFriend(){ if( app.userInfo._id){ } else{ wx.showToast({ title: '請先登陸', duration : 2000, // 然後不要讓它显示圖標 icon : 'none', success: ()=>{ // 如果成功的話就直接跳轉到我的頁面去 // 但是注意了這裏不能用 navigator to,因為它主要是跳轉 // 普通的頁面,而這裏“我的頁面”其實是同tabbar來進行配置的 } }) } }
這個時候就可以查找一下 小程序文檔 中關於“路由”的介紹了
可以看到要用wx.switchtab來進行操作了
然後因為我們設置了那個提示“請先登陸”是維持兩秒鐘,所以我們也要設置這個跳轉到我的頁面中的時間也是兩秒鐘
handleAddFriend(){ if( app.userInfo._id){ } else{ wx.showToast({ title: '請先登陸', duration : 2000, // 然後不要讓它显示圖標 icon : 'none', success: ()=>{ // 如果成功的話就直接跳轉到我的頁面去 // 但是注意了這裏不能用 navigator to,因為它主要是跳轉 // 普通的頁面,而這裏“我的頁面”其實是同tabbar來進行配置的 setTimeout(()=>{ wx.switchTab({ url: '/pages/user/user', }) } , 2000); } }) } }
上面的加入 沒登陸的情況也寫好了,下面就是對已經登陸了之後的設計了
就要在數據可以中建立一個message集合,主要是用來存儲好友消息,或者是系統的消息給這個用戶的一個信息集合的
這個集合裏面的每一個信息,包含了userID也就是這個好友請求或者是信息是發送給哪一個人的
然後還有一個其他想要加他好友的用戶id list數組,因為每個人都可以給這個人發起好友請求的,這就是對於數據庫1的建立了
所以在已經登陸之後,先查看一下有沒有這個發起好友的信息了,如果還有的話,就在數據庫中創立這個字段了
這個數據庫裏面的userid字段存的其實就是我們要加的這個人的id標識了,然後這個人的id我們可以從這個人的詳情頁面(detail)下的data中來獲得的
通過where就可以定位到在數據庫中這個用戶對應的字段了,然後用get就可以開始對這個字段裏面的東西進行查詢了
如果這個信息已經存在了就做更新操作,如果不存在的話就做創建操作即可了
if( app.userInfo._id){ db.collection('mesasge').where({ userId : this.data.detail._id }).get().then((res)=>{ if( res.data.length){//更新 } else{ //tianjia1 db.collection('message').add({ data : { userId : this.data.detail._id, list : [ app.userInfo._id] } }) } }); }
之後就可以查看數據可以中的message 集合
這樣的話,說明就調用成功了
二、更新message 信息
因為如果已經申請過了的話,就不能再往list裏面添加自己的id了,所以就要檢測一下現在是否在list中,
可以直接調用數組的include方法來進行查詢,如果找到了的話,就提示“已申請過了”如果沒找到的話就要往這個數組裡面進行數據更新了
查看了微信開放文檔之後會發現,如果是單個數據進行更新的話可以直接用doc好到之後進行更新就好了,但是如果對大量的數據進行批量的更新的話
因為在客戶端的更新能力還是有限的,所以就要到服務端上來完成了,也就是用雲函數來完成了(其實這個方法我們已經寫好了,也就是雲函數update了
else{ wx.cloud.callFunction({ // name也就是我們要修改的數據庫的名字,data就是在雲函數中 // 想要的參數了 name : 'updata', data : { collection : 'message', where :{ userId : this.data.detail._id }, data : { } } }) }
注意了,在調用雲函數的時候,前面的data和後面的data是不一樣的,錢買你的是給雲函數的參數,但是後面的是我們要修改的數據了
在要修改list的數據到時候,就涉及了要對數組進行添加,也就是push操作了,其實在數據可以中也內置了一些的方法,commend.push等等
注意:在detail.js文件中,如果找到了這個數據流,但是沒有申請的話,就是進行更新,在更新的時候用到了update雲更新函數,
給這個雲函數傳入的data中
data : `{list : _.unshift(' ${app.userInfo._id} ')}`
注意:最外面那層 並不是 單引號,而是 鍵盤 Esc下面的那個標點
三、添加好友功能之監聽message消息
在數據庫加入了一個 帶有list和userid的數據,所以在userid這個人登陸小程序之後,就應該可以看到有沒有人給他發送消息了
並且還是要實時的更新,就是這個人在登陸狀態的話,也可以直接收到了,也就是要實現實時的監聽數據庫中list的實時變化了
在開發者文檔中:雲開發-》實時數據的推送:
https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/database/realtime.html
(它的意思就是我們可以監聽到數據庫發送的變化
可以直接查看demo
const db = wx.cloud.database() const watcher = db.collection('todos') // 按 progress 降序 .orderBy('progress', 'desc') // 取按 orderBy 排序之後的前 10 個 .limit(10) .where({ team: 'our dev team' }) .watch({ onChange: function(snapshot) { console.log('docs\'s changed events', snapshot.docChanges) console.log('query result snapshot after the event', snapshot.docs) console.log('is init data', snapshot.type === 'init') }, onError: function(err) { console.error('the watch closed because of error', err) } }) // ... // 等到需要關閉監聽的時候調用 close() 方法 watcher.close()
我們可以在user頁面中進行檢測即可,也就是在登陸之後進行檢測了
我們創建了一個方法 getMessage()。只要用戶登陸了之後就可以進行觸發了,在onReady裏面的登陸成功代碼之後即可了
也就是在數據庫定位到uuseid是這個用戶的數據之後,得到了之後就可以用watch方法來進行監聽了
getMessage(){ db.collection('message').where({ userId : app.userInfo._id }).watch({ onChange: function (snapshot) { console.log(snapshot); } }); }
這個onChange就是進行監聽的函數了,我們在遇到陌生的一定要傳入參數的函數的時候,最好是把這個參數用console.log打印出來看看我們的想要的數據在哪個位置裏面的
注意了:如果是按照上面這樣的話,是會報錯的,以為缺少了錯誤返回的 onError函數的,示例的demo裏面的格式是怎麼樣的,最好就用怎麼樣的
不然可能都是會報錯的
但是會發現,我們沒拿到有用的數據
就可以用多賬號來調試一下了
(這裏用的多賬號最好還是用真實的賬號把,因為虛擬的出現的問題挺大的)
(然後還要設置給message權限是第一個,允許全部人看的那種,才可以看到在別的賬號上的加好友信息的
在別的賬號上面的話就可以看到打印的信息了,可以看到我們得到的消息其實是挺亂的,所以最好用判斷來搞一下
測試之後會發現,得到的 snapshot 數據中有一個 docChanges 數組的,只要有申請,就會有显示了
所以我們可以通過對數組的長度進行一個判斷
然後再對這個list進行判斷,通過長度來進行判斷,如果有長度的話說明就有消息了
有消息的話就要給用戶一個提示,就是在下面的tabbar中的消息圖標右上角添加一個紅色的小點
===其實這個功能在微信小程序中其實就已經幫我們設計好了
微信文檔-》API-》界面-》tabbar-》wx.showTabBarRedDot
https://developers.weixin.qq.com/miniprogram/dev/api/ui/tab-bar/wx.showTabBarRedDot.html
它需要定義一個index屬性,來指定放紅點的是tabbar中的哪一項的(它是從0開始的,所以我們設置為2即可了)
也就是說這個用戶拿到了這個list之後,通過這個list的長度來判斷有沒有消息,然後設置紅點提示,並且還要把這個得到的list用到消息頁面中去的
所以就涉及到了,怎麼把這個得到的list共享到消息中去,這個和之前的userInfo是類似的,點開全局的app.js
this.userMessage = []
這裏創立的是一個數組來的,不是對象了
然後在user.js裏面,判斷這個得到的list的長度,設置tabbar上面的小紅心,然後把得到的list賦值給全局的userMessage
但是如果檢測到這個list是空的話,就要把在tabbar上面的小紅心取消掉了
**然後還要讓我們全局的userMessage等於一個空的數組即可了
這樣,這個監聽的函數就完成了:
getMessage(){ db.collection('message').where({ userId : app.userInfo._id }).watch({ onChange: function (snapshot) { // console.log(snapshot) if( snapshot.docChanges.length){ //這裏就可以直接拿到message裏面所對應的消息列表了 let list = snapshow.docChanges[0].doc.list; if( list.length ){ wx.showTabBarRedDot({ index: 2, }); app.userMessage = list; } else{ wx.hideTabBarRedDot({ index: 2, }) app.userMessage = []; } } }, onError: function (err) { console.error('the watch closed because of error', err) } }); }
然後因為watch是實時監聽的,我們在數據庫裏面把給的信息刪掉的話
這個紅點也就會消失了
這就是因為正在實時的監聽着
四、下面搞的就是如何把共享的userMessage在消息頁面中渲染出來
===消息頁面和removeList組件布局
首先 切換編譯模式到消息頁面中,先做布局
<view class="message"> <view> <text>暫無消息:</text> </view> <view> <text>消息列表:</text> <view>第一條消息</view> <view>第二條消息</view> </view> </view>
之後就是先判斷有沒有消息。所以就要在js看i嗎添加一個東西,這裏添加的是一個userMessage數組,就是用來接收我們的那個全局的list的,
如果這個數組是空的話,說明就是沒有消息了,反之,所以就可以通過這個來進行判斷了
**之後就要測試一下message這個頁面裏面文件的生命周期了
這就是為了測試,在tabbar中的生命周期是怎麼樣的
在message.js裏面
onReady: function () {
console.log(1)
},
/**
* 生命周期函數--監聽頁面显示
*/
onShow: function () {
console.log(2)
},
在點擊了下main的tabbar的圖標之後,
打印的結果:
但是當我們幾點了個人頁面之後,再點擊消息頁面的時候,只打印了2
也就是說在tabbar裏面的onreay並不會再次的觸發(但是普通頁面的onreay是會被再次觸發的),但是他也是會觸發onshow的
所以基於這個就可以在onshow中添加東西,來監聽現在的消息變化情況了
因為如果想要進入消息頁面的話,就得先登陸,所以這力又要一個判斷了,如果沒登陸得話,就讓他跳轉到登陸頁面去的
(這個功能就和我們的detail裏面很像的,就可以直接COPY過來了)
const app = getApp() Page({ /** * 頁面的初始數據 */ data: { userMessage : [], logged : false }, /** * 生命周期函數--監聽頁面加載 */ onLoad: function (options) { }, /** * 生命周期函數--監聽頁面初次渲染完成 */ onReady: function () { // console.log(1) }, /** * 生命周期函數--監聽頁面显示 */ onShow: function () { // console.log(2) if( app.userInfo._id ){ this.setData({ logged : true, userMessage : app.userMessage }); }else{ wx.showToast({ title: '請先登陸', duration: 2000, // 然後不要讓它显示圖標 icon: 'none', success: () => { // 如果成功的話就直接跳轉到我的頁面去 // 但是注意了這裏不能用 navigator to,因為它主要是跳轉 // 普通的頁面,而這裏“我的頁面”其實是同tabbar來進行配置的 setTimeout(() => { wx.switchTab({ url: '/pages/user/user', }) }, 2000); } }) } }, /** * 生命周期函數--監聽頁面隱藏 */ onHide: function () { }, /** * 生命周期函數--監聽頁面卸載 */ onUnload: function () { }, /** * 頁面相關事件處理函數--監聽用戶下拉動作 */ onPullDownRefresh: function () { }, /** * 頁面上拉觸底事件的處理函數 */ onReachBottom: function () { }, /** * 用戶點擊右上角分享 */ onShareAppMessage: function () { } })
message.js
雖然我們吧list引入進來了,下面就是吧這個list显示出來了
我們用虛擬賬號,給我的主號提交了兩個申請來進行測試了
之後在message.wxml中對我們的信息進行打印:
<!--miniprogram/pages/message/message.wxml--> <view class="message" wx:if="{{ logged }}"> <view wx:if="{{ !userMessage.length }}"> <text class="message-text">暫無消息:</text> </view> <view wx:else> <text class="message-text">消息列表:</text> <view wx:for="{{ userMessage }}" wx:key="{{index}}">{{item}}</view> </view> </view>
我們還要進行優化,就是可以得到一個列表,有頭像有昵稱,等信息的,別還要有刪除的功能的
為了練習一下組件的功能,雖然這個刪除的功能可以直接在這個文件裏面寫,但是我們還是吧這個刪除變成一個組件的形式l
新建一個removeList的刪除組件,然後就是找到message的JSON文件引入這個組件
這個組件其實是可以拖拽的?
在文檔-》組件-》視圖容器 movable-area和movable-view相互配合的
demo:
<movable-area> <movable-view x="{{x}}" y="{{y}}" direction="all">text</movable-view> </movable-area>
我們設置的結構和樣式:
<!--components/removeList/removeList.wxml--> <movable-area class="area"> <movable-view class="view">小喵喵</movable-view> </movable-area>
/* components/removeList/removeList.wxss */ .area{width:800rpx; height: 150rpx; margin: 20rpx ; position: relative;background: blue;} .view{width:630rpx; height:100%; background: red;position: absolute;left:150rpx;top:0; line-height: 150rpx;text-indent: 10rpx;}
效果;
但是這個時候還是不能進行拖拽的,其中的direction定義的就是拖拽的方向
添加了這個屬性之後:
<!--components/removeList/removeList.wxml--> <movable-area class="area"> <movable-view direction="horizontal" class="view">小喵喵</movable-view> </movable-area>
就可以進行拖拽了
注意;下面設置的z-Index是在設置級別,z-index大的會覆蓋在小的上面的
<!--components/removeList/removeList.wxml--> <movable-area class="area"> <movable-view direction="horizontal" class="view">小喵喵</movable-view> <image src="" /> <view class="delete">刪除</view> </movable-area>
/* components/removeList/removeList.wxss */ .area{width:800rpx; height: 150rpx; margin: 20rpx ; position: relative;border-bottom: 1px #cdcdcd dashed} .view{width:630rpx; height:100%; position: absolute;left:150rpx;top:0; line-height: 150rpx;text-indent: 10rpx;z-index: 2;} image{ width: 100rpx; height: 100rpx; border-radius: 50%; position: absolute; left: 0; top: 0; z-index: 1; } .delete{ width: 200rpx; height: 150rpx; background: red; color: white; position: absolute; right: 0; top: 0; z-index: 1; }
但是得到的效果是:
會發現不管怎麼拖拉,都擋不住後面的刪除–原因就是class==view這塊沒加背景色,雖然層級夠了
給他在wxss裏面添加一個 background:white即可了
刪除那個文字要居中的話,
.delete{ width: 170rpx; height: 100rpx; background: red; color: white; position: absolute; right: 0; top: 0; z-index: 1; line-height: 100rpx; }
效果圖:
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※幫你省時又省力,新北清潔一流服務好口碑
※別再煩惱如何寫文案,掌握八大原則!
上篇文章我們演示了為Configuration添加Etcd數據源,並且了解到為Configuration擴展自定義數據源還是非常簡單的,核心就是把數據源的數據按照一定的規則讀取到指定的字典里,這些都得益於微軟設計的合理性和便捷性。本篇文章我們將一起探究Configuration源碼,去了解Configuration到底是如何工作的。
相信使用了.Net Core或者看過.Net Core源碼的同學都非常清楚,.Net Core使用了大量的Builder模式許多核心操作都是是用來了Builder模式,微軟在.Net Core使用了許多在傳統.Net框架上並未使用的設計模式,這也使得.Net Core使用更方便,代碼更合理。Configuration作為.Net Core的核心功能當然也不例外。
其實並沒有Configuration這個類,這隻是我們對配置模塊的代名詞。其核心是IConfiguration接口,IConfiguration又是由IConfigurationBuilder構建出來的,我們找到IConfigurationBuilder源碼大致定義如下
public interface IConfigurationBuilder
{
IDictionary<string, object> Properties { get; }
IList<IConfigurationSource> Sources { get; }
IConfigurationBuilder Add(IConfigurationSource source);
IConfigurationRoot Build();
}
Add方法我們上篇文章曾使用過,就是為ConfigurationBuilder添加ConfigurationSource數據源,添加的數據源被存放在Sources這個屬性里。當我們要使用IConfiguration的時候通過Build的方法得到IConfiguration實例,IConfigurationRoot接口是繼承自IConfiguration接口的,待會我們會探究這個接口。
我們找到IConfigurationBuilder的默認實現類ConfigurationBuilder大致代碼實現如下
public class ConfigurationBuilder : IConfigurationBuilder
{
/// <summary>
/// 添加的數據源被存放到了這裏
/// </summary>
public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();
public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();
/// <summary>
/// 添加IConfigurationSource數據源
/// </summary>
/// <returns></returns>
public IConfigurationBuilder Add(IConfigurationSource source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
Sources.Add(source);
return this;
}
public IConfigurationRoot Build()
{
//獲取所有添加的IConfigurationSource里的IConfigurationProvider
var providers = new List<IConfigurationProvider>();
foreach (var source in Sources)
{
var provider = source.Build(this);
providers.Add(provider);
}
//用providers去實例化ConfigurationRoot
return new ConfigurationRoot(providers);
}
}
這個類的定義非常的簡單,相信大家都能看明白。其實整個IConfigurationBuilder的工作流程都非常簡單就是將IConfigurationSource添加到Sources中,然後通過Sources里的Provider去構建IConfigurationRoot。
通過上面我們了解到通過ConfigurationBuilder構建出來的並非是直接實現IConfiguration的實現類而是另一個接口IConfigurationRoot
通過源代碼我們可以知道IConfigurationRoot是繼承自IConfiguration,具體定義關係如下
public interface IConfigurationRoot : IConfiguration
{
/// <summary>
/// 強制刷新數據
/// </summary>
/// <returns></returns>
void Reload();
IEnumerable<IConfigurationProvider> Providers { get; }
}
public interface IConfiguration
{
string this[string key] { get; set; }
/// <summary>
/// 獲取指定名稱子數據節點
/// </summary>
/// <returns></returns>
IConfigurationSection GetSection(string key);
/// <summary>
/// 獲取所有子數據節點
/// </summary>
/// <returns></returns>
IEnumerable<IConfigurationSection> GetChildren();
/// <summary>
/// 獲取IChangeToken用於當數據源有數據變化時,通知外部使用者
/// </summary>
/// <returns></returns>
IChangeToken GetReloadToken();
}
接下來我們查看IConfigurationRoot實現類ConfigurationRoot的大致實現,代碼有刪減
public class ConfigurationRoot : IConfigurationRoot, IDisposable
{
private readonly IList<IIConfigurationProvider> _providers;
private readonly IList<IDisposable> _changeTokenRegistrations;
private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
public ConfigurationRoot(IList<IConfigurationProvider> providers)
{
_providers = providers;
_changeTokenRegistrations = new List<IDisposable>(providers.Count);
//通過便利的方式調用ConfigurationProvider的Load方法,將數據加載到每個ConfigurationProvider的字典里
foreach (var p in providers)
{
p.Load();
//監聽每個ConfigurationProvider的ReloadToken實現如果數據源發生變化去刷新Token通知外部發生變化
_changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));
}
}
//// <summary>
/// 讀取或設置配置相關信息
/// </summary>
public string this[string key]
{
get
{
//通過這個我們可以了解到讀取的順序取決於註冊Source的順序,採用的是後來者居上的方式
//后註冊的會先被讀取到,如果讀取到直接return
for (var i = _providers.Count - 1; i >= 0; i--)
{
var provider = _providers[i];
if (provider.TryGet(key, out var value))
{
return value;
}
}
return null;
}
set
{
//這裏的設置只是把值放到內存中去,並不會持久化到相關數據源
foreach (var provider in _providers)
{
provider.Set(key, value);
}
}
}
public IEnumerable<IConfigurationSection> GetChildren() => this.GetChildrenImplementation(null);
public IChangeToken GetReloadToken() => _changeToken;
public IConfigurationSection GetSection(string key)
=> new ConfigurationSection(this, key);
//// <summary>
/// 手動調用該方法也可以實現強制刷新的效果
/// </summary>
public void Reload()
{
foreach (var provider in _providers)
{
provider.Load();
}
RaiseChanged();
}
//// <summary>
/// 強烈推薦不熟悉Interlocked的同學研究一下Interlocked具體用法
/// </summary>
private void RaiseChanged()
{
var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
previousToken.OnReload();
}
}
上面展示了ConfigurationRoot的核心實現其實主要就是兩點
其實通過上面的代碼我們會產生一個疑問,獲取子節點數據返回的是另一個接口類型IConfigurationSection,我們來看下具體的定義
public interface IConfigurationSection : IConfiguration
{
string Key { get; }
string Path { get; }
string Value { get; set; }
}
這個接口也是繼承了IConfiguration,這就奇怪了分明只有一套配置IConfiguration,為什麼還要區分IConfigurationRoot和IConfigurationSection呢?其實不難理解因為Configuration可以同時承載許多不同的配置源,而IConfigurationRoot正是表示承載所有配置信息的根節點,而配置又是可以表示層級化的一種結構,在根配置里獲取下來的子節點是可以表示承載一套相關配置的另一套系統,所以單獨使用IConfigurationSection去表示,會顯得結構更清晰,比如我們有如下的json數據格式
{
"OrderId":"202005202220",
"Address":"銀河系太陽系火星",
"Total":666.66,
"Products":[
{
"Id":1,
"Name":"果子狸",
"Price":66.6,
"Detail":{
"Color":"棕色",
"Weight":"1000g"
}
},
{
"Id":2,
"Name":"蝙蝠",
"Price":55.5,
"Detail":{
"Color":"黑色",
"Weight":"200g"
}
}
]
}
我們知道json是一個結構化的存儲結構,其存儲元素分為三種一是簡單類型,二是對象類型,三是集合類型。但是字典是KV結構,並不存在結構化關係,在.Net Corez中配置系統是這麼解決的,比如以上信息存儲到字典中的結構就是這種
| Key | Value |
| OrderId | 202005202220 |
| Address | 銀河系太陽系火星 |
| Products:0:Id | 1 |
| Products:0:Name | 果子狸 |
| Products:0:Detail:Color | 棕色 |
| Products:1:Id | 2 |
| Products:1:Name | 蝙蝠 |
| Products:1:Detail:Weight | 200g |
如果我想獲取Products節點下的第一條商品數據直接
IConfigurationSection productSection = configuration.GetSection("Products:0")
類比到這裏的話根配置IConfigurationRoot里存儲了訂單的所有數據,獲取下來的子節點IConfigurationSection表示了訂單里第一個商品的信息,而這個商品也是一個完整的描述商品信息的數據系統,所以這樣可以更清晰的區分Configuration的結構,我們來看一下ConfigurationSection的大致實現
public class ConfigurationSection : IConfigurationSection
{
private readonly IConfigurationRoot _root;
private readonly string _path;
private string _key;
public ConfigurationSection(IConfigurationRoot root, string path)
{
_root = root;
_path = path;
}
public string Path => _path;
public string Key
{
get
{
return _key;
}
}
public string Value
{
get
{
return _root[Path];
}
set
{
_root[Path] = value;
}
}
public string this[string key]
{
get
{
//獲取當前Section下的數據其實就是組合了Path和Key
return _root[ConfigurationPath.Combine(Path, key)];
}
set
{
_root[ConfigurationPath.Combine(Path, key)] = value;
}
}
//獲取當前節點下的某個子節點也是組合當前的Path和子節點的標識Key
public IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));
//獲取當前節點下的所有子節點其實就是在字典里獲取包含當前Path字符串的所有Key
public IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(Path);
public IChangeToken GetReloadToken() => _root.GetReloadToken();
}
這裏我們可以看到既然有Key可以獲取字典里對應的Value了,為何還需要Path?通過ConfigurationRoot里的代碼我們可以知道Path的初始值其實就是獲取ConfigurationSection的Key,說白了其實就是如何獲取到當前IConfigurationSection的路徑。比如
//當前productSection的Path是 Products:0
IConfigurationSection productSection = configuration.GetSection("Products:0");
//當前productDetailSection的Path是 Products:0:Detail
IConfigurationSection productDetailSection = productSection.GetSection("Detail");
//獲取到pColor的全路徑就是 Products:0:Detail:Color
string pColor = productDetailSection["Color"];
而獲取Section所有子節點
GetChildrenImplementation來自於IConfigurationRoot的擴展方法
internal static class InternalConfigurationRootExtensions
{
//// <summary>
/// 其實就是在數據源字典里獲取Key包含給定Path的所有值
/// </summary>
internal static IEnumerable<IConfigurationSection> GetChildrenImplementation(this IConfigurationRoot root, string path)
{
return root.Providers
.Aggregate(Enumerable.Empty<string>(),
(seed, source) => source.GetChildKeys(seed, path))
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(key => root.GetSection(path == null ? key : ConfigurationPath.Combine(path, key)));
}
}
相信講到這裏,大家對ConfigurationSection或者是對Configuration整體的思路有一定的了解,細節上的設計確實不少。但是整體實現思路還是比較清晰的。關於Configuration還有一個比較重要的擴展方法就是將配置綁定到具體POCO的擴展方法,該方法承載在ConfigurationBinder擴展類了,由於實現比較複雜,也不是本篇文章的重點,有興趣的同學可以自行查閱,這裏就不做探究了。
通過以上部分的講解,其實我們可以大概的將Configuration配置相關總結為兩大核心抽象接口IConfigurationBuilder,IConfiguration,整體結構關係可大致表示成如下關係
配置相關的整體實現思路就是IConfigurationSource作為一種特定類型的數據源,它提供了提供當前數據源的提供者ConfigurationProvider,Provider負責將數據源的數據按照一定的規則放入到字典里。IConfigurationSource添加到IConfigurationBuilder的容器中,後者使用Provide構建出整個程序的根配置容器IConfigurationRoot。通過獲取IConfigurationRoot子節點得到IConfigurationSection負責維護子節點容器相關。這二者都繼承自IConfiguration,然後通過他們就可以獲取到整個配置體系的數據數據操作了。
以上講解都是本人通過實踐和閱讀源碼得出的結論,可能會存在一定的偏差或理解上的誤區,但是我還是想把我的理解分享給大家,希望大家能多多包涵。如果有大家有不同的見解或者更深的理解,可以在評論區多多留言。
歡迎掃碼關注我的公眾號 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※新北清潔公司,居家、辦公、裝潢細清專業服務
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※超省錢租車方案
※教你寫出一流的銷售文案?