news 2026/9/12 17:02:54

Strapi 如何开发自定义 Upload Provider 对接自有对象存储(upload/delete/getSignedUrl)?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Strapi 如何开发自定义 Upload Provider 对接自有对象存储(upload/delete/getSignedUrl)?

Strapi 如何开发自定义 Upload Provider 对接自有对象存储(upload/delete/getSignedUrl)?

【免费下载链接】strapi🚀 Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.项目地址: https://gitcode.com/GitHub_Trending/st/strapi

如果你有自有的对象存储,希望 Strapi 中上传的媒体文件不再写到本地uploads/目录,而是落到你的存储里,就需要开发一个自定义 Upload Provider。完成本文任务后,Strapi 上传的媒体会写入你的对象存储,删除媒体时会同步删除对象,私有存储还能通过签名 URL 对外提供访问。本文依据仓库内的 Provider 契约与官方加载逻辑:契约见 Provider 文档,加载逻辑在 register.ts,官方 S3 provider 与 local provider 可作为参考实现。

前提:一个可用的 Strapi 项目(upload 插件已启用),以及你自己的对象存储服务及其 SDK 或 HTTP API。

Provider 契约:要实现哪些方法

官方文档的定义是:Provider 是一个包,它导出一个带init函数的对象,Strapi 启动时调用init(options),返回的对象就是 provider 实例。各方法的职责(引自 00-providers.md):

方法要求职责
upload(file)uploadStream至少实现一个把文件上传到 provider
uploadStream(file)upload至少实现一个(推荐)把流上传到 provider
delete(file)必须从 provider 删除文件
isPrivate()可选返回存储是否私有,默认false;为真时改用getSignedUrl获取文件 URL
getSignedUrl(file)可选存储需要鉴权时,返回访问文件的签名 URL

Strapi 在启动阶段校验实例(register.ts),对应报错如下:

  • 无法解析 provider 模块:Could not load upload provider "xxx".
  • 缺少deleteThe upload provider "xxx" doesn't implement the delete method.
  • uploaduploadStream都缺失:The upload provider "xxx" doesn't implement the uploadStream nor the upload method.
  • 只缺uploadStream(不致命,仅警告):The upload provider "xxx" doesn't implement the uploadStream function. Strapi will fallback on the upload method. Some performance issues may occur.

前三条决定项目能否启动,最后一条影响运行期性能,所以建议把uploadStream也实现掉。

Strapi 如何找到并调用你的 provider

配置写在./config/plugins.js

