Go語言-打包靜態文件

對於 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 使用

參考:https://www.cnblogs.com/landv/p/11577213.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章