golang1.16内嵌静态资源指南 (3)

如果你newgo doc embed的话会发现整个标准库里只有一个FS类型(之前按提案被命名为Files,后来考虑到用目录结构组织多个资源更类似新的io/fs.FS接口,故改名),而我们对静态资源的操作也全都依赖这个FS。下面接着用例子说明:

package main import ( "fmt" "embed" ) //go:embed texts var dir embed.FS // 两者没什么区别 //go:embed texts/* var files embed.FS func main(){ zh, err := files.ReadFile("texts/zh.txt") if err != nil { fmt.Println("read zh.txt error:", err) } else { fmt.Println("zh.txt:", string(zh)) } jp, err := dir.ReadFile("jp.txt") if err != nil { fmt.Println("read jp.txt error:", err) } else { fmt.Println("jp.txt:", string(jp)) } jp, err = dir.ReadFile("texts/jp.txt") if err != nil { fmt.Println("read jp.txt error:", err) } else { fmt.Println("jp.txt:", string(jp)) } }

运行结果:

zh.txt: 你好,世界 read jp.txt error: open jp.txt: file does not exist jp.txt: こんにちは、世界

我们想读取单个文件需要用ReadFile方法,它接受一个path字符串做参数,从中查找对应的文件然后返回([]byte, error)。

要注意的是文件路径必须要明确写出自己的父级目录,否则会报错,因为嵌入资源是按它存储路径相同的结构存储的,和通配符怎么指定无关。

Open是和ReadFile类似的方法,只不过返回了一个fs.File类型的io.Reader,因此这里就不再赘述,需要使用Open还是ReadFile可以由开发者根据自身需求决定。

embed.FS自身是只读的,所以我们不能在运行时添加或删除嵌入的文件,fs.File也是只读的,所以我们不能修改嵌入资源的内容。

如果只是提供了一个查找读取资源的能力,那未免小看了embed。在golang1.16里任意实现了io/fs.FS接口的类型都可以表现的像是真实存在于文件系统中的目录一样,哪怕它其实是在内存里的类map数据结构。因此我们也可以像遍历目录一样去处理embed.FS:

package main import ( "embed" "fmt" ) // 更推荐直接用imgs去匹配 //go:embed imgs/** var dir embed.FS // 遍历当前目录,有兴趣你可以改成递归版本的 func printDir(name string) { // 返回[]fs.DirEntry entries, err := dir.ReadDir(name) if err != nil { panic(err) } fmt.Println("dir:", name) for _, entry := range entries { // fs.DirEntry的Info接口会返回fs.FileInfo,这东西被从os移动到了io/fs,接口本身没有变化 info, _ := entry.Info() fmt.Println("file name:", entry.Name(), "\tisDir:", entry.IsDir(), "\tsize:", info.Size()) } fmt.Println() } func main() { printDir("imgs") printDir("imgs/jpg") printDir("imgs/png") }

运行结果:

dir: imgs file name: jpg isDir: true size: 0 file name: png isDir: true size: 0 file name: screenrecord.gif isDir: false size: 81100466 dir: imgs/jpg file name: a.jpg isDir: false size: 620419 file name: b.jpg isDir: false size: 999162 file name: c.jpg isDir: false size: 349725 dir: imgs/png file name: a.png isDir: false size: 4958264 file name: b.png isDir: false size: 1498303 file name: c.png isDir: false size: 1751934

唯一和真实的目录不一样的地方是目录文件的大小,在ext4等文件系统上目录会存储子项目的元信息,所以大小通常不为0。

如果想要内嵌整个module,则在引用的时候需要使用"."这个名字,但除了单独使用之外路径里不可以包含..或者.,换而言之,embed.FS不支持相对路径,把上面的代码稍加修改:

package main import ( "fmt" "embed" ) //go:embed * var dir embed.FS func main() { printDir(".") //printDir("./texts/../imgs") panic: open ./texts/../imgs: file does not exist }

程序输出:

dir: . file name: embed_fs.go isDir: false size: 484 file name: embed_img.go isDir: false size: 235 file name: embed_img2.go isDir: false size: 187 file name: embed_img_fs.go isDir: false size: 692 file name: embed_text.go isDir: false size: 211 file name: embed_text_fs.go isDir: false size: 603 file name: go.mod isDir: false size: 30 file name: imgs isDir: true size: 0 file name: macbeth.txt isDir: false size: 100095 file name: texts isDir: true size: 0

因为使用了错误的文件名或路径会在运行时panic,所以要格外小心。(当然//go:embed是在编译时检查的,而且同样不支持相对路径,同时也不支持超出了module目录的任何路径,比如go module在/tmp/proj,我们指定了/tmp/proj2)

你也可以用embed.FS处理单个文件,但我个人认为单个文件就没必要再多包装一层了。

由于是golang内建的支持,所以上述的代码无需调用任何第三方工具,也没有烦人的生成代码,不得不说golang对工程控制的把握上还是相当可靠的。

一些陷阱

方便的功能背后往往也会有陷阱相随,golang的内置静态资源嵌入也不例外。

资源是否应该被压缩

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsffsd.html