對於 Go 語言開發者來說,在享受語言便利性的同時,最終編譯的單一可執行文件也是我們所熱衷的。
但是,Go在編譯成二進制文件時並沒有把我們的靜態資源文件編譯進去,如果我們開發的是web應用的話就需要想辦法把我們的靜態文件也編譯進去。
本文收集了一些Go語言中用來在編譯過程中將靜態文件打包到編譯文件的方法。
go-bindata
在 Go 語言的 Awesome 中你可以看到很多靜態打包庫,但是,你卻看不到 go-bindata, go-bindata 明顯更受歡迎,更流行。
go-bindata 很簡單,設計理念也不難理解。它的任務就是講靜態文件封裝在一個 Go 語言的 Source Code 裏面,然後提供一個統一的接口,你通過這個接口傳入文件路徑,它將給你返回對應路徑的文件數據。這也就是說它不在乎你的文件是字符型的文件還是字節型的,你自己處理,它只管包裝。
簡單來說就是它可以把我們的靜態文件生成 .go 文件,這樣就可以編譯成二進制文件,項目啓動的時候再把這個 .go 文件再釋放成靜態文件
下載
go get -u github.com/jteeuwen/go-bindata/...
安裝確認
C:\Users\Administrator\Desktop>go-bindata -version
go-bindata 3.1.0 (Go runtime go1.14).
Copyright (c) 2010-2013, Jim Teeuwen.
注意: GOPATH必須在環境變量下
使用
例子一
ConfigTest
├── asset
│ └── asset.go 靜態文件編譯之後的go文件
├── cli # 運行目錄
├── config # 配置文件目錄
│ ├── config.json
│ └── config.yaml
└── main # 程序目錄
└── main.go # 源碼
config.yaml內容
enabled: true
path: aaaaa
id: 10
config.json內容
{
"enabled": true,
"path": "xxxx",
"id": 111
}
執行命令將靜態文件打包成go文件
go-bindata -o=./asset/asset.go -pkg=asset config/...
-o 輸出文件到 ./asset/asset.go
包名 -pkg=asset
config/… # 指定需要打包的靜態文件路徑, …包括所有子目錄
// 可以參考:https://www.cnblogs.com/landv/p/11577213.html
其路徑變爲:
ConfigTest
├── asset
│ └── asset.go 靜態文件編譯之後的go文件
├── cli # 運行目錄
├── config # 配置文件目錄
│ ├── config.json
│ └── config.yaml
└── main # 程序目錄
└── main.go # 源碼
main.go
package main
import (
"configTest/asset"
"encoding/json"
"fmt"
"gopkg.in/yaml.v2"
"log"
"os"
"path/filepath"
)
type conf struct {
Enabled bool
Path string
ID int
}
func (c *conf) ReadYaml() {
data, _ := asset.Asset("config/config.yaml")
err := yaml.Unmarshal(data, &c)
if err != nil {
log.Fatalf("Unmarshal: %v", err)
}
}
func (c *conf) ReadJson() {
data, _ := asset.Asset("config/config.json")
err := json.Unmarshal(data, &c)
if err != nil {
log.Fatalf("Unmarshal: %v", err)
}
}
// 將dir解壓到當前目錄:根據生成的.go文件,將其解壓爲當前文件
func restore() {
// 可以參考:https://www.cnblogs.com/landv/p/11577213.html
dirs := []string{"config"} // 設置需要釋放的目錄
isSuccess := true
for _, dir := range dirs {
// 解壓dir目錄到當前目錄
if err := asset.RestoreAssets("./", dir); err != nil {
isSuccess = false
break
}
}
if !isSuccess {
for _, dir := range dirs {
os.RemoveAll(filepath.Join("./", dir))
}
}
}
func main() {
var c, j conf
j.ReadJson()
fmt.Println("json:", j)
c.ReadYaml()
fmt.Println("yaml:", c)
fmt.Println("釋放靜態文件")
restore()
}
- 編譯 main.go 執行二進制文件
cd cli && go build ../main/main.go
./main
json: {true xxxx 111}
yaml: {true aaaaa 10}
釋放靜態文件
執行之後會自動解壓出config目錄以及下面的靜態文件
ConfigTest
├── asset
│ └── asset.go 靜態文件編譯之後的go文件
├── cli # 運行目錄
│ ├── config # 調用了restore()會生成這個文件
│ │ ├── config.json
│ │ └── config.yaml
│ └── main # main.go編譯之後生成的二進制執行文件
├── config # 配置文件目錄
│ ├── config.json
│ └── config.yaml
└── main # 程序目錄
└── main.go # 源碼
main.go
http.FileSystem是定義HTTP靜態文件服務的接口。go-bindata的第三方包,go-bindata-assetfs實現了這個接口,支持http訪問靜態文件目錄的行爲。
package main
import (
"configTest/asset"
assetfs "github.com/elazarl/go-bindata-assetfs"
"net/http"
)
func myhttp() {
fs := assetfs.AssetFS{
Asset: asset.Asset,
AssetDir: asset.AssetDir,
AssetInfo: asset.AssetInfo,
}
http.Handle("/", http.FileServer(&fs))
http.ListenAndServe(":12345", nil)
}
func main() {
myhttp()
}
訪問 http://localhost:12345
,就可以看到嵌入的靜態資源目錄的內容了
和 Nginx 查看靜態文件目錄一樣的。
例子二
- 目錄
ConfigTest
├── asset
│ └── asset.go 靜態文件編譯之後的go文件
├── cli # 運行目錄
├── config # 配置文件目錄
│ ├── config.html
└── main # 程序目錄
└── main.go # 源碼
- config.html內容
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
</body>
</html>
- 打包命令:生成./asset/asset.go
go-bindata -o=./asset/asset.go -pkg=asset config/...
- main內容:
package main
import (
"configTest/asset"
"fmt"
assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/gin-contrib/multitemplate"
"github.com/gin-gonic/gin"
"html/template"
)
func main() {
fs := assetfs.AssetFS{
Asset: asset.Asset,
AssetDir: asset.AssetDir,
AssetInfo: asset.AssetInfo,
}
router := gin.Default()
router.StaticFS("/static", &fs)
r := multitemplate.New()
bytes, err := asset.Asset("config/config.html") // 根據地址獲取對應內容
if err != nil {
fmt.Println(err)
return
}
t, err := template.New("index").Parse(string(bytes)) // 比如用於模板處理
r.Add("index", t)
router.HTMLRender = r
router.GET("/image", func(c *gin.Context) {
c.HTML(200, "index", gin.H{})
})
router.Run(":12345")
}
go.rice【不好用】
go.rice
也支持打包靜態文件到 go
文件中,但是行爲和 go-bindata
很不相同。從使用角度,go.rice
其實是更便捷的靜態文件操作庫。打包靜態文件反而是順帶的功能。
安裝
go get github.com/GeertJohan/go.rice/...
使用一
import (
"fmt"
"html/template"
"github.com/GeertJohan/go.rice"
)
func main() {
// 這裏寫相對於的執行文件的地址
box, err := rice.FindBox("theme/default")
if err != nil {
println(err.Error())
return
}
// 從目錄 Box 讀取文件
str, err := box.String("post.html")
if err != nil {
println(err.Error())
return
}
t, err := template.New("tpl").Parse(str)
fmt.Println(t, err)
}
使用二
go.rice 是直接支持 http.FileSystem 接口:
package main
import (
"github.com/GeertJohan/go.rice"
"net/http"
)
func main() {
http.Handle("/", http.FileServer(rice.MustFindBox("../config").HTTPBox()))
http.ListenAndServe(":12345", nil)
}
有點略繁瑣的是 rice.FindBox(dir) 只能加載一個目錄。因此需要多個目錄的場景,會有代碼:
func main() {
http.Handle("/img", http.FileServer(rice.MustFindBox("static/img").HTTPBox()))
http.Handle("/css", http.FileServer(rice.MustFindBox("static/css").HTTPBox()))
http.Handle("/js", http.FileServer(rice.MustFindBox("static/js").HTTPBox()))
http.ListenAndServe(":12345", nil)
}
esc
esc 的作者在研究幾款嵌入靜態資源的 工具 後,發覺都不好用,就自己寫出了 esc。它的需求很簡單,就是嵌入靜態資源 和 支持 http.FileSystem 。esc 工具也這兩個主要功能。
安裝
go get github.com/mjibson/esc
使用
使用方法和 go-bindata 類似:
// 注意 esc 不支持 source/... 三個點表示所有子目錄
go-bindata -o=asset/asset.go -pkg=asset source theme doc/source doc/theme
import (
"net/http"
"asset" // esc 生成 asset/asset.go
)
func main() {
fmt.Println(asset.FSString(false, "/theme/default/post.html")) // 讀取單個文件
http.ListenAndServe(":12345", http.FileServer(asset.FS(false))) // 支持 http.FileSystem,但是沒有做展示目錄的支持
}
esc 有個較大的問題是隻能一個一個文件操作,不能文件夾操作,沒有類似 go-bindata 的 asset.RestoreDir() 方法。並且沒有方法可以列出嵌入的文件的列表,導致也無法一個一個文件操作,除非自己寫死。這是我不使用他的最大原因。
go generate
嵌入靜態資源的工具推薦配合 go generate 使用