RRuna

存储 Storage

本地磁盘与对象存储统一访问

storage 管理命名磁盘。默认使用本地文件驱动,生产环境可以按需接入 S3 兼容对象存储。

可以把它理解成“文件系统抽象”:业务代码只关心 PutGetURL,不关心底层是本地目录还是对象存储。

安装

go get github.com/duxweb/runa/storage

S3 驱动按需安装:

go get github.com/duxweb/runa/storage/s3

接入应用

package main

import (
    "context"

    "github.com/duxweb/runa"
    "github.com/duxweb/runa/storage"
)

func main() {
    app := runa.New()
    app.Install(storage.Provider(
        storage.RegisterDisk("public", storage.Prefix("public"), storage.Public()),
    ))

    if err := app.Freeze(context.Background()); err != nil {
        panic(err)
    }

    disk := storage.Default().MustOf("public")
    _ = disk.PutString(context.Background(), "hello.txt", "Hello Runa", storage.ContentType("text/plain"))
}

storage.Provider() 会把 *storage.Registry 注册进 DI,并读取磁盘配置。

独立 New 使用

registry := storage.New(storage.Root("./data/storage"), storage.DriverURLPrefix("/files"))
disk := registry.MustOf(storage.DiskPublic)

_ = disk.PutString(context.Background(), "hello.txt", "Hello", storage.ContentType("text/plain"))
body, _ := disk.GetString(context.Background(), "hello.txt")
_ = body

配置

storage 读取 storage.disks.<name>,只作用到已经注册的磁盘。New() 默认注册 localpublicprivatecloud

[storage.disks.public]
driver = "local"
prefix = "public"
public = true
url_prefix = "/files"
domain = "https://cdn.example.com"

[storage.disks.private]
driver = "local"
prefix = "private"
public = false
类型 说明
driver string 驱动名,默认 local
prefix string 磁盘路径前缀
public bool 是否公开访问
url_prefix string URL 前缀
domain string URL 域名
meta table 自定义元数据

public 和 private 怎么选

类型 用途
Public() 头像、公开附件、前端可直接访问的文件
Private() 合同、报表、内部文件,需要后端鉴权后再访问

公开文件可以直接生成 URL。私有文件通常需要你自己写下载接口并做权限判断。

S3 驱动

同时安装 storage provider 和 S3 驱动 provider。s3storage.Provider(...) 会注册名为 s3 的驱动,磁盘可以通过配置里的 driver = "s3" 或代码里的 storage.Use("s3") 使用它。

import s3storage "github.com/duxweb/runa/storage/s3"

app.Install(
    storage.Provider(storage.RegisterDisk("cloud", storage.Use("s3"), storage.Public())),
    s3storage.Provider(
        s3storage.Bucket("app"),
        s3storage.Region("us-east-1"),
        s3storage.Endpoint("https://s3.example.com"),
        s3storage.Credentials("access", "secret"),
        s3storage.PathStyle(true),
    ),
)

纯配置接入:

[storage.disks.cloud]
driver = "s3"
prefix = "uploads"
public = true

[storage.s3]
bucket = "app"
region = "us-east-1"
endpoint = "https://s3.example.com"
access_key = "access"
secret_key = "secret"
path_style = true
domain = "https://cdn.example.com"
url_prefix = "/files"

storage.s3 会覆盖共享 [s3] 连接配置。需要多套 S3 兼容连接时,可以写 [s3.<name>],再用 s3storage.Use(name) 选择。没有显式配置静态 AK/SK 时,S3 驱动会走 AWS SDK 默认凭证链,包括环境变量、共享配置文件和 IAM Role。MinIO、R2、OSS 等 S3 兼容服务按需配置 endpointpath_style

当配置了 endpoint 时,驱动会把目标视为通用 S3 兼容服务。代码不会识别或分支判断 OSS、COS、R2、MinIO 等具体厂商,而是启用厂商无关的兼容策略:请求 checksum 计算降到 when_required,复制源路径对中文和保留字符做 URL 编码,删除使用逐个 DeleteObject 而不是批量删除。这样可以避开 S3 兼容服务常见的 checksum、复制源路径和 Content-MD5 差异;未配置 endpoint 的原生 AWS S3 仍保持 SDK 默认行为。

S3 兼容云配置菜谱

