Sails 文件上传到 Amazon S3:使用 Skipper-S3 适配器实现流式上传
2026/9/20 17:46:35 网站建设 项目流程

Sails 文件上传到 Amazon S3:使用 Skipper-S3 适配器实现流式上传

【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails

导读

本文讲解如何在 Sails 应用中,借助 Skipper 生态的skipper-s3文件上传适配器,以流式方式将用户上传的文件直接传输到 Amazon S3 对象存储,无需先将文件落盘到本地文件系统。读完本文你将掌握:S3 桶的区域硬性要求、req.file(...).upload({...})的完整配置写法、上传回调与响应组织方式,以及 Sails 内置 Skipper 底层流式处理机制与安全注意事项。

背景:Sails 的文件上传机制

Sails 内置了名为 Skipper 的"body parser",它让流式文件上传变得非常简单——上传目标不局限于服务器本地磁盘,还可以是 Amazon S3、MongoDB GridFS 或其他受支持的存储适配器(参见 docs/concepts/File Uploads/File Uploads.md 中的整体介绍)。

这一机制在仓库源码中有直接体现:在 lib/hooks/http/get-configured-http-middleware-fns.js 中,默认的bodyParser中间件在未显式配置时会require('skipper');而 package.json 中skipper: "^0.9.5"也确认了它是 Sails 运行时默认依赖。也就是说,一个常规的 Sails 应用开箱即可在 action 中调用req.file()接收 multipart 上传。

需要注意:req.file()只在 HTTP 请求上下文中可用。在 lib/router/index.js 中,Sails 为虚拟请求(例如 socket.io 请求)设置了req.file的 mock 实现,直接返回 500 并提示 "Streaming file uploads viareq.file()are only available over HTTP with Skipper."——因此 S3 上传同样仅适用于 HTTP 上传场景。

前置条件:S3 桶必须位于 US East (N. Virginia) 区域

skipper-s3适配器存在一个容易踩坑的硬性约束:

你的 Amazon S3 bucket 必须创建在US East (N. Virginia)区域(即区域代码us-east-1)。如果桶创建在其他区域,上传将无法工作,AWS 会返回InvalidRequest错误。

这是文档开篇就强调的关键前提(见 docs/concepts/File Uploads/uploading-to-amazon-s3.md)。在创建桶时务必选择该区域,否则后续所有上传请求都会失败,且错误信息来自 AWS 服务端,排查成本较高。

此外你还需要准备好 AWS 凭据:一组拥有该桶写权限的Access KeySecret Key(即下文代码中的keysecret)。

安装 skipper-s3 适配器

在 Sails 项目根目录执行:

npm install skipper-s3 --save

该命令会把skipper-s3加入项目package.jsondependencies。它是 Skipper 生态中的一个"blob receiver"(存储接收器),负责把 Skipper 解析出的上传流写入 S3。

在控制器中使用适配器上传

安装完成后,在任意 controller action 中通过req.file('<字段名>')拿到 Skipper Upstream,再调用.upload()并传入 S3 配置。以下示例直接来自官方文档(见 docs/concepts/File Uploads/uploading-to-amazon-s3.md):

uploadFile: function (req, res) { req.file('avatar').upload({ adapter: require('skipper-s3'), key: 'S3 Key', secret: 'S3 Secret', bucket: 'Bucket Name' }, function (err, filesUploaded) { if (err) return res.serverError(err); return res.ok({ files: filesUploaded, textParams: req.allParams() }); }); }

配置项说明:

选项类型说明
adapter((function))指定上传接收适配器,这里为require('skipper-s3')返回的适配器构造器
key((string))AWS Access Key ID,用于鉴权
secret((string))AWS Secret Access Key,用于鉴权
bucket((string))目标 S3 桶名称(必须位于us-east-1区域)

.upload()的第二个参数是完成回调(err, filesUploaded)

  • 上传过程中出错时,err非空,此时应返回res.serverError(err)
  • 成功时,filesUploaded是本次上传产生的文件描述数组,可返回给前端使用;
  • 示例中同时通过req.allParams()取回请求中的文本参数一并返回。req.allParams()由 lib/hooks/request/params.all.js 注入,它合并了 query string、request body 与路由参数。

关于安全硬编码凭据的提醒

