Skip to content

文件系统

概述

Goyave 基于 Go 标准的 io/fs API,设计时考虑了灵活的文件系统。所有依赖文件的功能都与此 API 兼容,并且可以使用任何实现。以下是非详尽的文件系统用途列表:

  • 嵌入式配置
  • 嵌入式语言文件
  • 嵌入式静态资源(HTML 模板或 Web 资源,如 CSS、JS 等)
  • 远程静态资源(例如由云存储桶提供)
  • 用户内容存储(在云存储上或本地操作系统文件系统上)

goyave.dev/goyave/v5/util/fsutil 包提供了一些有用的文件相关函数,以及文件系统的附加接口,例如:

  • fsutil.FS: 同时实现 fs.ReadDirFSfs.StatFS 的 FS
  • fsutil.WorkingDirFS: 可以返回工作目录的 FS
  • fsutil.MkdirFS: 能够使用 Mkdir()MkdirAll() 创建目录的 FS
  • fsutil.WritableFS: 可以 OpenFile() 并支持写操作的 FS
  • fsutil.RemoveFS: 能够使用 Remove()RemoveAll() 删除文件的 FS

TIP

建议在处理任何类型的文件时使用文件系统接口。这不仅能灵活处理文件来源,还有助于编写测试。

操作系统 (OS)

本地操作系统文件系统的实现可在 goyave.dev/goyave/v5/util/fsutil/osfs 找到。这样,您可以像使用任何其他文件系统一样使用本地文件系统。

此实现与需要以下接口的功能兼容:

  • fsutil.FS
  • fsutil.WorkingDirFS
  • fsutil.MkdirFS
  • fsutil.WritableFS
  • fsutil.RemoveFS

您还可以使用 (&osfs.FS{}).Sub("path") 获取子文件系统。使用生成的文件系统将始终在请求的文件名前附加路径前缀。例如,使用此文件系统时,Open("test.txt") 将尝试打开 path/test.txt

INFO

如果您使用 Go 1.24+,可以使用 os.Root 代替 osfs.FS 以获得额外的安全性。FS() 方法返回的 fs.FSfsutil.FS 接口兼容。

go
root, _ := os.OpenRoot("resources/public")
fs := root.FS().(fsutil.FS)

嵌入 (Embed)

标准的 embed.FS 文件系统允许将多个文件作为只读集合嵌入到编译后的应用程序二进制文件中。但是,当您需要 Stat()Sub()(获取子文件系统)时,使用起来很不方便,因为这些方法没有实现。

Goyave 提供了一个包装器,扩展了标准类型,使其也与需要 fs.StatFSfs.SubFS 的功能兼容。此包装器名为 fsutil.Embed

go
import (
	"embed"
	"fmt"
	"os"

	"goyave.dev/goyave/v5/util/errors"
	"goyave.dev/goyave/v5/util/fsutil"
)

//go:embed resources
var resources embed.FS

func main() {
	resources := fsutil.NewEmbed(resources)
	langFS, err := resources.Sub("resources/lang")
	if err != nil {
		fmt.Fprintln(os.Stderr, err.(*errors.Error).String())
		os.Exit(1)
	}
	//...
}

TIP

您可以按照此处的说明使用此类嵌入式文件系统来提供静态资源。

文件系统服务

文件系统实例理想情况下应存储在服务中。例如,如果您嵌入了 resources 目录,请创建一个“static”服务,以便您的静态资源可以从应用程序的任何位置访问:

go
// service/static/static.go
package static

import (
	"goyave.dev/goyave/v5/util/fsutil"
	"my-project/service"
)

type Service struct {
	fs fsutil.Embed
}

func NewService(embed fsutil.Embed) *Service {
	return &Service{
		fs: embed,
	}
}

func (s Service) FS() fsutil.Embed {
	return s.fs
}

func (s Service) Name() string {
	return service.Static
}

文件上传

Goyave 的解析中间件支持使用 multipart/form-data 表单进行文件上传。所有文件部分都会自动转换为 []fsutil.File。在 request.Data 中,类型为 "file" 的字段因此始终是 []fsutil.File 类型(即使未验证)。它是一个切片,因此支持在单个字段中进行多文件上传。

fsutil.File 存储 *multipart.FileHeader 并通过读取文件的前 512 字节自动确定文件的 MIME 类型。

它还提供了一个 Save() 方法以便于存储。此方法将在给定的文件名后附加时间戳,以避免重复的文件名。

go
func (ctrl *Controller) UploadFiles(response *goyave.Response, request *goyave.Request) {
	data := request.Data.(map[string]any) // 多部分表单始终是对象
	files := data["files"].([]fsutil.File)
	for i, f := range files {
		actualName, err := f.Save(&osfs.FS{}, "/usercontent", fmt.Sprintf("userfile_%d", i+1))
		if err != nil {
			response.Error(err)
			return
		}
		ctrl.Logger().Debug("File saved", "filename", actualName)
	}
	//...
}

INFO

fsutil.File 可以在 DTO 中使用,这得益于 json.Marhsaler/json.Unmarhsaler 的特殊实现。