Help us shape the future of Go! Take the 2026 Go Developer Survey

storage

package
v0.32.41 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

backends.go 存储后端注册表:类型 → Operator 构建器。

此前 buildOperator 是 14 后端巨型 switch,ProbeConfig 另持一份"远端类型" case 清单——新增后端须同步改两处,漏列 ProbeConfig 的坏配置会静默按 local 探测通过(2026-10-05 215 实测事故:不存在的桶 Probe 照样通过,上传全挂)。 现收口为单一注册表:ProbeConfig/BuildAndProbe 的远端判定直接由注册表派生, 未注册且非 local 的类型 fail-loud(未知类型不再静默当 local)。

新增后端只需:① 本表注册构建器;② opendal 子包实现 Scheme 驱动; ③ StorageConfig/ConfigFromConf 补配置段。探测链路自动生效。

chunks.go 分片上传:chunk key、保存/合并/清理,与远端合并用的链式顺序读器。

config.go 存储配置映射:StorageConfig 与 conf.StorageConfig 的互转(bootstrap 与管理端切换共用)。

diskusage_unix.go 本地磁盘容量查询(linux/darwin:Statfs)。

local.go 本地后端路径语义:逃逸收口(localContain)与历史双层布局迁移 (MigrateLegacyLayout)。

presign.go 真预签名直传/直下 URL 生成与 s3 直传核实(HeadObject)。

probe.go 存储后端探测:构造 Operator 并做认证级验证(管理端「测试连接」/切换保存前调用)。

providers.go 云厂商对象存储预设(S3 兼容协议)。

腾讯云 COS / 阿里云 OSS / 百度云 BOS / 金山云 KS3 / 华为云 OBS / AWS S3 均提供 S3 兼容端点,统一经 s3 驱动(minio-go SigV4)读写; 本表负责三件事:

  1. endpoint 推导:用户只填 region(endpoint 留空时按厂商模板生成)
  2. path_style 缺省:各厂商均为 virtual-host 风格(MinIO/自建才用 path style)
  3. type 白名单与展示名

Index

Constants

View Source
const (
	StorageTypeOSS = StorageType("oss")
	StorageTypeCOS = StorageType("cos")
	StorageTypeBOS = StorageType("bos")
	StorageTypeKS3 = StorageType("ks3")
	StorageTypeOBS = StorageType("obs")
)

Variables

View Source
var ErrPresignUnsupported = errors.New("presign direct transfer not supported by current storage backend")

ErrPresignUnsupported 当前存储后端不支持真预签名直传/直下(local/webdav 走服务端中转)。

View Source
var ErrRangeUnsupported = opendal.ErrRangeUnsupported

ErrRangeUnsupported 当前存储后端不支持按字节区间读取(Range 下载/断点续传)。 转发 opendal.ErrRangeUnsupported,调用方用 errors.Is 判断后回退全量流。

Functions

func BuildAndProbe

func BuildAndProbe(ctx context.Context, cfg *StorageConfig) error

BuildAndProbe 构造 Operator 并做认证级探测(管理端「测试连接」用)。 local 返回 nil operator(无需探测)。

func DiskUsage added in v0.15.1

func DiskUsage(path string) (total, free uint64, err error)

DiskUsage 返回 path 所在文件系统的 (总容量, 可用容量) 字节数。 可用口径 = Bavail(非特权用户实际可用,不含 root 保留块)。

func IsCloudProviderType

func IsCloudProviderType(t StorageType) bool

IsCloudProviderType type 是否为受支持的云厂商(S3 兼容)

func MigrateLegacyLayout added in v0.32.14

func MigrateLegacyLayout(dataPath string) (int, int, error)

MigrateLegacyLayout 迁移历史双层布局(幂等,可重复调用)。

历史形态:config 的 storage.storage_path 写成 "./data/uploads"(带了 uploads 段), 而存储相对路径本身又以 "uploads/" 开头 ⇒ 物理路径变成 <DataPath>/uploads/uploads/… 多了一层。当前形态:DataPath 是存储根,相对路径 "uploads/2026/…" 解析为 <DataPath>/uploads/2026/…(单层)。

迁移动作:若 <DataPath>/uploads/uploads 存在且非空,把它的子项上提到 <DataPath>/uploads/(同盘 rename,瞬时完成,不复制文件内容)。上提后旧布局目录 变空即删除。冲突(目标同名已存在)时跳过该项并告警,绝不覆盖已有文件。

返回 (上提的条目数, 跳过的条目数, error)。无可迁移内容时返回 (0,0,nil)。

调用时机:bootstrap 拿到存储实例后、对外服务前(读路径必须先于迁移完成)。