示例中的key/secret是占位写法。生产环境中切勿把 AWS 凭据硬编码进控制器代码,更不要提交到版本库。建议将凭据放入 Sails 的环境变量或config/local.js(该文件通常已被.gitignore忽略),再在 action 中通过process.envsails.config读取。从源码结构看,Sails 的配置体系(如config/local.js与环境变量)天然支持这种注入方式。

底层原理:Skipper 如何流式处理上传

理解req.file()的行为有助于排查 S3 上传问题。根据 docs/reference/req/req.file.md 的说明:

  • req.file(field)返回一个 SkipperUpstream,表示来自指定字段(如avatar)的 multipart 文件上传。Upstream 本质上是一个 Node.js Readable 流(object mode),其中的每个对象都是一次传入的 multipart 文件上传流。
  • Skipper 把所有文件上传都当作流处理。当 multipart 请求到达服务器时,Skipper 并不会把临时文件写入磁盘,而是只缓冲请求足够长的时间来运行你的应用代码,让你"插入"一个兼容的 blob receiver(在这里就是skipper-s3)。
  • 如果某个字段的数据没有"插入"到任何 receiver,Upstream 会达到其 high water mark,缓冲区被刷新,之后到达的字节将被忽略。
  • 底层实现上,.upload()所做的工作就是把 Upstream 管道(pipe)到指定的 receiver 实例,并在 Upstream 触发finisherror事件时执行回调。

这套流式设计带来的好处是:用户可以上传体积很大的文件,而对内存与磁盘占用影响很小,同时还能抵御利用 TMP 文件发起的拒绝服务攻击。

文本参数必须在文件参数之前

由于 Skipper 是流式处理,multipart 请求体中所有文本参数必须位于任何文件参数之前。这样 Sails 可以在文件仍在上传时就开始执行你的 action 代码,而不必等待上传全部完成(这是为了避免 Express 系应用中常见的 body parser DDoS 漏洞)。如果你通过req.allParams()req.body读取表单文本字段时发现取不到值,请先检查客户端提交顺序:文本字段在前,<input type="file">在后。

从本地磁盘上传迁移到 S3

作为对照,Sails 默认(不指定adapter)时使用磁盘接收器(skipper-disk),文件会写入myApp/.tmp/uploads/目录,可通过dirname选项自定义(参见 docs/concepts/File Uploads/File Uploads.md)。例如:

req.file('avatar').upload({ dirname: require('path').resolve(sails.config.appPath, 'assets/images') }, function (err, uploadedFiles) { if (err) return res.serverError(err); return res.json({ message: uploadedFiles.length + ' file(s) uploaded successfully!' }); });

迁移到 S3 时,你只需在.upload()的选项对象中把adapter换成require('skipper-s3')并提供key/secret/bucket,即可把落盘目标整体替换为对象存储。两者共用同一套回调签名(err, filesUploaded),因此返回给前端的文件描述结构保持一致;同时由于文件直接进入 S3,也规避了将不可信文件存放在 Web 根目录下的安全隐患。

安全注意事项

  • 不要信任请求中的 MIME 类型:文件上传请求头里的 MIME 类型不可靠,永远不要依赖它来判断文件真实内容。
  • 不自动病毒扫描:Sails/Skipper 不会对上传做病毒扫描或异常检测。若你的应用允许用户互相上传、共享文件,保护用户免受恶意文件侵害是你自己的责任。
  • 永远假设请求是恶意的:进入服务器的任何请求都可能被伪造或伪装,涉及文件存储的路径、命名与访问控制都要按不可信输入处理。

完整实战流程小结

  1. 在 AWS 控制台创建 S3 桶,区域选择 US East (N. Virginia)(us-east-1),否则上传会报InvalidRequest
  2. 准备拥有该桶写权限的 AWS Access Key / Secret Key;
  3. 在项目根目录执行npm install skipper-s3 --save
  4. 在 controller action 中调用req.file('avatar').upload({ adapter: require('skipper-s3'), key, secret, bucket }, callback)
  5. 回调中处理err(返回res.serverError)与filesUploaded(返回给前端),并可通过req.allParams()一并返回文本参数;
  6. 生产环境将凭据收敛到环境变量或config/local.js,避免硬编码。

【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询