fs

package module
v0.3.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 25, 2025 License: MIT Imports: 7 Imported by: 0

README

Go FileSystem

Go Reference Go Report Card License Ask DeepWiki

Go FileSystem 是一个统一的文件系统接口实现,支持本地文件系统和多种云存储服务。它提供了一致的 API 来操作不同的存储系统,使得在不同存储系统之间切换变得简单。

Features

  • 统一的文件系统接口
  • 支持多种存储驱动
    • 本地文件系统
    • MinIO 对象存储
    • 阿里云 OSS
    • 华为云 OBS
    • 腾讯云 COS
    • AWS S3
  • 完整的文件操作支持
    • 文件的读写、复制、移动、删除
    • 目录的创建、删除、遍历
    • 文件元数据的读写
    • MIME 类型检测
    • 文件上传
      • 普通文件上传
      • 支持大文件分片上传
      • 支持分片断点续传

Installation

go get github.com/dysodeng/fs

Usage

本地文件系统
package main

import (
    "context"
    f "github.com/dysodeng/fs"
    "github.com/dysodeng/fs/driver/local"
)

func main() {
    fs, err := local.New(local.Config{RootPath: "./storage"})
	if err != nil {
		panic(err)
    }
    
    // 写入文件
    writer, err := fs.Create(
		context.Background(), 
		"test.txt", 
		f.WithContentType("text/plain"),
    )
    if err != nil {
        panic(err)
    }
    writer.Write([]byte("Hello, World!"))
    writer.Close()
}
MinIO 对象存储
package main

import (
    "context"
    f "github.com/dysodeng/fs"
    "github.com/dysodeng/fs/driver/minio"
)

func main() {
    config := minio.Config{
        Endpoint:        "play.min.io",
        AccessKeyID:     "your-access-key",
        SecretAccessKey: "your-secret-key",
        UseSSL:          true,
        BucketName:      "your-bucket",
        Location:        "us-east-1",
    }
	
    fs, err := minio.New(config)
    if err != nil {
        panic(err)
    }
    
    // 写入文件
    writer, err := fs.Create(
        context.Background(),
        "test.txt",
        f.WithContentType("text/plain"),
    )
    if err != nil {
        panic(err)
    }
    writer.Write([]byte("Hello, MinIO!"))
    writer.Close()
}
阿里云 OSS
package main

import (
    "context"
    f "github.com/dysodeng/fs"
    "github.com/dysodeng/fs/driver/alioss"
)

func main() {
    config := alioss.Config{
        Endpoint:        "oss-cn-hangzhou.aliyuncs.com",
        AccessKeyID:     "your-access-key",
        SecretAccessKey: "your-secret-key",
        BucketName:      "your-bucket",
    }
	
    fs, err := alioss.New(config)
    if err != nil {
        panic(err)
    }
    
    // 写入文件
    writer, err := fs.Create(
        context.Background(),
        "test.txt",
        f.WithContentType("text/plain"),
    )
    if err != nil {
        panic(err)
    }
    writer.Write([]byte("Hello, OSS!"))
    writer.Close()
}
华为云 OBS
package main

import (
    "context"
    f "github.com/dysodeng/fs"
    "github.com/dysodeng/fs/driver/hwobs"
)

func main() {
    config := hwobs.Config{
        Endpoint:        "obs.cn-north-4.myhuaweicloud.com",
        AccessKeyID:     "your-access-key",
        SecretAccessKey: "your-secret-key",
        BucketName:      "your-bucket",
    }
	
    fs, err := hwobs.New(config)
    if err != nil {
        panic(err)
    }
    
    // 写入文件
    writer, err := fs.Create(
        context.Background(),
        "test.txt",
        f.WithContentType("text/plain"),
    )
    if err != nil {
        panic(err)
    }
    writer.Write([]byte("Hello, OBS!"))
    writer.Close()
}
腾讯云 COS
package main

import (
    "context"
    f "github.com/dysodeng/fs"
    "github.com/dysodeng/fs/driver/txcos"
)