func ProbeConfig

func ProbeConfig(ctx context.Context, cfg *StorageConfig) error

ProbeConfig 认证级验证存储配置(管理端切换/保存前调用)。 s3/webdav/ftp/sftp/云厂商:凭据+可达性;local:路径可创建可写。 远端/本地分流由 backends.go 注册表派生——此前是手写 case 清单,云厂商类型 漏列即落 default 被当 local 只探本地路径,坏配置静默上线(2026-10-05 215 实测:不存在的桶 Probe 照样通过,上传全挂);现新后端注册即自动被远端探测, 未知类型显式报错而非静默按 local 放行。

func ResolveCloudProvider

func ResolveCloudProvider(t StorageType, region, bucket, accessKey, secretKey, endpoint string, useSSL, pathStyle *bool) (map[string]string, error)

ResolveCloudProvider 把厂商配置归一为 S3 驱动 options。 返回 (options, error);endpoint 由显式配置或 region 推导得出。

Types

type FileOperationResult

type FileOperationResult struct {
	Success   bool                   `json:"success"`
	Message   string                 `json:"message,omitempty"`
	Error     error                  `json:"-"`
	FilePath  string                 `json:"file_path,omitempty"`
	FileSize  int64                  `json:"file_size,omitempty"`
	FileHash  string                 `json:"file_hash,omitempty"`
	Metadata  map[string]interface{} `json:"metadata,omitempty"`
	Timestamp time.Time              `json:"timestamp"`
}

FileOperationResult 文件操作结果

type ProviderPreset

type ProviderPreset struct {
	// DeriveEndpoint 按 region 推导 S3 兼容端点(不带协议前缀;返回空表示必须显式配置)
	DeriveEndpoint func(region string) string
	// DefaultPathStyle 各厂商均为 virtual-host;MinIO/自建 S3 才默认 path style
	DefaultPathStyle bool
	// DisplayName 运维展示名
	DisplayName string
}

ProviderPreset 厂商预设

type RemoteObjectInfo

type RemoteObjectInfo struct {
	Key     string
	Size    int64
	ModTime time.Time
}

RemoteObjectInfo 远端对象列举项(presign 孤儿清理用)。

type StorageConfig

type StorageConfig struct {
	Type     StorageType
	DataPath string // 本地存储路径
	BaseURL  string // 基础URL

	// S3 配置
	Endpoint  string
	AccessKey string
	SecretKey string
	Bucket    string
	Region    string
	UseSSL    bool
	PathStyle bool // 路径风格寻址(MinIO/Ceph 自建场景)

	// WebDAV 配置
	WebDAVURL      string
	WebDAVUsername string
	WebDAVPassword string
	// Root 远端根目录(webdav:所有对象挂其下,避免绝对路径写入;空 = "pigeonbox")
	Root string

	// FTP/SFTP/Azure Blob/HDFS/OneDrive 配置(存储驱动扩展;指针复用 conf 结构)
	FTP      *conf.FTPConfig
	SFTP     *conf.SFTPConfig
	Azure    *conf.AzureBlobConfig
	HDFS     *conf.HDFSConfig
	OneDrive *conf.OneDriveConfig
}

StorageConfig 存储配置

func ConfigFromConf

func ConfigFromConf(c *conf.StorageConfig, baseURL string) *StorageConfig

ConfigFromConf 把 conf 的存储配置映射为 StorageConfig(bootstrap 与管理端切换共用)。

func (*StorageConfig) ToConf

func (c *StorageConfig) ToConf() *conf.StorageConfig

ToConf 反向映射(运行时切换后持久化回 conf.StorageConfig 形态)。

type StorageInterface

type StorageInterface interface {
	// 基础操作
	SaveFile(ctx context.Context, file *multipart.FileHeader, savePath string) (*FileOperationResult, error)
	DeleteFile(ctx context.Context, filePath string) error
	GetFile(ctx context.Context, filePath string) ([]byte, error)
	FileExists(ctx context.Context, filePath string) bool

	// 流式写入(本地导入/服务端拷贝用;返回写入字节数)
	SaveStream(ctx context.Context, savePath string, r io.Reader, expectedSize int64) (int64, error)

	// 分片操作
	SaveChunk(ctx context.Context, uploadID string, chunkIndex int, data []byte) error
	MergeChunks(ctx context.Context, uploadID string, totalChunks int, savePath string) error
	CleanChunks(ctx context.Context, uploadID string) error

	// 工具方法
	GetFileSize(ctx context.Context, filePath string) (int64, error)
	GetFileURL(ctx context.Context, filePath string) (string, error)

	// 流式下载方法
	GetFileReader(ctx context.Context, filePath string) (io.ReadCloser, int64, error)
}

