存储引擎
plugin/storage 提供统一的文件存储抽象,支持本地磁盘与阿里云 OSS(S3 兼容)两种引擎,以及基于 HMAC 签名的预签名分片直传。所有接口定义在 storage_interface.go 的 StorageEngine 接口中。
一、StorageEngine 接口
go
// plugin/storage/storage_interface.go
type MultipartPart struct {
PartNumber int `json:"partNumber"`
ETag string `json:"etag"`
}
type StorageEngine interface {
PutObject(key string, data io.Reader, size int64) (string, error) // 上传完整对象
ObjectExists(key string) (bool, error) // 秒传判断
GetObjectURL(key string) (string, error) // 获取访问 URL
// 分片上传
InitMultipartUpload(key string) (string, error) // 返回 uploadId
UploadPart(key, uploadId string, partNumber int, data io.Reader, size int64) (string, error) // 返回 etag
CompleteMultipartUpload(key, uploadId string, parts []MultipartPart) (string, error)
AbortMultipartUpload(key, uploadId string) error
ListParts(key, uploadId string) ([]MultipartPart, error)
}key:对象存储路径(不含访问前缀),如20240101/12/{uuidv7}.png。GetObjectURL返回相对 URL,如/api/uploads/20240101/12/{uuidv7}.png(前缀来自config.FileConfig.UploadPrefix)。
二、两种引擎
| 引擎 | 文件 | 说明 |
|---|---|---|
| 本地 | storage_local.go | 写本地磁盘,分片临时目录在 config.FileConfig.ChunkTmpDir |
| 阿里云 OSS | storage_oss.go | 基于 aws-sdk-go-v2(S3 兼容协议),虚拟主机风格寻址 |
引擎构造入口
go
// plugin/storage/storage_factory.go —— 仅本地
func GetStorageEngine() StorageEngine // 固定返回 newLocalStorageEngine()
// plugin/storage/storage_oss.go —— 按配置选择
func NewOssStorage() StorageEngine // 配置了 Oss.Bucket/AK/SK/Endpoint 则用 OSS,否则回退本地NewOssStorage()根据config.OssConfig判断是否启用 OSS;配置加载失败也安全回退本地,避免启动崩溃。- OSS 配置字段位于
config/oss.go的OssConfig(Endpoint / PublicEndpoint / Bucket / AccessKeyId / AccessKeySecret / Region)。 - 业务分片上传(
common_controller/upload_chunk.go)与文件上传实际走NewOssStorage()。
本地引擎行为备注
PutObject:写入{UploadDirectory}/{key}。InitMultipartUpload:以{ChunkTmpDir}/{去掉扩展名的 key}作为uploadId(临时分片目录)。UploadPart:每个分片以「分片序号」命名文件,ETag即序号字符串。CompleteMultipartUpload:mu互斥锁保护,按PartNumber排序合并,合并后RemoveAll临时目录。ListParts:读取临时目录,目录不存在返回空切片(非错误)。
三、顶层上传入口 storageDriver
go
// plugin/storage/storage.go
var StorageDriver = storageDriver{}
func (sd storageDriver) Upload(file *multipart.FileHeader) (*UploadFile, error)UploadFile{Name, Size, Ext, Uri}:Uri为完整访问地址(util.UrlUtil.HashUrl(id,file_name)=/api/uploads/<id>/<file_name>),业务表直接存此字段,无需前端拼接。- 保存路径规则:
{年}{月}{日}/{时}/{分}/{uuidv7}.{ext}(见util.UrlUtil.BuildFileSavePath),磁盘文件名即<uuid>.<ext>;访问通过GET /api/uploads/:id路由按id查x_common_file_hash.FilePath返回文件流(见common_controller/file_controller.go)。 - 文件校验
checkFile:按扩展名分类(图片/视频/文件),分别比对UploadImageSize/UploadVideoSize/UploadFileSize大小上限,不支持的类型直接报错。 - 内部
engine当前写死"local"(源码标TODO),实际仅本地落地。
四、预签名分片直传(presign.go)
无需第三方 SDK,采用 HMAC-SHA256 自签名。用于前端直传时的鉴权与防篡改。
go
// plugin/storage/presign.go
func GeneratePresignedParams(action, fileKey, uploadId string, partNumber int) (*PresignParams, error)
func VerifyPresign(action, fileKey, uploadId string, partNumber int, sig string, expiresAt int64) boolaction:"putObject"或"uploadPart"。- 签名载荷:
action|fileKey|uploadId|partNumber|expiresAt,密钥config.FileConfig.PresignSecret(为空时自动生成 32 字节随机密钥,重启失效,生产应显式配置)。 VerifyPresign:先校验expiresAt是否过期,再hmac.Equal比对签名。- 前端流程:
GetObjectURL/预签名参数 → 直传 OSS/S3 → 回调完成合并。
五、临时分片清理(cron_cleanup.go)
go
func CleanChunkTmpDir()- 扫描
config.FileConfig.ChunkTmpDir,删除修改时间超过ChunkExpireHour小时(默认 24)的子目录。 - 由定时任务
FixedTasks的CleanChunkTmpDir(每小时整点)调用(见 定时任务)。
六、访问与路由
routes/api.go 将 config.FileConfig.UploadPrefix(默认 /api/uploads)静态映射到 config.FileConfig.UploadDirectory,因此 GetObjectURL 返回的相对路径可直接通过 HTTP 访问。
七、扩展新的存储后端
- 在
plugin/storage实现StorageEngine接口(如myEngine)。 - 若需分片直传,配合使用
presign.go的签名校验。