func main() {
    config := txcos.Config{
        BucketURL:      "https://example-1234567890.cos.ap-guangzhou.myqcloud.com",
        SecretID:       "your-secret-id",
        SecretKey:      "your-secret-key",
    }
	
    fs, err := txcos.New(config)
    if err != nil {
        panic(err)
    }
    
    // 写入文件
    writer, err := fs.Create(
        context.Background(),
        "test.txt",
        f.WithContentType("text/plain"),
    )
    if err != nil {
        panic(err)
    }
    writer.Write([]byte("Hello, COS!"))
    writer.Close()
}
AWS S3
package main

import (
    "context"
    f "github.com/dysodeng/fs"
    "github.com/dysodeng/fs/driver/s3"
)

func main() {
    config := s3.Config{
        Region:          "us-east-1",
        Endpoint:        "https://s3.amazonaws.com", // S3 服务地址(可选,用于兼容其他 S3 协议的存储服务)
        AccessKeyID:     "your-access-key",
        SecretAccessKey: "your-secret-key",
        BucketName:      "your-bucket",
        UsePathStyle:    false,                      // 是否使用路径样式访问
    }
    
    fs, err := s3.New(config)
    if err != nil {
        panic(err)
    }
    
    // 写入文件
    writer, err := fs.Create(
        context.Background(),
        "test.txt",
        f.WithContentType("text/plain"),
    )
    if err != nil {
        panic(err)
    }
    writer.Write([]byte("Hello, S3!"))
    writer.Close()
}

文件上传功能

所有存储驱动都支持三种文件上传方式:普通文件上传、分片文件上传和分片断点续传。

普通文件上传

适用于小文件上传的场景,使用简单直接:

package main

import (
    "context"
    "os"
    "github.com/dysodeng/fs/driver/alioss" // 这里以阿里云OSS为例
)

