将本地可用的Multer上传函数适配为Google Cloud Functions的Busboy实现
迁移本地文件上传函数到Google Cloud Functions的解决方案
问题背景
我在本地使用multer的memoryStorage实现了以下文件上传代码,运行正常,但迁移到Google Cloud Functions时遇到问题,尝试用busboy但理不清思路,有两个疑问:
- 直接用
req.rawBody替代file.buffer可行吗? - 网上建议用
os和fs,不知道原因是什么?
本地代码如下:
上传函数
async function uploadFile(file: any, fileName: string, targetFolder: string) { return new Promise((resolve, reject) => { const destinationFilepath: string = `${targetFolder ? targetFolder + '/' : ''}${fileName}`; const storage = new Storage(); const bucket = storage.bucket(UPLOAD_BUCKET); const blob = bucket.file(destinationFilepath); const blobStream = blob.createWriteStream(); blobStream.on('error', (err: Error) => reject(err)); blobStream.on('finish', () => { resolve({ message: `Success! File uploaded to ${blob.publicUrl()}`, publicUrl: blob.publicUrl(), bucket: bucket.name, fileName: blob.name }); }); blobStream.end(file.buffer); }); }
Express POST路由
app.post('/gcs/upload/:folder?', async (req: Request, res: Response) => { const folder: string = req.params.folder || ''; const fileName: string = req?.file?.originalname || 'FileWithoutFileName.txt'; res.status(200).send(await uploadFile(req.rawBody, fileName, folder)); });
解决方案
1. 核心原理
Google Cloud Functions的请求处理逻辑和本地Express存在差异,multer的memoryStorage无法直接适配GCF的请求对象,必须用busboy来解析multipart/form-data类型的请求(文件上传的标准格式)。
2. 具体修改步骤
(1)安装依赖
先安装busboy包:
npm install busboy
(2)复用并优化上传函数
你的uploadFile函数几乎不需要改动,仅需调整类型定义让逻辑更严谨:
import { Storage } from '@google-cloud/storage'; // 替换为你的实际存储桶名称 const UPLOAD_BUCKET = 'your-bucket-name'; async function uploadFile(fileBuffer: Buffer, fileName: string, targetFolder: string) { return new Promise((resolve, reject) => { const destinationFilepath = `${targetFolder ? `${targetFolder}/` : ''}${fileName}`; const storage = new Storage(); const bucket = storage.bucket(UPLOAD_BUCKET); const blob = bucket.file(destinationFilepath); const blobStream = blob.createWriteStream(); blobStream.on('error', (err: Error) => reject(err)); blobStream.on('finish', () => { resolve({ message: `Success! File uploaded to ${blob.publicUrl()}`, publicUrl: blob.publicUrl(), bucket: bucket.name, fileName: blob.name }); }); blobStream.end(fileBuffer); }); }
(3)修改GCF请求处理函数
替换原Express路由,改为GCF的导出函数,用busboy解析请求:
import * as Busboy from 'busboy'; import { Request, Response } from 'express'; export const uploadToGCS = async (req: Request, res: Response) => { // 拦截非POST请求 if (req.method !== 'POST') { return res.status(405).send('Method Not Allowed'); } const busboy = new Busboy({ headers: req.headers }); const folder = req.params.folder || ''; let uploadedFile: { buffer: Buffer, filename: string }; // 解析上传的文件字段 busboy.on('file', (fieldname, file, filename) => { const chunks: Buffer[] = []; file.on('data', (chunk) => chunks.push(chunk)); file.on('end', () => { uploadedFile = { buffer: Buffer.concat(chunks), filename: filename || 'FileWithoutFileName.txt' }; }); }); // 解析完成后执行上传逻辑 busboy.on('finish', async () => { if (!uploadedFile) { return res.status(400).send('No file uploaded'); } try { const result = await uploadFile(uploadedFile.buffer, uploadedFile.filename, folder); res.status(200).send(result); } catch (err) { res.status(500).send(`Upload failed: ${err.message}`); } }); // 启动请求体解析 busboy.end(req.rawBody); };
疑问解答
直接用req.rawBody可行吗?
不行。multipart/form-data类型的请求体包含分隔符(boundary)、文件头和文件内容等额外信息,req.rawBody是整个请求的原始二进制数据,直接写入GCS会导致文件包含冗余信息而损坏,必须通过busboy解析后才能得到纯净的文件buffer。为什么网上建议用os和fs?
这类方案是把文件先写入GCF的临时目录(os.tmpdir()),再通过fs读取后上传,适合处理超大文件(避免内存溢出)。但如果你的文件体积不大,直接用内存buffer更高效,无需写入本地文件。
内容的提问来源于stack exchange,提问作者Spurious
相关产品推荐
相关产品推荐

