前言
记录使用到其中一项 Go 1.16 的 embed 功能,可以把任何文件在编译时就包进来,进而不用烦恼路径与环境问题。
使用时机
在写某项权限功能时会需要读取本地的 JSON 设置,我本可以直接读取文件使用它:
if data, err := os.ReadFile("./rbac.json"); err != nil { panic(err)}但可能会遇到:
- 部署到 Docker 后忘了把文件 COPY 进去
- CI / 测试环境的工作目录不同
- 二进制文件被放到其他机器执行,却找不到相对路径
- 想做成单一可执行文件发布
单文件
我自己在跑测试环境时就觉得动态抓文件很麻烦。为了不让程序依赖外部文件设置,可以在编译时就透过 embed 包进 binary:
package constants
import _ "embed"
//go:embed rbac.jsonvar RBACJSON []byteif err := json.Unmarshal(constants.RBACJSON, &parsed); err != nil { println("ERROR: Failed to unmarshal embedded rbac.json:", err.Error()) return}这个像魔法的注解://go:embed 是一个编译指令(compiler directive),编译后,rbac.json 内容就已经存在 rbacData 变量中。
变量类型可以是:
string[]byteembed.FS
多文件
package main
import ( _ "embed" "fmt")
//go:embed messages/*.txtvar messages embed.FS
func main() { files, _ := messages.ReadDir("messages") for _, file := range files { data, _ := messages.ReadFile("messages/" + file.Name()) fmt.Printf("File: %snContent: %snn", file.Name(), data) }}总结
| 比较项 | os.ReadFile | embed |
|---|---|---|
| 文件来源 | 运行时读取 | 编译时嵌入 |
| 是否依赖路径 | 是 | 否 |
| 发布方式 | 需携带文件 | 单一二进制文件 |
| 适合场景 | 动态变更文件 | 静态配置文件 |
如果数据需要在 runtime 被修改,那就不适合使用 embed,但如果它是「版本的一部分」,那 embed 是更安全的选择。
- embed 是编译时决定内容,修改文件后必须重新编译
- 文件会增加 binary 体积,不适合嵌入大型资源(例如视频)
- 不能嵌入项目外的文件