← bufio 包 | exp 包 →

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 迁移文件
  • 单文件分发: 需要独立运行的工具

📖 延伸阅读