这些配置都配合上面的 s3storage.Provider(...)[storage.disks.<name>] 使用。

# MinIO
[storage.s3]
bucket = "app"
region = "us-east-1"
endpoint = "http://127.0.0.1:9000"
access_key = "minioadmin"
secret_key = "minioadmin"
path_style = true

# Cloudflare R2
[storage.s3]
bucket = "app"
region = "auto"
endpoint = "https://<account-id>.r2.cloudflarestorage.com"
access_key = "<access-key-id>"
secret_key = "<secret-access-key>"
path_style = true

# 阿里云 OSS
[storage.s3]
bucket = "app"
region = "oss-cn-hangzhou"
endpoint = "https://oss-cn-hangzhou.aliyuncs.com"
access_key = "<access-key-id>"
secret_key = "<access-key-secret>"
path_style = false

# 腾讯云 COS
[storage.s3]
bucket = "app-1250000000"
region = "ap-guangzhou"
endpoint = "https://cos.ap-guangzhou.myqcloud.com"
access_key = "<secret-id>"
secret_key = "<secret-key>"
path_style = false

# 七牛 Kodo S3
[storage.s3]
bucket = "app"
region = "z0"
endpoint = "https://s3-cn-east-1.qiniucs.com"
access_key = "<access-key>"
secret_key = "<secret-key>"
path_style = true

# 火山引擎 TOS
[storage.s3]
bucket = "app"
region = "cn-beijing"
endpoint = "https://tos-s3-cn-beijing.volces.com"
access_key = "<access-key>"
secret_key = "<secret-key>"
path_style = false

# 华为云 OBS
[storage.s3]
bucket = "app"
region = "cn-north-4"
endpoint = "https://obs.cn-north-4.myhuaweicloud.com"
access_key = "<access-key>"
secret_key = "<secret-key>"
path_style = false
服务 CRUD TempURL SignPut SignPost
AWS S3 支持 支持 支持 支持
MinIO 支持 支持 支持 通常支持
Cloudflare R2 支持 支持 支持 通常支持
阿里云 OSS S3 支持 支持 支持 部分支持
腾讯云 COS S3 支持 支持 支持 通常支持
七牛 Kodo S3 支持 支持 支持 部分支持
火山 TOS S3 支持 支持 支持 部分支持
华为 OBS S3 支持 支持 支持 部分支持

浏览器直传优先使用 SignPut,除非你已经确认目标云的 SigV4 POST policy 完整可用。上传回调、上传 token、持久化处理、图片处理参数等属于厂商原生能力,不在 S3 协议里;只有真实业务需要这些 native-only 能力时,才值得新增对应厂商驱动子模块。

常用 API

disk := storage.Default().MustOf("public")

_ = disk.PutString(ctx, "avatars/1.txt", "hello", storage.ContentType("text/plain"))
body, err := disk.GetString(ctx, "avatars/1.txt")
files, err := disk.List(ctx, "avatars", storage.Limit(100), storage.Recursive())
exists, err := disk.Exists(ctx, "avatars/1.txt")
url, err := disk.URL(ctx, "avatars/1.txt")
_ = body
_ = files
_ = exists
_ = url
_ = err

List 返回一页结果,包含 ItemsCommonDirsCursorHasMore。继续翻页时传入 storage.Cursor(previous.Cursor)

常见错误

本地磁盘路径不清楚

本地驱动的根目录由 storage.Root(...) 或应用 data 路径决定,磁盘的 Prefix(...) 会继续拼到根目录下面。

URL 为空或不是预期域名

生成公开 URL 需要磁盘设置 storage.Public(),并按需要设置 storage.URLPrefix(...)storage.Domain(...)

生产环境还用本地磁盘存公开文件

单机部署可以用本地磁盘。多实例部署通常应使用 S3 兼容对象存储或共享存储。

API 速查

  • storage.New(options...) 创建独立注册表
  • storage.Provider(...) 接入框架生命周期
  • storage.Default() 从默认 DI 取 *storage.Registry
  • storage.RegisterDriver(name, driver) 注册驱动
  • storage.RegisterDisk(name, options...) 注册磁盘
  • registry.MustOf(name) 获取磁盘
  • storage.LocalDriver(...) 创建本地驱动
  • s3storage.Provider(...) 从选项或配置注册 S3 兼容驱动
编辑此页