embed 包 - Go 文件嵌入
embed 包是 Go 1.16 引入的特性,允许在编译时将文件嵌入到 Go 二进制中。这使得分发单文件应用、打包静态资源变得简单高效。
📌 核心概念
📦
嵌入文件
编译时打包
//go:embed
📄
embed.FS
文件系统接口
fs.FS
🖼️
嵌入图片
二进制数据
[]byte
🌐
Web 服务
静态文件服务
http.FileServer
快速开始
📝 基础嵌入
package main
import (
"embed"
"fmt"
)
// 嵌入单个文件
//go:embed message.txt
var message string
// 嵌入为 []byte
//go:embed data.bin
var data []byte
// 嵌入多个文件到 FS
//go:embed config/*.yaml
//go:embed templates/*.html
var files embed.FS
func main() {
// 读取嵌入的字符串
fmt.Println(message)
// 读取嵌入的字节
fmt.Printf("Data size: %d bytes\n", len(data))
// 从 FS 读取文件
content, _ := files.ReadFile("config/app.yaml")
fmt.Println(string(content))
}
💡 embed 要点
- 编译时嵌入: 文件内容在编译时打包到二进制
- 路径相对: 路径相对于 .go 文件所在目录
- 不支持变量: embed 指令必须是字符串字面量
- 只读: 嵌入的文件不可修改
嵌入类型
📝 三种嵌入方式
package main
import "embed"
// 方式 1: 嵌入为 string
//go:embed text.txt
var textString string
// 方式 2: 嵌入为 []byte
//go:embed image.png
var imageData []byte
// 方式 3: 嵌入为 embed.FS
//go:embed assets/*
//go:embed config/*.json
var embeddedFS embed.FS
// 通配符模式
//go:embed templates/*
//go:embed templates/**/*.html // Go 1.18+
embed.FS 文件系统
📝 使用 embed.FS
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed templates/*
var templatesFS embed.FS
func main() {
// 读取文件
content, err := templatesFS.ReadFile("templates/index.html")
if err != nil {
panic(err)
}
fmt.Println(string(content))
// 遍历目录
fs.WalkDir(templatesFS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Printf("Found: %s (dir: %v)\n", path, d.IsDir())
return nil
})
// 读取多个文件
files, _ := templatesFS.ReadDir("templates")
for _, f := range files {
fmt.Println(f.Name())
}
}
Web 静态文件服务
📝 嵌入静态文件服务
package main
import (
"embed"
"io/fs"
"log"
"net/http"
)
//go:embed static/*
var staticFS embed.FS
func main() {
// 创建子 FS (去掉 static 前缀)
subFS, err := fs.Sub(staticFS, "static")
if err != nil {
log.Fatal(err)
}
// 静态文件服务
http.Handle("/", http.FileServer(http.FS(subFS)))
// 或使用 http.ServeEmbedFS (Go 1.16+)
// http.Handle("/static/", http.FileServer(http.FS(staticFS)))
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// 目录结构:
// project/
// ├── main.go
// └── static/
// ├── index.html
// ├── style.css
// └── script.js
//
// 访问:http://localhost:8080/index.html
与模板引擎集成
📝 嵌入 HTML 模板
package main
import (
"embed"
"html/template"
"log"
"net/http"
)
//go:embed templates/*.html
var templatesFS embed.FS
var templates *template.Template
func init() {
// 从 embed.FS 解析模板
var err error
templates, err = template.ParseFS(templatesFS, "templates/*.html")
if err != nil {
log.Fatal(err)
}
}
type PageData struct {
Title string
Name string
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
data := PageData{
Title: "Home Page",
Name: "Visitor",
}
templates.ExecuteTemplate(w, "index.html", data)
}
func main() {
http.HandleFunc("/", homeHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
嵌入配置文件
📝 嵌入配置和迁移文件
package main
import (
"embed"
"encoding/json"
"fmt"
)
//go:embed config/app.json
var configFS embed.FS
type Config struct {
AppName string `json:"app_name"`
ServerPort int `json:"server_port"`
Debug bool `json:"debug"`
}
func LoadConfig() (*Config, error) {
data, err := configFS.ReadFile("config/app.json")
if err != nil {
return nil, err
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}
func main() {
cfg, err := LoadConfig()
if err != nil {
panic(err)
}
fmt.Printf("App: %s, Port: %d, Debug: %v\n",
cfg.AppName, cfg.ServerPort, cfg.Debug)
}
数据库迁移文件
📝 嵌入 SQL 迁移文件
package main
import (
"database/sql"
"embed"
"fmt"
_ "github.com/lib/pq"
)
//go:embed migrations/*.sql
var migrationsFS embed.FS
func RunMigrations(db *sql.DB) error {
// 读取迁移文件
files, err := migrationsFS.ReadDir("migrations")
if err != nil {
return err
}
for _, f := range files {
if f.IsDir() {
continue
}
// 读取 SQL 文件
content, err := migrationsFS.ReadFile("migrations/" + f.Name())
if err != nil {
return err
}
// 执行迁移
fmt.Printf("Running migration: %s\n", f.Name())
if _, err := db.Exec(string(content)); err != nil {
return err
}
}
return nil
}
注意事项
⚠️ embed 限制
- 路径限制: 不能嵌入父目录文件(../)
- 不支持变量: 路径必须是字符串字面量
- 只读访问: 嵌入的文件不可修改
- 编译时检查: 文件不存在会导致编译错误
- 通配符: 只能匹配文件,不递归匹配隐藏文件
最佳实践
✅ embed 使用建议
- 静态资源: HTML/CSS/JS 等前端资源
- 配置文件: 默认配置、示例配置
- 模板文件: HTML 模板、邮件模板
- 迁移脚本: SQL 迁移文件
- 单文件分发: 需要独立运行的工具