You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Multer与Stream直接上传文件至Google Cloud Storage?

问题本质与解决思路

你当前代码用multer.memoryStorage()会把整个文件先加载到后端内存,后续的两个方案只是从内存读数据的方式不同,完全没实现**边接收用户上传边同步传给Google Cloud Storage(GCS)**的流式处理,大文件依然会占用大量服务器内存,存在性能风险。

正确的流式上传实现

要实现直接流式上传,必须绕开Multer的内存存储,直接将用户上传的请求流分段传递给GCS。这里用busboy(Multer底层依赖的表单解析工具)来处理multipart/form-data的流式解析,收到文件片段就立刻传给GCS:

import Koa from 'koa';
import KoaRouter from '@koa/router';
import { Storage } from '@google-cloud/storage';
import busboy from 'busboy';

const kr = new KoaRouter();
const storage = new Storage();
const bucket = storage.bucket('testbucket');

kr.post('/upload', async (ctx) => {
  return new Promise((resolve, reject) => {
    // 初始化busboy解析表单
    const bb = busboy({ headers: ctx.req.headers });
    
    // 监听文件字段
    bb.on('file', (fieldName, fileStream, fileInfo) => {
      const { filename, mimeType } = fileInfo;
      // 创建GCS写入流
      const gcsWriteStream = bucket.file(filename).createWriteStream({
        resumable: false,
        gzip: true,
        contentType: mimeType
      });

      // 直接将busboy的文件流pipe到GCS,实现边接收边上传
      fileStream.pipe(gcsWriteStream)
        .on('finish', () => {
          ctx.body = { status: 'success', message: '文件上传完成' };
          resolve();
        })
        .on('error', (err) => {
          ctx.status = 500;
          ctx.body = { status: 'error', message: err.message };
          reject(err);
        });
    });

    // 处理表单解析错误
    bb.on('error', (err) => {
      ctx.status = 400;
      ctx.body = { status: 'error', message: err.message };
      reject(err);
    });

    // 将原生请求流pipe给busboy
    ctx.req.pipe(bb);
  });
});
你的两个方案差异解析

方案A:PassThrough流

  • Stream.PassThrough是一个无转换的中间流,仅负责将输入数据原样输出。
  • 你先把内存中的完整文件Buffer一次性写入PassThrough,再立刻调用end(),相当于把整个Buffer一次性推给GCS流。
  • 本质和直接将Buffer传给GCS写入流没有区别,完全没有流式分段传输的效果。

方案B:Readable.from(Buffer)

  • Readable.from(fileToUpload)将完整的文件Buffer转换为可读流,会把Buffer分成默认64KB的小块逐步输出。
  • 相比方案A,它是分批次向GCS发送内存中的数据,但依然是先把整个文件加载到内存后再分批发送,没有解决大文件占用内存的核心问题。
  • 两者唯一差异是数据推送粒度:方案A一次性发送全部数据,方案B分小块发送,但数据源都是已加载到内存的完整文件。

内容的提问来源于stack exchange,提问作者crivella

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 19:42:15