go:embed 在 Go 開發中的應用與最佳實踐


背景

在使用 Go 開發命令行工具或桌面軟件時,將配置文件、模板,甚至整個前端應用直接嵌入到 Go 二進制文件中是一種提高應用部署效率和簡化操作的有效方法。這種方法可以減少外部依賴,讓應用在沒有額外資源文件的情況下也能獨立運行,特別適合需要便捷分發和部署的場景。自 Go 1.16 版本起,Go 語言官方引入了 //go:embed 指令,使得嵌入靜態資源變得異常簡單而直接。這一新特性大大簡化了開發流程,使開發者能夠更加專注於核心功能的實現。

go:embed 介紹

go:embed 是一個編譯器指令,能夠在程序編譯時期將任意文件和目錄嵌入到 Go 的二進制文件中。通過這種方式,開發者可以在不依賴外部文件的情況下,直接在代碼中訪問這些資源,從而實現了高效、靈活的文件管理。

特點

使用方法

go:embed 的使用非常簡單,以下是幾種常見的用法:

import "embed"

//go:embed mobile.txt
var mobile string

//go:embed hello.txt
var contentBytes []byte

//go:embed hello.txt
var fileFS embed.FS
var data, _ = fileFS.ReadFile("hello.txt")

如上所示,直接在註釋中使用 go:embed 指令,就可以將文件嵌入到程序中。

嵌入爲字符串

將文件內容嵌入爲字符串的方式適合處理文本數據,如配置文件、模板或其他小型文件。

package main
import (
 _ "embed"
 "fmt"
)
//go:embed pfinalclub.txt
var pfinal string
func main() {
 fmt.Println(pfinal)
}

嵌入爲 byte slice

將文件內容嵌入爲字節切片適合處理二進制數據,如圖片、字體或其他非文本數據。

package main
import (
 _ "embed"
 "fmt"
)
//go:embed pfinalclub.txt
var pfinal []byte
func main() {
 fmt.Println(pfinal)
}

嵌入爲 embed.FS

將多個文件或整個目錄嵌入爲文件系統 (embed.FS) 適合需要訪問多個文件的場景,如嵌入靜態網站的所有資源。

package main
import (
 _ "embed"
 "embed"
 "fmt"
)
//go:embed pfinalclub.txt
var pfinal embed.FS
func main() {
data, _ := pfinal.ReadFile("pfinalclub.txt")
 fmt.Println(string(data))
}

多文件嵌入

有時候, 要嵌入多個文件, go:embed 支持同一個變量上多個 go:embed 指令 (嵌入爲 string 或者 byte slice 是不能有多個 go:embed 指令的):

//go:embed pfinal.txt
//go:embed pfinalclub.txt
var pfinal embed.FS
func main() {
 data, _ := f.ReadFile("pfinal.txt")
 fmt.Println(string(data))
 data, _ = f.ReadFile("pfinalclub.txt")
    fmt.Println(string(data))
}

根據不同的需求,可以選擇最適合的嵌入方式:

注意事項

只讀限制

嵌入的內容在運行時是隻讀的,即在編譯期嵌入文件的內容是什麼,運行時讀取到的內容也將保持不變。embed.FS 提供了文件系統相關的基本操作,如打開和讀取文件,但不支持寫操作,這使得其在多線程環境中是安全的,多個 goroutine 可以併發訪問 embed.FS 實例而不會產生競爭條件。

type FS
    func (f FS) Open(name string) (fs.File, error)
    func (f FS) ReadDir(name string) ([]fs.DirEntry, error)
    func (f FS) ReadFile(name string) ([]byte, error)

文件模式匹配

go:embed 指令中,可以只寫文件夾名,此時該文件夾中除了以 . 和 _ 開頭的文件和文件夾外,其他文件都會被嵌入,並且子文件夾也會被遞歸嵌入,形成一個完整的文件系統。

如果需要嵌入以 . 和 _ 開頭的文件或文件夾(如 .hello.txt),則需要使用通配符 ,如 go:embed p/。但請注意,* 不具有遞歸性,子文件夾下的 . 和 _ 開頭的文件不會被嵌入,除非在子文件夾中單獨使用通配符進行嵌入。

//go:embed pfinal/*
var pfinal embed.FS
func main() {
 data, _ := f.ReadFile("pfinal/.pfinal.txt")
 fmt.Println(string(data))
 data, _ = f.ReadFile("pfinal/q/.pf.txt") // 沒有嵌入 pfinal/q/.hi.txt
 fmt.Println(string(data))
}

此外,嵌入模式不支持絕對路徑,也不支持路徑中包含 . 和 ..。如果想嵌入當前 Go 源文件所在路徑的文件,可以使用通配符 *:

package main
import (
 "embed"
 "fmt"
)
//go:embed "he llo.txt" `hello-2.txt`
var f embed.FS
func main() {
 data, _ := f.ReadFile("he llo.txt")
 fmt.Println(string(data))
}

參考

/**
 * @Author: PFinal南丞
 * @Author: lampxiezi@163.com
 * @Date: 2023/11/9
 * @Desc:示例僞代碼
 * @Project: pf_tools
 */
const (
 IntLen           = 4
 CharLen          = 1
 PhoneIndexLength = 9
 CHUNK            = 100
 PhoneDat         = "phone.dat"
)

//go:embed phone.dat
var fsContent embed.FS

type PhoneRecord struct {
 PhoneNum string
 Province string
 City     string
 ZipCode  string
 AreaZone string
 CardType string
}

var content []byte

func init() {
 var err error
 content, err = fsContent.ReadFile(PhoneDat)
 if err != nil {
  panic(err)
 }
}

....

通過這些示例,可以看到 go:embed 指令在嵌入靜態資源方面的強大功能和靈活性。開發者可以根據實際需求,選擇最適合的嵌入方式,提高程序的可移植性和部署效率。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/IGv5J0lBjJ6h-eI5ME0nag