func main() {
    // 初始化存储驱动
    config := alioss.Config{
        Endpoint:        "oss-cn-hangzhou.aliyuncs.com",
        AccessKeyID:     "your-access-key",
        SecretAccessKey: "your-secret-key",
        BucketName:      "your-bucket",
    }
    
    fs, err := alioss.New(config)
    if err != nil {
        panic(err)
    }

    // 打开本地文件
    file, err := os.Open("local-file.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // 使用 Uploader 接口上传文件
    uploader := fs.Uploader()
    err = uploader.Upload(context.Background(), "remote-file.txt", file)
    if err != nil {
        panic(err)
    }
}
文件分片上传

分片上传是将大文件分割成多个小文件进行上传,每个小文件的大小可以根据实际情况进行调整。以下是一个示例:

package main

import (
    "context"
    "io"
    "os"
    "github.com/dysodeng/fs/driver/alioss"
)

func main() {
    // 初始化存储驱动
    config := alioss.Config{
        Endpoint:        "oss-cn-hangzhou.aliyuncs.com",
        AccessKeyID:     "your-access-key",
        SecretAccessKey: "your-secret-key",
        BucketName:      "your-bucket",
    }
    
    fs, err := alioss.New(config)
    if err != nil {
        panic(err)
    }

    uploader := fs.Uploader()

    // 1. 初始化分片上传
    uploadID, err := uploader.InitMultipartUpload(context.Background(), "large-file.zip")
    if err != nil {
        panic(err)
    }

    // 2. 分片上传
    var parts []fs.MultipartPart
    partSize := int64(5 * 1024 * 1024) // 5MB per part
    file, _ := os.Open("local-large-file.zip")
    defer file.Close()
    
    buffer := make([]byte, partSize)
    for partNumber := 1; ; partNumber++ {
        n, err := file.Read(buffer)
        if err == io.EOF {
            break
        }
        
        etag, err := uploader.UploadPart(context.Background(), "large-file.zip", uploadID, partNumber, bytes.NewReader(buffer[:n]))
        if err != nil {
            // 出错时可以中断上传
            uploader.AbortMultipartUpload(context.Background(), "large-file.zip", uploadID)
            panic(err)
        }
        
        parts = append(parts, fs.MultipartPart{
            PartNumber: partNumber,
            ETag:      etag,
        })
    }

    // 3. 完成上传
    err = uploader.CompleteMultipartUpload(context.Background(), "large-file.zip", uploadID, parts)
    if err != nil {
        panic(err)
    }
}
分片断点续传

断点续传是在上传过程中,如果网络中断或上传失败,可以从上次中断的位置继续上传。以下是一个示例:

func resumeUpload(fsCli fs.FileSystem, localFile, remotePath string) error {
    ctx := context.Background()
    uploader := fsCli.Uploader()
    
    // 1. 查找未完成的上传任务
    uploads, err := uploader.ListMultipartUploads(ctx)
    if err != nil {
        return err
    }
    
    var targetUpload fs.MultipartUploadInfo
    for _, upload := range uploads {
        if upload.Path == remotePath {
            targetUpload = upload
            break
        }
    }
    
    // 2. 获取已上传的分片
    parts, err := uploader.ListUploadedParts(ctx, remotePath, targetUpload.UploadID)
    if err != nil {
        return err
    }
    
    // 3. 创建已上传分片的映射
    uploadedParts := make(map[int]struct{})
    for _, part := range parts {
        uploadedParts[part.PartNumber] = struct{}{}
    }
    
    // 4. 继续上传未完成的分片
    file, err := os.Open(localFile)
    if err != nil {
        return err
    }
    defer file.Close()
    
    partSize := int64(5 * 1024 * 1024)
    for partNumber := 1; ; partNumber++ {
        // 跳过已上传的分片
        if _, exists := uploadedParts[partNumber]; exists {
            file.Seek(int64(partNumber-1)*partSize, io.SeekStart)
            continue
        }
        
        buffer := make([]byte, partSize)
        n, err := file.Read(buffer)
        if err == io.EOF {
            break
        }
        
        etag, err := uploader.UploadPart(ctx, remotePath, targetUpload.UploadID, partNumber, bytes.NewReader(buffer[:n]))
        if err != nil {
            return err
        }
        
        parts = append(parts, fs.MultipartPart{
            PartNumber: partNumber,
            ETag:      etag,
        })
    }
    
    // 5. 完成上传
    return uploader.CompleteMultipartUpload(ctx, remotePath, targetUpload.UploadID, parts)
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func TypeByExtension added in v0.3.2

func TypeByExtension(filePath string) string

TypeByExtension returns the MIME type associated with the file extension ext. gets the file's MIME type for HTTP header Content-Type

Types

type AccessMode added in v0.3.4

type AccessMode uint8

AccessMode 访问模式

const (
	Private         AccessMode = iota // 私有读写
	PublicRead                        // 公共读
	PublicReadWrite                   // 公共读写
)

type FileInfo

type FileInfo interface {
	Name() string
	Size() int64
	Mode() os.FileMode
	ModTime() time.Time
	IsDir() bool
	Sys() interface{}
}

FileInfo 文件信息,实现 os.FileInfo 接口

type FileSystem

type FileSystem interface {
	// List 列出目录内容
	List(ctx context.Context, path string, opts ...Option) ([]FileInfo, error)
	// MakeDir 创建目录
	MakeDir(ctx context.Context, path string, perm os.FileMode, opts ...Option) error
	// RemoveDir 删除目录
	RemoveDir(ctx context.Context, path string, opts ...Option) error

	// Create 创建文件并返回io.WriteCloser
	Create(ctx context.Context, path string, opts ...Option) (io.WriteCloser, error)
	// Open 打开文件并返回io.ReadCloser
	Open(ctx context.Context, path string, opts ...Option) (io.ReadCloser, error)
	// OpenFile 以指定模式打开文件
	OpenFile(ctx context.Context, path string, flag int, perm os.FileMode, opts ...Option) (io.ReadWriteCloser, error)
	// Remove 删除文件
	Remove(ctx context.Context, path string, opts ...Option) error
	// Copy 复制文件
	Copy(ctx context.Context, src, dst string, opts ...Option) error
	// Move 移动文件
	Move(ctx context.Context, src, dst string, opts ...Option) error
	// Rename 重命名文件或目录
	Rename(ctx context.Context, oldPath, newPath string, opts ...Option) error

	// Stat 获取文件/目录信息
	Stat(ctx context.Context, path string, opts ...Option) (FileInfo, error)
	// GetMimeType 获取文件的 MIME 类型
	GetMimeType(ctx context.Context, path string, opts ...Option) (string, error)
	// SetMetadata 设置元数据
	SetMetadata(ctx context.Context, path string, metadata map[string]interface{}, opts ...Option) error
	// GetMetadata 获取元数据
	GetMetadata(ctx context.Context, path string, opts ...Option) (map[string]interface{}, error)

	// Exists 判断文件或目录是否存在
	Exists(ctx context.Context, path string, opts ...Option) (bool, error)
	// IsDir 判断是否为目录
	IsDir(ctx context.Context, path string, opts ...Option) (bool, error)
	// IsFile 判断是否为文件
	IsFile(ctx context.Context, path string, opts ...Option) (bool, error)

	// SignFullUrl 带签名的全路径文件访问url
	SignFullUrl(ctx context.Context, path string, opts ...Option) (string, error)
	// FullUrl 全路径文件访问url
	FullUrl(ctx context.Context, path string, opts ...Option) (string, error)
	// RelativePath 还原文件路径
	RelativePath(ctx context.Context, fullUrl string, opts ...Option) (string, error)

	// Uploader 文件上传
	Uploader() Uploader
}

FileSystem 文件系统接口

type Metadata

type Metadata map[string]interface{}

Metadata 文件元数据

type MultipartPart added in v0.2.0

type MultipartPart struct {
	PartNumber int    `json:"part_number"` // 分片号
	ETag       string `json:"etag"`        // 分片ETag
	Size       int64  `json:"size"`        // 分片大小
}

MultipartPart 分片信息

type MultipartUploadInfo added in v0.3.0

type MultipartUploadInfo struct {
	UploadID   string          `json:"upload_id"`   // 上传ID
	Path       string          `json:"path"`        // 文件路径
	Parts      []MultipartPart `json:"parts"`       // 已上传的分片
	CreateTime time.Time       `json:"create_time"` // 创建时间
}

MultipartUploadInfo 分片上传信息

type Option added in v0.3.2

type Option func(*Options)

func WithCdnDomain added in v0.3.4

func WithCdnDomain(cdnDomain string) Option

WithCdnDomain 设置cdn域名

func WithContentType added in v0.3.2

func WithContentType(contentType string) Option

WithContentType 设置文件类型

func WithMetadata added in v0.3.2

func WithMetadata(metadata Metadata) Option

WithMetadata 设置元数据

func WithSignUrlExpires added in v0.3.4

func WithSignUrlExpires(expires time.Duration) Option

type Options added in v0.3.2

type Options struct {
	Metadata       Metadata
	ContentType    string
	CdnDomain      string
	SignUrlExpires time.Duration
}

type Uploader added in v0.3.1

type Uploader interface {
	// Upload 文件上传
	Upload(ctx context.Context, path string, reader io.Reader, opts ...Option) error
	// InitMultipartUpload 初始化分片上传
	InitMultipartUpload(ctx context.Context, path string, opts ...Option) (string, error)
	// UploadPart 上传分片
	UploadPart(ctx context.Context, path string, uploadID string, partNumber int, data io.Reader, opts ...Option) (string, error)
	// CompleteMultipartUpload 完成分片上传
	CompleteMultipartUpload(ctx context.Context, path string, uploadID string, parts []MultipartPart, opts ...Option) error
	// AbortMultipartUpload 取消分片上传
	AbortMultipartUpload(ctx context.Context, path string, uploadID string, opts ...Option) error
	// ListMultipartUploads 列出所有未完成的分片上传
	ListMultipartUploads(ctx context.Context, opts ...Option) ([]MultipartUploadInfo, error)
	// ListUploadedParts 列出已上传的分片
	ListUploadedParts(ctx context.Context, path string, uploadID string, opts ...Option) ([]MultipartPart, error)
}

Uploader 文件上传器

Directories

Path Synopsis
driver
s3
cmd command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL