Go 1.16新特性-embed包及其使用

embed是什么

embed是在Go 1.16中新加包。它通过//go:embed指令,可以在编译阶段将静态资源文件打包进编译好的程序中,并提供访问这些文件的能力。

为什么需要embed包

  • 部署过程更简单。传统部署要么需要将静态资源与已编译程序打包在一起上传,或者使用docker和dockerfile自动化前者,这在精神上是很麻烦的。
  • 确保程序的完整性。在运行过程中损坏或丢失静态资源通常会影响程序的正常运行。
  • 您可以独立控制程序所需的静态资源。

embed的常用场景

以下列举一些静态资源文件需要被嵌入到程序中的常用场景:

  • Go模板:模板文件必须可用于二进制文件(模板文件需要对二进制文件可用)。 对于Web服务器二进制文件或那些通过提供init命令的CLI应用程序,这是一个相当常见的用例。 在没有嵌入的情况下,模板通常内联在代码中。例如示例qbec init的init命令:https://qbec.io/userguide/tour/#initialize-a-new-qbec-app
  • 静态web服务:有时,静态文件(如index.html或其他HTML,JavaScript和CSS文件之类的静态文件)需要使用golang服务器二进制文件进行传输,以便用户可以运行服务器并访问这些文件。例如示例web server中嵌入静态资源文件:https://github.com/gobuffalo/toodo/tree/master/assets
  • 数据库迁移:另一个使用场景是通过嵌入文件被用于数据库迁移脚本。参考示例数据库迁移文件:https://github.com/bigpanther/trober/tree/786dc471ea0d9b4a9e934d7e3c192de214f7c173/migrations

基本使用

基本语法非常简单,首先导入embed包,然后使用指令//go:embed 文件名 将对应的文件或目录结构导入到对应的变量上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
    _ "embed"
    "fmt"
)

//go:embed version.txt
var version string

func main() {
    fmt.Printf("version: %q\n", version)
}

在version.txt文件中输入内容,编译打包后,可将文件中的内容绑定到version变量。

三种数据类型支持

在embed中,可以将静态资源文件嵌入到三种类型的变量,分别为:字符串、字节数组、embed.FS文件类型

1
2
3
4
5
6
7
8
//go:embed hello.txt
var s string

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

//go:embed hello.txt
var f embed.FS

将文件目录结构映射成embed.FS文件类型。embed.FS结构主要有3个对外方法

1
2
3
4
5
6
7
8
// Open 打开要读取的文件,并返回文件的fs.File结构.
func (f FS) Open(name string) (fs.File, error)

// ReadDir 读取并返回整个命名目录
func (f FS) ReadDir(name string) ([]fs.DirEntry, error)

// ReadFile 读取并返回name文件的内容.
func (f FS) ReadFile(name string) ([]byte, error)

拓展用法

在指定 go:embed 注解时可以一次性多个文件来读取,并且也可以一个变量多行注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//go:embed hello1.txt hello2.txt
var f embed.FS

func main() {
 data1, _ := f.ReadFile("hello1.txt")
 fmt.Println(string(data1))

 data2, _ := f.ReadFile("hello2.txt")
 fmt.Println(string(data2))
}

多行注解

1
2
3
//go:embed hello1.txt 
//go:embed hello2.txt
var f embed.FS

也可以通过在注解中指定目录 helloworld,再对应读取文件:

1
2
//go:embed helloworld
var f embed.FS

同时既然能够支持目录读取,也能支持贪婪模式的匹配:

1
2
//go:embed helloworld/*
var f embed.FS

embed.FS 也能调各类文件系统的接口,其实本质是 embed.FS 实现了 io/fs 接口。

参考