跳到主要内容
版本:0.4.x(Latest)

基本介绍

源码插件通过services.Storage()使用对象存储能力。动态插件通过plugin.yaml声明service: storage后使用pluginbridge.Default().Storage()客户端。

Storage能力为每个插件提供独立的对象存储沙箱。插件在声明的授权路径前缀下读写对象,宿主负责路径校验、插件隔离和存储后端管理。

能力阶段:运行期

类型支持:源码插件、动态插件

能力设计

沙箱隔离模型

Storage采用插件+租户的双重隔离。每个插件的对象自动限定在plugins/{pluginID}/前缀下,租户数据进一步隔离在tenant/{tenantID}/子路径中。平台级数据使用platform/子路径:

对象键映射

插件使用的逻辑路径会由storageAdapter自动映射为物理存储对象键,映射规则如下:

范围对象键格式
租户级plugins/{pluginID}/tenant/{tenantID}/{logicalPath}
平台级plugins/{pluginID}/platform/{logicalPath}

插件只需操作逻辑路径(如exports/report.csv),无需关心底层对象键结构。

对象元数据

插件可见的对象元数据不暴露物理存储路径、Provider密钥或宿主文件管理ID:

字段说明
Path逻辑路径
Size对象大小
ContentType内容类型
ETag实实体标签,由SHA-1(key + size + modtime)计算生成
UpdatedAt最后更新时间
Visibility可见性标识,privatepublic

对象可见性由宿主控制,Visibility字段标识对象是否可公开服务。插件不能假设写入后必然公网可见,具体访问策略由宿主的服务层决定。

内容类型检测

写入对象时,storageAdapter按以下优先级检测内容类型:

  1. 请求中显式指定的contentType
  2. 正文嗅探(读取前512字节进行检测)
  3. 文件扩展名推断
  4. 兜底为application/octet-stream

存储Provider架构

Storage支持可插拔的存储Provider架构:

Provider说明
local内置本地磁盘Provider,存储在.capability-storage/目录下
自定义通过Provide()注册的插件Provider,支持OSSS3MinIO

Provider通过plugin.storage.activeProviderPluginId配置选择。未配置时使用本地Provider。集群模式下本地Provider默认拒绝服务,需显式设置allowLocalProviderInCluster

Provider注册机制

Provider通过进程级全局注册表管理。源码插件调用storagecap.Provide(pluginID, factory)注册一个ProviderFactory函数,宿主在运行时通过ResolveProvider解析当前活跃的Provider

  • 未配置activeProviderPluginId时,使用内置本地Provider
  • 配置了activeProviderPluginId时,必须匹配已注册且可用的插件Provider,不存在静默降级

与Files能力的区别

维度FilesStorage
用途宿主文件管理系统的只读视图插件作用域的对象存储沙箱
数据库sys_file表,完整元数据无数据库,纯对象存储
隔离租户+数据范围插件+租户路径隔离
操作只读视图+受控删除完整CRUD
大小限制upload.maxSize配置无限制
Provider内置本地存储可插拔Provider注册

接口定义

源码插件接口

方法说明
Put写入对象,支持ContentTypeOverwrite控制
Get读取对象内容和元数据
Delete删除授权路径下的对象
List按前缀列出对象
Stat读取对象元数据,不返回内容
ProviderStatuses查询所有注册的Provider状态(仅源码插件可用)

PutOverwrite参数控制覆盖行为:设为false时,若对象已存在则返回PLUGIN_STORAGE_OBJECT_EXISTS错误。

动态插件接口

动态方法动态SDK方法说明
putStorage().Put写入对象,支持ContentTypeOverwrite控制
put.init初始化分块上传会话,返回上传ID
put.chunk按偏移量顺序写入分块数据
put.commit提交分块上传,合并为最终对象
put.abort取消分块上传,清理临时文件
getStorage().Get读取对象内容和元数据
deleteStorage().Delete删除授权路径下的对象
listStorage().List按前缀列出对象
statStorage().Stat读取对象元数据,不返回内容

动态插件的Guest SDK在写入时自动选择模式:对象体不超过1 MB使用直接上传(单次调用),超过1 MB或大小未知时自动切换为分块上传(1 MB分块)。分块上传的宿主侧最大分块为4 MB,会话有效期15分钟。分块失败时自动尝试abort清理临时文件。

ProviderStatuses不可通过动态插件传输协议使用。

能力使用

源码插件使用

源码插件通过services.Storage()直接操作对象:

// 写入对象
_, err := services.Storage().Put(ctx, storagecap.PutInput{
Path: "exports/report.csv",
Body: reader,
ContentType: "text/csv",
Overwrite: true,
})

// 读取对象
output, err := services.Storage().Get(ctx, storagecap.GetInput{
Path: "exports/report.csv",
})

// 列出对象
list, err := services.Storage().List(ctx, storagecap.ListInput{
Prefix: "exports/",
Limit: 100,
})

// 查询Provider状态
statuses, err := services.Storage().ProviderStatuses(ctx)

动态插件使用

动态插件在plugin.yaml中声明storage服务和授权路径:

hostServices:
- service: storage
methods:
- put
- get
- delete
- list
- stat
resources:
paths:
- exports/
- temp/reports/

授权粒度是逻辑路径前缀。所有请求路径会在WASM宿主服务层进行归一化和授权校验,确保插件只能访问声明的路径范围。

在动态插件侧使用:

storageSvc := pluginbridge.Default().Storage()

// 写入对象(小对象直接上传)
_, err := storageSvc.Put(ctx, storagecap.PutInput{
Path: "exports/report-2024.csv",
Body: data,
ContentType: "text/csv",
})

// 读取对象
output, err := storageSvc.Get(ctx, storagecap.GetInput{
Path: "exports/report-2024.csv",
})

// 列出对象
list, err := storageSvc.List(ctx, storagecap.ListInput{
Prefix: "exports/",
})

系统约束

约束项限制
单对象大小无限制
逻辑路径长度512字节
列举默认限制100
列举最大限制1000
直接上传阈值1 MB(Guest SDK自动切换分块)
分块大小(Guest)1 MB
分块大小(宿主)4 MB
分块会话有效期15分钟

设计约束

  • 路径不是物理路径。 paths是逻辑授权范围,插件不能通过相对路径逃逸授权前缀。WASM宿主服务层对每个请求路径进行归一化和授权校验。
  • 对象可见性由宿主控制。 对象是否可公开服务由宿主元数据和后续服务策略决定,插件不能假设写入后必然公网可见。
  • 不暴露底层细节。 对象元数据不包含物理路径、Provider密钥或宿主文件管理ID。
  • 插件卸载自动清理。 插件卸载时,宿主按授权路径前缀列举并批量删除所有对象。
  • Provider无静默降级。 配置了自定义Provider后,若该Provider不可用,操作直接失败而非回退到本地Provider

相关服务