StorageInterface 存储接口(简化版)

type StorageService

type StorageService struct {
	// contains filtered or unexported fields
}

StorageService 存储服务。 local 走本地文件系统(实现与拆分前完全一致);s3/webdav 经 opendal.Operator 分派到对应驱动。管理端在线切换后端时通过 Reload 热替换(在途请求持旧后端完成)。

func NewLocalFallback added in v0.15.1

func NewLocalFallback(dataPath, baseURL string) *StorageService

NewLocalFallback 构造本地后端兜底实例(未注入统一存储时的测试/降级路径; share/chunk 两域各自懒加载缓存一份,DataPath 沿用各自历史约定—— ./data/uploads 与 ./data 的差异是 2026-10 修复后的刻意参数,勿"统一")。 构造收口此处:兜底语义演进(如 BaseURL 策略)只改一点。

func NewStorageService

func NewStorageService(config *StorageConfig) *StorageService

NewStorageService 创建存储服务(兼容入口)。 远端后端配置非法时降级 local 并记录原因(InitError 可查),与拆分前行为兼容。

func NewStorageServiceE

func NewStorageServiceE(config *StorageConfig) (*StorageService, error)

NewStorageServiceE 创建存储服务;远端后端配置非法时返回错误(bootstrap 用, 失败要显式暴露而不是静默落本地盘)。

func (*StorageService) CleanChunks

func (s *StorageService) CleanChunks(ctx context.Context, uploadID string) error

CleanChunks 清理分片

func (*StorageService) DataPath

func (s *StorageService) DataPath() string

DataPath 返回本地存储根目录(分片临时区;远端模式下仍承载合并副本)。

⚠️ **必须持读锁**(2026-10-31 修):Reload(在线切换存储后端)在写锁下替换 s.config 指针,而本方法此前**无锁读**该指针 ⇒ data race。 调用点都在运行期并发路径上:/admin/diagnostics(pb doctor 消费)、admin 系统信息、后台 janitor 任务(周期性跑,与在线切换并发)。

与私有的 dataPath() 同款:都走读锁取当前快照。

func (*StorageService) DeleteFile

func (s *StorageService) DeleteFile(ctx context.Context, filePath string) error

DeleteFile 删除文件

func (*StorageService) EffectiveType

func (s *StorageService) EffectiveType() StorageType

EffectiveType 实际生效的存储类型(远端构造失败降级 local 时如实返回 local)。

func (*StorageService) FileExists

func (s *StorageService) FileExists(ctx context.Context, filePath string) bool

FileExists 检查文件是否存在

func (*StorageService) GetFile

func (s *StorageService) GetFile(ctx context.Context, filePath string) ([]byte, error)

GetFile 获取文件内容

func (*StorageService) GetFileReader

func (s *StorageService) GetFileReader(ctx context.Context, filePath string) (io.ReadCloser, int64, error)

GetFileReader 获取文件读取器(用于流式下载)

func (*StorageService) GetFileReaderRange

func (s *StorageService) GetFileReaderRange(ctx context.Context, filePath string, start, length int64) (io.ReadCloser, int64, error)

GetFileReaderRange 区间读取文件(Range 下载/断点续传)。 返回 (区间读器, 对象总大小)。当前后端不支持区间读时返回 opendal.ErrRangeUnsupported,调用方应回退全量流式(200)。 start 越界(>= 文件大小)返回 os.ErrNotExist 语义错误(调用方 416)。

func (*StorageService) GetFileSize

func (s *StorageService) GetFileSize(ctx context.Context, filePath string) (int64, error)

GetFileSize 获取文件大小

func (*StorageService) GetFileURL

func (s *StorageService) GetFileURL(ctx context.Context, filePath string) (string, error)

GetFileURL 获取文件URL

func (*StorageService) HeadObject

func (s *StorageService) HeadObject(ctx context.Context, objectKey string) (int64, string, error)

HeadObject 返回对象大小与 ETag(s3 直传 Complete 时的存在性/大小核实)。

func (*StorageService) InitError

func (s *StorageService) InitError() error

InitError 返回构造降级原因(无降级时为 nil)。

func (*StorageService) ListRemoteObjects

func (s *StorageService) ListRemoteObjects(ctx context.Context, prefix string) ([]RemoteObjectInfo, error)

ListRemoteObjects 列举远端后端 prefix 下对象(递归)。 仅远端后端支持;本地后端返回错误(本地对账走 ReconcileOrphans 磁盘遍历)。

func (*StorageService) LocalAbsPath

func (s *StorageService) LocalAbsPath(rel string) string

