本文由go語(yǔ)言教程欄目給大家介紹Golang1.16怎么使用embed加載靜態(tài)文件 ,希望對(duì)需要的朋友有所幫助!
embed是什么
embed是在Go 1.16中新加入的包。它通過(guò)//go:embed
指令,可以在編譯階段將靜態(tài)資源文件打包進(jìn)編譯好的程序中,并提供訪問(wèn)這些文件的能力。
為什么需要embed
在以前,很多從其他語(yǔ)言轉(zhuǎn)過(guò)來(lái)Go語(yǔ)言的小伙伴會(huì)問(wèn)到,或者踩到一個(gè)坑:就是以為Go語(yǔ)言所打包的二進(jìn)制文件中會(huì)包含配置文件的聯(lián)同編譯和打包。
結(jié)果往往一把二進(jìn)制文件挪來(lái)挪去,就無(wú)法把應(yīng)用程序運(yùn)行起來(lái)了,因?yàn)闊o(wú)法讀取到靜態(tài)文件的資源。
無(wú)法將靜態(tài)資源編譯打包二進(jìn)制文件的話,通常會(huì)有兩種解決方法:
- 第一種是識(shí)別這類靜態(tài)資源,是否需要跟著程序走。
- 第二種就是將其打包進(jìn)二進(jìn)制文件中。
第二種情況的話,Go以前是不支持的,大家就會(huì)借助各種花式的開(kāi)源庫(kù),例如:go-bindata/go-bindata
來(lái)實(shí)現(xiàn)。
但是在Go1.16起,Go語(yǔ)言自身正式支持了該項(xiàng)特性。
它有以下優(yōu)點(diǎn)
- 能夠?qū)㈧o態(tài)資源打包到二進(jìn)制包中,部署過(guò)程更簡(jiǎn)單。傳統(tǒng)部署要么需要將靜態(tài)資源與已編譯程序打包在一起上傳,或者使用
docker
和dockerfile
自動(dòng)化前者,這是很麻煩的。 - 確保程序的完整性。在運(yùn)行過(guò)程中損壞或丟失靜態(tài)資源通常會(huì)影響程序的正常運(yùn)行。
- 靜態(tài)資源訪問(wèn)沒(méi)有io操作,速度會(huì)非???/strong>。
embed基礎(chǔ)用法
通過(guò) 官方文檔 我們知道embed嵌入的三種方式:string、bytes 和 FS(File Systems)。其中string
和[]byte
類型都只能匹配一個(gè)文件,如果要匹配多個(gè)文件或者一個(gè)目錄,就要使用embed.FS
類型。
特別注意:embed這個(gè)包一定要導(dǎo)入,如果導(dǎo)入不使用的話,使用 _ 導(dǎo)入即可
一、嵌入為字符串
比如當(dāng)前文件下有個(gè)hello.txt的文件,文件內(nèi)容為hello,world!
。通過(guò)go:embed
指令,在編譯后下面程序中的s變量的值就變?yōu)榱?code>hello,world!。
package mainimport ( _ "embed" "fmt")//go:embed hello.txtvar s stringfunc main() { fmt.Println(s)}
二、嵌入為byte slice
你還可以把單個(gè)文件的內(nèi)容嵌入為slice of byte,也就是一個(gè)字節(jié)數(shù)組。
package mainimport ( _ "embed" "fmt")//go:embed hello.txtvar b []bytefunc main() { fmt.Println(b)}
三、嵌入為fs.FS
甚至你可以嵌入為一個(gè)文件系統(tǒng),這在嵌入多個(gè)文件的時(shí)候非常有用。
比如嵌入一個(gè)文件:
package mainimport ( "embed" "fmt")//go:embed hello.txtvar f embed.FSfunc main() { data, _ := f.ReadFile("hello.txt") fmt.Println(string(data))}
嵌入本地的另外一個(gè)文件hello2.txt, 支持同一個(gè)變量上多個(gè)go:embed
指令(嵌入為string或者byte slice是不能有多個(gè)go:embed
指令的):
package mainimport ( "embed" "fmt")//go:embed hello.txt//go:embed hello2.txtvar f embed.FSfunc main() { data, _ := f.ReadFile("hello.txt") fmt.Println(string(data)) data, _ = f.ReadFile("hello2.txt") fmt.Println(string(data))}
當(dāng)前重復(fù)的go:embed
指令嵌入為embed.FS是支持的,相當(dāng)于一個(gè):
package mainimport ( "embed" "fmt")//go:embed hello.txt//go:embed hello.txtvar f embed.FSfunc main() { data, _ := f.ReadFile("hello.txt") fmt.Println(string(data))}
還可以嵌入子文件夾下的文件:
package mainimport ( "embed" "fmt")//go:embed p/hello.txt//go:embed p/hello2.txtvar f embed.FSfunc main() { data, _ := f.ReadFile("p/hello.txt") fmt.Println(string(data)) data, _ = f.ReadFile("p/hello2.txt") fmt.Println(string(data))}
embed進(jìn)階用法
Go1.16 為了對(duì) embed
的支持也添加了一個(gè)新包 io/fs
。兩者結(jié)合起來(lái)可以像之前操作普通文件一樣。
一、只讀
嵌入的內(nèi)容是只讀的。也就是在編譯期嵌入文件的內(nèi)容是什么,那么在運(yùn)行時(shí)的內(nèi)容也就是什么。
FS文件系統(tǒng)值提供了打開(kāi)和讀取的方法,并沒(méi)有write的方法,也就是說(shuō)FS實(shí)例是線程安全的,多個(gè)goroutine可以并發(fā)使用。
embed.FS結(jié)構(gòu)主要有3個(gè)對(duì)外方法,如下:
// Open 打開(kāi)要讀取的文件,并返回文件的fs.File結(jié)構(gòu).func (f FS) Open(name string) (fs.File, error)// ReadDir 讀取并返回整個(gè)命名目錄func (f FS) ReadDir(name string) ([]fs.DirEntry, error)// ReadFile 讀取并返回name文件的內(nèi)容.func (f FS) ReadFile(name string) ([]byte, error)
二、嵌入多個(gè)文件
package mainimport ( "embed" "fmt")//go:embed hello.txt hello2.txtvar f embed.FSfunc main() { data, _ := f.ReadFile("hello.txt") fmt.Println(string(data)) data, _ = f.ReadFile("hello2.txt") fmt.Println(string(data))}
當(dāng)然你也可以像前面的例子一樣寫成多行go:embed
:
package mainimport ( "embed" "fmt")//go:embed hello.txt//go:embed hello2.txtvar f embed.FSfunc main() { data, _ := f.ReadFile("hello.txt") fmt.Println(string(data)) data, _ = f.ReadFile("hello2.txt") fmt.Println(string(data))}
三、支持文件夾
文件夾分隔符采用正斜杠/
,即使是windows系統(tǒng)也采用這個(gè)模式。
package mainimport ( "embed" "fmt")//go:embed pvar f embed.FSfunc main() { data, _ := f.ReadFile("p/hello.txt") fmt.Println(string(data)) data, _ = f.ReadFile("p/hello2.txt") fmt.Println(string(data))}
應(yīng)用
在我們的項(xiàng)目中,是將應(yīng)用的常用的一些配置寫在了.env的一個(gè)文件上,所以我們?cè)谶@里就可以使用go:embed
指令。
main.go
文件:
//go:embed ".env" "v1d0/.env"var FS embed.FSfunc main(){ setting.InitSetting(FS) manager.InitManager() cron.InitCron() routersInit := routers.InitRouter() readTimeout := setting.ServerSetting.ReadTimeout writeTimeout := setting.ServerSetting.WriteTimeout endPoint := fmt.Sprintf(":%d", setting.ServerSetting.HttpPort) maxHeaderBytes := 1 << 20 server := &http.Server{ Addr: endPoint, Handler: routersInit, ReadTimeout: readTimeout, WriteTimeout: writeTimeout, MaxHeaderBytes: maxHeaderBytes, } server.ListenAndServe()}
setting.go
文件:
func InitSetting(FS embed.FS) { // 總配置處理 var err error data, err := FS.ReadFile(".env") if err != nil { log.Fatalf("Fail to parse '.env': %v", err) } cfg, err = ini.Load(data) if err != nil { log.Fatal(err) } mapTo("server", ServerSetting) ServerSetting.ReadTimeout = ServerSetting.ReadTimeout * time.Second ServerSetting.WriteTimeout = ServerSetting.WriteTimeout * time.Second // 分版本配置引入 v1d0Setting.InitSetting(FS)}