const pluginConfig = { upload: { // provider 名称,会被转成小写后使用 provider: 'my-oss', // 整个对象会作为参数传给 provider 的 init(options) providerOptions: { // 你自己的对象存储参数,例如 bucket、endpoint、凭据 }, }, };

加载逻辑(register.ts):

  1. 读取plugin::upload配置,取provider名称并转小写;
  2. 先尝试解析 npm 包@strapi/provider-upload-<名称>;若未安装(MODULE_NOT_FOUND),则直接require你配置的这个名字——也就是说 provider 既可以是安装的包,也可以是项目内可被引用的模块;
  3. 调用provider.init(providerOptions)得到实例。

之后 Strapi 对实例上的每个函数做包装(register.ts):调用同名方法时会把配置里actionOptions.<方法名>作为第二个参数传入。也就是说你可以在 upload 配置里声明actionOptions: { upload: {...} },按方法名给不同方法传参,方法签名写成(file, options)即可。

实例还继承baseProvider的几个默认实现:isPrivate()默认返回falsegetSignedUrl(file)默认原样返回文件;checkFileSize(file, { sizeLimit })在文件超过 upload 配置中的sizeLimit时抛出PayloadTooLargeError

Provider 收到的 File 对象

file是 Strapi 组装好的媒体记录,provider 实现中常用到的字段(定义见 S3 provider 的File,index.ts):

  • file.hash/file.ext:文件哈希与扩展名,官方 provider 都用它们拼对象 Key;
  • file.stream(流式路径)或file.buffer(缓冲路径):文件内容;
  • file.mime:MIME 类型,写入时作为 Content-Type;
  • file.path:目录路径,S3 provider 会把它拼进 Key 前缀;
  • file.urlprovider 必须在这里写回文件的访问地址,Strapi 最终保存的媒体 URL 就是你设置的值。

对象 Key 的拼法参考两个官方实现:local provider 直接用${file.hash}${file.ext};S3 provider 按rootPath前缀 →file.pathhash+ext的顺序拼接(getFileKey)。

实现 uploadStream

local provider 的实现(upload-local/src/index.ts)展示了最小模式:消费file.stream写入存储,成功后写回file.url

uploadStream(file: File): Promise<void> { if (!file.stream) { return Promise.reject(new Error('Missing file stream')); } return new Promise((resolve, reject) => { pipeline( file.stream, fs.createWriteStream(path.join(uploadPath, `${file.hash}${file.ext}`)), (err) => { if (err) return reject(err); file.url = `/uploads/${file.hash}${file.ext}`; resolve(); } ); }); }

S3 provider 的写法(upload):请求体为file.stream || Buffer.from(file.buffer, 'binary'),ContentType 取file.mime;上传完成后把访问 URL 写回file.url,把ETag去掉引号后写入file.etag。你的对象存储返回的 URL 拼接方式可参考它的优先级:配置了baseUrl(CDN 或自定义域名)时优先用它,其次使用存储返回的地址,兜底用endpoint/bucket/key拼接。

实现 delete

delete(file)按同样的规则算出 Key,然后删除对象即可。S3 provider 用DeleteObjectCommandBucket + Key删除(delete)。官方文档明确要求 provider "should be able to upload files to a remote server and delete them"——即 Strapi 删除媒体时,你存储里的对象也要随之移除。

私有文件与 getSignedUrl

如果存储不允许匿名访问,让isPrivate()返回true。URL 签名流程在 file.ts 中:

  1. 从 provider 读取isPrivate;若为false,或文件自身的provider字段与当前配置的 provider 名称不一致,直接返回原文件,不签名;
  2. 否则调用getSignedUrl(file),把返回结果里的url写进文件的url,并标记file.isUrlSigned = truefile.formats里的每个尺寸(响应式图片)也会逐个签名。

因此getSignedUrl必须返回带url字段的对象。S3 provider 的实现(getSignedUrl)用 presigner 对GetObjectCommand生成临时 URL,expiresIn取配置params.signedUrlExpires(默认15 * 60秒),返回{ url }

自定义 provider 的最小骨架

综合以上契约,对接自有对象存储的 provider 骨架如下。// TODO行是需要替换为你自己的对象存储 SDK 调用的部分,其余结构可直接保留:

// 自定义对象存储 provider export default { init(options) { // options 就是 ./config/plugins.js 里的 providerOptions // 对象 Key:官方惯例为 hash + ext,需要目录前缀时可自行拼接 const buildKey = (file) => `${file.hash}${file.ext}`; return { // 推荐实现:流式写入,比缓冲路径性能好 uploadStream(file) { const key = buildKey(file); // TODO: 把 file.stream 以 key 写入你的对象存储 // TODO: 成功后把对象的访问 URL 写入 file.url }, // 可选:缓冲路径;只实现 uploadStream 时可以不写 upload upload(file) { // TODO: 把 file.buffer 以 buildKey(file) 写入你的对象存储,并写回 file.url }, // 必须实现 delete(file) { // TODO: 从你的对象存储删除 buildKey(file) 对应的对象 }, // 存储私有时返回 true,启用 URL 签名 isPrivate() { return true; }, // 仅当 isPrivate() 为 true 时被调用;必须返回 { url } async getSignedUrl(file) { // TODO: 用你的存储签名机制为对象生成临时访问 URL // 形如:return { url: await signObject(buildKey(file)) }; }, }; }, };

把该模块放到 provider 名称能解析到的位置(安装为 npm 包,或项目中可require的模块路径),再在./config/plugins.js中按上文格式配置。

验证方式

Strapi 启动后逐项核对:

  1. 启动校验:provider 包解析不到、delete缺失或upload/uploadStream都缺失时,启动会抛出第一节列出的对应错误;能正常启动说明基础契约满足。若看到uploadStream警告,说明流式实现还没补齐。
  2. 上传链路:在管理端或调用上传接口做一次媒体上传,返回的媒体url应正是你的 provider 写入file.url的地址,你的对象存储里应出现 Key 为${hash}${ext}的新对象。
  3. 删除链路:删除该媒体后,你存储中对应的对象应不再存在。
  4. 签名 URLisPrivate()返回true、且媒体的provider与配置的 provider 名称一致时,API 返回的媒体url应是被替换后的签名 URL,isUrlSigned字段为true

可选分支:对象存储兼容 S3 协议时

如果你的自有对象存储实现了 S3 协议,可以不写自定义 provider,直接使用官方@strapi/provider-upload-aws-s3,把s3Optionsendpoint指向你的存储即可。源码中有几点限制需要注意:

  • params.Bucket必填,否则启动报错:Upload AWS S3 provider: \params` are required in the config object`;
  • endpointcredentials等要放进s3Options对象里,平铺在providerOptions根级别是已弃用的方式,会触发告警;
  • 当 endpoint 不是 AWS 时,storageClass、非AES256的加密等 AWS 专属配置可能被你的存储忽略,Strapi 会发出告警(例如Storage class 'STANDARD_IA' is AWS S3-specific and may be ignored by your S3-compatible provider.)。

限制说明

  • provider 名称会被_.toLower转小写后再解析,配置名与实际包名注意保持一致;
  • URL 签名只对provider字段等于当前配置 provider 名称的媒体生效,其它 provider 写入的历史媒体不会被签名;
  • getSignedUrl只在isPrivate()返回true时才被调用;
  • 大小校验的sizeLimit是 upload 配置的顶层参数,从providerOptions传入属于已弃用方式(local provider 会发出弃用告警,提示迁移到upload.config)。

参考文件

  • Provider 契约文档:docs/docs/docs/01-core/upload/01-backend/00-providers.md
  • Provider 加载与启动校验:packages/core/upload/server/src/register.ts
  • 签名 URL 流程:packages/core/upload/server/src/services/file.ts
  • 官方 S3 provider 实现:packages/providers/upload-aws-s3/src/index.ts
  • 官方 local provider 实现:packages/providers/upload-local/src/index.ts

【免费下载链接】strapi🚀 Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.项目地址: https://gitcode.com/GitHub_Trending/st/strapi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 17:02:14

GA-VMD参数自适应寻优:旋转机械故障诊断中的振动信号处理实践

简介&#xff1a;遗传算法优化VMD&#xff08;GA-VMD&#xff09;技术结合遗传算法与变分模态分解&#xff0c;面向非线性、非平稳信号分析需求&#xff0c;适用于电力系统故障诊断、机械健康监测、声音识别等场景。资源包共27个文件&#xff0c;以15个Matlab脚本为核心&#x…

作者头像 李华
网站建设 2026/9/12 17:01:40

TEC半导体制冷片在医用冷敷仪中的精准控温原理与工程实现

1. 为什么冷敷仪不能只靠冰袋&#xff1f;TEC半导体制冷片的不可替代性 市面上卖的手持冷敷仪&#xff0c;十有八九是“伪冷敷”——要么是内置凝胶冰袋&#xff0c;靠提前冷冻后缓慢释冷&#xff1b;要么是压缩机制冷&#xff0c;体积大、噪音响、功耗高&#xff0c;根本没法做…

作者头像 李华