storage

package
v0.33.5 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 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