Skip to content

存储引擎 ​

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
阿里云 OSSstorage_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) bool
  • action:"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 访问。

七、扩展新的存储后端 ​

  1. 在 plugin/storage 实现 StorageEngine 接口(如 myEngine)。
  2. 若需分片直传,配合使用 presign.go 的签名校验。