LocalAbsPath 本地后端下解析存储相对路径为绝对路径(仅 local 后端;远端返回空)。 供下载链路走 c.File(原生 Range/断点续传/MIME 推断);路径不存在返回空。

func (*StorageService) MergeChunks

func (s *StorageService) MergeChunks(ctx context.Context, uploadID string, totalChunks int, savePath string) error

MergeChunks 合并分片。 local:顺序拼接落盘;远端:懒打开分片的链式读器 + 按总大小流式上传, 不整文件进内存。

func (*StorageService) PresignGetURL

func (s *StorageService) PresignGetURL(ctx context.Context, objectKey string, expire time.Duration, disposition ...string) (string, error)

PresignGetURL 生成真预签名直下 GET URL(短时效,下载 302 用)。 disposition 可选(首项生效):非空时作为 response-content-disposition 签入 URL——对象存储按此回 Content-Disposition,直下 302 客户端才能拿到文件名 (2026-10-07 修复:直下 302 曾无文件名,浏览器按对象 UUID key 命名)。

func (*StorageService) PresignPutURL

func (s *StorageService) PresignPutURL(ctx context.Context, objectKey string, expire time.Duration) (string, error)

PresignPutURL 生成真预签名直传 PUT URL(客户端直传对象存储,服务器不过流量)。 仅 s3 等支持离线签名的后端可用;local/webdav 返回 ErrPresignUnsupported, 调用方(presign 域)据此回退自家中转。

func (*StorageService) Reload

func (s *StorageService) Reload(config *StorageConfig) error

Reload 热切换存储后端(管理端切换存储时调用)。构造失败时保留原后端并返回错误。

func (*StorageService) SaveBytes

func (s *StorageService) SaveBytes(ctx context.Context, savePath string, data []byte) error

SaveBytes 写入内存数据到相对路径(presign 直传使用)。 路径防御收敛在此处:相对 DataPath 计算清洗后的偏移,逃逸(../)即拒绝。

func (*StorageService) SaveChunk

func (s *StorageService) SaveChunk(ctx context.Context, uploadID string, chunkIndex int, data []byte) error

SaveChunk 保存分片

func (*StorageService) SaveFile

func (s *StorageService) SaveFile(ctx context.Context, file *multipart.FileHeader, savePath string) (*FileOperationResult, error)

SaveFile 保存文件。 落盘的同时用 io.TeeReader 流式计算 SHA-256(不额外占用整文件内存), 哈希写入 result.FileHash——这是秒传(按哈希秒传)与完整性的地基。

func (*StorageService) SaveStream

func (s *StorageService) SaveStream(ctx context.Context, savePath string, r io.Reader, expectedSize int64) (int64, error)

SaveStream 流式写入 reader 到相对路径(presign 中转大文件使用,避免整文件进内存)。 expectedSize > 0 时远端驱动按确切 size 写入;返回实际写入字节数。 路径防御与 SaveBytes 一致(相对 DataPath,逃逸即拒绝)。

func (*StorageService) StatFile

func (s *StorageService) StatFile(ctx context.Context, filePath string) (int64, error)

StatFile 获取单个文件大小(Range 下载等需要先知总大小的场景)。

type StorageType

type StorageType string

StorageType 存储类型

const (
	StorageTypeLocal  StorageType = "local"
	StorageTypeS3     StorageType = "s3"
	StorageTypeWebDAV StorageType = "webdav"
	StorageTypeFTP    StorageType = "ftp"
	StorageTypeSFTP   StorageType = "sftp"
	StorageTypeGCS    StorageType = "gcs"
	StorageTypeAzBlob StorageType = "azureblob"
	StorageTypeHDFS   StorageType = "hdfs"
	StorageTypeOneDrv StorageType = "onedrive"
)

type StoredFileEntry

type StoredFileEntry struct {
	// RelPath 存储相对路径(含唯一文件名;local 下与合并写入路径一致)
	RelPath string
	// FileName 原始文件名(消毒后,仅展示)
	FileName string
	// Size 实际大小(handler 已复核)
	Size int64
	// FileHash SHA-256(可空:哈希失败不阻断分享)
	FileHash string
}

StoredFileEntry 已落存储的文件项(多文件分享/访客投递等跨域流转的统一载体)。 语义约定:传输层保证物理文件已就位(直传保存/分片合并/presign 核实), 业务层只做装配。定义在本包使 request 等域引用它时无需依赖具体业务域。

Directories

Path Synopsis
Package opendal 提供 OpenDAL 风格的存储抽象。
Package opendal 提供 OpenDAL 风格的存储抽象。

Jump to

Keyboard shortcuts

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